@real-router/memory-plugin 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;KAAY,eAAA;AAAA,UAEK,aAAA;EAAA,SACN,SAAA,EAAW,eAAA;EAAA,SACX,YAAA;AAAA;AAAA,UAGM,mBAAA;EAPU;;AAE3B;;;;;;;;;EAiBE,gBAAA;AAAA;;;iBCZc,mBAAA,CACd,OAAA,GAAS,mBAAA,GACR,aAAA;;;;YCAS,YAAA;IACR,MAAA,GAJa,aAAA;EAAA;AAAA;AAAA;EAAA,UASL,MAAA;IACR,IAAA;IACA,OAAA;IACA,EAAA,GAAK,KAAA;IACL,SAAA;IACA,YAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;KAAY,eAAA;AAAA,UAEK,aAAA;EAAA,SACN,SAAA,EAAW,eAAe;EAAA,SAC1B,YAAA;AAAA;AAAA,UAGM,mBAAA;EAPU;AAAA;AAE3B;;;;;;;;AAEuB;EAerB,gBAAgB;AAAA;;;iBCZF,mBAAA,CACd,OAAA,GAAS,mBAAA,GACR,aAAa;;;;YCAJ,YAAA;IACR,MAAA,GAJa,aAI2B;EAAA;AAAA;AAAA;EAAA,UAKhC,MAAA;IACR,IAAA;IACA,OAAA;IACA,EAAA,GAAK,KAAA;IACL,SAAA;IACA,YAAA;EAAA;AAAA"}
package/dist/cjs/index.js CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@real-router/core/api`);var t=class{#e;#t;#n;#r=[];#i;#a;#o=-1;#s=!1;#c=`navigate`;#l=0;#u=!1;constructor(e,t,n){this.#e=e,this.#t=t,this.#n=n.maxHistoryLength??1e3,this.#a=t.claimContextNamespace(`memory`),this.#i=t.extendRouter({back:()=>{this.#f(-1)},forward:()=>{this.#f(1)},go:e=>{this.#f(e)},canGoBack:()=>this.#o>0,canGoForward:()=>this.#o<this.#r.length-1})}getPlugin(){return{onTransitionSuccess:(e,t,n)=>{if(this.#s){this.#d(e,this.#c);return}if(n.replace&&this.#o>=0)this.#r[this.#o]=e;else if(this.#r.length=this.#o+1,this.#r.push(e),this.#o=this.#r.length-1,this.#n>0&&this.#r.length>this.#n){let e=this.#r.length-this.#n;this.#r.splice(0,e),this.#o=Math.max(0,this.#o-e)}this.#d(e,`navigate`)},onStop:()=>{this.#l++,this.#p()},teardown:()=>{this.#u||(this.#u=!0,this.#l++,this.#i(),this.#a.release(),this.#p())}}}#d(e,t){this.#a.write(e,{direction:t,historyIndex:this.#o})}#f(e){if(!Number.isInteger(e)||e===0)return;let t=this.#o+e;if(t<0||t>=this.#r.length)return;let n=this.#r[t],r=this.#e.getState();if(n.path===r?.path){this.#o=t,this.#d(r,e>0?`forward`:`back`);return}let i=this.#o,a=++this.#l;this.#c=e>0?`forward`:`back`,this.#s=!0,this.#o=t,this.#t.navigateToState(n,{replace:!0}).then(()=>{this.#l===a&&(this.#s=!1)},()=>{this.#l===a&&(this.#o=i,this.#s=!1)})}#p(){this.#r.length=0,this.#o=-1,this.#s=!1,this.#c=`navigate`}};function n(n={}){if(n.maxHistoryLength!==void 0){let e=n.maxHistoryLength;if(typeof e!=`number`||!Number.isFinite(e)||!Number.isInteger(e)||e<0)throw TypeError(`[memory-plugin] Invalid maxHistoryLength: expected non-negative integer, got ${String(e)}.`)}let r=Object.freeze({...n});return n=>new t(n,(0,e.getPluginApi)(n),r).getPlugin()}exports.memoryPluginFactory=n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@real-router/core/api");var t=class{#e;#t;#n;#r=[];#i;#a;#o=-1;#s=!1;#c=`navigate`;#l=0;#u=!1;constructor(e,t,n){this.#e=e,this.#t=t,this.#n=n.maxHistoryLength??1e3,this.#a=t.claimContextNamespace(`memory`),this.#i=t.extendRouter({back:()=>{this.#f(-1)},forward:()=>{this.#f(1)},go:e=>{this.#f(e)},canGoBack:()=>this.#o>0,canGoForward:()=>this.#o<this.#r.length-1})}getPlugin(){return{onTransitionSuccess:(e,t,n)=>{if(this.#s){this.#d(e,this.#c);return}if(n.replace&&this.#o>=0)this.#r[this.#o]=e;else if(this.#r.length=this.#o+1,this.#r.push(e),this.#o=this.#r.length-1,this.#n>0&&this.#r.length>this.#n){let e=this.#r.length-this.#n;this.#r.splice(0,e),this.#o=Math.max(0,this.#o-e)}this.#d(e,`navigate`)},onStop:()=>{this.#l++,this.#p()},teardown:()=>{this.#u||(this.#u=!0,this.#l++,this.#i(),this.#a.release(),this.#p())}}}#d(e,t){this.#a.write(e,{direction:t,historyIndex:this.#o})}#f(e){if(!Number.isInteger(e)||e===0)return;let t=this.#o+e;if(t<0||t>=this.#r.length)return;let n=this.#r[t],r=this.#e.getState();if(n.path===r?.path){this.#o=t,this.#d(r,e>0?`forward`:`back`);return}let i=this.#o,a=++this.#l;this.#c=e>0?`forward`:`back`,this.#s=!0,this.#o=t,this.#t.navigateToState(n,{replace:!0}).then(()=>{this.#l===a&&(this.#s=!1)},()=>{this.#l===a&&(this.#o=i,this.#s=!1)})}#p(){this.#r.length=0,this.#o=-1,this.#s=!1,this.#c=`navigate`}};function n(n={}){if(n.maxHistoryLength!==void 0){let e=n.maxHistoryLength;if(typeof e!=`number`||!Number.isFinite(e)||!Number.isInteger(e)||e<0)throw TypeError(`[memory-plugin] Invalid maxHistoryLength: expected non-negative integer, got ${String(e)}.`)}let r=Object.freeze({...n});return n=>new t(n,(0,e.getPluginApi)(n),r).getPlugin()}exports.memoryPluginFactory=n;
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["#router","#api","#maxHistory","#entries","#removeExtensions","#claim","#go","#index","#navigatingFromHistory","#writeMemoryContext","#pendingDirection","#goGeneration","#clear","#disposed"],"sources":["../../src/plugin.ts","../../src/factory.ts"],"sourcesContent":["import type {\n MemoryContext,\n MemoryDirection,\n MemoryPluginOptions,\n} from \"./types\";\nimport type {\n NavigationOptions,\n Plugin,\n Router,\n State,\n} from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nconst DEFAULT_MAX_HISTORY = 1000;\n\n/** @internal — instantiated by `memoryPluginFactory`; not part of the public API. */\nexport class MemoryPlugin {\n readonly #router: Router;\n readonly #api: PluginApi;\n readonly #maxHistory: number;\n // Stored entries are full State snapshots (#561). Snapshot semantics for\n // back/forward replay: api.navigateToState commits the stored State as-is,\n // immune to post-recording route mutations (routes.update / routes.replace\n // changing defaultParams or meta) and to non-idempotent dynamic\n // forwardFn / buildPath interceptors. Activation guards still run at\n // replay time — that is where current-world-state checks belong, not in\n // the navigation pipeline.\n readonly #entries: State[] = [];\n readonly #removeExtensions: () => void;\n readonly #claim: {\n write: (state: State, value: MemoryContext) => void;\n release: () => void;\n };\n #index = -1;\n #navigatingFromHistory = false;\n #pendingDirection: MemoryDirection = \"navigate\";\n #goGeneration = 0;\n #disposed = false;\n\n constructor(router: Router, api: PluginApi, options: MemoryPluginOptions) {\n this.#router = router;\n this.#api = api;\n this.#maxHistory = options.maxHistoryLength ?? DEFAULT_MAX_HISTORY;\n this.#claim = api.claimContextNamespace(\"memory\");\n\n this.#removeExtensions = api.extendRouter({\n back: () => {\n this.#go(-1);\n },\n forward: () => {\n this.#go(1);\n },\n go: (delta: number) => {\n this.#go(delta);\n },\n canGoBack: () => this.#index > 0,\n canGoForward: () => this.#index < this.#entries.length - 1,\n });\n }\n\n getPlugin(): Plugin {\n return {\n onTransitionSuccess: (\n toState: State,\n _fromState: State | undefined,\n opts: NavigationOptions,\n ) => {\n if (this.#navigatingFromHistory) {\n this.#writeMemoryContext(toState, this.#pendingDirection);\n\n return;\n }\n\n if (opts.replace && this.#index >= 0) {\n this.#entries[this.#index] = toState;\n } else {\n this.#entries.length = this.#index + 1;\n this.#entries.push(toState);\n this.#index = this.#entries.length - 1;\n\n if (this.#maxHistory > 0 && this.#entries.length > this.#maxHistory) {\n const overflow = this.#entries.length - this.#maxHistory;\n\n this.#entries.splice(0, overflow);\n this.#index = Math.max(0, this.#index - overflow);\n }\n }\n\n this.#writeMemoryContext(toState, \"navigate\");\n },\n\n onStop: () => {\n // Bump generation so any in-flight #go settler observes a mismatch\n // and skips its revert / flag reset — writing into cleared state\n // would otherwise leave #index pointing into an empty #entries (#505).\n this.#goGeneration++;\n this.#clear();\n },\n\n teardown: () => {\n /* v8 ignore next 3 -- @preserve: core's unsubscribe() already guards via `unsubscribed` flag; this idempotency check covers router.dispose() + unsubscribe() ordering edge cases */\n if (this.#disposed) {\n return;\n }\n\n this.#disposed = true;\n // Same generation bump as onStop — pre-teardown in-flight #go settlers\n // must not write into a released plugin (#505).\n this.#goGeneration++;\n this.#removeExtensions();\n this.#claim.release();\n this.#clear();\n },\n };\n }\n\n #writeMemoryContext(toState: State, direction: MemoryDirection): void {\n this.#claim.write(toState, { direction, historyIndex: this.#index });\n }\n\n #go(delta: number): void {\n if (!Number.isInteger(delta) || delta === 0) {\n return;\n }\n\n const targetIndex = this.#index + delta;\n\n if (targetIndex < 0 || targetIndex >= this.#entries.length) {\n return;\n }\n\n const entry = this.#entries[targetIndex];\n const currentState = this.#router.getState();\n\n if (entry.path === currentState?.path) {\n // Short-circuit: landing on an entry whose path matches the current\n // state skips api.navigateToState. Still rewrite state.context.memory\n // so subscribers see the new historyIndex + direction — otherwise\n // UI animation driven by `direction` sees a stale \"navigate\" value\n // and `state.context.memory.historyIndex` diverges from `#index`\n // until the next full transition (#508).\n this.#index = targetIndex;\n this.#writeMemoryContext(currentState, delta > 0 ? \"forward\" : \"back\");\n\n return;\n }\n\n const previousIndex = this.#index;\n const generation = ++this.#goGeneration;\n\n this.#pendingDirection = delta > 0 ? \"forward\" : \"back\";\n this.#navigatingFromHistory = true;\n this.#index = targetIndex;\n\n // navigateToState commits the stored snapshot verbatim — same primitive\n // every URL-driven flow uses (start, popstate, navigate-event). Skips\n // forwardState + buildPath re-resolution and their interceptors; route\n // mutations between record and replay do not retroactively change what\n // back/forward commits (#561).\n void this.#api.navigateToState(entry, { replace: true }).then(\n () => {\n if (this.#goGeneration === generation) {\n this.#navigatingFromHistory = false;\n }\n },\n () => {\n if (this.#goGeneration === generation) {\n this.#index = previousIndex;\n this.#navigatingFromHistory = false;\n }\n },\n );\n }\n\n #clear(): void {\n this.#entries.length = 0;\n this.#index = -1;\n // Reset transient #go state as well: if #clear runs while a #go is in\n // flight, the reject-handler skips (generation mismatch) and would\n // otherwise leave #navigatingFromHistory stuck at true — the next\n // onTransitionSuccess after restart would take the history-restore\n // branch and silently skip pushing a new entry. Both fields are\n // \"current #go intent\", not persistent history, so resetting them on\n // clear is always correct (#505).\n this.#navigatingFromHistory = false;\n this.#pendingDirection = \"navigate\";\n }\n}\n","import { getPluginApi } from \"@real-router/core/api\";\n\nimport { MemoryPlugin } from \"./plugin\";\n\nimport type { MemoryPluginOptions } from \"./types\";\nimport type { PluginFactory, Plugin, Router } from \"@real-router/core\";\n\nexport function memoryPluginFactory(\n options: MemoryPluginOptions = {},\n): PluginFactory {\n if (options.maxHistoryLength !== undefined) {\n const length = options.maxHistoryLength;\n\n if (\n typeof length !== \"number\" ||\n !Number.isFinite(length) ||\n !Number.isInteger(length) ||\n length < 0\n ) {\n throw new TypeError(\n `[memory-plugin] Invalid maxHistoryLength: expected non-negative integer, got ${String(length)}.`,\n );\n }\n }\n\n const frozenOptions: MemoryPluginOptions = Object.freeze({ ...options });\n\n return (router): Plugin => {\n const api = getPluginApi(router);\n const plugin = new MemoryPlugin(router as Router, api, frozenOptions);\n\n return plugin.getPlugin();\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["#router","#api","#maxHistory","#entries","#removeExtensions","#claim","#go","#index","#navigatingFromHistory","#writeMemoryContext","#pendingDirection","#goGeneration","#clear","#disposed"],"sources":["../../src/plugin.ts","../../src/factory.ts"],"sourcesContent":["import type {\n MemoryContext,\n MemoryDirection,\n MemoryPluginOptions,\n} from \"./types\";\nimport type {\n NavigationOptions,\n Plugin,\n Router,\n State,\n} from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nconst DEFAULT_MAX_HISTORY = 1000;\n\n/** @internal — instantiated by `memoryPluginFactory`; not part of the public API. */\nexport class MemoryPlugin {\n readonly #router: Router;\n readonly #api: PluginApi;\n readonly #maxHistory: number;\n // Stored entries are full State snapshots (#561). Snapshot semantics for\n // back/forward replay: api.navigateToState commits the stored State as-is,\n // immune to post-recording route mutations (routes.update / routes.replace\n // changing defaultParams or meta) and to non-idempotent dynamic\n // forwardFn / buildPath interceptors. Activation guards still run at\n // replay time — that is where current-world-state checks belong, not in\n // the navigation pipeline.\n readonly #entries: State[] = [];\n readonly #removeExtensions: () => void;\n readonly #claim: {\n write: (state: State, value: MemoryContext) => void;\n release: () => void;\n };\n #index = -1;\n #navigatingFromHistory = false;\n #pendingDirection: MemoryDirection = \"navigate\";\n #goGeneration = 0;\n #disposed = false;\n\n constructor(router: Router, api: PluginApi, options: MemoryPluginOptions) {\n this.#router = router;\n this.#api = api;\n this.#maxHistory = options.maxHistoryLength ?? DEFAULT_MAX_HISTORY;\n this.#claim = api.claimContextNamespace(\"memory\");\n\n this.#removeExtensions = api.extendRouter({\n back: () => {\n this.#go(-1);\n },\n forward: () => {\n this.#go(1);\n },\n go: (delta: number) => {\n this.#go(delta);\n },\n canGoBack: () => this.#index > 0,\n canGoForward: () => this.#index < this.#entries.length - 1,\n });\n }\n\n getPlugin(): Plugin {\n return {\n onTransitionSuccess: (\n toState: State,\n _fromState: State | undefined,\n opts: NavigationOptions,\n ) => {\n if (this.#navigatingFromHistory) {\n this.#writeMemoryContext(toState, this.#pendingDirection);\n\n return;\n }\n\n if (opts.replace && this.#index >= 0) {\n this.#entries[this.#index] = toState;\n } else {\n this.#entries.length = this.#index + 1;\n this.#entries.push(toState);\n this.#index = this.#entries.length - 1;\n\n if (this.#maxHistory > 0 && this.#entries.length > this.#maxHistory) {\n const overflow = this.#entries.length - this.#maxHistory;\n\n this.#entries.splice(0, overflow);\n this.#index = Math.max(0, this.#index - overflow);\n }\n }\n\n this.#writeMemoryContext(toState, \"navigate\");\n },\n\n onStop: () => {\n // Bump generation so any in-flight #go settler observes a mismatch\n // and skips its revert / flag reset — writing into cleared state\n // would otherwise leave #index pointing into an empty #entries (#505).\n this.#goGeneration++;\n this.#clear();\n },\n\n teardown: () => {\n /* v8 ignore next 3 -- @preserve: core's unsubscribe() already guards via `unsubscribed` flag; this idempotency check covers router.dispose() + unsubscribe() ordering edge cases */\n if (this.#disposed) {\n return;\n }\n\n this.#disposed = true;\n // Same generation bump as onStop — pre-teardown in-flight #go settlers\n // must not write into a released plugin (#505).\n this.#goGeneration++;\n this.#removeExtensions();\n this.#claim.release();\n this.#clear();\n },\n };\n }\n\n #writeMemoryContext(toState: State, direction: MemoryDirection): void {\n this.#claim.write(toState, { direction, historyIndex: this.#index });\n }\n\n #go(delta: number): void {\n if (!Number.isInteger(delta) || delta === 0) {\n return;\n }\n\n const targetIndex = this.#index + delta;\n\n if (targetIndex < 0 || targetIndex >= this.#entries.length) {\n return;\n }\n\n const entry = this.#entries[targetIndex];\n const currentState = this.#router.getState();\n\n if (entry.path === currentState?.path) {\n // Short-circuit: landing on an entry whose path matches the current\n // state skips api.navigateToState. Still rewrite state.context.memory\n // so subscribers see the new historyIndex + direction — otherwise\n // UI animation driven by `direction` sees a stale \"navigate\" value\n // and `state.context.memory.historyIndex` diverges from `#index`\n // until the next full transition (#508).\n this.#index = targetIndex;\n this.#writeMemoryContext(currentState, delta > 0 ? \"forward\" : \"back\");\n\n return;\n }\n\n const previousIndex = this.#index;\n const generation = ++this.#goGeneration;\n\n this.#pendingDirection = delta > 0 ? \"forward\" : \"back\";\n this.#navigatingFromHistory = true;\n this.#index = targetIndex;\n\n // navigateToState commits the stored snapshot verbatim — same primitive\n // every URL-driven flow uses (start, popstate, navigate-event). Skips\n // forwardState + buildPath re-resolution and their interceptors; route\n // mutations between record and replay do not retroactively change what\n // back/forward commits (#561).\n void this.#api.navigateToState(entry, { replace: true }).then(\n () => {\n if (this.#goGeneration === generation) {\n this.#navigatingFromHistory = false;\n }\n },\n () => {\n if (this.#goGeneration === generation) {\n this.#index = previousIndex;\n this.#navigatingFromHistory = false;\n }\n },\n );\n }\n\n #clear(): void {\n this.#entries.length = 0;\n this.#index = -1;\n // Reset transient #go state as well: if #clear runs while a #go is in\n // flight, the reject-handler skips (generation mismatch) and would\n // otherwise leave #navigatingFromHistory stuck at true — the next\n // onTransitionSuccess after restart would take the history-restore\n // branch and silently skip pushing a new entry. Both fields are\n // \"current #go intent\", not persistent history, so resetting them on\n // clear is always correct (#505).\n this.#navigatingFromHistory = false;\n this.#pendingDirection = \"navigate\";\n }\n}\n","import { getPluginApi } from \"@real-router/core/api\";\n\nimport { MemoryPlugin } from \"./plugin\";\n\nimport type { MemoryPluginOptions } from \"./types\";\nimport type { PluginFactory, Plugin, Router } from \"@real-router/core\";\n\nexport function memoryPluginFactory(\n options: MemoryPluginOptions = {},\n): PluginFactory {\n if (options.maxHistoryLength !== undefined) {\n const length = options.maxHistoryLength;\n\n if (\n typeof length !== \"number\" ||\n !Number.isFinite(length) ||\n !Number.isInteger(length) ||\n length < 0\n ) {\n throw new TypeError(\n `[memory-plugin] Invalid maxHistoryLength: expected non-negative integer, got ${String(length)}.`,\n );\n }\n }\n\n const frozenOptions: MemoryPluginOptions = Object.freeze({ ...options });\n\n return (router): Plugin => {\n const api = getPluginApi(router);\n const plugin = new MemoryPlugin(router as Router, api, frozenOptions);\n\n return plugin.getPlugin();\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;KAAY,eAAA;AAAA,UAEK,aAAA;EAAA,SACN,SAAA,EAAW,eAAA;EAAA,SACX,YAAA;AAAA;AAAA,UAGM,mBAAA;EAPU;;AAE3B;;;;;;;;;EAiBE,gBAAA;AAAA;;;iBCZc,mBAAA,CACd,OAAA,GAAS,mBAAA,GACR,aAAA;;;;YCAS,YAAA;IACR,MAAA,GAJa,aAAA;EAAA;AAAA;AAAA;EAAA,UASL,MAAA;IACR,IAAA;IACA,OAAA;IACA,EAAA,GAAK,KAAA;IACL,SAAA;IACA,YAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;KAAY,eAAA;AAAA,UAEK,aAAA;EAAA,SACN,SAAA,EAAW,eAAe;EAAA,SAC1B,YAAA;AAAA;AAAA,UAGM,mBAAA;EAPU;AAAA;AAE3B;;;;;;;;AAEuB;EAerB,gBAAgB;AAAA;;;iBCZF,mBAAA,CACd,OAAA,GAAS,mBAAA,GACR,aAAa;;;;YCAJ,YAAA;IACR,MAAA,GAJa,aAI2B;EAAA;AAAA;AAAA;EAAA,UAKhC,MAAA;IACR,IAAA;IACA,OAAA;IACA,EAAA,GAAK,KAAA;IACL,SAAA;IACA,YAAA;EAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["#router","#api","#maxHistory","#entries","#removeExtensions","#claim","#go","#index","#navigatingFromHistory","#writeMemoryContext","#pendingDirection","#goGeneration","#clear","#disposed"],"sources":["../../src/plugin.ts","../../src/factory.ts"],"sourcesContent":["import type {\n MemoryContext,\n MemoryDirection,\n MemoryPluginOptions,\n} from \"./types\";\nimport type {\n NavigationOptions,\n Plugin,\n Router,\n State,\n} from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nconst DEFAULT_MAX_HISTORY = 1000;\n\n/** @internal — instantiated by `memoryPluginFactory`; not part of the public API. */\nexport class MemoryPlugin {\n readonly #router: Router;\n readonly #api: PluginApi;\n readonly #maxHistory: number;\n // Stored entries are full State snapshots (#561). Snapshot semantics for\n // back/forward replay: api.navigateToState commits the stored State as-is,\n // immune to post-recording route mutations (routes.update / routes.replace\n // changing defaultParams or meta) and to non-idempotent dynamic\n // forwardFn / buildPath interceptors. Activation guards still run at\n // replay time — that is where current-world-state checks belong, not in\n // the navigation pipeline.\n readonly #entries: State[] = [];\n readonly #removeExtensions: () => void;\n readonly #claim: {\n write: (state: State, value: MemoryContext) => void;\n release: () => void;\n };\n #index = -1;\n #navigatingFromHistory = false;\n #pendingDirection: MemoryDirection = \"navigate\";\n #goGeneration = 0;\n #disposed = false;\n\n constructor(router: Router, api: PluginApi, options: MemoryPluginOptions) {\n this.#router = router;\n this.#api = api;\n this.#maxHistory = options.maxHistoryLength ?? DEFAULT_MAX_HISTORY;\n this.#claim = api.claimContextNamespace(\"memory\");\n\n this.#removeExtensions = api.extendRouter({\n back: () => {\n this.#go(-1);\n },\n forward: () => {\n this.#go(1);\n },\n go: (delta: number) => {\n this.#go(delta);\n },\n canGoBack: () => this.#index > 0,\n canGoForward: () => this.#index < this.#entries.length - 1,\n });\n }\n\n getPlugin(): Plugin {\n return {\n onTransitionSuccess: (\n toState: State,\n _fromState: State | undefined,\n opts: NavigationOptions,\n ) => {\n if (this.#navigatingFromHistory) {\n this.#writeMemoryContext(toState, this.#pendingDirection);\n\n return;\n }\n\n if (opts.replace && this.#index >= 0) {\n this.#entries[this.#index] = toState;\n } else {\n this.#entries.length = this.#index + 1;\n this.#entries.push(toState);\n this.#index = this.#entries.length - 1;\n\n if (this.#maxHistory > 0 && this.#entries.length > this.#maxHistory) {\n const overflow = this.#entries.length - this.#maxHistory;\n\n this.#entries.splice(0, overflow);\n this.#index = Math.max(0, this.#index - overflow);\n }\n }\n\n this.#writeMemoryContext(toState, \"navigate\");\n },\n\n onStop: () => {\n // Bump generation so any in-flight #go settler observes a mismatch\n // and skips its revert / flag reset — writing into cleared state\n // would otherwise leave #index pointing into an empty #entries (#505).\n this.#goGeneration++;\n this.#clear();\n },\n\n teardown: () => {\n /* v8 ignore next 3 -- @preserve: core's unsubscribe() already guards via `unsubscribed` flag; this idempotency check covers router.dispose() + unsubscribe() ordering edge cases */\n if (this.#disposed) {\n return;\n }\n\n this.#disposed = true;\n // Same generation bump as onStop — pre-teardown in-flight #go settlers\n // must not write into a released plugin (#505).\n this.#goGeneration++;\n this.#removeExtensions();\n this.#claim.release();\n this.#clear();\n },\n };\n }\n\n #writeMemoryContext(toState: State, direction: MemoryDirection): void {\n this.#claim.write(toState, { direction, historyIndex: this.#index });\n }\n\n #go(delta: number): void {\n if (!Number.isInteger(delta) || delta === 0) {\n return;\n }\n\n const targetIndex = this.#index + delta;\n\n if (targetIndex < 0 || targetIndex >= this.#entries.length) {\n return;\n }\n\n const entry = this.#entries[targetIndex];\n const currentState = this.#router.getState();\n\n if (entry.path === currentState?.path) {\n // Short-circuit: landing on an entry whose path matches the current\n // state skips api.navigateToState. Still rewrite state.context.memory\n // so subscribers see the new historyIndex + direction — otherwise\n // UI animation driven by `direction` sees a stale \"navigate\" value\n // and `state.context.memory.historyIndex` diverges from `#index`\n // until the next full transition (#508).\n this.#index = targetIndex;\n this.#writeMemoryContext(currentState, delta > 0 ? \"forward\" : \"back\");\n\n return;\n }\n\n const previousIndex = this.#index;\n const generation = ++this.#goGeneration;\n\n this.#pendingDirection = delta > 0 ? \"forward\" : \"back\";\n this.#navigatingFromHistory = true;\n this.#index = targetIndex;\n\n // navigateToState commits the stored snapshot verbatim — same primitive\n // every URL-driven flow uses (start, popstate, navigate-event). Skips\n // forwardState + buildPath re-resolution and their interceptors; route\n // mutations between record and replay do not retroactively change what\n // back/forward commits (#561).\n void this.#api.navigateToState(entry, { replace: true }).then(\n () => {\n if (this.#goGeneration === generation) {\n this.#navigatingFromHistory = false;\n }\n },\n () => {\n if (this.#goGeneration === generation) {\n this.#index = previousIndex;\n this.#navigatingFromHistory = false;\n }\n },\n );\n }\n\n #clear(): void {\n this.#entries.length = 0;\n this.#index = -1;\n // Reset transient #go state as well: if #clear runs while a #go is in\n // flight, the reject-handler skips (generation mismatch) and would\n // otherwise leave #navigatingFromHistory stuck at true — the next\n // onTransitionSuccess after restart would take the history-restore\n // branch and silently skip pushing a new entry. Both fields are\n // \"current #go intent\", not persistent history, so resetting them on\n // clear is always correct (#505).\n this.#navigatingFromHistory = false;\n this.#pendingDirection = \"navigate\";\n }\n}\n","import { getPluginApi } from \"@real-router/core/api\";\n\nimport { MemoryPlugin } from \"./plugin\";\n\nimport type { MemoryPluginOptions } from \"./types\";\nimport type { PluginFactory, Plugin, Router } from \"@real-router/core\";\n\nexport function memoryPluginFactory(\n options: MemoryPluginOptions = {},\n): PluginFactory {\n if (options.maxHistoryLength !== undefined) {\n const length = options.maxHistoryLength;\n\n if (\n typeof length !== \"number\" ||\n !Number.isFinite(length) ||\n !Number.isInteger(length) ||\n length < 0\n ) {\n throw new TypeError(\n `[memory-plugin] Invalid maxHistoryLength: expected non-negative integer, got ${String(length)}.`,\n );\n }\n }\n\n const frozenOptions: MemoryPluginOptions = Object.freeze({ ...options });\n\n return (router): Plugin => {\n const api = getPluginApi(router);\n const plugin = new MemoryPlugin(router as Router, api, frozenOptions);\n\n return plugin.getPlugin();\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["#router","#api","#maxHistory","#entries","#removeExtensions","#claim","#go","#index","#navigatingFromHistory","#writeMemoryContext","#pendingDirection","#goGeneration","#clear","#disposed"],"sources":["../../src/plugin.ts","../../src/factory.ts"],"sourcesContent":["import type {\n MemoryContext,\n MemoryDirection,\n MemoryPluginOptions,\n} from \"./types\";\nimport type {\n NavigationOptions,\n Plugin,\n Router,\n State,\n} from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nconst DEFAULT_MAX_HISTORY = 1000;\n\n/** @internal — instantiated by `memoryPluginFactory`; not part of the public API. */\nexport class MemoryPlugin {\n readonly #router: Router;\n readonly #api: PluginApi;\n readonly #maxHistory: number;\n // Stored entries are full State snapshots (#561). Snapshot semantics for\n // back/forward replay: api.navigateToState commits the stored State as-is,\n // immune to post-recording route mutations (routes.update / routes.replace\n // changing defaultParams or meta) and to non-idempotent dynamic\n // forwardFn / buildPath interceptors. Activation guards still run at\n // replay time — that is where current-world-state checks belong, not in\n // the navigation pipeline.\n readonly #entries: State[] = [];\n readonly #removeExtensions: () => void;\n readonly #claim: {\n write: (state: State, value: MemoryContext) => void;\n release: () => void;\n };\n #index = -1;\n #navigatingFromHistory = false;\n #pendingDirection: MemoryDirection = \"navigate\";\n #goGeneration = 0;\n #disposed = false;\n\n constructor(router: Router, api: PluginApi, options: MemoryPluginOptions) {\n this.#router = router;\n this.#api = api;\n this.#maxHistory = options.maxHistoryLength ?? DEFAULT_MAX_HISTORY;\n this.#claim = api.claimContextNamespace(\"memory\");\n\n this.#removeExtensions = api.extendRouter({\n back: () => {\n this.#go(-1);\n },\n forward: () => {\n this.#go(1);\n },\n go: (delta: number) => {\n this.#go(delta);\n },\n canGoBack: () => this.#index > 0,\n canGoForward: () => this.#index < this.#entries.length - 1,\n });\n }\n\n getPlugin(): Plugin {\n return {\n onTransitionSuccess: (\n toState: State,\n _fromState: State | undefined,\n opts: NavigationOptions,\n ) => {\n if (this.#navigatingFromHistory) {\n this.#writeMemoryContext(toState, this.#pendingDirection);\n\n return;\n }\n\n if (opts.replace && this.#index >= 0) {\n this.#entries[this.#index] = toState;\n } else {\n this.#entries.length = this.#index + 1;\n this.#entries.push(toState);\n this.#index = this.#entries.length - 1;\n\n if (this.#maxHistory > 0 && this.#entries.length > this.#maxHistory) {\n const overflow = this.#entries.length - this.#maxHistory;\n\n this.#entries.splice(0, overflow);\n this.#index = Math.max(0, this.#index - overflow);\n }\n }\n\n this.#writeMemoryContext(toState, \"navigate\");\n },\n\n onStop: () => {\n // Bump generation so any in-flight #go settler observes a mismatch\n // and skips its revert / flag reset — writing into cleared state\n // would otherwise leave #index pointing into an empty #entries (#505).\n this.#goGeneration++;\n this.#clear();\n },\n\n teardown: () => {\n /* v8 ignore next 3 -- @preserve: core's unsubscribe() already guards via `unsubscribed` flag; this idempotency check covers router.dispose() + unsubscribe() ordering edge cases */\n if (this.#disposed) {\n return;\n }\n\n this.#disposed = true;\n // Same generation bump as onStop — pre-teardown in-flight #go settlers\n // must not write into a released plugin (#505).\n this.#goGeneration++;\n this.#removeExtensions();\n this.#claim.release();\n this.#clear();\n },\n };\n }\n\n #writeMemoryContext(toState: State, direction: MemoryDirection): void {\n this.#claim.write(toState, { direction, historyIndex: this.#index });\n }\n\n #go(delta: number): void {\n if (!Number.isInteger(delta) || delta === 0) {\n return;\n }\n\n const targetIndex = this.#index + delta;\n\n if (targetIndex < 0 || targetIndex >= this.#entries.length) {\n return;\n }\n\n const entry = this.#entries[targetIndex];\n const currentState = this.#router.getState();\n\n if (entry.path === currentState?.path) {\n // Short-circuit: landing on an entry whose path matches the current\n // state skips api.navigateToState. Still rewrite state.context.memory\n // so subscribers see the new historyIndex + direction — otherwise\n // UI animation driven by `direction` sees a stale \"navigate\" value\n // and `state.context.memory.historyIndex` diverges from `#index`\n // until the next full transition (#508).\n this.#index = targetIndex;\n this.#writeMemoryContext(currentState, delta > 0 ? \"forward\" : \"back\");\n\n return;\n }\n\n const previousIndex = this.#index;\n const generation = ++this.#goGeneration;\n\n this.#pendingDirection = delta > 0 ? \"forward\" : \"back\";\n this.#navigatingFromHistory = true;\n this.#index = targetIndex;\n\n // navigateToState commits the stored snapshot verbatim — same primitive\n // every URL-driven flow uses (start, popstate, navigate-event). Skips\n // forwardState + buildPath re-resolution and their interceptors; route\n // mutations between record and replay do not retroactively change what\n // back/forward commits (#561).\n void this.#api.navigateToState(entry, { replace: true }).then(\n () => {\n if (this.#goGeneration === generation) {\n this.#navigatingFromHistory = false;\n }\n },\n () => {\n if (this.#goGeneration === generation) {\n this.#index = previousIndex;\n this.#navigatingFromHistory = false;\n }\n },\n );\n }\n\n #clear(): void {\n this.#entries.length = 0;\n this.#index = -1;\n // Reset transient #go state as well: if #clear runs while a #go is in\n // flight, the reject-handler skips (generation mismatch) and would\n // otherwise leave #navigatingFromHistory stuck at true — the next\n // onTransitionSuccess after restart would take the history-restore\n // branch and silently skip pushing a new entry. Both fields are\n // \"current #go intent\", not persistent history, so resetting them on\n // clear is always correct (#505).\n this.#navigatingFromHistory = false;\n this.#pendingDirection = \"navigate\";\n }\n}\n","import { getPluginApi } from \"@real-router/core/api\";\n\nimport { MemoryPlugin } from \"./plugin\";\n\nimport type { MemoryPluginOptions } from \"./types\";\nimport type { PluginFactory, Plugin, Router } from \"@real-router/core\";\n\nexport function memoryPluginFactory(\n options: MemoryPluginOptions = {},\n): PluginFactory {\n if (options.maxHistoryLength !== undefined) {\n const length = options.maxHistoryLength;\n\n if (\n typeof length !== \"number\" ||\n !Number.isFinite(length) ||\n !Number.isInteger(length) ||\n length < 0\n ) {\n throw new TypeError(\n `[memory-plugin] Invalid maxHistoryLength: expected non-negative integer, got ${String(length)}.`,\n );\n }\n }\n\n const frozenOptions: MemoryPluginOptions = Object.freeze({ ...options });\n\n return (router): Plugin => {\n const api = getPluginApi(router);\n const plugin = new MemoryPlugin(router as Router, api, frozenOptions);\n\n return plugin.getPlugin();\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/memory-plugin",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "type": "commonjs",
5
5
  "description": "In-memory history engine for Real-Router — non-browser environments and benchmarks",
6
6
  "main": "./dist/cjs/index.js",
@@ -45,7 +45,7 @@
45
45
  "homepage": "https://github.com/greydragon888/real-router",
46
46
  "sideEffects": false,
47
47
  "dependencies": {
48
- "@real-router/core": "^0.53.0",
48
+ "@real-router/core": "^0.55.0",
49
49
  "@real-router/types": "^0.35.0"
50
50
  },
51
51
  "scripts": {