@real-router/sources 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,7 +60,7 @@ const source = createRouteSource(router);
60
60
 
61
61
  ### `createRouteNodeSource(router, nodeName)`
62
62
 
63
- Creates a source scoped to a specific route node. Only updates when the node is in the transition path, avoiding unnecessary re-renders for unrelated navigations.\
63
+ Creates a source scoped to a specific route node. Only updates when the node is in the transition path, avoiding unnecessary re-renders for unrelated navigations. Uses a lazy-connection pattern: subscribes to the router on the first listener and unsubscribes when all listeners are removed.\
64
64
  `router: Router` — router instance\
65
65
  `nodeName: string` — route node name to scope updates to\
66
66
  Returns: `RouterSource<RouteNodeSnapshot>`
@@ -69,8 +69,6 @@ Returns: `RouterSource<RouteNodeSnapshot>`
69
69
  const source = createRouteNodeSource(router, "users");
70
70
  ```
71
71
 
72
- Call `source.destroy()` when the source is no longer needed.
73
-
74
72
  ---
75
73
 
76
74
  ### `createActiveRouteSource(router, routeName, params?, options?)`
@@ -209,8 +207,8 @@ const unsubscribe = source.subscribe(() => {
209
207
  console.log("Users section route:", route?.name);
210
208
  });
211
209
 
212
- // Tear down completely when the component unmounts
213
- source.destroy();
210
+ // Later, clean up (automatically unsubscribes from router)
211
+ unsubscribe();
214
212
  ```
215
213
 
216
214
  ---
