@real-router/memory-plugin 0.3.3 → 0.3.5
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/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.mts +2 -2
- package/dist/esm/index.d.mts.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/plugin.ts +38 -15
- package/src/types.ts +2 -2
package/dist/cjs/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { PluginFactory } from "@real-router/core";
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
type MemoryDirection = "back" | "forward" | "navigate";
|
|
5
5
|
interface MemoryContext {
|
|
6
|
-
direction: MemoryDirection;
|
|
7
|
-
historyIndex: number;
|
|
6
|
+
readonly direction: MemoryDirection;
|
|
7
|
+
readonly historyIndex: number;
|
|
8
8
|
}
|
|
9
9
|
interface MemoryPluginOptions {
|
|
10
10
|
/**
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;KAEY,eAAA;AAAA,UAEK,aAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;KAEY,eAAA;AAAA,UAEK,aAAA;EAAA,SACN,SAAA,EAAW,eAAA;EAAA,SACX,YAAA;AAAA;AAAA,UAGM,mBAAA;EAPU;AAE3B;;;;;;;;;AAKA;EAYE,gBAAA;AAAA;;;iBCdc,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"}
|
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=-1;#o=!1;#s=`navigate`;#c=0;#l=!1;constructor(e,t,n){this.#e=e,this.#t=n.maxHistoryLength??1e3,this.#i=t.claimContextNamespace(`memory`),this.#r=t.extendRouter({back:()=>{this.#d(-1)},forward:()=>{this.#d(1)},go:e=>{this.#d(e)},canGoBack:()=>this.#a>0,canGoForward:()=>this.#a<this.#n.length-1})}getPlugin(){return{onTransitionSuccess:(e,t,n)=>{if(this.#o){this.#u(e,this.#s);return}let r={name:e.name,params:e.params,path:e.path};if(n.replace&&this.#a>=0)this.#n[this.#a]=r;else if(this.#n.length=this.#a+1,this.#n.push(r),this.#a=this.#n.length-1,this.#t>0&&this.#n.length>this.#t){let e=this.#n.length-this.#t;this.#n.splice(0,e),this.#a=Math.max(0,this.#a-e)}this.#u(e,`navigate`)},onStop:()=>{this.#f()},teardown:()=>{this.#l||(this.#l=!0,this.#r(),this.#i.release(),this.#f())}}}#u(e,t){this.#i.write(e,
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@real-router/core/api`);var t=class{#e;#t;#n=[];#r;#i;#a=-1;#o=!1;#s=`navigate`;#c=0;#l=!1;constructor(e,t,n){this.#e=e,this.#t=n.maxHistoryLength??1e3,this.#i=t.claimContextNamespace(`memory`),this.#r=t.extendRouter({back:()=>{this.#d(-1)},forward:()=>{this.#d(1)},go:e=>{this.#d(e)},canGoBack:()=>this.#a>0,canGoForward:()=>this.#a<this.#n.length-1})}getPlugin(){return{onTransitionSuccess:(e,t,n)=>{if(this.#o){this.#u(e,this.#s);return}let r={name:e.name,params:e.params,path:e.path};if(n.replace&&this.#a>=0)this.#n[this.#a]=r;else if(this.#n.length=this.#a+1,this.#n.push(r),this.#a=this.#n.length-1,this.#t>0&&this.#n.length>this.#t){let e=this.#n.length-this.#t;this.#n.splice(0,e),this.#a=Math.max(0,this.#a-e)}this.#u(e,`navigate`)},onStop:()=>{this.#c++,this.#f()},teardown:()=>{this.#l||(this.#l=!0,this.#c++,this.#r(),this.#i.release(),this.#f())}}}#u(e,t){this.#i.write(e,{direction:t,historyIndex:this.#a})}#d(e){if(!Number.isInteger(e)||e===0)return;let t=this.#a+e;if(t<0||t>=this.#n.length)return;let n=this.#n[t],r=this.#e.getState();if(n.path===r?.path){this.#a=t,this.#u(r,e>0?`forward`:`back`);return}let i=this.#a,a=++this.#c;this.#s=e>0?`forward`:`back`,this.#o=!0,this.#a=t,this.#e.navigate(n.name,n.params,{replace:!0}).then(()=>{this.#c===a&&(this.#o=!1)},()=>{this.#c===a&&(this.#a=i,this.#o=!1)})}#f(){this.#n.length=0,this.#a=-1,this.#o=!1,this.#s=`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
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#router","#maxHistory","#entries","#removeExtensions","#claim","#go","#index","#navigatingFromHistory","#writeMemoryContext","#pendingDirection","#
|
|
1
|
+
{"version":3,"file":"index.js","names":["#router","#maxHistory","#entries","#removeExtensions","#claim","#go","#index","#navigatingFromHistory","#writeMemoryContext","#pendingDirection","#goGeneration","#clear","#disposed"],"sources":["../../src/plugin.ts","../../src/factory.ts"],"sourcesContent":["import type {\n HistoryEntry,\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 #maxHistory: number;\n readonly #entries: HistoryEntry[] = [];\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.#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 const entry: HistoryEntry = {\n name: toState.name,\n params: toState.params,\n path: toState.path,\n };\n\n if (opts.replace && this.#index >= 0) {\n this.#entries[this.#index] = entry;\n } else {\n this.#entries.length = this.#index + 1;\n this.#entries.push(entry);\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 router.navigate(). 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 void this.#router\n .navigate(entry.name, entry.params, { replace: true })\n .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":"0GAiBA,IAAa,EAAb,KAA0B,CACxB,GACA,GACA,GAAoC,EAAE,CACtC,GACA,GAIA,GAAS,GACT,GAAyB,GACzB,GAAqC,WACrC,GAAgB,EAChB,GAAY,GAEZ,YAAY,EAAgB,EAAgB,EAA8B,CACxE,MAAA,EAAe,EACf,MAAA,EAAmB,EAAQ,kBAAoB,IAC/C,MAAA,EAAc,EAAI,sBAAsB,SAAS,CAEjD,MAAA,EAAyB,EAAI,aAAa,CACxC,SAAY,CACV,MAAA,EAAS,GAAG,EAEd,YAAe,CACb,MAAA,EAAS,EAAE,EAEb,GAAK,GAAkB,CACrB,MAAA,EAAS,EAAM,EAEjB,cAAiB,MAAA,EAAc,EAC/B,iBAAoB,MAAA,EAAc,MAAA,EAAc,OAAS,EAC1D,CAAC,CAGJ,WAAoB,CAClB,MAAO,CACL,qBACE,EACA,EACA,IACG,CACH,GAAI,MAAA,EAA6B,CAC/B,MAAA,EAAyB,EAAS,MAAA,EAAuB,CAEzD,OAGF,IAAM,EAAsB,CAC1B,KAAM,EAAQ,KACd,OAAQ,EAAQ,OAChB,KAAM,EAAQ,KACf,CAED,GAAI,EAAK,SAAW,MAAA,GAAe,EACjC,MAAA,EAAc,MAAA,GAAe,UAE7B,MAAA,EAAc,OAAS,MAAA,EAAc,EACrC,MAAA,EAAc,KAAK,EAAM,CACzB,MAAA,EAAc,MAAA,EAAc,OAAS,EAEjC,MAAA,EAAmB,GAAK,MAAA,EAAc,OAAS,MAAA,EAAkB,CACnE,IAAM,EAAW,MAAA,EAAc,OAAS,MAAA,EAExC,MAAA,EAAc,OAAO,EAAG,EAAS,CACjC,MAAA,EAAc,KAAK,IAAI,EAAG,MAAA,EAAc,EAAS,CAIrD,MAAA,EAAyB,EAAS,WAAW,EAG/C,WAAc,CAIZ,MAAA,IACA,MAAA,GAAa,EAGf,aAAgB,CAEV,MAAA,IAIJ,MAAA,EAAiB,GAGjB,MAAA,IACA,MAAA,GAAwB,CACxB,MAAA,EAAY,SAAS,CACrB,MAAA,GAAa,GAEhB,CAGH,GAAoB,EAAgB,EAAkC,CACpE,MAAA,EAAY,MAAM,EAAS,CAAE,YAAW,aAAc,MAAA,EAAa,CAAC,CAGtE,GAAI,EAAqB,CACvB,GAAI,CAAC,OAAO,UAAU,EAAM,EAAI,IAAU,EACxC,OAGF,IAAM,EAAc,MAAA,EAAc,EAElC,GAAI,EAAc,GAAK,GAAe,MAAA,EAAc,OAClD,OAGF,IAAM,EAAQ,MAAA,EAAc,GACtB,EAAe,MAAA,EAAa,UAAU,CAE5C,GAAI,EAAM,OAAS,GAAc,KAAM,CAOrC,MAAA,EAAc,EACd,MAAA,EAAyB,EAAc,EAAQ,EAAI,UAAY,OAAO,CAEtE,OAGF,IAAM,EAAgB,MAAA,EAChB,EAAa,EAAE,MAAA,EAErB,MAAA,EAAyB,EAAQ,EAAI,UAAY,OACjD,MAAA,EAA8B,GAC9B,MAAA,EAAc,EAET,MAAA,EACF,SAAS,EAAM,KAAM,EAAM,OAAQ,CAAE,QAAS,GAAM,CAAC,CACrD,SACO,CACA,MAAA,IAAuB,IACzB,MAAA,EAA8B,SAG5B,CACA,MAAA,IAAuB,IACzB,MAAA,EAAc,EACd,MAAA,EAA8B,KAGnC,CAGL,IAAe,CACb,MAAA,EAAc,OAAS,EACvB,MAAA,EAAc,GAQd,MAAA,EAA8B,GAC9B,MAAA,EAAyB,aC7K7B,SAAgB,EACd,EAA+B,EAAE,CAClB,CACf,GAAI,EAAQ,mBAAqB,IAAA,GAAW,CAC1C,IAAM,EAAS,EAAQ,iBAEvB,GACE,OAAO,GAAW,UAClB,CAAC,OAAO,SAAS,EAAO,EACxB,CAAC,OAAO,UAAU,EAAO,EACzB,EAAS,EAET,MAAU,UACR,gFAAgF,OAAO,EAAO,CAAC,GAChG,CAIL,IAAM,EAAqC,OAAO,OAAO,CAAE,GAAG,EAAS,CAAC,CAExE,MAAQ,IAES,IAAI,EAAa,GAAA,EAAA,EAAA,cADP,EAAO,CACuB,EAAc,CAEvD,WAAW"}
|
package/dist/esm/index.d.mts
CHANGED
|
@@ -3,8 +3,8 @@ import { PluginFactory } from "@real-router/core";
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
type MemoryDirection = "back" | "forward" | "navigate";
|
|
5
5
|
interface MemoryContext {
|
|
6
|
-
direction: MemoryDirection;
|
|
7
|
-
historyIndex: number;
|
|
6
|
+
readonly direction: MemoryDirection;
|
|
7
|
+
readonly historyIndex: number;
|
|
8
8
|
}
|
|
9
9
|
interface MemoryPluginOptions {
|
|
10
10
|
/**
|
package/dist/esm/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;KAEY,eAAA;AAAA,UAEK,aAAA;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;KAEY,eAAA;AAAA,UAEK,aAAA;EAAA,SACN,SAAA,EAAW,eAAA;EAAA,SACX,YAAA;AAAA;AAAA,UAGM,mBAAA;EAPU;AAE3B;;;;;;;;;AAKA;EAYE,gBAAA;AAAA;;;iBCdc,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"}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getPluginApi as e}from"@real-router/core/api";var t=class{#e;#t;#n=[];#r;#i;#a=-1;#o=!1;#s=`navigate`;#c=0;#l=!1;constructor(e,t,n){this.#e=e,this.#t=n.maxHistoryLength??1e3,this.#i=t.claimContextNamespace(`memory`),this.#r=t.extendRouter({back:()=>{this.#d(-1)},forward:()=>{this.#d(1)},go:e=>{this.#d(e)},canGoBack:()=>this.#a>0,canGoForward:()=>this.#a<this.#n.length-1})}getPlugin(){return{onTransitionSuccess:(e,t,n)=>{if(this.#o){this.#u(e,this.#s);return}let r={name:e.name,params:e.params,path:e.path};if(n.replace&&this.#a>=0)this.#n[this.#a]=r;else if(this.#n.length=this.#a+1,this.#n.push(r),this.#a=this.#n.length-1,this.#t>0&&this.#n.length>this.#t){let e=this.#n.length-this.#t;this.#n.splice(0,e),this.#a=Math.max(0,this.#a-e)}this.#u(e,`navigate`)},onStop:()=>{this.#f()},teardown:()=>{this.#l||(this.#l=!0,this.#r(),this.#i.release(),this.#f())}}}#u(e,t){this.#i.write(e,
|
|
1
|
+
import{getPluginApi as e}from"@real-router/core/api";var t=class{#e;#t;#n=[];#r;#i;#a=-1;#o=!1;#s=`navigate`;#c=0;#l=!1;constructor(e,t,n){this.#e=e,this.#t=n.maxHistoryLength??1e3,this.#i=t.claimContextNamespace(`memory`),this.#r=t.extendRouter({back:()=>{this.#d(-1)},forward:()=>{this.#d(1)},go:e=>{this.#d(e)},canGoBack:()=>this.#a>0,canGoForward:()=>this.#a<this.#n.length-1})}getPlugin(){return{onTransitionSuccess:(e,t,n)=>{if(this.#o){this.#u(e,this.#s);return}let r={name:e.name,params:e.params,path:e.path};if(n.replace&&this.#a>=0)this.#n[this.#a]=r;else if(this.#n.length=this.#a+1,this.#n.push(r),this.#a=this.#n.length-1,this.#t>0&&this.#n.length>this.#t){let e=this.#n.length-this.#t;this.#n.splice(0,e),this.#a=Math.max(0,this.#a-e)}this.#u(e,`navigate`)},onStop:()=>{this.#c++,this.#f()},teardown:()=>{this.#l||(this.#l=!0,this.#c++,this.#r(),this.#i.release(),this.#f())}}}#u(e,t){this.#i.write(e,{direction:t,historyIndex:this.#a})}#d(e){if(!Number.isInteger(e)||e===0)return;let t=this.#a+e;if(t<0||t>=this.#n.length)return;let n=this.#n[t],r=this.#e.getState();if(n.path===r?.path){this.#a=t,this.#u(r,e>0?`forward`:`back`);return}let i=this.#a,a=++this.#c;this.#s=e>0?`forward`:`back`,this.#o=!0,this.#a=t,this.#e.navigate(n.name,n.params,{replace:!0}).then(()=>{this.#c===a&&(this.#o=!1)},()=>{this.#c===a&&(this.#a=i,this.#o=!1)})}#f(){this.#n.length=0,this.#a=-1,this.#o=!1,this.#s=`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,e(n),r).getPlugin()}export{n as memoryPluginFactory};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["#router","#maxHistory","#entries","#removeExtensions","#claim","#go","#index","#navigatingFromHistory","#writeMemoryContext","#pendingDirection","#
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["#router","#maxHistory","#entries","#removeExtensions","#claim","#go","#index","#navigatingFromHistory","#writeMemoryContext","#pendingDirection","#goGeneration","#clear","#disposed"],"sources":["../../src/plugin.ts","../../src/factory.ts"],"sourcesContent":["import type {\n HistoryEntry,\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 #maxHistory: number;\n readonly #entries: HistoryEntry[] = [];\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.#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 const entry: HistoryEntry = {\n name: toState.name,\n params: toState.params,\n path: toState.path,\n };\n\n if (opts.replace && this.#index >= 0) {\n this.#entries[this.#index] = entry;\n } else {\n this.#entries.length = this.#index + 1;\n this.#entries.push(entry);\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 router.navigate(). 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 void this.#router\n .navigate(entry.name, entry.params, { replace: true })\n .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
package/src/plugin.ts
CHANGED
|
@@ -88,6 +88,10 @@ export class MemoryPlugin {
|
|
|
88
88
|
},
|
|
89
89
|
|
|
90
90
|
onStop: () => {
|
|
91
|
+
// Bump generation so any in-flight #go settler observes a mismatch
|
|
92
|
+
// and skips its revert / flag reset — writing into cleared state
|
|
93
|
+
// would otherwise leave #index pointing into an empty #entries (#505).
|
|
94
|
+
this.#goGeneration++;
|
|
91
95
|
this.#clear();
|
|
92
96
|
},
|
|
93
97
|
|
|
@@ -98,6 +102,9 @@ export class MemoryPlugin {
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
this.#disposed = true;
|
|
105
|
+
// Same generation bump as onStop — pre-teardown in-flight #go settlers
|
|
106
|
+
// must not write into a released plugin (#505).
|
|
107
|
+
this.#goGeneration++;
|
|
101
108
|
this.#removeExtensions();
|
|
102
109
|
this.#claim.release();
|
|
103
110
|
this.#clear();
|
|
@@ -106,14 +113,11 @@ export class MemoryPlugin {
|
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
#writeMemoryContext(toState: State, direction: MemoryDirection): void {
|
|
109
|
-
this.#claim.write(
|
|
110
|
-
toState,
|
|
111
|
-
Object.freeze({ direction, historyIndex: this.#index }),
|
|
112
|
-
);
|
|
116
|
+
this.#claim.write(toState, { direction, historyIndex: this.#index });
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
#go(delta: number): void {
|
|
116
|
-
if (
|
|
120
|
+
if (!Number.isInteger(delta) || delta === 0) {
|
|
117
121
|
return;
|
|
118
122
|
}
|
|
119
123
|
|
|
@@ -127,7 +131,14 @@ export class MemoryPlugin {
|
|
|
127
131
|
const currentState = this.#router.getState();
|
|
128
132
|
|
|
129
133
|
if (entry.path === currentState?.path) {
|
|
134
|
+
// Short-circuit: landing on an entry whose path matches the current
|
|
135
|
+
// state skips router.navigate(). Still rewrite state.context.memory
|
|
136
|
+
// so subscribers see the new historyIndex + direction — otherwise
|
|
137
|
+
// UI animation driven by `direction` sees a stale "navigate" value
|
|
138
|
+
// and `state.context.memory.historyIndex` diverges from `#index`
|
|
139
|
+
// until the next full transition (#508).
|
|
130
140
|
this.#index = targetIndex;
|
|
141
|
+
this.#writeMemoryContext(currentState, delta > 0 ? "forward" : "back");
|
|
131
142
|
|
|
132
143
|
return;
|
|
133
144
|
}
|
|
@@ -141,20 +152,32 @@ export class MemoryPlugin {
|
|
|
141
152
|
|
|
142
153
|
void this.#router
|
|
143
154
|
.navigate(entry.name, entry.params, { replace: true })
|
|
144
|
-
.
|
|
145
|
-
|
|
146
|
-
this.#
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.#
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
.then(
|
|
156
|
+
() => {
|
|
157
|
+
if (this.#goGeneration === generation) {
|
|
158
|
+
this.#navigatingFromHistory = false;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
() => {
|
|
162
|
+
if (this.#goGeneration === generation) {
|
|
163
|
+
this.#index = previousIndex;
|
|
164
|
+
this.#navigatingFromHistory = false;
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
);
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
#clear(): void {
|
|
157
171
|
this.#entries.length = 0;
|
|
158
172
|
this.#index = -1;
|
|
173
|
+
// Reset transient #go state as well: if #clear runs while a #go is in
|
|
174
|
+
// flight, the reject-handler skips (generation mismatch) and would
|
|
175
|
+
// otherwise leave #navigatingFromHistory stuck at true — the next
|
|
176
|
+
// onTransitionSuccess after restart would take the history-restore
|
|
177
|
+
// branch and silently skip pushing a new entry. Both fields are
|
|
178
|
+
// "current #go intent", not persistent history, so resetting them on
|
|
179
|
+
// clear is always correct (#505).
|
|
180
|
+
this.#navigatingFromHistory = false;
|
|
181
|
+
this.#pendingDirection = "navigate";
|
|
159
182
|
}
|
|
160
183
|
}
|
package/src/types.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { Params } from "@real-router/core";
|
|
|
3
3
|
export type MemoryDirection = "back" | "forward" | "navigate";
|
|
4
4
|
|
|
5
5
|
export interface MemoryContext {
|
|
6
|
-
direction: MemoryDirection;
|
|
7
|
-
historyIndex: number;
|
|
6
|
+
readonly direction: MemoryDirection;
|
|
7
|
+
readonly historyIndex: number;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export interface MemoryPluginOptions {
|