@@ -32,6 +32,13 @@ interface RouterTransitionSnapshot {
32
32
  */
33
33
  declare function createRouteSource(router: Router): RouterSource<RouteSnapshot>;
34
34
 
35
+ /**
36
+ * Creates a source scoped to a specific route node.
37
+ *
38
+ * Uses a lazy-connection pattern: the router subscription is created when the
39
+ * first listener subscribes and removed when the last listener unsubscribes.
40
+ * This is compatible with React's useSyncExternalStore and Strict Mode.
41
+ */
35
42
  declare function createRouteNodeSource(router: Router, nodeName: string): RouterSource<RouteNodeSnapshot>;
36
43
 
37
44
  declare function createActiveRouteSource(router: Router, routeName: string, params?: Params, options?: ActiveRouteSourceOptions): RouterSource<boolean>;
package/dist/cjs/index.js CHANGED
@@ -1 +1 @@
1
- var t=require("@real-router/route-utils"),s=require("@real-router/core"),e=class{#t=null;#s;#e=new Set;#r;constructor(t){this.#r=t,this.#s={route:t.getState(),previousRoute:void 0},this.subscribe=this.subscribe.bind(this),this.destroy=this.destroy.bind(this),this.getSnapshot=this.getSnapshot.bind(this)}subscribe(t){return 0===this.#e.size&&(this.#t=this.#r.subscribe(t=>{this.#s={route:t.route,previousRoute:t.previousRoute},this.#e.forEach(t=>{t()})})),this.#e.add(t),()=>{this.#e.delete(t),0===this.#e.size&&this.#t&&(this.#t(),this.#t=null)}}getSnapshot(){return this.#s}destroy(){this.#t&&(this.#t(),this.#t=null),this.#e.clear()}},r=class{#s;#i=!1;#e=new Set;constructor(t){this.#s=t}subscribe(t){return this.#i?()=>{}:(this.#e.add(t),()=>{this.#e.delete(t)})}getSnapshot(){return this.#s}updateSnapshot(t){this.#i||(this.#s=t,this.#e.forEach(t=>{t()}))}destroy(){this.#i||(this.#i=!0,this.#e.clear())}};function i(t,s,e,r){const i=r?.route??s.getState(),o=r?.previousRoute,u=""===e||void 0!==i&&(i.name===e||i.name.startsWith(`${e}.`))?i:void 0;return u===t.route&&o===t.previousRoute?t:{route:u,previousRoute:o}}var o=new WeakMap,u=class{#o;#u;constructor(t,s){const e=i({route:void 0,previousRoute:void 0},t,s);this.#o=new r(e);const u=function(t,s){let e=o.get(t);e||(e=new Map,o.set(t,e));let r=e.get(s);return r||(r=t.shouldUpdateNode(s),e.set(s,r)),r}(t,s);this.#u=t.subscribe(e=>{if(!u(e.route,e.previousRoute))return;const r=i(this.#o.getSnapshot(),t,s,e);Object.is(this.#o.getSnapshot(),r)||this.#o.updateSnapshot(r)}),this.subscribe=this.subscribe.bind(this),this.getSnapshot=this.getSnapshot.bind(this),this.destroy=this.destroy.bind(this)}subscribe(t){return this.#o.subscribe(t)}getSnapshot(){return this.#o.getSnapshot()}destroy(){this.#u(),this.#o.destroy()}},n=class{#o;#u;constructor(s,e,i,o){const u=o?.strict??!1,n=o?.ignoreQueryParams??!0,h=s.isActiveRoute(e,i,u,n);this.#o=new r(h),this.#u=s.subscribe(r=>{const o=t.areRoutesRelated(e,r.route.name),h=r.previousRoute&&t.areRoutesRelated(e,r.previousRoute.name);if(!o&&!h)return;const c=!!o&&s.isActiveRoute(e,i,u,n);Object.is(this.#o.getSnapshot(),c)||this.#o.updateSnapshot(c)}),this.subscribe=this.subscribe.bind(this),this.getSnapshot=this.getSnapshot.bind(this),this.destroy=this.destroy.bind(this)}subscribe(t){return this.#o.subscribe(t)}getSnapshot(){return this.#o.getSnapshot()}destroy(){this.#u(),this.#o.destroy()}},h={isTransitioning:!1,toRoute:null,fromRoute:null},c=class{#o;#n;constructor(t){this.#o=new r(h);const e=s.getPluginApi(t),i=()=>{this.#o.updateSnapshot(h)};this.#n=[e.addEventListener(s.events.TRANSITION_START,(t,s)=>{this.#o.updateSnapshot({isTransitioning:!0,toRoute:t,fromRoute:s??null})}),e.addEventListener(s.events.TRANSITION_SUCCESS,i),e.addEventListener(s.events.TRANSITION_ERROR,i),e.addEventListener(s.events.TRANSITION_CANCEL,i)],this.subscribe=this.subscribe.bind(this),this.getSnapshot=this.getSnapshot.bind(this),this.destroy=this.destroy.bind(this)}subscribe(t){return this.#o.subscribe(t)}getSnapshot(){return this.#o.getSnapshot()}destroy(){this.#n.forEach(t=>{t()}),this.#o.destroy()}};exports.createActiveRouteSource=function(t,s,e,r){return new n(t,s,e,r)},exports.createRouteNodeSource=function(t,s){return new u(t,s)},exports.createRouteSource=function(t){return new e(t)},exports.createTransitionSource=function(t){return new c(t)};//# sourceMappingURL=index.js.map
1
+ var t=require("@real-router/route-utils"),e=require("@real-router/core"),s=class{#t;#e=!1;#s=new Set;#o;#r;#n;constructor(t,e){this.#t=t,this.#o=e?.onFirstSubscribe,this.#r=e?.onLastUnsubscribe,this.#n=e?.onDestroy,this.subscribe=this.subscribe.bind(this),this.getSnapshot=this.getSnapshot.bind(this),this.destroy=this.destroy.bind(this)}subscribe(t){return this.#e?()=>{}:(0===this.#s.size&&this.#o&&this.#o(),this.#s.add(t),()=>{this.#s.delete(t),!this.#e&&0===this.#s.size&&this.#r&&this.#r()})}getSnapshot(){return this.#t}updateSnapshot(t){this.#e||(this.#t=t,this.#s.forEach(t=>{t()}))}destroy(){this.#e||(this.#e=!0,this.#n?.(),this.#s.clear())}};function o(t,e,s,o){const r=o?.route??e.getState(),n=o?.previousRoute,i=""===s||void 0!==r&&(r.name===s||r.name.startsWith(`${s}.`))?r:void 0;return i===t.route&&n===t.previousRoute?t:{route:i,previousRoute:n}}var r=new WeakMap,n={isTransitioning:!1,toRoute:null,fromRoute:null};exports.createActiveRouteSource=function(e,o,r,n){const i=n?.strict??!1,u=n?.ignoreQueryParams??!0,a=e.isActiveRoute(o,r,i,u),c=new s(a,{onDestroy:()=>{h()}}),h=e.subscribe(s=>{const n=t.areRoutesRelated(o,s.route.name),a=s.previousRoute&&t.areRoutesRelated(o,s.previousRoute.name);if(!n&&!a)return;const h=!!n&&e.isActiveRoute(o,r,i,u);Object.is(c.getSnapshot(),h)||c.updateSnapshot(h)});return c},exports.createRouteNodeSource=function(t,e){let n=null;const i=function(t,e){let s=r.get(t);s||(s=new Map,r.set(t,s));let o=s.get(e);return o||(o=t.shouldUpdateNode(e),s.set(e,o)),o}(t,e),u=()=>{const t=n;n=null,t?.()},a=new s(o({route:void 0,previousRoute:void 0},t,e),{onFirstSubscribe:()=>{a.updateSnapshot(o(a.getSnapshot(),t,e)),n=t.subscribe(s=>{if(!i(s.route,s.previousRoute))return;const r=o(a.getSnapshot(),t,e,s);Object.is(a.getSnapshot(),r)||a.updateSnapshot(r)})},onLastUnsubscribe:u,onDestroy:u});return a},exports.createRouteSource=function(t){let e=null;const o=()=>{const t=e;e=null,t?.()},r=new s({route:t.getState(),previousRoute:void 0},{onFirstSubscribe:()=>{e=t.subscribe(t=>{r.updateSnapshot({route:t.route,previousRoute:t.previousRoute})})},onLastUnsubscribe:o,onDestroy:o});return r},exports.createTransitionSource=function(t){const o=new s(n,{onDestroy:()=>{u.forEach(t=>{t()})}}),r=e.getPluginApi(t),i=()=>{o.updateSnapshot(n)},u=[r.addEventListener(e.events.TRANSITION_START,(t,e)=>{o.updateSnapshot({isTransitioning:!0,toRoute:t,fromRoute:e??null})}),r.addEventListener(e.events.TRANSITION_SUCCESS,i),r.addEventListener(e.events.TRANSITION_ERROR,i),r.addEventListener(e.events.TRANSITION_CANCEL,i)];return o};//# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/createRouteSource.ts","../../src/BaseSource.ts","../../src/computeSnapshot.ts","../../src/shouldUpdateCache.ts","../../src/createRouteNodeSource.ts","../../src/createActiveRouteSource.ts","../../src/createTransitionSource.ts"],"names":["areRoutesRelated","getPluginApi","events"],"mappings":"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","file":"index.js","sourcesContent":["import type { RouteSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nclass RouteSource implements RouterSource<RouteSnapshot> {\n #routerUnsubscribe: (() => void) | null = null;\n #currentSnapshot: RouteSnapshot;\n\n readonly #listeners = new Set<() => void>();\n readonly #router: Router;\n\n constructor(router: Router) {\n this.#router = router;\n\n this.#currentSnapshot = {\n route: router.getState(),\n previousRoute: undefined,\n };\n\n this.subscribe = this.subscribe.bind(this);\n this.destroy = this.destroy.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n if (this.#listeners.size === 0) {\n // Connect to router on first subscription\n this.#routerUnsubscribe = this.#router.subscribe((next) => {\n this.#currentSnapshot = {\n route: next.route,\n previousRoute: next.previousRoute,\n };\n this.#listeners.forEach((cb) => {\n cb();\n });\n });\n }\n\n this.#listeners.add(listener);\n\n return () => {\n this.#listeners.delete(listener);\n\n if (this.#listeners.size === 0 && this.#routerUnsubscribe) {\n this.#routerUnsubscribe();\n this.#routerUnsubscribe = null;\n }\n };\n }\n\n getSnapshot(): RouteSnapshot {\n return this.#currentSnapshot;\n }\n\n destroy(): void {\n if (this.#routerUnsubscribe) {\n this.#routerUnsubscribe();\n this.#routerUnsubscribe = null;\n }\n\n this.#listeners.clear();\n }\n}\n\n/**\n * Creates a source for the full route state.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n */\nexport function createRouteSource(router: Router): RouterSource<RouteSnapshot> {\n return new RouteSource(router);\n}\n","export class BaseSource<T> {\n #currentSnapshot: T;\n #destroyed = false;\n\n readonly #listeners = new Set<() => void>();\n\n constructor(initialSnapshot: T) {\n this.#currentSnapshot = initialSnapshot;\n }\n\n subscribe(listener: () => void): () => void {\n if (this.#destroyed) {\n return () => {};\n }\n\n this.#listeners.add(listener);\n\n return () => {\n this.#listeners.delete(listener);\n };\n }\n\n getSnapshot(): T {\n return this.#currentSnapshot;\n }\n\n updateSnapshot(snapshot: T): void {\n /* v8 ignore next 2 -- @preserve: defensive guard unreachable via public API (destroy() removes router subscription first) */\n if (this.#destroyed) {\n return;\n }\n\n this.#currentSnapshot = snapshot;\n this.#listeners.forEach((listener) => {\n listener();\n });\n }\n\n destroy(): void {\n if (this.#destroyed) {\n return;\n }\n\n this.#destroyed = true;\n this.#listeners.clear();\n }\n}\n","import type { RouteNodeSnapshot } from \"./types.js\";\nimport type { Router, SubscribeState } from \"@real-router/core\";\n\nexport function computeSnapshot(\n currentSnapshot: RouteNodeSnapshot,\n router: Router,\n nodeName: string,\n next?: SubscribeState,\n): RouteNodeSnapshot {\n const currentRoute = next?.route ?? router.getState();\n const previousRoute = next?.previousRoute;\n\n const isNodeActive =\n nodeName === \"\" ||\n (currentRoute !== undefined &&\n (currentRoute.name === nodeName ||\n currentRoute.name.startsWith(`${nodeName}.`)));\n\n const route = isNodeActive ? currentRoute : undefined;\n\n if (\n route === currentSnapshot.route &&\n previousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n return { route, previousRoute };\n}\n","import type { Router, State } from \"@real-router/core\";\n\nconst shouldUpdateCache = new WeakMap<\n Router,\n Map<string, (toState: State, fromState?: State) => boolean>\n>();\n\nexport function getCachedShouldUpdate(\n router: Router,\n nodeName: string,\n): (toState: State, fromState?: State) => boolean {\n let routerCache = shouldUpdateCache.get(router);\n\n if (!routerCache) {\n routerCache = new Map();\n shouldUpdateCache.set(router, routerCache);\n }\n\n let fn = routerCache.get(nodeName);\n\n if (!fn) {\n fn = router.shouldUpdateNode(nodeName);\n routerCache.set(nodeName, fn);\n }\n\n return fn;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { computeSnapshot } from \"./computeSnapshot.js\";\nimport { getCachedShouldUpdate } from \"./shouldUpdateCache.js\";\n\nimport type { RouteNodeSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nclass RouteNodeSource implements RouterSource<RouteNodeSnapshot> {\n readonly #source: BaseSource<RouteNodeSnapshot>;\n readonly #unsubscribe: () => void;\n\n constructor(router: Router, nodeName: string) {\n const initialSnapshot: RouteNodeSnapshot = {\n route: undefined,\n previousRoute: undefined,\n };\n const computedInitial = computeSnapshot(initialSnapshot, router, nodeName);\n\n this.#source = new BaseSource(computedInitial);\n const shouldUpdate = getCachedShouldUpdate(router, nodeName);\n\n this.#unsubscribe = router.subscribe((next) => {\n if (!shouldUpdate(next.route, next.previousRoute)) {\n return;\n }\n\n const newSnapshot = computeSnapshot(\n this.#source.getSnapshot(),\n router,\n nodeName,\n next,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup guard; shouldUpdateNode filters accurately so computeSnapshot always returns new ref */\n if (!Object.is(this.#source.getSnapshot(), newSnapshot)) {\n this.#source.updateSnapshot(newSnapshot);\n }\n });\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n return this.#source.subscribe(listener);\n }\n\n getSnapshot(): RouteNodeSnapshot {\n return this.#source.getSnapshot();\n }\n\n destroy(): void {\n this.#unsubscribe();\n this.#source.destroy();\n }\n}\n\nexport function createRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n return new RouteNodeSource(router, nodeName);\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { BaseSource } from \"./BaseSource\";\n\nimport type { ActiveRouteSourceOptions, RouterSource } from \"./types.js\";\nimport type { Params, Router } from \"@real-router/core\";\n\nclass ActiveRouteSource implements RouterSource<boolean> {\n readonly #source: BaseSource<boolean>;\n readonly #unsubscribe: () => void;\n\n constructor(\n router: Router,\n routeName: string,\n params?: Params,\n options?: ActiveRouteSourceOptions,\n ) {\n const strict = options?.strict ?? false;\n const ignoreQueryParams = options?.ignoreQueryParams ?? true;\n\n const initialValue = router.isActiveRoute(\n routeName,\n params,\n strict,\n ignoreQueryParams,\n );\n\n this.#source = new BaseSource(initialValue);\n\n this.#unsubscribe = router.subscribe((next) => {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n if (!isNewRelated && !isPrevRelated) {\n return;\n }\n\n // If new route is not related, we know the route is inactive —\n // avoid calling isActiveRoute for the optimization\n const newValue = isNewRelated\n ? router.isActiveRoute(routeName, params, strict, ignoreQueryParams)\n : false;\n\n if (!Object.is(this.#source.getSnapshot(), newValue)) {\n this.#source.updateSnapshot(newValue);\n }\n });\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n return this.#source.subscribe(listener);\n }\n\n getSnapshot(): boolean {\n return this.#source.getSnapshot();\n }\n\n destroy(): void {\n this.#unsubscribe();\n this.#source.destroy();\n }\n}\n\nexport function createActiveRouteSource(\n router: Router,\n routeName: string,\n params?: Params,\n options?: ActiveRouteSourceOptions,\n): RouterSource<boolean> {\n return new ActiveRouteSource(router, routeName, params, options);\n}\n","import { getPluginApi, events } from \"@real-router/core\";\n\nimport { BaseSource } from \"./BaseSource\";\n\nimport type { RouterTransitionSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State } from \"@real-router/core\";\n\nconst IDLE_SNAPSHOT: RouterTransitionSnapshot = {\n isTransitioning: false,\n toRoute: null,\n fromRoute: null,\n};\n\nclass TransitionSource implements RouterSource<RouterTransitionSnapshot> {\n readonly #source: BaseSource<RouterTransitionSnapshot>;\n readonly #unsubs: (() => void)[];\n\n constructor(router: Router) {\n this.#source = new BaseSource(IDLE_SNAPSHOT);\n\n const api = getPluginApi(router);\n\n const resetToIdle = (): void => {\n this.#source.updateSnapshot(IDLE_SNAPSHOT);\n };\n\n this.#unsubs = [\n api.addEventListener(\n events.TRANSITION_START,\n (toState: State, fromState?: State) => {\n this.#source.updateSnapshot({\n isTransitioning: true,\n toRoute: toState,\n fromRoute: fromState ?? null,\n });\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),\n api.addEventListener(events.TRANSITION_ERROR, resetToIdle),\n api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),\n ];\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n return this.#source.subscribe(listener);\n }\n\n getSnapshot(): RouterTransitionSnapshot {\n return this.#source.getSnapshot();\n }\n\n destroy(): void {\n this.#unsubs.forEach((u) => {\n u();\n });\n this.#source.destroy();\n }\n}\n\nexport function createTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n return new TransitionSource(router);\n}\n"]}
1
+ {"version":3,"sources":["../../src/BaseSource.ts","../../src/createRouteSource.ts","../../src/computeSnapshot.ts","../../src/shouldUpdateCache.ts","../../src/createRouteNodeSource.ts","../../src/createActiveRouteSource.ts","../../src/createTransitionSource.ts"],"names":["areRoutesRelated","getPluginApi","events"],"mappings":";AAMO,IAAM,aAAN,MAAoB;AAAA,EACzB,gBAAA;AAAA,EACA,UAAA,GAAa,KAAA;AAAA,EAEJ,UAAA,uBAAiB,GAAA,EAAgB;AAAA,EACjC,iBAAA;AAAA,EACA,kBAAA;AAAA,EACA,UAAA;AAAA,EAET,WAAA,CAAY,iBAAoB,OAAA,EAA6B;AAC3D,IAAA,IAAA,CAAK,gBAAA,GAAmB,eAAA;AACxB,IAAA,IAAA,CAAK,oBAAoB,OAAA,EAAS,gBAAA;AAClC,IAAA,IAAA,CAAK,qBAAqB,OAAA,EAAS,iBAAA;AACnC,IAAA,IAAA,CAAK,aAAa,OAAA,EAAS,SAAA;AAE3B,IAAA,IAAA,CAAK,SAAA,GAAY,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA;AACzC,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA;AAC7C,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA;AAAA,EACvC;AAAA,EAEA,UAAU,QAAA,EAAkC;AAC1C,IAAA,IAAI,KAAK,UAAA,EAAY;AACnB,MAAA,OAAO,MAAM;AAAA,MAAC,CAAA;AAAA,IAChB;AAEA,IAAA,IAAI,IAAA,CAAK,UAAA,CAAW,IAAA,KAAS,CAAA,IAAK,KAAK,iBAAA,EAAmB;AACxD,MAAA,IAAA,CAAK,iBAAA,EAAkB;AAAA,IACzB;AAEA,IAAA,IAAA,CAAK,UAAA,CAAW,IAAI,QAAQ,CAAA;AAE5B,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,UAAA,CAAW,OAAO,QAAQ,CAAA;AAE/B,MAAA,IACE,CAAC,KAAK,UAAA,IACN,IAAA,CAAK,WAAW,IAAA,KAAS,CAAA,IACzB,KAAK,kBAAA,EACL;AACA,QAAA,IAAA,CAAK,kBAAA,EAAmB;AAAA,MAC1B;AAAA,IACF,CAAA;AAAA,EACF;AAAA,EAEA,WAAA,GAAiB;AACf,IAAA,OAAO,IAAA,CAAK,gBAAA;AAAA,EACd;AAAA,EAEA,eAAe,QAAA,EAAmB;AAEhC,IAAA,IAAI,KAAK,UAAA,EAAY;AACnB,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,gBAAA,GAAmB,QAAA;AACxB,IAAA,IAAA,CAAK,UAAA,CAAW,OAAA,CAAQ,CAAC,QAAA,KAAa;AACpC,MAAA,QAAA,EAAS;AAAA,IACX,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,IAAI,KAAK,UAAA,EAAY;AACnB,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAClB,IAAA,IAAA,CAAK,UAAA,IAAa;AAClB,IAAA,IAAA,CAAK,WAAW,KAAA,EAAM;AAAA,EACxB;AACF,CAAA;;;AC/DO,SAAS,kBAAkB,MAAA,EAA6C;AAC7E,EAAA,IAAI,iBAAA,GAAyC,IAAA;AAE7C,EAAA,MAAM,aAAa,MAAY;AAC7B,IAAA,MAAM,KAAA,GAAQ,iBAAA;AAEd,IAAA,iBAAA,GAAoB,IAAA;AACpB,IAAA,KAAA,IAAQ;AAAA,EACV,CAAA;AAEA,EAAA,MAAM,SAAS,IAAI,UAAA;AAAA,IACjB;AAAA,MACE,KAAA,EAAO,OAAO,QAAA,EAAS;AAAA,MACvB,aAAA,EAAe;AAAA,KACjB;AAAA,IACA;AAAA,MACE,kBAAkB,MAAM;AACtB,QAAA,iBAAA,GAAoB,MAAA,CAAO,SAAA,CAAU,CAAC,IAAA,KAAS;AAC7C,UAAA,MAAA,CAAO,cAAA,CAAe;AAAA,YACpB,OAAO,IAAA,CAAK,KAAA;AAAA,YACZ,eAAe,IAAA,CAAK;AAAA,WACrB,CAAA;AAAA,QACH,CAAC,CAAA;AAAA,MACH,CAAA;AAAA,MACA,iBAAA,EAAmB,UAAA;AAAA,MACnB,SAAA,EAAW;AAAA;AACb,GACF;AAEA,EAAA,OAAO,MAAA;AACT;;;ACvCO,SAAS,eAAA,CACd,eAAA,EACA,MAAA,EACA,QAAA,EACA,IAAA,EACmB;AACnB,EAAA,MAAM,YAAA,GAAe,IAAA,EAAM,KAAA,IAAS,MAAA,CAAO,QAAA,EAAS;AACpD,EAAA,MAAM,gBAAgB,IAAA,EAAM,aAAA;AAE5B,EAAA,MAAM,YAAA,GACJ,QAAA,KAAa,EAAA,IACZ,YAAA,KAAiB,MAAA,KACf,YAAA,CAAa,IAAA,KAAS,QAAA,IACrB,YAAA,CAAa,IAAA,CAAK,UAAA,CAAW,CAAA,EAAG,QAAQ,CAAA,CAAA,CAAG,CAAA,CAAA;AAEjD,EAAA,MAAM,KAAA,GAAQ,eAAe,YAAA,GAAe,MAAA;AAE5C,EAAA,IACE,KAAA,KAAU,eAAA,CAAgB,KAAA,IAC1B,aAAA,KAAkB,gBAAgB,aAAA,EAClC;AACA,IAAA,OAAO,eAAA;AAAA,EACT;AAEA,EAAA,OAAO,EAAE,OAAO,aAAA,EAAc;AAChC;;;AC1BA,IAAM,iBAAA,uBAAwB,OAAA,EAG5B;AAEK,SAAS,qBAAA,CACd,QACA,QAAA,EACgD;AAChD,EAAA,IAAI,WAAA,GAAc,iBAAA,CAAkB,GAAA,CAAI,MAAM,CAAA;AAE9C,EAAA,IAAI,CAAC,WAAA,EAAa;AAChB,IAAA,WAAA,uBAAkB,GAAA,EAAI;AACtB,IAAA,iBAAA,CAAkB,GAAA,CAAI,QAAQ,WAAW,CAAA;AAAA,EAC3C;AAEA,EAAA,IAAI,EAAA,GAAK,WAAA,CAAY,GAAA,CAAI,QAAQ,CAAA;AAEjC,EAAA,IAAI,CAAC,EAAA,EAAI;AACP,IAAA,EAAA,GAAK,MAAA,CAAO,iBAAiB,QAAQ,CAAA;AACrC,IAAA,WAAA,CAAY,GAAA,CAAI,UAAU,EAAE,CAAA;AAAA,EAC9B;AAEA,EAAA,OAAO,EAAA;AACT;;;ACZO,SAAS,qBAAA,CACd,QACA,QAAA,EACiC;AACjC,EAAA,IAAI,iBAAA,GAAyC,IAAA;AAE7C,EAAA,MAAM,YAAA,GAAe,qBAAA,CAAsB,MAAA,EAAQ,QAAQ,CAAA;AAE3D,EAAA,MAAM,eAAA,GAAqC;AAAA,IACzC,KAAA,EAAO,MAAA;AAAA,IACP,aAAA,EAAe;AAAA,GACjB;AAEA,EAAA,MAAM,aAAa,MAAY;AAC7B,IAAA,MAAM,KAAA,GAAQ,iBAAA;AAEd,IAAA,iBAAA,GAAoB,IAAA;AACpB,IAAA,KAAA,IAAQ;AAAA,EACV,CAAA;AAEA,EAAA,MAAM,SAAS,IAAI,UAAA;AAAA,IACjB,eAAA,CAAgB,eAAA,EAAiB,MAAA,EAAQ,QAAQ,CAAA;AAAA,IACjD;AAAA,MACE,kBAAkB,MAAM;AAItB,QAAA,MAAA,CAAO,cAAA;AAAA,UACL,eAAA,CAAgB,MAAA,CAAO,WAAA,EAAY,EAAG,QAAQ,QAAQ;AAAA,SACxD;AAGA,QAAA,iBAAA,GAAoB,MAAA,CAAO,SAAA,CAAU,CAAC,IAAA,KAAS;AAC7C,UAAA,IAAI,CAAC,YAAA,CAAa,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,aAAa,CAAA,EAAG;AACjD,YAAA;AAAA,UACF;AAEA,UAAA,MAAM,WAAA,GAAc,eAAA;AAAA,YAClB,OAAO,WAAA,EAAY;AAAA,YACnB,MAAA;AAAA,YACA,QAAA;AAAA,YACA;AAAA,WACF;AAGA,UAAA,IAAI,CAAC,MAAA,CAAO,EAAA,CAAG,OAAO,WAAA,EAAY,EAAG,WAAW,CAAA,EAAG;AACjD,YAAA,MAAA,CAAO,eAAe,WAAW,CAAA;AAAA,UACnC;AAAA,QACF,CAAC,CAAA;AAAA,MACH,CAAA;AAAA,MACA,iBAAA,EAAmB,UAAA;AAAA,MACnB,SAAA,EAAW;AAAA;AACb,GACF;AAEA,EAAA,OAAO,MAAA;AACT;AC/DO,SAAS,uBAAA,CACd,MAAA,EACA,SAAA,EACA,MAAA,EACA,OAAA,EACuB;AACvB,EAAA,MAAM,MAAA,GAAS,SAAS,MAAA,IAAU,KAAA;AAClC,EAAA,MAAM,iBAAA,GAAoB,SAAS,iBAAA,IAAqB,IAAA;AAExD,EAAA,MAAM,eAAe,MAAA,CAAO,aAAA;AAAA,IAC1B,SAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,MAAA,GAAS,IAAI,UAAA,CAAW,YAAA,EAAc;AAAA,IAC1C,WAAW,MAAM;AACf,MAAA,WAAA,EAAY;AAAA,IACd;AAAA,GACD,CAAA;AAGD,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,SAAA,CAAU,CAAC,IAAA,KAAS;AAC7C,IAAA,MAAM,YAAA,GAAeA,2BAAA,CAAiB,SAAA,EAAW,IAAA,CAAK,MAAM,IAAI,CAAA;AAChE,IAAA,MAAM,gBACJ,IAAA,CAAK,aAAA,IACLA,4BAAiB,SAAA,EAAW,IAAA,CAAK,cAAc,IAAI,CAAA;AAErD,IAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,aAAA,EAAe;AACnC,MAAA;AAAA,IACF;AAIA,IAAA,MAAM,QAAA,GAAW,eACb,MAAA,CAAO,aAAA,CAAc,WAAW,MAAA,EAAQ,MAAA,EAAQ,iBAAiB,CAAA,GACjE,KAAA;AAEJ,IAAA,IAAI,CAAC,MAAA,CAAO,EAAA,CAAG,OAAO,WAAA,EAAY,EAAG,QAAQ,CAAA,EAAG;AAC9C,MAAA,MAAA,CAAO,eAAe,QAAQ,CAAA;AAAA,IAChC;AAAA,EACF,CAAC,CAAA;AAED,EAAA,OAAO,MAAA;AACT;AC7CA,IAAM,aAAA,GAA0C;AAAA,EAC9C,eAAA,EAAiB,KAAA;AAAA,EACjB,OAAA,EAAS,IAAA;AAAA,EACT,SAAA,EAAW;AACb,CAAA;AAEO,SAAS,uBACd,MAAA,EACwC;AACxC,EAAA,MAAM,MAAA,GAAS,IAAI,UAAA,CAAW,aAAA,EAAe;AAAA,IAC3C,WAAW,MAAM;AACf,MAAA,MAAA,CAAO,OAAA,CAAQ,CAAC,CAAA,KAAM;AACpB,QAAA,CAAA,EAAE;AAAA,MACJ,CAAC,CAAA;AAAA,IACH;AAAA,GACD,CAAA;AAED,EAAA,MAAM,GAAA,GAAMC,kBAAa,MAAM,CAAA;AAE/B,EAAA,MAAM,cAAc,MAAY;AAC9B,IAAA,MAAA,CAAO,eAAe,aAAa,CAAA;AAAA,EACrC,CAAA;AAGA,EAAA,MAAM,MAAA,GAAS;AAAA,IACb,GAAA,CAAI,gBAAA;AAAA,MACFC,WAAA,CAAO,gBAAA;AAAA,MACP,CAAC,SAAgB,SAAA,KAAsB;AACrC,QAAA,MAAA,CAAO,cAAA,CAAe;AAAA,UACpB,eAAA,EAAiB,IAAA;AAAA,UACjB,OAAA,EAAS,OAAA;AAAA,UACT,WAAW,SAAA,IAAa;AAAA,SACzB,CAAA;AAAA,MACH;AAAA,KACF;AAAA,IACA,GAAA,CAAI,gBAAA,CAAiBA,WAAA,CAAO,kBAAA,EAAoB,WAAW,CAAA;AAAA,IAC3D,GAAA,CAAI,gBAAA,CAAiBA,WAAA,CAAO,gBAAA,EAAkB,WAAW,CAAA;AAAA,IACzD,GAAA,CAAI,gBAAA,CAAiBA,WAAA,CAAO,iBAAA,EAAmB,WAAW;AAAA,GAC5D;AAEA,EAAA,OAAO,MAAA;AACT","file":"index.js","sourcesContent":["export interface BaseSourceOptions {\n onFirstSubscribe?: () => void;\n onLastUnsubscribe?: () => void;\n onDestroy?: () => void;\n}\n\nexport class BaseSource<T> {\n #currentSnapshot: T;\n #destroyed = false;\n\n readonly #listeners = new Set<() => void>();\n readonly #onFirstSubscribe: (() => void) | undefined;\n readonly #onLastUnsubscribe: (() => void) | undefined;\n readonly #onDestroy: (() => void) | undefined;\n\n constructor(initialSnapshot: T, options?: BaseSourceOptions) {\n this.#currentSnapshot = initialSnapshot;\n this.#onFirstSubscribe = options?.onFirstSubscribe;\n this.#onLastUnsubscribe = options?.onLastUnsubscribe;\n this.#onDestroy = options?.onDestroy;\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n if (this.#destroyed) {\n return () => {};\n }\n\n if (this.#listeners.size === 0 && this.#onFirstSubscribe) {\n this.#onFirstSubscribe();\n }\n\n this.#listeners.add(listener);\n\n return () => {\n this.#listeners.delete(listener);\n\n if (\n !this.#destroyed &&\n this.#listeners.size === 0 &&\n this.#onLastUnsubscribe\n ) {\n this.#onLastUnsubscribe();\n }\n };\n }\n\n getSnapshot(): T {\n return this.#currentSnapshot;\n }\n\n updateSnapshot(snapshot: T): void {\n /* v8 ignore next 2 -- @preserve: defensive guard unreachable via public API (destroy() removes router subscription first) */\n if (this.#destroyed) {\n return;\n }\n\n this.#currentSnapshot = snapshot;\n this.#listeners.forEach((listener) => {\n listener();\n });\n }\n\n destroy(): void {\n if (this.#destroyed) {\n return;\n }\n\n this.#destroyed = true;\n this.#onDestroy?.();\n this.#listeners.clear();\n }\n}\n","import { BaseSource } from \"./BaseSource\";\n\nimport type { RouteSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\n/**\n * Creates a source for the full route state.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n */\nexport function createRouteSource(router: Router): RouterSource<RouteSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteSnapshot>(\n {\n route: router.getState(),\n previousRoute: undefined,\n },\n {\n onFirstSubscribe: () => {\n routerUnsubscribe = router.subscribe((next) => {\n source.updateSnapshot({\n route: next.route,\n previousRoute: next.previousRoute,\n });\n });\n },\n onLastUnsubscribe: disconnect,\n onDestroy: disconnect,\n },\n );\n\n return source;\n}\n","import type { RouteNodeSnapshot } from \"./types.js\";\nimport type { Router, SubscribeState } from \"@real-router/core\";\n\nexport function computeSnapshot(\n currentSnapshot: RouteNodeSnapshot,\n router: Router,\n nodeName: string,\n next?: SubscribeState,\n): RouteNodeSnapshot {\n const currentRoute = next?.route ?? router.getState();\n const previousRoute = next?.previousRoute;\n\n const isNodeActive =\n nodeName === \"\" ||\n (currentRoute !== undefined &&\n (currentRoute.name === nodeName ||\n currentRoute.name.startsWith(`${nodeName}.`)));\n\n const route = isNodeActive ? currentRoute : undefined;\n\n if (\n route === currentSnapshot.route &&\n previousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n return { route, previousRoute };\n}\n","import type { Router, State } from \"@real-router/core\";\n\nconst shouldUpdateCache = new WeakMap<\n Router,\n Map<string, (toState: State, fromState?: State) => boolean>\n>();\n\nexport function getCachedShouldUpdate(\n router: Router,\n nodeName: string,\n): (toState: State, fromState?: State) => boolean {\n let routerCache = shouldUpdateCache.get(router);\n\n if (!routerCache) {\n routerCache = new Map();\n shouldUpdateCache.set(router, routerCache);\n }\n\n let fn = routerCache.get(nodeName);\n\n if (!fn) {\n fn = router.shouldUpdateNode(nodeName);\n routerCache.set(nodeName, fn);\n }\n\n return fn;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { computeSnapshot } from \"./computeSnapshot.js\";\nimport { getCachedShouldUpdate } from \"./shouldUpdateCache.js\";\n\nimport type { RouteNodeSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\n/**\n * Creates a source scoped to a specific route node.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n */\nexport function createRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n const shouldUpdate = getCachedShouldUpdate(router, nodeName);\n\n const initialSnapshot: RouteNodeSnapshot = {\n route: undefined,\n previousRoute: undefined,\n };\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteNodeSnapshot>(\n computeSnapshot(initialSnapshot, router, nodeName),\n {\n onFirstSubscribe: () => {\n // Reconcile snapshot with current router state before connecting.\n // Covers reconnection after Activity hide/show cycles where the\n // source was disconnected and missed navigation events.\n source.updateSnapshot(\n computeSnapshot(source.getSnapshot(), router, nodeName),\n );\n\n // Connect to router on first subscription\n routerUnsubscribe = router.subscribe((next) => {\n if (!shouldUpdate(next.route, next.previousRoute)) {\n return;\n }\n\n const newSnapshot = computeSnapshot(\n source.getSnapshot(),\n router,\n nodeName,\n next,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup guard; shouldUpdateNode filters accurately so computeSnapshot always returns new ref */\n if (!Object.is(source.getSnapshot(), newSnapshot)) {\n source.updateSnapshot(newSnapshot);\n }\n });\n },\n onLastUnsubscribe: disconnect,\n onDestroy: disconnect,\n },\n );\n\n return source;\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { BaseSource } from \"./BaseSource\";\n\nimport type { ActiveRouteSourceOptions, RouterSource } from \"./types.js\";\nimport type { Params, Router } from \"@real-router/core\";\n\nexport function createActiveRouteSource(\n router: Router,\n routeName: string,\n params?: Params,\n options?: ActiveRouteSourceOptions,\n): RouterSource<boolean> {\n const strict = options?.strict ?? false;\n const ignoreQueryParams = options?.ignoreQueryParams ?? true;\n\n const initialValue = router.isActiveRoute(\n routeName,\n params,\n strict,\n ignoreQueryParams,\n );\n\n const source = new BaseSource(initialValue, {\n onDestroy: () => {\n unsubscribe();\n },\n });\n\n // Eager connection: subscribe to router immediately\n const unsubscribe = router.subscribe((next) => {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n if (!isNewRelated && !isPrevRelated) {\n return;\n }\n\n // If new route is not related, we know the route is inactive —\n // avoid calling isActiveRoute for the optimization\n const newValue = isNewRelated\n ? router.isActiveRoute(routeName, params, strict, ignoreQueryParams)\n : false;\n\n if (!Object.is(source.getSnapshot(), newValue)) {\n source.updateSnapshot(newValue);\n }\n });\n\n return source;\n}\n","import { getPluginApi, events } from \"@real-router/core\";\n\nimport { BaseSource } from \"./BaseSource\";\n\nimport type { RouterTransitionSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State } from \"@real-router/core\";\n\nconst IDLE_SNAPSHOT: RouterTransitionSnapshot = {\n isTransitioning: false,\n toRoute: null,\n fromRoute: null,\n};\n\nexport function createTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n const source = new BaseSource(IDLE_SNAPSHOT, {\n onDestroy: () => {\n unsubs.forEach((u) => {\n u();\n });\n },\n });\n\n const api = getPluginApi(router);\n\n const resetToIdle = (): void => {\n source.updateSnapshot(IDLE_SNAPSHOT);\n };\n\n // Eager connection: subscribe to router events immediately\n const unsubs = [\n api.addEventListener(\n events.TRANSITION_START,\n (toState: State, fromState?: State) => {\n source.updateSnapshot({\n isTransitioning: true,\n toRoute: toState,\n fromRoute: fromState ?? null,\n });\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),\n api.addEventListener(events.TRANSITION_ERROR, resetToIdle),\n api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),\n ];\n\n return source;\n}\n"]}
@@ -1 +1 @@
1
- {"inputs":{"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js":{"bytes":569,"imports":[],"format":"esm"},"src/createRouteSource.ts":{"bytes":1956,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/BaseSource.ts":{"bytes":941,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/computeSnapshot.ts":{"bytes":790,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/shouldUpdateCache.ts":{"bytes":610,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/createRouteNodeSource.ts":{"bytes":1911,"imports":[{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"},{"path":"src/computeSnapshot.ts","kind":"import-statement","original":"./computeSnapshot.js"},{"path":"src/shouldUpdateCache.ts","kind":"import-statement","original":"./shouldUpdateCache.js"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/createActiveRouteSource.ts":{"bytes":2124,"imports":[{"path":"@real-router/route-utils","kind":"import-statement","external":true},{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/createTransitionSource.ts":{"bytes":1845,"imports":[{"path":"@real-router/core","kind":"import-statement","external":true},{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":407,"imports":[{"path":"src/createRouteSource.ts","kind":"import-statement","original":"./createRouteSource"},{"path":"src/createRouteNodeSource.ts","kind":"import-statement","original":"./createRouteNodeSource"},{"path":"src/createActiveRouteSource.ts","kind":"import-statement","original":"./createActiveRouteSource"},{"path":"src/createTransitionSource.ts","kind":"import-statement","original":"./createTransitionSource"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/cjs/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":15986},"dist/cjs/index.js":{"imports":[{"path":"@real-router/route-utils","kind":"import-statement","external":true},{"path":"@real-router/core","kind":"import-statement","external":true}],"exports":["createActiveRouteSource","createRouteNodeSource","createRouteSource","createTransitionSource"],"entryPoint":"src/index.ts","inputs":{"src/createRouteSource.ts":{"bytesInOutput":1303},"src/index.ts":{"bytesInOutput":0},"src/BaseSource.ts":{"bytesInOutput":752},"src/computeSnapshot.ts":{"bytesInOutput":536},"src/shouldUpdateCache.ts":{"bytesInOutput":425},"src/createRouteNodeSource.ts":{"bytesInOutput":1234},"src/createActiveRouteSource.ts":{"bytesInOutput":1490},"src/createTransitionSource.ts":{"bytesInOutput":1367}},"bytes":7679}}}
1
+ {"inputs":{"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js":{"bytes":569,"imports":[],"format":"esm"},"src/BaseSource.ts":{"bytes":1857,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/createRouteSource.ts":{"bytes":1168,"imports":[{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/computeSnapshot.ts":{"bytes":790,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/shouldUpdateCache.ts":{"bytes":610,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/createRouteNodeSource.ts":{"bytes":2245,"imports":[{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"},{"path":"src/computeSnapshot.ts","kind":"import-statement","original":"./computeSnapshot.js"},{"path":"src/shouldUpdateCache.ts","kind":"import-statement","original":"./shouldUpdateCache.js"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/createActiveRouteSource.ts":{"bytes":1461,"imports":[{"path":"@real-router/route-utils","kind":"import-statement","external":true},{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/createTransitionSource.ts":{"bytes":1278,"imports":[{"path":"@real-router/core","kind":"import-statement","external":true},{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":407,"imports":[{"path":"src/createRouteSource.ts","kind":"import-statement","original":"./createRouteSource"},{"path":"src/createRouteNodeSource.ts","kind":"import-statement","original":"./createRouteNodeSource"},{"path":"src/createActiveRouteSource.ts","kind":"import-statement","original":"./createActiveRouteSource"},{"path":"src/createTransitionSource.ts","kind":"import-statement","original":"./createTransitionSource"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/cjs/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":14149},"dist/cjs/index.js":{"imports":[{"path":"@real-router/route-utils","kind":"import-statement","external":true},{"path":"@real-router/core","kind":"import-statement","external":true}],"exports":["createActiveRouteSource","createRouteNodeSource","createRouteSource","createTransitionSource"],"entryPoint":"src/index.ts","inputs":{"src/BaseSource.ts":{"bytesInOutput":1375},"src/createRouteSource.ts":{"bytesInOutput":627},"src/index.ts":{"bytesInOutput":0},"src/computeSnapshot.ts":{"bytesInOutput":536},"src/shouldUpdateCache.ts":{"bytesInOutput":425},"src/createRouteNodeSource.ts":{"bytesInOutput":1120},"src/createActiveRouteSource.ts":{"bytesInOutput":977},"src/createTransitionSource.ts":{"bytesInOutput":921}},"bytes":6553}}}
@@ -32,6 +32,13 @@ interface RouterTransitionSnapshot {
32
32
  */
33
33
  declare function createRouteSource(router: Router): RouterSource<RouteSnapshot>;
34
34
 
35
+ /**
36
+ * Creates a source scoped to a specific route node.
37
+ *
38
+ * Uses a lazy-connection pattern: the router subscription is created when the
39
+ * first listener subscribes and removed when the last listener unsubscribes.
40
+ * This is compatible with React's useSyncExternalStore and Strict Mode.
41
+ */
35
42
  declare function createRouteNodeSource(router: Router, nodeName: string): RouterSource<RouteNodeSnapshot>;
36
43
 
37
44
  declare function createActiveRouteSource(router: Router, routeName: string, params?: Params, options?: ActiveRouteSourceOptions): RouterSource<boolean>;
@@ -1 +1 @@
1
- import{areRoutesRelated as s}from"@real-router/route-utils";import{getPluginApi as t,events as e}from"@real-router/core";var r=class{#s=null;#t;#e=new Set;#r;constructor(s){this.#r=s,this.#t={route:s.getState(),previousRoute:void 0},this.subscribe=this.subscribe.bind(this),this.destroy=this.destroy.bind(this),this.getSnapshot=this.getSnapshot.bind(this)}subscribe(s){return 0===this.#e.size&&(this.#s=this.#r.subscribe(s=>{this.#t={route:s.route,previousRoute:s.previousRoute},this.#e.forEach(s=>{s()})})),this.#e.add(s),()=>{this.#e.delete(s),0===this.#e.size&&this.#s&&(this.#s(),this.#s=null)}}getSnapshot(){return this.#t}destroy(){this.#s&&(this.#s(),this.#s=null),this.#e.clear()}};function i(s){return new r(s)}var o=class{#t;#i=!1;#e=new Set;constructor(s){this.#t=s}subscribe(s){return this.#i?()=>{}:(this.#e.add(s),()=>{this.#e.delete(s)})}getSnapshot(){return this.#t}updateSnapshot(s){this.#i||(this.#t=s,this.#e.forEach(s=>{s()}))}destroy(){this.#i||(this.#i=!0,this.#e.clear())}};function u(s,t,e,r){const i=r?.route??t.getState(),o=r?.previousRoute,u=""===e||void 0!==i&&(i.name===e||i.name.startsWith(`${e}.`))?i:void 0;return u===s.route&&o===s.previousRoute?s:{route:u,previousRoute:o}}var n=new WeakMap,h=class{#o;#u;constructor(s,t){const e=u({route:void 0,previousRoute:void 0},s,t);this.#o=new o(e);const r=function(s,t){let e=n.get(s);e||(e=new Map,n.set(s,e));let r=e.get(t);return r||(r=s.shouldUpdateNode(t),e.set(t,r)),r}(s,t);this.#u=s.subscribe(e=>{if(!r(e.route,e.previousRoute))return;const i=u(this.#o.getSnapshot(),s,t,e);Object.is(this.#o.getSnapshot(),i)||this.#o.updateSnapshot(i)}),this.subscribe=this.subscribe.bind(this),this.getSnapshot=this.getSnapshot.bind(this),this.destroy=this.destroy.bind(this)}subscribe(s){return this.#o.subscribe(s)}getSnapshot(){return this.#o.getSnapshot()}destroy(){this.#u(),this.#o.destroy()}};function c(s,t){return new h(s,t)}var b=class{#o;#u;constructor(t,e,r,i){const u=i?.strict??!1,n=i?.ignoreQueryParams??!0,h=t.isActiveRoute(e,r,u,n);this.#o=new o(h),this.#u=t.subscribe(i=>{const o=s(e,i.route.name),h=i.previousRoute&&s(e,i.previousRoute.name);if(!o&&!h)return;const c=!!o&&t.isActiveRoute(e,r,u,n);Object.is(this.#o.getSnapshot(),c)||this.#o.updateSnapshot(c)}),this.subscribe=this.subscribe.bind(this),this.getSnapshot=this.getSnapshot.bind(this),this.destroy=this.destroy.bind(this)}subscribe(s){return this.#o.subscribe(s)}getSnapshot(){return this.#o.getSnapshot()}destroy(){this.#u(),this.#o.destroy()}};function a(s,t,e,r){return new b(s,t,e,r)}var d={isTransitioning:!1,toRoute:null,fromRoute:null},p=class{#o;#n;constructor(s){this.#o=new o(d);const r=t(s),i=()=>{this.#o.updateSnapshot(d)};this.#n=[r.addEventListener(e.TRANSITION_START,(s,t)=>{this.#o.updateSnapshot({isTransitioning:!0,toRoute:s,fromRoute:t??null})}),r.addEventListener(e.TRANSITION_SUCCESS,i),r.addEventListener(e.TRANSITION_ERROR,i),r.addEventListener(e.TRANSITION_CANCEL,i)],this.subscribe=this.subscribe.bind(this),this.getSnapshot=this.getSnapshot.bind(this),this.destroy=this.destroy.bind(this)}subscribe(s){return this.#o.subscribe(s)}getSnapshot(){return this.#o.getSnapshot()}destroy(){this.#n.forEach(s=>{s()}),this.#o.destroy()}};function S(s){return new p(s)}export{a as createActiveRouteSource,c as createRouteNodeSource,i as createRouteSource,S as createTransitionSource};//# sourceMappingURL=index.mjs.map
1
+ import{areRoutesRelated as t}from"@real-router/route-utils";import{getPluginApi as e,events as s}from"@real-router/core";var o=class{#t;#e=!1;#s=new Set;#o;#r;#n;constructor(t,e){this.#t=t,this.#o=e?.onFirstSubscribe,this.#r=e?.onLastUnsubscribe,this.#n=e?.onDestroy,this.subscribe=this.subscribe.bind(this),this.getSnapshot=this.getSnapshot.bind(this),this.destroy=this.destroy.bind(this)}subscribe(t){return this.#e?()=>{}:(0===this.#s.size&&this.#o&&this.#o(),this.#s.add(t),()=>{this.#s.delete(t),!this.#e&&0===this.#s.size&&this.#r&&this.#r()})}getSnapshot(){return this.#t}updateSnapshot(t){this.#e||(this.#t=t,this.#s.forEach(t=>{t()}))}destroy(){this.#e||(this.#e=!0,this.#n?.(),this.#s.clear())}};function r(t){let e=null;const s=()=>{const t=e;e=null,t?.()},r=new o({route:t.getState(),previousRoute:void 0},{onFirstSubscribe:()=>{e=t.subscribe(t=>{r.updateSnapshot({route:t.route,previousRoute:t.previousRoute})})},onLastUnsubscribe:s,onDestroy:s});return r}function n(t,e,s,o){const r=o?.route??e.getState(),n=o?.previousRoute,i=""===s||void 0!==r&&(r.name===s||r.name.startsWith(`${s}.`))?r:void 0;return i===t.route&&n===t.previousRoute?t:{route:i,previousRoute:n}}var i=new WeakMap;function u(t,e){let s=null;const r=function(t,e){let s=i.get(t);s||(s=new Map,i.set(t,s));let o=s.get(e);return o||(o=t.shouldUpdateNode(e),s.set(e,o)),o}(t,e),u=()=>{const t=s;s=null,t?.()},a=new o(n({route:void 0,previousRoute:void 0},t,e),{onFirstSubscribe:()=>{a.updateSnapshot(n(a.getSnapshot(),t,e)),s=t.subscribe(s=>{if(!r(s.route,s.previousRoute))return;const o=n(a.getSnapshot(),t,e,s);Object.is(a.getSnapshot(),o)||a.updateSnapshot(o)})},onLastUnsubscribe:u,onDestroy:u});return a}function a(e,s,r,n){const i=n?.strict??!1,u=n?.ignoreQueryParams??!0,a=e.isActiveRoute(s,r,i,u),h=new o(a,{onDestroy:()=>{c()}}),c=e.subscribe(o=>{const n=t(s,o.route.name),a=o.previousRoute&&t(s,o.previousRoute.name);if(!n&&!a)return;const c=!!n&&e.isActiveRoute(s,r,i,u);Object.is(h.getSnapshot(),c)||h.updateSnapshot(c)});return h}var h={isTransitioning:!1,toRoute:null,fromRoute:null};function c(t){const r=new o(h,{onDestroy:()=>{u.forEach(t=>{t()})}}),n=e(t),i=()=>{r.updateSnapshot(h)},u=[n.addEventListener(s.TRANSITION_START,(t,e)=>{r.updateSnapshot({isTransitioning:!0,toRoute:t,fromRoute:e??null})}),n.addEventListener(s.TRANSITION_SUCCESS,i),n.addEventListener(s.TRANSITION_ERROR,i),n.addEventListener(s.TRANSITION_CANCEL,i)];return r}export{a as createActiveRouteSource,u as createRouteNodeSource,r as createRouteSource,c as createTransitionSource};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/createRouteSource.ts","../../src/BaseSource.ts","../../src/computeSnapshot.ts","../../src/shouldUpdateCache.ts","../../src/createRouteNodeSource.ts","../../src/createActiveRouteSource.ts","../../src/createTransitionSource.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { RouteSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nclass RouteSource implements RouterSource<RouteSnapshot> {\n #routerUnsubscribe: (() => void) | null = null;\n #currentSnapshot: RouteSnapshot;\n\n readonly #listeners = new Set<() => void>();\n readonly #router: Router;\n\n constructor(router: Router) {\n this.#router = router;\n\n this.#currentSnapshot = {\n route: router.getState(),\n previousRoute: undefined,\n };\n\n this.subscribe = this.subscribe.bind(this);\n this.destroy = this.destroy.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n if (this.#listeners.size === 0) {\n // Connect to router on first subscription\n this.#routerUnsubscribe = this.#router.subscribe((next) => {\n this.#currentSnapshot = {\n route: next.route,\n previousRoute: next.previousRoute,\n };\n this.#listeners.forEach((cb) => {\n cb();\n });\n });\n }\n\n this.#listeners.add(listener);\n\n return () => {\n this.#listeners.delete(listener);\n\n if (this.#listeners.size === 0 && this.#routerUnsubscribe) {\n this.#routerUnsubscribe();\n this.#routerUnsubscribe = null;\n }\n };\n }\n\n getSnapshot(): RouteSnapshot {\n return this.#currentSnapshot;\n }\n\n destroy(): void {\n if (this.#routerUnsubscribe) {\n this.#routerUnsubscribe();\n this.#routerUnsubscribe = null;\n }\n\n this.#listeners.clear();\n }\n}\n\n/**\n * Creates a source for the full route state.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n */\nexport function createRouteSource(router: Router): RouterSource<RouteSnapshot> {\n return new RouteSource(router);\n}\n","export class BaseSource<T> {\n #currentSnapshot: T;\n #destroyed = false;\n\n readonly #listeners = new Set<() => void>();\n\n constructor(initialSnapshot: T) {\n this.#currentSnapshot = initialSnapshot;\n }\n\n subscribe(listener: () => void): () => void {\n if (this.#destroyed) {\n return () => {};\n }\n\n this.#listeners.add(listener);\n\n return () => {\n this.#listeners.delete(listener);\n };\n }\n\n getSnapshot(): T {\n return this.#currentSnapshot;\n }\n\n updateSnapshot(snapshot: T): void {\n /* v8 ignore next 2 -- @preserve: defensive guard unreachable via public API (destroy() removes router subscription first) */\n if (this.#destroyed) {\n return;\n }\n\n this.#currentSnapshot = snapshot;\n this.#listeners.forEach((listener) => {\n listener();\n });\n }\n\n destroy(): void {\n if (this.#destroyed) {\n return;\n }\n\n this.#destroyed = true;\n this.#listeners.clear();\n }\n}\n","import type { RouteNodeSnapshot } from \"./types.js\";\nimport type { Router, SubscribeState } from \"@real-router/core\";\n\nexport function computeSnapshot(\n currentSnapshot: RouteNodeSnapshot,\n router: Router,\n nodeName: string,\n next?: SubscribeState,\n): RouteNodeSnapshot {\n const currentRoute = next?.route ?? router.getState();\n const previousRoute = next?.previousRoute;\n\n const isNodeActive =\n nodeName === \"\" ||\n (currentRoute !== undefined &&\n (currentRoute.name === nodeName ||\n currentRoute.name.startsWith(`${nodeName}.`)));\n\n const route = isNodeActive ? currentRoute : undefined;\n\n if (\n route === currentSnapshot.route &&\n previousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n return { route, previousRoute };\n}\n","import type { Router, State } from \"@real-router/core\";\n\nconst shouldUpdateCache = new WeakMap<\n Router,\n Map<string, (toState: State, fromState?: State) => boolean>\n>();\n\nexport function getCachedShouldUpdate(\n router: Router,\n nodeName: string,\n): (toState: State, fromState?: State) => boolean {\n let routerCache = shouldUpdateCache.get(router);\n\n if (!routerCache) {\n routerCache = new Map();\n shouldUpdateCache.set(router, routerCache);\n }\n\n let fn = routerCache.get(nodeName);\n\n if (!fn) {\n fn = router.shouldUpdateNode(nodeName);\n routerCache.set(nodeName, fn);\n }\n\n return fn;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { computeSnapshot } from \"./computeSnapshot.js\";\nimport { getCachedShouldUpdate } from \"./shouldUpdateCache.js\";\n\nimport type { RouteNodeSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nclass RouteNodeSource implements RouterSource<RouteNodeSnapshot> {\n readonly #source: BaseSource<RouteNodeSnapshot>;\n readonly #unsubscribe: () => void;\n\n constructor(router: Router, nodeName: string) {\n const initialSnapshot: RouteNodeSnapshot = {\n route: undefined,\n previousRoute: undefined,\n };\n const computedInitial = computeSnapshot(initialSnapshot, router, nodeName);\n\n this.#source = new BaseSource(computedInitial);\n const shouldUpdate = getCachedShouldUpdate(router, nodeName);\n\n this.#unsubscribe = router.subscribe((next) => {\n if (!shouldUpdate(next.route, next.previousRoute)) {\n return;\n }\n\n const newSnapshot = computeSnapshot(\n this.#source.getSnapshot(),\n router,\n nodeName,\n next,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup guard; shouldUpdateNode filters accurately so computeSnapshot always returns new ref */\n if (!Object.is(this.#source.getSnapshot(), newSnapshot)) {\n this.#source.updateSnapshot(newSnapshot);\n }\n });\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n return this.#source.subscribe(listener);\n }\n\n getSnapshot(): RouteNodeSnapshot {\n return this.#source.getSnapshot();\n }\n\n destroy(): void {\n this.#unsubscribe();\n this.#source.destroy();\n }\n}\n\nexport function createRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n return new RouteNodeSource(router, nodeName);\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { BaseSource } from \"./BaseSource\";\n\nimport type { ActiveRouteSourceOptions, RouterSource } from \"./types.js\";\nimport type { Params, Router } from \"@real-router/core\";\n\nclass ActiveRouteSource implements RouterSource<boolean> {\n readonly #source: BaseSource<boolean>;\n readonly #unsubscribe: () => void;\n\n constructor(\n router: Router,\n routeName: string,\n params?: Params,\n options?: ActiveRouteSourceOptions,\n ) {\n const strict = options?.strict ?? false;\n const ignoreQueryParams = options?.ignoreQueryParams ?? true;\n\n const initialValue = router.isActiveRoute(\n routeName,\n params,\n strict,\n ignoreQueryParams,\n );\n\n this.#source = new BaseSource(initialValue);\n\n this.#unsubscribe = router.subscribe((next) => {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n if (!isNewRelated && !isPrevRelated) {\n return;\n }\n\n // If new route is not related, we know the route is inactive —\n // avoid calling isActiveRoute for the optimization\n const newValue = isNewRelated\n ? router.isActiveRoute(routeName, params, strict, ignoreQueryParams)\n : false;\n\n if (!Object.is(this.#source.getSnapshot(), newValue)) {\n this.#source.updateSnapshot(newValue);\n }\n });\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n return this.#source.subscribe(listener);\n }\n\n getSnapshot(): boolean {\n return this.#source.getSnapshot();\n }\n\n destroy(): void {\n this.#unsubscribe();\n this.#source.destroy();\n }\n}\n\nexport function createActiveRouteSource(\n router: Router,\n routeName: string,\n params?: Params,\n options?: ActiveRouteSourceOptions,\n): RouterSource<boolean> {\n return new ActiveRouteSource(router, routeName, params, options);\n}\n","import { getPluginApi, events } from \"@real-router/core\";\n\nimport { BaseSource } from \"./BaseSource\";\n\nimport type { RouterTransitionSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State } from \"@real-router/core\";\n\nconst IDLE_SNAPSHOT: RouterTransitionSnapshot = {\n isTransitioning: false,\n toRoute: null,\n fromRoute: null,\n};\n\nclass TransitionSource implements RouterSource<RouterTransitionSnapshot> {\n readonly #source: BaseSource<RouterTransitionSnapshot>;\n readonly #unsubs: (() => void)[];\n\n constructor(router: Router) {\n this.#source = new BaseSource(IDLE_SNAPSHOT);\n\n const api = getPluginApi(router);\n\n const resetToIdle = (): void => {\n this.#source.updateSnapshot(IDLE_SNAPSHOT);\n };\n\n this.#unsubs = [\n api.addEventListener(\n events.TRANSITION_START,\n (toState: State, fromState?: State) => {\n this.#source.updateSnapshot({\n isTransitioning: true,\n toRoute: toState,\n fromRoute: fromState ?? null,\n });\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),\n api.addEventListener(events.TRANSITION_ERROR, resetToIdle),\n api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),\n ];\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n return this.#source.subscribe(listener);\n }\n\n getSnapshot(): RouterTransitionSnapshot {\n return this.#source.getSnapshot();\n }\n\n destroy(): void {\n this.#unsubs.forEach((u) => {\n u();\n });\n this.#source.destroy();\n }\n}\n\nexport function createTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n return new TransitionSource(router);\n}\n"]}
1
+ {"version":3,"sources":["../../src/BaseSource.ts","../../src/createRouteSource.ts","../../src/computeSnapshot.ts","../../src/shouldUpdateCache.ts","../../src/createRouteNodeSource.ts","../../src/createActiveRouteSource.ts","../../src/createTransitionSource.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["export interface BaseSourceOptions {\n onFirstSubscribe?: () => void;\n onLastUnsubscribe?: () => void;\n onDestroy?: () => void;\n}\n\nexport class BaseSource<T> {\n #currentSnapshot: T;\n #destroyed = false;\n\n readonly #listeners = new Set<() => void>();\n readonly #onFirstSubscribe: (() => void) | undefined;\n readonly #onLastUnsubscribe: (() => void) | undefined;\n readonly #onDestroy: (() => void) | undefined;\n\n constructor(initialSnapshot: T, options?: BaseSourceOptions) {\n this.#currentSnapshot = initialSnapshot;\n this.#onFirstSubscribe = options?.onFirstSubscribe;\n this.#onLastUnsubscribe = options?.onLastUnsubscribe;\n this.#onDestroy = options?.onDestroy;\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n if (this.#destroyed) {\n return () => {};\n }\n\n if (this.#listeners.size === 0 && this.#onFirstSubscribe) {\n this.#onFirstSubscribe();\n }\n\n this.#listeners.add(listener);\n\n return () => {\n this.#listeners.delete(listener);\n\n if (\n !this.#destroyed &&\n this.#listeners.size === 0 &&\n this.#onLastUnsubscribe\n ) {\n this.#onLastUnsubscribe();\n }\n };\n }\n\n getSnapshot(): T {\n return this.#currentSnapshot;\n }\n\n updateSnapshot(snapshot: T): void {\n /* v8 ignore next 2 -- @preserve: defensive guard unreachable via public API (destroy() removes router subscription first) */\n if (this.#destroyed) {\n return;\n }\n\n this.#currentSnapshot = snapshot;\n this.#listeners.forEach((listener) => {\n listener();\n });\n }\n\n destroy(): void {\n if (this.#destroyed) {\n return;\n }\n\n this.#destroyed = true;\n this.#onDestroy?.();\n this.#listeners.clear();\n }\n}\n","import { BaseSource } from \"./BaseSource\";\n\nimport type { RouteSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\n/**\n * Creates a source for the full route state.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n */\nexport function createRouteSource(router: Router): RouterSource<RouteSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteSnapshot>(\n {\n route: router.getState(),\n previousRoute: undefined,\n },\n {\n onFirstSubscribe: () => {\n routerUnsubscribe = router.subscribe((next) => {\n source.updateSnapshot({\n route: next.route,\n previousRoute: next.previousRoute,\n });\n });\n },\n onLastUnsubscribe: disconnect,\n onDestroy: disconnect,\n },\n );\n\n return source;\n}\n","import type { RouteNodeSnapshot } from \"./types.js\";\nimport type { Router, SubscribeState } from \"@real-router/core\";\n\nexport function computeSnapshot(\n currentSnapshot: RouteNodeSnapshot,\n router: Router,\n nodeName: string,\n next?: SubscribeState,\n): RouteNodeSnapshot {\n const currentRoute = next?.route ?? router.getState();\n const previousRoute = next?.previousRoute;\n\n const isNodeActive =\n nodeName === \"\" ||\n (currentRoute !== undefined &&\n (currentRoute.name === nodeName ||\n currentRoute.name.startsWith(`${nodeName}.`)));\n\n const route = isNodeActive ? currentRoute : undefined;\n\n if (\n route === currentSnapshot.route &&\n previousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n return { route, previousRoute };\n}\n","import type { Router, State } from \"@real-router/core\";\n\nconst shouldUpdateCache = new WeakMap<\n Router,\n Map<string, (toState: State, fromState?: State) => boolean>\n>();\n\nexport function getCachedShouldUpdate(\n router: Router,\n nodeName: string,\n): (toState: State, fromState?: State) => boolean {\n let routerCache = shouldUpdateCache.get(router);\n\n if (!routerCache) {\n routerCache = new Map();\n shouldUpdateCache.set(router, routerCache);\n }\n\n let fn = routerCache.get(nodeName);\n\n if (!fn) {\n fn = router.shouldUpdateNode(nodeName);\n routerCache.set(nodeName, fn);\n }\n\n return fn;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { computeSnapshot } from \"./computeSnapshot.js\";\nimport { getCachedShouldUpdate } from \"./shouldUpdateCache.js\";\n\nimport type { RouteNodeSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\n/**\n * Creates a source scoped to a specific route node.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n */\nexport function createRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n const shouldUpdate = getCachedShouldUpdate(router, nodeName);\n\n const initialSnapshot: RouteNodeSnapshot = {\n route: undefined,\n previousRoute: undefined,\n };\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteNodeSnapshot>(\n computeSnapshot(initialSnapshot, router, nodeName),\n {\n onFirstSubscribe: () => {\n // Reconcile snapshot with current router state before connecting.\n // Covers reconnection after Activity hide/show cycles where the\n // source was disconnected and missed navigation events.\n source.updateSnapshot(\n computeSnapshot(source.getSnapshot(), router, nodeName),\n );\n\n // Connect to router on first subscription\n routerUnsubscribe = router.subscribe((next) => {\n if (!shouldUpdate(next.route, next.previousRoute)) {\n return;\n }\n\n const newSnapshot = computeSnapshot(\n source.getSnapshot(),\n router,\n nodeName,\n next,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup guard; shouldUpdateNode filters accurately so computeSnapshot always returns new ref */\n if (!Object.is(source.getSnapshot(), newSnapshot)) {\n source.updateSnapshot(newSnapshot);\n }\n });\n },\n onLastUnsubscribe: disconnect,\n onDestroy: disconnect,\n },\n );\n\n return source;\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { BaseSource } from \"./BaseSource\";\n\nimport type { ActiveRouteSourceOptions, RouterSource } from \"./types.js\";\nimport type { Params, Router } from \"@real-router/core\";\n\nexport function createActiveRouteSource(\n router: Router,\n routeName: string,\n params?: Params,\n options?: ActiveRouteSourceOptions,\n): RouterSource<boolean> {\n const strict = options?.strict ?? false;\n const ignoreQueryParams = options?.ignoreQueryParams ?? true;\n\n const initialValue = router.isActiveRoute(\n routeName,\n params,\n strict,\n ignoreQueryParams,\n );\n\n const source = new BaseSource(initialValue, {\n onDestroy: () => {\n unsubscribe();\n },\n });\n\n // Eager connection: subscribe to router immediately\n const unsubscribe = router.subscribe((next) => {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n if (!isNewRelated && !isPrevRelated) {\n return;\n }\n\n // If new route is not related, we know the route is inactive —\n // avoid calling isActiveRoute for the optimization\n const newValue = isNewRelated\n ? router.isActiveRoute(routeName, params, strict, ignoreQueryParams)\n : false;\n\n if (!Object.is(source.getSnapshot(), newValue)) {\n source.updateSnapshot(newValue);\n }\n });\n\n return source;\n}\n","import { getPluginApi, events } from \"@real-router/core\";\n\nimport { BaseSource } from \"./BaseSource\";\n\nimport type { RouterTransitionSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State } from \"@real-router/core\";\n\nconst IDLE_SNAPSHOT: RouterTransitionSnapshot = {\n isTransitioning: false,\n toRoute: null,\n fromRoute: null,\n};\n\nexport function createTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n const source = new BaseSource(IDLE_SNAPSHOT, {\n onDestroy: () => {\n unsubs.forEach((u) => {\n u();\n });\n },\n });\n\n const api = getPluginApi(router);\n\n const resetToIdle = (): void => {\n source.updateSnapshot(IDLE_SNAPSHOT);\n };\n\n // Eager connection: subscribe to router events immediately\n const unsubs = [\n api.addEventListener(\n events.TRANSITION_START,\n (toState: State, fromState?: State) => {\n source.updateSnapshot({\n isTransitioning: true,\n toRoute: toState,\n fromRoute: fromState ?? null,\n });\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),\n api.addEventListener(events.TRANSITION_ERROR, resetToIdle),\n api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),\n ];\n\n return source;\n}\n"]}
@@ -1 +1 @@
1
- {"inputs":{"src/createRouteSource.ts":{"bytes":1956,"imports":[],"format":"esm"},"src/BaseSource.ts":{"bytes":941,"imports":[],"format":"esm"},"src/computeSnapshot.ts":{"bytes":790,"imports":[],"format":"esm"},"src/shouldUpdateCache.ts":{"bytes":610,"imports":[],"format":"esm"},"src/createRouteNodeSource.ts":{"bytes":1911,"imports":[{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"},{"path":"src/computeSnapshot.ts","kind":"import-statement","original":"./computeSnapshot.js"},{"path":"src/shouldUpdateCache.ts","kind":"import-statement","original":"./shouldUpdateCache.js"}],"format":"esm"},"src/createActiveRouteSource.ts":{"bytes":2124,"imports":[{"path":"@real-router/route-utils","kind":"import-statement","external":true},{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"}],"format":"esm"},"src/createTransitionSource.ts":{"bytes":1845,"imports":[{"path":"@real-router/core","kind":"import-statement","external":true},{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"}],"format":"esm"},"src/index.ts":{"bytes":407,"imports":[{"path":"src/createRouteSource.ts","kind":"import-statement","original":"./createRouteSource"},{"path":"src/createRouteNodeSource.ts","kind":"import-statement","original":"./createRouteNodeSource"},{"path":"src/createActiveRouteSource.ts","kind":"import-statement","original":"./createActiveRouteSource"},{"path":"src/createTransitionSource.ts","kind":"import-statement","original":"./createTransitionSource"}],"format":"esm"}},"outputs":{"dist/esm/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":15986},"dist/esm/index.mjs":{"imports":[{"path":"@real-router/route-utils","kind":"import-statement","external":true},{"path":"@real-router/core","kind":"import-statement","external":true}],"exports":["createActiveRouteSource","createRouteNodeSource","createRouteSource","createTransitionSource"],"entryPoint":"src/index.ts","inputs":{"src/createRouteSource.ts":{"bytesInOutput":1303},"src/index.ts":{"bytesInOutput":0},"src/BaseSource.ts":{"bytesInOutput":752},"src/computeSnapshot.ts":{"bytesInOutput":536},"src/shouldUpdateCache.ts":{"bytesInOutput":425},"src/createRouteNodeSource.ts":{"bytesInOutput":1234},"src/createActiveRouteSource.ts":{"bytesInOutput":1490},"src/createTransitionSource.ts":{"bytesInOutput":1367}},"bytes":7679}}}
1
+ {"inputs":{"src/BaseSource.ts":{"bytes":1857,"imports":[],"format":"esm"},"src/createRouteSource.ts":{"bytes":1168,"imports":[{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"}],"format":"esm"},"src/computeSnapshot.ts":{"bytes":790,"imports":[],"format":"esm"},"src/shouldUpdateCache.ts":{"bytes":610,"imports":[],"format":"esm"},"src/createRouteNodeSource.ts":{"bytes":2245,"imports":[{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"},{"path":"src/computeSnapshot.ts","kind":"import-statement","original":"./computeSnapshot.js"},{"path":"src/shouldUpdateCache.ts","kind":"import-statement","original":"./shouldUpdateCache.js"}],"format":"esm"},"src/createActiveRouteSource.ts":{"bytes":1461,"imports":[{"path":"@real-router/route-utils","kind":"import-statement","external":true},{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"}],"format":"esm"},"src/createTransitionSource.ts":{"bytes":1278,"imports":[{"path":"@real-router/core","kind":"import-statement","external":true},{"path":"src/BaseSource.ts","kind":"import-statement","original":"./BaseSource"}],"format":"esm"},"src/index.ts":{"bytes":407,"imports":[{"path":"src/createRouteSource.ts","kind":"import-statement","original":"./createRouteSource"},{"path":"src/createRouteNodeSource.ts","kind":"import-statement","original":"./createRouteNodeSource"},{"path":"src/createActiveRouteSource.ts","kind":"import-statement","original":"./createActiveRouteSource"},{"path":"src/createTransitionSource.ts","kind":"import-statement","original":"./createTransitionSource"}],"format":"esm"}},"outputs":{"dist/esm/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":14149},"dist/esm/index.mjs":{"imports":[{"path":"@real-router/route-utils","kind":"import-statement","external":true},{"path":"@real-router/core","kind":"import-statement","external":true}],"exports":["createActiveRouteSource","createRouteNodeSource","createRouteSource","createTransitionSource"],"entryPoint":"src/index.ts","inputs":{"src/BaseSource.ts":{"bytesInOutput":1375},"src/createRouteSource.ts":{"bytesInOutput":627},"src/index.ts":{"bytesInOutput":0},"src/computeSnapshot.ts":{"bytesInOutput":536},"src/shouldUpdateCache.ts":{"bytesInOutput":425},"src/createRouteNodeSource.ts":{"bytesInOutput":1120},"src/createActiveRouteSource.ts":{"bytesInOutput":977},"src/createTransitionSource.ts":{"bytesInOutput":921}},"bytes":6553}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/sources",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "commonjs",
5
5
  "description": "Framework-agnostic subscription layer for Real-Router state",
6
6
  "main": "./dist/cjs/index.js",
@@ -43,7 +43,7 @@
43
43
  "homepage": "https://github.com/greydragon888/real-router",
44
44
  "sideEffects": false,
45
45
  "dependencies": {
46
- "@real-router/core": "^0.35.1",
46
+ "@real-router/core": "^0.35.2",
47
47
  "@real-router/route-utils": "^0.1.4"
48
48
  },
49
49
  "devDependencies": {
@@ -56,6 +56,8 @@
56
56
  "lint": "eslint --cache --ext .ts src/ tests/ --fix --max-warnings 0 --no-error-on-unmatched-pattern",
57
57
  "lint:package": "publint",
58
58
  "lint:types": "attw --pack .",
59
+ "test:properties": "vitest --config vitest.config.properties.mts --run",
60
+ "test:stress": "vitest run --config vitest.config.stress.mts",
59
61
  "bench": "NODE_OPTIONS='--expose-gc --max-old-space-size=4096' npx tsx tests/benchmarks/index.ts"
60
62
  }
61
63
  }
package/src/BaseSource.ts CHANGED
@@ -1,11 +1,27 @@
1
+ export interface BaseSourceOptions {
2
+ onFirstSubscribe?: () => void;
3
+ onLastUnsubscribe?: () => void;
4
+ onDestroy?: () => void;
5
+ }
6
+
1
7
  export class BaseSource<T> {
2
8
  #currentSnapshot: T;
3
9
  #destroyed = false;
4
10
 
5
11
  readonly #listeners = new Set<() => void>();
12
+ readonly #onFirstSubscribe: (() => void) | undefined;
13
+ readonly #onLastUnsubscribe: (() => void) | undefined;
14
+ readonly #onDestroy: (() => void) | undefined;
6
15
 
7
- constructor(initialSnapshot: T) {
16
+ constructor(initialSnapshot: T, options?: BaseSourceOptions) {
8
17
  this.#currentSnapshot = initialSnapshot;
18
+ this.#onFirstSubscribe = options?.onFirstSubscribe;
19
+ this.#onLastUnsubscribe = options?.onLastUnsubscribe;
20
+ this.#onDestroy = options?.onDestroy;
21
+
22
+ this.subscribe = this.subscribe.bind(this);
23
+ this.getSnapshot = this.getSnapshot.bind(this);
24
+ this.destroy = this.destroy.bind(this);
9
25
  }
10
26
 
11
27
  subscribe(listener: () => void): () => void {
@@ -13,10 +29,22 @@ export class BaseSource<T> {
13
29
  return () => {};
14
30
  }
15
31
 
32
+ if (this.#listeners.size === 0 && this.#onFirstSubscribe) {
33
+ this.#onFirstSubscribe();
34
+ }
35
+
16
36
  this.#listeners.add(listener);
17
37
 
18
38
  return () => {
19
39
  this.#listeners.delete(listener);
40
+
41
+ if (
42
+ !this.#destroyed &&
43
+ this.#listeners.size === 0 &&
44
+ this.#onLastUnsubscribe
45
+ ) {
46
+ this.#onLastUnsubscribe();
47
+ }
20
48
  };
21
49
  }
22
50
 
@@ -42,6 +70,7 @@ export class BaseSource<T> {
42
70
  }
43
71
 
44
72
  this.#destroyed = true;
73
+ this.#onDestroy?.();
45
74
  this.#listeners.clear();
46
75
  }
47
76
  }
@@ -5,73 +5,49 @@ import { BaseSource } from "./BaseSource";
5
5
  import type { ActiveRouteSourceOptions, RouterSource } from "./types.js";
6
6
  import type { Params, Router } from "@real-router/core";
7
7
 
8
- class ActiveRouteSource implements RouterSource<boolean> {
9
- readonly #source: BaseSource<boolean>;
10
- readonly #unsubscribe: () => void;
11
-
12
- constructor(
13
- router: Router,
14
- routeName: string,
15
- params?: Params,
16
- options?: ActiveRouteSourceOptions,
17
- ) {
18
- const strict = options?.strict ?? false;
19
- const ignoreQueryParams = options?.ignoreQueryParams ?? true;
20
-
21
- const initialValue = router.isActiveRoute(
22
- routeName,
23
- params,
24
- strict,
25
- ignoreQueryParams,
26
- );
27
-
28
- this.#source = new BaseSource(initialValue);
29
-
30
- this.#unsubscribe = router.subscribe((next) => {
31
- const isNewRelated = areRoutesRelated(routeName, next.route.name);
32
- const isPrevRelated =
33
- next.previousRoute &&
34
- areRoutesRelated(routeName, next.previousRoute.name);
35
-
36
- if (!isNewRelated && !isPrevRelated) {
37
- return;
38
- }
39
-
40
- // If new route is not related, we know the route is inactive —
41
- // avoid calling isActiveRoute for the optimization
42
- const newValue = isNewRelated
43
- ? router.isActiveRoute(routeName, params, strict, ignoreQueryParams)
44
- : false;
45
-
46
- if (!Object.is(this.#source.getSnapshot(), newValue)) {
47
- this.#source.updateSnapshot(newValue);
48
- }
49
- });
50
-
51
- this.subscribe = this.subscribe.bind(this);
52
- this.getSnapshot = this.getSnapshot.bind(this);
53
- this.destroy = this.destroy.bind(this);
54
- }
55
-
56
- subscribe(listener: () => void): () => void {
57
- return this.#source.subscribe(listener);
58
- }
59
-
60
- getSnapshot(): boolean {
61
- return this.#source.getSnapshot();
62
- }
63
-
64
- destroy(): void {
65
- this.#unsubscribe();
66
- this.#source.destroy();
67
- }
68
- }
69
-
70
8
  export function createActiveRouteSource(
71
9
  router: Router,
72
10
  routeName: string,
73
11
  params?: Params,
74
12
  options?: ActiveRouteSourceOptions,
75
13
  ): RouterSource<boolean> {
76
- return new ActiveRouteSource(router, routeName, params, options);
14
+ const strict = options?.strict ?? false;
15
+ const ignoreQueryParams = options?.ignoreQueryParams ?? true;
16
+
17
+ const initialValue = router.isActiveRoute(
18
+ routeName,
19
+ params,
20
+ strict,
21
+ ignoreQueryParams,
22
+ );
23
+
24
+ const source = new BaseSource(initialValue, {
25
+ onDestroy: () => {
26
+ unsubscribe();
27
+ },
28
+ });
29
+
30
+ // Eager connection: subscribe to router immediately
31
+ const unsubscribe = router.subscribe((next) => {
32
+ const isNewRelated = areRoutesRelated(routeName, next.route.name);
33
+ const isPrevRelated =
34
+ next.previousRoute &&
35
+ areRoutesRelated(routeName, next.previousRoute.name);
36
+
37
+ if (!isNewRelated && !isPrevRelated) {
38
+ return;
39
+ }
40
+
41
+ // If new route is not related, we know the route is inactive —
42
+ // avoid calling isActiveRoute for the optimization
43
+ const newValue = isNewRelated
44
+ ? router.isActiveRoute(routeName, params, strict, ignoreQueryParams)
45
+ : false;
46
+
47
+ if (!Object.is(source.getSnapshot(), newValue)) {
48
+ source.updateSnapshot(newValue);
49
+ }
50
+ });
51
+
52
+ return source;
77
53
  }
@@ -5,60 +5,67 @@ import { getCachedShouldUpdate } from "./shouldUpdateCache.js";
5
5
  import type { RouteNodeSnapshot, RouterSource } from "./types.js";
6
6
  import type { Router } from "@real-router/core";
7
7
 
8
- class RouteNodeSource implements RouterSource<RouteNodeSnapshot> {
9
- readonly #source: BaseSource<RouteNodeSnapshot>;
10
- readonly #unsubscribe: () => void;
11
-
12
- constructor(router: Router, nodeName: string) {
13
- const initialSnapshot: RouteNodeSnapshot = {
14
- route: undefined,
15
- previousRoute: undefined,
16
- };
17
- const computedInitial = computeSnapshot(initialSnapshot, router, nodeName);
8
+ /**
9
+ * Creates a source scoped to a specific route node.
10
+ *
11
+ * Uses a lazy-connection pattern: the router subscription is created when the
12
+ * first listener subscribes and removed when the last listener unsubscribes.
13
+ * This is compatible with React's useSyncExternalStore and Strict Mode.
14
+ */
15
+ export function createRouteNodeSource(
16
+ router: Router,
17
+ nodeName: string,
18
+ ): RouterSource<RouteNodeSnapshot> {
19
+ let routerUnsubscribe: (() => void) | null = null;
18
20
 
19
- this.#source = new BaseSource(computedInitial);
20
- const shouldUpdate = getCachedShouldUpdate(router, nodeName);
21
+ const shouldUpdate = getCachedShouldUpdate(router, nodeName);
21
22
 
22
- this.#unsubscribe = router.subscribe((next) => {
23
- if (!shouldUpdate(next.route, next.previousRoute)) {
24
- return;
25
- }
23
+ const initialSnapshot: RouteNodeSnapshot = {
24
+ route: undefined,
25
+ previousRoute: undefined,
26
+ };
26
27
 
27
- const newSnapshot = computeSnapshot(
28
- this.#source.getSnapshot(),
29
- router,
30
- nodeName,
31
- next,
32
- );
28
+ const disconnect = (): void => {
29
+ const unsub = routerUnsubscribe;
33
30
 
34
- /* v8 ignore next 3 -- @preserve: dedup guard; shouldUpdateNode filters accurately so computeSnapshot always returns new ref */
35
- if (!Object.is(this.#source.getSnapshot(), newSnapshot)) {
36
- this.#source.updateSnapshot(newSnapshot);
37
- }
38
- });
31
+ routerUnsubscribe = null;
32
+ unsub?.();
33
+ };
39
34
 
40
- this.subscribe = this.subscribe.bind(this);
41
- this.getSnapshot = this.getSnapshot.bind(this);
42
- this.destroy = this.destroy.bind(this);
43
- }
35
+ const source = new BaseSource<RouteNodeSnapshot>(
36
+ computeSnapshot(initialSnapshot, router, nodeName),
37
+ {
38
+ onFirstSubscribe: () => {
39
+ // Reconcile snapshot with current router state before connecting.
40
+ // Covers reconnection after Activity hide/show cycles where the
41
+ // source was disconnected and missed navigation events.
42
+ source.updateSnapshot(
43
+ computeSnapshot(source.getSnapshot(), router, nodeName),
44
+ );
44
45
 
45
- subscribe(listener: () => void): () => void {
46
- return this.#source.subscribe(listener);
47
- }
46
+ // Connect to router on first subscription
47
+ routerUnsubscribe = router.subscribe((next) => {
48
+ if (!shouldUpdate(next.route, next.previousRoute)) {
49
+ return;
50
+ }
48
51
 
49
- getSnapshot(): RouteNodeSnapshot {
50
- return this.#source.getSnapshot();
51
- }
52
+ const newSnapshot = computeSnapshot(
53
+ source.getSnapshot(),
54
+ router,
55
+ nodeName,
56
+ next,
57
+ );
52
58
 
53
- destroy(): void {
54
- this.#unsubscribe();
55
- this.#source.destroy();
56
- }
57
- }
59
+ /* v8 ignore next 3 -- @preserve: dedup guard; shouldUpdateNode filters accurately so computeSnapshot always returns new ref */
60
+ if (!Object.is(source.getSnapshot(), newSnapshot)) {
61
+ source.updateSnapshot(newSnapshot);
62
+ }
63
+ });
64
+ },
65
+ onLastUnsubscribe: disconnect,
66
+ onDestroy: disconnect,
67
+ },
68
+ );
58
69
 
59
- export function createRouteNodeSource(
60
- router: Router,
61
- nodeName: string,
62
- ): RouterSource<RouteNodeSnapshot> {
63
- return new RouteNodeSource(router, nodeName);
70
+ return source;
64
71
  }
@@ -1,66 +1,8 @@
1
+ import { BaseSource } from "./BaseSource";
2
+
1
3
  import type { RouteSnapshot, RouterSource } from "./types.js";
2
4
  import type { Router } from "@real-router/core";
3
5
 
4
- class RouteSource implements RouterSource<RouteSnapshot> {
5
- #routerUnsubscribe: (() => void) | null = null;
6
- #currentSnapshot: RouteSnapshot;
7
-
8
- readonly #listeners = new Set<() => void>();
9
- readonly #router: Router;
10
-
11
- constructor(router: Router) {
12
- this.#router = router;
13
-
14
- this.#currentSnapshot = {
15
- route: router.getState(),
16
- previousRoute: undefined,
17
- };
18
-
19
- this.subscribe = this.subscribe.bind(this);
20
- this.destroy = this.destroy.bind(this);
21
- this.getSnapshot = this.getSnapshot.bind(this);
22
- }
23
-
24
- subscribe(listener: () => void): () => void {
25
- if (this.#listeners.size === 0) {
26
- // Connect to router on first subscription
27
- this.#routerUnsubscribe = this.#router.subscribe((next) => {
28
- this.#currentSnapshot = {
29
- route: next.route,
30
- previousRoute: next.previousRoute,
31
- };
32
- this.#listeners.forEach((cb) => {
33
- cb();
34
- });
35
- });
36
- }
37
-
38
- this.#listeners.add(listener);
39
-
40
- return () => {
41
- this.#listeners.delete(listener);
42
-
43
- if (this.#listeners.size === 0 && this.#routerUnsubscribe) {
44
- this.#routerUnsubscribe();
45
- this.#routerUnsubscribe = null;
46
- }
47
- };
48
- }
49
-
50
- getSnapshot(): RouteSnapshot {
51
- return this.#currentSnapshot;
52
- }
53
-
54
- destroy(): void {
55
- if (this.#routerUnsubscribe) {
56
- this.#routerUnsubscribe();
57
- this.#routerUnsubscribe = null;
58
- }
59
-
60
- this.#listeners.clear();
61
- }
62
- }
63
-
64
6
  /**
65
7
  * Creates a source for the full route state.
66
8
  *
@@ -69,5 +11,33 @@ class RouteSource implements RouterSource<RouteSnapshot> {
69
11
  * This is compatible with React's useSyncExternalStore and Strict Mode.
70
12
  */
71
13
  export function createRouteSource(router: Router): RouterSource<RouteSnapshot> {
72
- return new RouteSource(router);
14
+ let routerUnsubscribe: (() => void) | null = null;
15
+
16
+ const disconnect = (): void => {
17
+ const unsub = routerUnsubscribe;
18
+
19
+ routerUnsubscribe = null;
20
+ unsub?.();
21
+ };
22
+
23
+ const source = new BaseSource<RouteSnapshot>(
24
+ {
25
+ route: router.getState(),
26
+ previousRoute: undefined,
27
+ },
28
+ {
29
+ onFirstSubscribe: () => {
30
+ routerUnsubscribe = router.subscribe((next) => {
31
+ source.updateSnapshot({
32
+ route: next.route,
33
+ previousRoute: next.previousRoute,
34
+ });
35
+ });
36
+ },
37
+ onLastUnsubscribe: disconnect,
38
+ onDestroy: disconnect,
39
+ },
40
+ );
41
+
42
+ return source;
73
43
  }
@@ -11,58 +11,39 @@ const IDLE_SNAPSHOT: RouterTransitionSnapshot = {
11
11
  fromRoute: null,
12
12
  };
13
13
 
14
- class TransitionSource implements RouterSource<RouterTransitionSnapshot> {
15
- readonly #source: BaseSource<RouterTransitionSnapshot>;
16
- readonly #unsubs: (() => void)[];
17
-
18
- constructor(router: Router) {
19
- this.#source = new BaseSource(IDLE_SNAPSHOT);
20
-
21
- const api = getPluginApi(router);
22
-
23
- const resetToIdle = (): void => {
24
- this.#source.updateSnapshot(IDLE_SNAPSHOT);
25
- };
26
-
27
- this.#unsubs = [
28
- api.addEventListener(
29
- events.TRANSITION_START,
30
- (toState: State, fromState?: State) => {
31
- this.#source.updateSnapshot({
32
- isTransitioning: true,
33
- toRoute: toState,
34
- fromRoute: fromState ?? null,
35
- });
36
- },
37
- ),
38
- api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),
39
- api.addEventListener(events.TRANSITION_ERROR, resetToIdle),
40
- api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),
41
- ];
42
-
43
- this.subscribe = this.subscribe.bind(this);
44
- this.getSnapshot = this.getSnapshot.bind(this);
45
- this.destroy = this.destroy.bind(this);
46
- }
47
-
48
- subscribe(listener: () => void): () => void {
49
- return this.#source.subscribe(listener);
50
- }
51
-
52
- getSnapshot(): RouterTransitionSnapshot {
53
- return this.#source.getSnapshot();
54
- }
55
-
56
- destroy(): void {
57
- this.#unsubs.forEach((u) => {
58
- u();
59
- });
60
- this.#source.destroy();
61
- }
62
- }
63
-
64
14
  export function createTransitionSource(
65
15
  router: Router,
66
16
  ): RouterSource<RouterTransitionSnapshot> {
67
- return new TransitionSource(router);
17
+ const source = new BaseSource(IDLE_SNAPSHOT, {
18
+ onDestroy: () => {
19
+ unsubs.forEach((u) => {
20
+ u();
21
+ });
22
+ },
23
+ });
24
+
25
+ const api = getPluginApi(router);
26
+
27
+ const resetToIdle = (): void => {
28
+ source.updateSnapshot(IDLE_SNAPSHOT);
29
+ };
30
+
31
+ // Eager connection: subscribe to router events immediately
32
+ const unsubs = [
33
+ api.addEventListener(
34
+ events.TRANSITION_START,
35
+ (toState: State, fromState?: State) => {
36
+ source.updateSnapshot({
37
+ isTransitioning: true,
38
+ toRoute: toState,
39
+ fromRoute: fromState ?? null,
40
+ });
41
+ },
42
+ ),
43
+ api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),
44
+ api.addEventListener(events.TRANSITION_ERROR, resetToIdle),
45
+ api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),
46
+ ];
47
+
48
+ return source;
68
49
  }