@osiris-smarttv/keep-alive 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/cachePolicy-8JmypsRQ.d.ts +149 -0
- package/dist/core.d.ts +42 -0
- package/dist/core.js +1 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +1 -0
- package/dist/keep-alive.css +11 -0
- package/dist/router.d.ts +15 -0
- package/dist/router.js +1 -0
- package/package.json +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OsirisTech SmartTV
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# @osiris-smarttv/keep-alive
|
|
2
|
+
|
|
3
|
+
Keep-alive and page-cache toolkit for React TV applications.
|
|
4
|
+
|
|
5
|
+
`@osiris-smarttv/keep-alive` helps retain expensive screens/components across route transitions, with explicit lifecycle hooks and cache control policies designed for lean-back TV UX.
|
|
6
|
+
|
|
7
|
+
## Why use it
|
|
8
|
+
|
|
9
|
+
- Preserve screen state when navigating between tabs/routes
|
|
10
|
+
- Avoid remount cost for heavy TV pages
|
|
11
|
+
- Control eviction with LRU and manual policies
|
|
12
|
+
- Emit lifecycle events for analytics/cleanup
|
|
13
|
+
- Integrate with React Router through dedicated adapters
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i @osiris-smarttv/keep-alive
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Exports
|
|
22
|
+
|
|
23
|
+
- `@osiris-smarttv/keep-alive` - React bindings + router helpers + core types
|
|
24
|
+
- `@osiris-smarttv/keep-alive/core` - pure cache/policy/event primitives
|
|
25
|
+
- `@osiris-smarttv/keep-alive/router` - React Router-focused entry
|
|
26
|
+
- `@osiris-smarttv/keep-alive/styles.css` - optional base styles
|
|
27
|
+
|
|
28
|
+
## Quick start (React)
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { AliveScope, KeepAlive } from '@osiris-smarttv/keep-alive'
|
|
32
|
+
|
|
33
|
+
export function App() {
|
|
34
|
+
return (
|
|
35
|
+
<AliveScope>
|
|
36
|
+
<KeepAlive cacheKey="home-screen">
|
|
37
|
+
<HomeScreen />
|
|
38
|
+
</KeepAlive>
|
|
39
|
+
</AliveScope>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## React Router usage
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { KeepAliveOutlet } from '@osiris-smarttv/keep-alive/router'
|
|
48
|
+
|
|
49
|
+
export function ShellLayout() {
|
|
50
|
+
return <KeepAliveOutlet />
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Core API highlights
|
|
55
|
+
|
|
56
|
+
From `@osiris-smarttv/keep-alive/core`:
|
|
57
|
+
|
|
58
|
+
- `AliveEventBus`
|
|
59
|
+
- `CacheEvictionMode`
|
|
60
|
+
- `mergeCachePolicy`
|
|
61
|
+
- `DEFAULT_CACHE_POLICY`
|
|
62
|
+
- `DEFAULT_PAGE_CACHE_POLICY`
|
|
63
|
+
- `DEFAULT_STACK_CACHE_POLICY`
|
|
64
|
+
|
|
65
|
+
## React hooks
|
|
66
|
+
|
|
67
|
+
- `useAliveController()`
|
|
68
|
+
- `usePageCache()`
|
|
69
|
+
- `useActivate()`
|
|
70
|
+
- `useUnactivate()`
|
|
71
|
+
- `useAliveLifecycle()`
|
|
72
|
+
|
|
73
|
+
## Source privacy on npm
|
|
74
|
+
|
|
75
|
+
This package is published as a **public** npm package, but npm tarballs include only:
|
|
76
|
+
|
|
77
|
+
- `dist/**`
|
|
78
|
+
- `README.md`
|
|
79
|
+
- metadata files (`package.json`, `LICENSE`)
|
|
80
|
+
|
|
81
|
+
Source files under `src/**` are excluded by the `files` allowlist in `package.json`.
|
|
82
|
+
|
|
83
|
+
## Local development
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
yarn install
|
|
87
|
+
yarn build
|
|
88
|
+
yarn typecheck
|
|
89
|
+
yarn test
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Manual publish
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
yarn publish:check
|
|
96
|
+
yarn publish:dry-run
|
|
97
|
+
yarn publish:manual
|
|
98
|
+
```
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** Lifecycle phase for a cache slot (framework-agnostic). */
|
|
2
|
+
declare const CachePhase: {
|
|
3
|
+
/** Registered in the registry but not yet visible. */
|
|
4
|
+
readonly Mounted: "mounted";
|
|
5
|
+
/** Visible and interactive. */
|
|
6
|
+
readonly Active: "active";
|
|
7
|
+
/** Hidden but retained in the registry (keeper). */
|
|
8
|
+
readonly Cached: "cached";
|
|
9
|
+
};
|
|
10
|
+
type CachePhase = (typeof CachePhase)[keyof typeof CachePhase];
|
|
11
|
+
|
|
12
|
+
/** Stable registry key from KeepAlive `name` + optional `id`. */
|
|
13
|
+
declare function resolveCacheKey(name: string, id?: string): string;
|
|
14
|
+
declare function matchesCacheName(name: string, pattern: string | RegExp): boolean;
|
|
15
|
+
declare function entryMatchesName(entry: {
|
|
16
|
+
name: string;
|
|
17
|
+
}, pattern: string | RegExp): boolean;
|
|
18
|
+
|
|
19
|
+
declare const EvictReason: {
|
|
20
|
+
readonly Lru: "lru";
|
|
21
|
+
readonly Manual: "manual";
|
|
22
|
+
readonly Policy: "policy";
|
|
23
|
+
readonly Refresh: "refresh";
|
|
24
|
+
};
|
|
25
|
+
type EvictReason = (typeof EvictReason)[keyof typeof EvictReason];
|
|
26
|
+
/** @deprecated Use `EvictReason`. */
|
|
27
|
+
declare const PageCacheEvictReason: {
|
|
28
|
+
readonly Lru: "lru";
|
|
29
|
+
readonly Manual: "manual";
|
|
30
|
+
readonly Policy: "policy";
|
|
31
|
+
readonly Refresh: "refresh";
|
|
32
|
+
};
|
|
33
|
+
type PageCacheEvictReason = EvictReason;
|
|
34
|
+
type EvictEvent = {
|
|
35
|
+
cacheKey: string;
|
|
36
|
+
name: string;
|
|
37
|
+
reason: EvictReason;
|
|
38
|
+
};
|
|
39
|
+
/** @deprecated Use `EvictEvent`. */
|
|
40
|
+
type PageCacheEvictEvent = EvictEvent;
|
|
41
|
+
declare const CacheEvictionMode: {
|
|
42
|
+
/** Evict oldest entries when registry exceeds `maxEntries`. */
|
|
43
|
+
readonly Lru: "lru";
|
|
44
|
+
/** No automatic eviction — caller drops via `clearEntry` / stack pop. */
|
|
45
|
+
readonly Manual: "manual";
|
|
46
|
+
};
|
|
47
|
+
type CacheEvictionMode = (typeof CacheEvictionMode)[keyof typeof CacheEvictionMode];
|
|
48
|
+
type CachePolicy = {
|
|
49
|
+
maxEntries: number;
|
|
50
|
+
neverCache: readonly string[];
|
|
51
|
+
clearOnEnter: readonly string[];
|
|
52
|
+
/** @default CacheEvictionMode.Lru */
|
|
53
|
+
evictionMode?: CacheEvictionMode;
|
|
54
|
+
};
|
|
55
|
+
/** @deprecated Use `CachePolicy`. */
|
|
56
|
+
type PageCachePolicy = CachePolicy;
|
|
57
|
+
type CacheRecord = {
|
|
58
|
+
cacheKey: string;
|
|
59
|
+
name: string;
|
|
60
|
+
id?: string;
|
|
61
|
+
parentCacheKey: string | null;
|
|
62
|
+
phase: CachePhase;
|
|
63
|
+
createTime: number;
|
|
64
|
+
updateTime: number;
|
|
65
|
+
lastActiveTime: number;
|
|
66
|
+
activateCount: number;
|
|
67
|
+
};
|
|
68
|
+
type RegisterRecordInput = {
|
|
69
|
+
cacheKey: string;
|
|
70
|
+
name: string;
|
|
71
|
+
id?: string;
|
|
72
|
+
parentCacheKey: string | null;
|
|
73
|
+
};
|
|
74
|
+
type CachingNodeSnapshot = {
|
|
75
|
+
cacheKey: string;
|
|
76
|
+
name: string;
|
|
77
|
+
id?: string;
|
|
78
|
+
parentCacheKey: string | null;
|
|
79
|
+
phase: CachePhase;
|
|
80
|
+
createTime: number;
|
|
81
|
+
updateTime: number;
|
|
82
|
+
lastActiveTime: number;
|
|
83
|
+
activateCount: number;
|
|
84
|
+
isActive: boolean;
|
|
85
|
+
};
|
|
86
|
+
/** @deprecated Use `CachingNodeSnapshot`. */
|
|
87
|
+
type CachingNode = CachingNodeSnapshot;
|
|
88
|
+
type AliveLifecycleEvent = {
|
|
89
|
+
type: 'cache:register';
|
|
90
|
+
record: CacheRecord;
|
|
91
|
+
} | {
|
|
92
|
+
type: 'cache:activate';
|
|
93
|
+
record: CacheRecord;
|
|
94
|
+
previousPhase: CachePhase;
|
|
95
|
+
} | {
|
|
96
|
+
type: 'cache:deactivate';
|
|
97
|
+
record: CacheRecord;
|
|
98
|
+
} | {
|
|
99
|
+
type: 'cache:evict';
|
|
100
|
+
record: CacheRecord;
|
|
101
|
+
reason: EvictReason;
|
|
102
|
+
} | {
|
|
103
|
+
type: 'policy:clear-on-enter';
|
|
104
|
+
targetKey: string;
|
|
105
|
+
};
|
|
106
|
+
type AliveEventType = AliveLifecycleEvent['type'];
|
|
107
|
+
type AliveController = {
|
|
108
|
+
drop: (name: string | RegExp) => Promise<boolean>;
|
|
109
|
+
dropScope: (name: string | RegExp) => Promise<boolean>;
|
|
110
|
+
refresh: (name: string | RegExp) => Promise<boolean>;
|
|
111
|
+
refreshScope: (name: string | RegExp) => Promise<boolean>;
|
|
112
|
+
/** Remount one screen on next visit (by exact cache key). */
|
|
113
|
+
refreshEntry: (cacheKey: string) => Promise<boolean>;
|
|
114
|
+
clear: () => Promise<boolean>;
|
|
115
|
+
/** Drop one route/screen by exact cache key (stack pop). */
|
|
116
|
+
clearEntry: (cacheKey: string) => Promise<boolean>;
|
|
117
|
+
/** Drop all cached screens except `cacheKey`. */
|
|
118
|
+
clearExcept: (cacheKey: string) => Promise<boolean>;
|
|
119
|
+
isCached: (cacheKey: string) => boolean;
|
|
120
|
+
getCachingNodes: () => CachingNodeSnapshot[];
|
|
121
|
+
};
|
|
122
|
+
/** @deprecated Use `PageCacheRouteKey` alias below. */
|
|
123
|
+
type PageCacheRouteKey = string;
|
|
124
|
+
|
|
125
|
+
type Handler = (event: AliveLifecycleEvent) => void;
|
|
126
|
+
/**
|
|
127
|
+
* Lightweight pub/sub for cache lifecycle — no React dependency.
|
|
128
|
+
* Handlers must be unsubscribed or call `dispose()` when the scope unmounts.
|
|
129
|
+
*/
|
|
130
|
+
declare class AliveEventBus {
|
|
131
|
+
private readonly allHandlers;
|
|
132
|
+
private readonly typedHandlers;
|
|
133
|
+
subscribe(handler: Handler): () => void;
|
|
134
|
+
subscribeType(type: AliveEventType, handler: Handler): () => void;
|
|
135
|
+
emit(event: AliveLifecycleEvent): void;
|
|
136
|
+
/** Drop every handler — call on AliveScope unmount to prevent leaks. */
|
|
137
|
+
dispose(): void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
declare const DEFAULT_CACHE_POLICY: CachePolicy;
|
|
141
|
+
/** Stack-driven apps: no LRU — evict only via `clearEntry` / `clear()`. */
|
|
142
|
+
declare const DEFAULT_STACK_CACHE_POLICY: CachePolicy;
|
|
143
|
+
/** @deprecated Use `DEFAULT_CACHE_POLICY`. */
|
|
144
|
+
declare const DEFAULT_PAGE_CACHE_POLICY: CachePolicy;
|
|
145
|
+
declare function mergeCachePolicy(partial?: Partial<CachePolicy>): CachePolicy;
|
|
146
|
+
declare function isManualEviction(policy: CachePolicy): boolean;
|
|
147
|
+
declare function matchesPolicyKeyList(cacheKey: string, list: readonly string[]): boolean;
|
|
148
|
+
|
|
149
|
+
export { type AliveController as A, type CacheRecord as C, DEFAULT_CACHE_POLICY as D, type EvictEvent as E, type PageCacheEvictEvent as P, type RegisterRecordInput as R, EvictReason as a, CachePhase as b, AliveEventBus as c, type AliveEventType as d, type AliveLifecycleEvent as e, CacheEvictionMode as f, type CachePolicy as g, type CachingNode as h, type CachingNodeSnapshot as i, DEFAULT_PAGE_CACHE_POLICY as j, DEFAULT_STACK_CACHE_POLICY as k, entryMatchesName as l, isManualEviction as m, matchesCacheName as n, matchesPolicyKeyList as o, mergeCachePolicy as p, PageCacheEvictReason as q, resolveCacheKey as r, type PageCachePolicy as s, type PageCacheRouteKey as t };
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { C as CacheRecord, E as EvictEvent, R as RegisterRecordInput, a as EvictReason, b as CachePhase } from './cachePolicy-8JmypsRQ.js';
|
|
2
|
+
export { A as AliveController, c as AliveEventBus, d as AliveEventType, e as AliveLifecycleEvent, f as CacheEvictionMode, g as CachePolicy, h as CachingNode, i as CachingNodeSnapshot, D as DEFAULT_CACHE_POLICY, j as DEFAULT_PAGE_CACHE_POLICY, k as DEFAULT_STACK_CACHE_POLICY, l as entryMatchesName, m as isManualEviction, n as matchesCacheName, o as matchesPolicyKeyList, p as mergeCachePolicy, r as resolveCacheKey } from './cachePolicy-8JmypsRQ.js';
|
|
3
|
+
|
|
4
|
+
type CacheRegistryState = {
|
|
5
|
+
records: Map<string, CacheRecord>;
|
|
6
|
+
/** LRU order — oldest first. */
|
|
7
|
+
order: string[];
|
|
8
|
+
refreshNames: Set<string | RegExp>;
|
|
9
|
+
};
|
|
10
|
+
declare function createEmptyRegistryState(): CacheRegistryState;
|
|
11
|
+
declare function touchRecord(state: CacheRegistryState, cacheKey: string): CacheRegistryState;
|
|
12
|
+
declare function registerRecord(state: CacheRegistryState, input: RegisterRecordInput, active: boolean): {
|
|
13
|
+
state: CacheRegistryState;
|
|
14
|
+
created: boolean;
|
|
15
|
+
record: CacheRecord;
|
|
16
|
+
};
|
|
17
|
+
declare function setRecordPhase(state: CacheRegistryState, cacheKey: string, phase: CachePhase, opts?: {
|
|
18
|
+
bumpActivate?: boolean;
|
|
19
|
+
}): {
|
|
20
|
+
state: CacheRegistryState;
|
|
21
|
+
record: CacheRecord | null;
|
|
22
|
+
previousPhase: CachePhase | null;
|
|
23
|
+
};
|
|
24
|
+
declare function removeRecord(state: CacheRegistryState, cacheKey: string): CacheRegistryState;
|
|
25
|
+
declare function removeRecords(state: CacheRegistryState, cacheKeys: readonly string[], reason: EvictReason): {
|
|
26
|
+
state: CacheRegistryState;
|
|
27
|
+
evicted: EvictEvent[];
|
|
28
|
+
removedRecords: CacheRecord[];
|
|
29
|
+
};
|
|
30
|
+
declare function clearAllRecords(): CacheRegistryState;
|
|
31
|
+
declare function clearRecordsExcept(state: CacheRegistryState, keepCacheKey: string): CacheRegistryState;
|
|
32
|
+
declare function evictLru(state: CacheRegistryState, maxEntries: number, activeKey: string | null): {
|
|
33
|
+
state: CacheRegistryState;
|
|
34
|
+
evicted: EvictEvent[];
|
|
35
|
+
removedRecords: CacheRecord[];
|
|
36
|
+
};
|
|
37
|
+
declare function findCacheKeysByName(state: CacheRegistryState, pattern: string | RegExp): string[];
|
|
38
|
+
declare function findScopeCacheKeys(state: CacheRegistryState, rootCacheKey: string): string[];
|
|
39
|
+
declare function findScopeCacheKeysByName(state: CacheRegistryState, pattern: string | RegExp): string[];
|
|
40
|
+
declare function markRefreshByName(state: CacheRegistryState, pattern: string | RegExp): CacheRegistryState;
|
|
41
|
+
|
|
42
|
+
export { CachePhase, CacheRecord, type CacheRegistryState, EvictEvent, EvictReason, RegisterRecordInput, clearAllRecords, clearRecordsExcept, createEmptyRegistryState, evictLru, findCacheKeysByName, findScopeCacheKeys, findScopeCacheKeysByName, markRefreshByName, registerRecord, removeRecord, removeRecords, setRecordPhase, touchRecord };
|
package/dist/core.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var d={Mounted:"mounted",Active:"active",Cached:"cached"};function S(t,e){return e!=null&&e!==""?`${t}::${e}`:t}function l(t,e){return typeof e=="string"?t===e:e.test(t)}function g(t,e){return l(t.name,e)}var v=class{allHandlers=new Set;typedHandlers=new Map;subscribe(e){return this.allHandlers.add(e),()=>{this.allHandlers.delete(e);}}subscribeType(e,r){let c=this.typedHandlers.get(e)??new Set;return c.add(r),this.typedHandlers.set(e,c),()=>{c.delete(r),c.size===0&&this.typedHandlers.delete(e);}}emit(e){let r=this.typedHandlers.get(e.type);if(r!=null)for(let c of r)c(e);for(let c of this.allHandlers)c(e);}dispose(){this.allHandlers.clear(),this.typedHandlers.clear();}};var m={Lru:"lru"};var h={Lru:"lru",Manual:"manual"};var i={maxEntries:5,neverCache:[],clearOnEnter:[],evictionMode:h.Lru},A={maxEntries:i.maxEntries,neverCache:[],clearOnEnter:[],evictionMode:h.Manual},b=i;function M(t){return {maxEntries:t?.maxEntries??i.maxEntries,neverCache:t?.neverCache??i.neverCache,clearOnEnter:t?.clearOnEnter??i.clearOnEnter,evictionMode:t?.evictionMode??i.evictionMode}}function K(t){return t.evictionMode===h.Manual}function N(t,e){return e.some(r=>r===t)}function u(){return {records:new Map,order:[],refreshNames:new Set}}function f(t,e){let r=t.records.get(e);if(r==null)return t;let c=t.order.filter(n=>n!==e);c.push(e);let o=new Map(t.records);return o.set(e,{...r,updateTime:Date.now()}),{...t,records:o,order:c}}function T(t,e){for(let r of t.refreshNames)if(l(e,r))return true;return false}function L(t,e){let r=new Set(t.refreshNames);for(let c of [...r])l(e,c)&&r.delete(c);return {...t,refreshNames:r}}function H(t,e,r){let c=t.records.get(e.cacheKey),o=T(t,e.name);if(c!=null&&!o){let y=f(t,e.cacheKey);return {state:y,created:false,record:y.records.get(e.cacheKey)}}let n=Date.now(),s={cacheKey:e.cacheKey,name:e.name,id:e.id,parentCacheKey:e.parentCacheKey,phase:r?d.Active:d.Mounted,createTime:c?.createTime??n,updateTime:n,lastActiveTime:r?n:c?.lastActiveTime??0,activateCount:c?.activateCount??0},a=new Map(t.records);a.set(e.cacheKey,s);let p=t.order.filter(y=>y!==e.cacheKey);p.push(e.cacheKey);let C={...t,records:a,order:p};return o&&(C=L(C,e.name)),{state:C,created:true,record:s}}function w(t,e,r,c){let o=t.records.get(e);if(o==null)return {state:t,record:null,previousPhase:null};let n=Date.now(),s={...o,phase:r,updateTime:n,lastActiveTime:r===d.Active?n:o.lastActiveTime,activateCount:c?.bumpActivate===true?o.activateCount+1:o.activateCount},a=new Map(t.records);return a.set(e,s),{state:{...t,records:a},record:s,previousPhase:o.phase}}function R(t,e){if(!t.records.has(e))return t;let r=new Map(t.records);return r.delete(e),{...t,records:r,order:t.order.filter(c=>c!==e)}}function E(t,e,r){let c=t,o=[],n=[];for(let s of e){let a=c.records.get(s);a!=null&&(o.push({cacheKey:s,name:a.name,reason:r}),n.push(a),c=R(c,s));}return {state:c,evicted:o,removedRecords:n}}function _(){return u()}function O(t,e){let r=t.records.get(e);return r==null?u():{records:new Map([[e,r]]),order:[e],refreshNames:new Set(t.refreshNames)}}function I(t,e,r){let c=t,o=[],n=[];for(;c.order.length>e;){let s=c.order.find(p=>p!==r)??c.order[0];if(s==null)break;let a=E(c,[s],m.Lru);c=a.state,o.push(...a.evicted),n.push(...a.removedRecords);}return {state:c,evicted:o,removedRecords:n}}function x(t,e){return [...t.records.values()].filter(r=>g(r,e)).map(r=>r.cacheKey)}function P(t,e){let r=new Set([e]),c=o=>{for(let n of t.records.values())n.parentCacheKey===o&&!r.has(n.cacheKey)&&(r.add(n.cacheKey),c(n.cacheKey));};return c(e),[...r]}function D(t,e){let r=new Set;for(let c of x(t,e))for(let o of P(t,c))r.add(o);return [...r]}function k(t,e){let r=new Set(t.refreshNames);return r.add(e),{...t,refreshNames:r}}export{v as AliveEventBus,h as CacheEvictionMode,d as CachePhase,i as DEFAULT_CACHE_POLICY,b as DEFAULT_PAGE_CACHE_POLICY,A as DEFAULT_STACK_CACHE_POLICY,_ as clearAllRecords,O as clearRecordsExcept,u as createEmptyRegistryState,g as entryMatchesName,I as evictLru,x as findCacheKeysByName,P as findScopeCacheKeys,D as findScopeCacheKeysByName,K as isManualEviction,k as markRefreshByName,l as matchesCacheName,N as matchesPolicyKeyList,M as mergeCachePolicy,H as registerRecord,R as removeRecord,E as removeRecords,S as resolveCacheKey,w as setRecordPhase,f as touchRecord};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ReactElement } from 'react';
|
|
3
|
+
import { g as CachePolicy, E as EvictEvent, e as AliveLifecycleEvent, R as RegisterRecordInput, i as CachingNodeSnapshot, A as AliveController, d as AliveEventType } from './cachePolicy-8JmypsRQ.js';
|
|
4
|
+
export { c as AliveEventBus, f as CacheEvictionMode, b as CachePhase, C as CacheRecord, h as CachingNode, D as DEFAULT_CACHE_POLICY, j as DEFAULT_PAGE_CACHE_POLICY, k as DEFAULT_STACK_CACHE_POLICY, a as EvictReason, P as PageCacheEvictEvent, q as PageCacheEvictReason, s as PageCachePolicy, t as PageCacheRouteKey, n as matchesCacheName, p as mergeCachePolicy, r as resolveCacheKey } from './cachePolicy-8JmypsRQ.js';
|
|
5
|
+
export { KeepAliveOutlet, createPageCacheRouteKey, createRouteCacheKey } from './router.js';
|
|
6
|
+
import 'react-router-dom';
|
|
7
|
+
|
|
8
|
+
type AliveScopeProps = {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
policy?: Partial<CachePolicy>;
|
|
11
|
+
/** @deprecated Prefer `onLifecycle` */
|
|
12
|
+
onEvict?: (event: EvictEvent) => void;
|
|
13
|
+
onLifecycle?: (event: AliveLifecycleEvent) => void;
|
|
14
|
+
};
|
|
15
|
+
declare function AliveScope({ children, policy: policyOverride, onEvict, onLifecycle }: AliveScopeProps): react.JSX.Element;
|
|
16
|
+
/** @deprecated Use `AliveScope`. */
|
|
17
|
+
declare const PageCacheProvider: typeof AliveScope;
|
|
18
|
+
|
|
19
|
+
type KeepAliveProps = {
|
|
20
|
+
name: string;
|
|
21
|
+
id?: string;
|
|
22
|
+
when?: boolean | (() => boolean);
|
|
23
|
+
saveScrollPosition?: boolean | string;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Declarative keep-alive — react-activation-style `name` + optional `when`. */
|
|
28
|
+
declare function KeepAlive({ name, id, when, children }: KeepAliveProps): react.JSX.Element | null;
|
|
29
|
+
|
|
30
|
+
type Props$1 = {
|
|
31
|
+
cacheKey: string;
|
|
32
|
+
};
|
|
33
|
+
/** Renders a registered keep-alive node from the node store (sync, no effect). */
|
|
34
|
+
declare function AliveNodeShell({ cacheKey }: Props$1): react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | null;
|
|
35
|
+
|
|
36
|
+
type Props = {
|
|
37
|
+
isActive: boolean;
|
|
38
|
+
inKeeper: boolean;
|
|
39
|
+
routeKey: string;
|
|
40
|
+
children: ReactNode;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Active route fills the layout slot. Inactive routes use the in-tree keeper bucket
|
|
44
|
+
* (`display: none`, `inert`) — no portal reparenting.
|
|
45
|
+
*/
|
|
46
|
+
declare function PageCacheSlot({ isActive, inKeeper, routeKey, children }: Props): react.JSX.Element;
|
|
47
|
+
|
|
48
|
+
type LifecycleEffect = () => void | (() => void);
|
|
49
|
+
type AliveContextValue = {
|
|
50
|
+
readonly activeKey: string | null;
|
|
51
|
+
readonly activeKeys: ReadonlySet<string>;
|
|
52
|
+
readonly cachedKeys: readonly string[];
|
|
53
|
+
readonly entries: ReadonlyArray<readonly [string, ReactElement]>;
|
|
54
|
+
readonly policy: CachePolicy;
|
|
55
|
+
registerEntry: (input: RegisterRecordInput, node: ReactElement, active: boolean) => void;
|
|
56
|
+
setNodeActive: (cacheKey: string, active: boolean) => void;
|
|
57
|
+
getCachedNode: (cacheKey: string) => ReactElement | null;
|
|
58
|
+
isCached: (cacheKey: string) => boolean;
|
|
59
|
+
shouldCache: (cacheKey: string) => boolean;
|
|
60
|
+
subscribeActivate: (cacheKey: string, effect: LifecycleEffect) => () => void;
|
|
61
|
+
subscribeUnactivate: (cacheKey: string, effect: LifecycleEffect) => () => void;
|
|
62
|
+
subscribeLifecycle: (handler: (event: AliveLifecycleEvent) => void) => () => void;
|
|
63
|
+
subscribeLifecycleType: (type: AliveLifecycleEvent['type'], handler: (event: AliveLifecycleEvent) => void) => () => void;
|
|
64
|
+
drop: (name: string | RegExp) => Promise<boolean>;
|
|
65
|
+
dropScope: (name: string | RegExp) => Promise<boolean>;
|
|
66
|
+
refresh: (name: string | RegExp) => Promise<boolean>;
|
|
67
|
+
refreshScope: (name: string | RegExp) => Promise<boolean>;
|
|
68
|
+
refreshEntry: (cacheKey: string) => Promise<boolean>;
|
|
69
|
+
clear: () => Promise<boolean>;
|
|
70
|
+
getCachingNodes: () => CachingNodeSnapshot[];
|
|
71
|
+
getSnapshot: () => {
|
|
72
|
+
entries: CachingNodeSnapshot[];
|
|
73
|
+
activeKey: string | null;
|
|
74
|
+
};
|
|
75
|
+
/** @deprecated */
|
|
76
|
+
ensureEntry: (cacheKey: string, node: ReactElement) => void;
|
|
77
|
+
touch: (cacheKey: string) => void;
|
|
78
|
+
clearEntry: (cacheKey: string) => Promise<boolean>;
|
|
79
|
+
clearAll: () => void;
|
|
80
|
+
clearExcept: (cacheKey: string) => Promise<boolean>;
|
|
81
|
+
setActiveKey: (cacheKey: string | null) => void;
|
|
82
|
+
};
|
|
83
|
+
declare function useAliveInternals(): AliveContextValue;
|
|
84
|
+
/** @deprecated */
|
|
85
|
+
declare function usePageCacheInternals(): AliveContextValue;
|
|
86
|
+
declare function useAliveParentCacheKey(): string | null;
|
|
87
|
+
declare function useAliveSelfCacheKey(): string | null;
|
|
88
|
+
|
|
89
|
+
/** Manual cache control — stack-friendly (`clearEntry`, `refreshEntry`, `clear`). */
|
|
90
|
+
declare function useAliveController(): AliveController & {
|
|
91
|
+
getSnapshot: ReturnType<typeof useAliveInternals>['getSnapshot'];
|
|
92
|
+
};
|
|
93
|
+
/** @deprecated Prefer `useAliveController`. */
|
|
94
|
+
declare function usePageCache(): {
|
|
95
|
+
activeKey: string | null;
|
|
96
|
+
cachedKeys: readonly string[];
|
|
97
|
+
ensureEntry: (cacheKey: string, node: react.ReactElement) => void;
|
|
98
|
+
touch: (cacheKey: string) => void;
|
|
99
|
+
clear: (cacheKey: string) => Promise<boolean>;
|
|
100
|
+
clearAll: () => void;
|
|
101
|
+
clearExcept: (cacheKey: string) => Promise<boolean>;
|
|
102
|
+
isCached: (cacheKey: string) => boolean;
|
|
103
|
+
shouldCache: (cacheKey: string) => boolean;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** Runs when this KeepAlive node becomes visible again. */
|
|
107
|
+
declare function useActivate(effect: () => void | (() => void)): void;
|
|
108
|
+
/** Runs when this KeepAlive node is hidden (cached off-screen). */
|
|
109
|
+
declare function useUnactivate(effect: () => void | (() => void)): void;
|
|
110
|
+
/** Subscribe to global cache lifecycle events (register, activate, evict, …). */
|
|
111
|
+
declare function useAliveLifecycle(handler: (event: AliveLifecycleEvent) => void): void;
|
|
112
|
+
declare function useAliveLifecycle(type: AliveEventType, handler: (event: AliveLifecycleEvent) => void): void;
|
|
113
|
+
|
|
114
|
+
export { AliveController, AliveEventType, AliveLifecycleEvent, AliveNodeShell, AliveScope, CachePolicy, CachingNodeSnapshot, EvictEvent, KeepAlive, type KeepAliveProps, PageCacheProvider, PageCacheSlot, useActivate, useAliveController, useAliveInternals, useAliveLifecycle, useAliveParentCacheKey, useAliveSelfCacheKey, usePageCache, usePageCacheInternals, useUnactivate };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {createContext,useContext,useMemo,useRef,useLayoutEffect,useEffect,useState,useCallback}from'react';import {jsx,Fragment}from'react/jsx-runtime';import vt from'clsx';import {useLocation,useOutlet}from'react-router-dom';var b={Mounted:"mounted",Active:"active",Cached:"cached"};var _=class{allHandlers=new Set;typedHandlers=new Map;subscribe(e){return this.allHandlers.add(e),()=>{this.allHandlers.delete(e);}}subscribeType(e,r){let o=this.typedHandlers.get(e)??new Set;return o.add(r),this.typedHandlers.set(e,o),()=>{o.delete(r),o.size===0&&this.typedHandlers.delete(e);}}emit(e){let r=this.typedHandlers.get(e.type);if(r!=null)for(let o of r)o(e);for(let o of this.allHandlers)o(e);}dispose(){this.allHandlers.clear(),this.typedHandlers.clear();}};function ie(t,e){return e!=null&&e!==""?`${t}::${e}`:t}function O(t,e){return typeof e=="string"?t===e:e.test(t)}function Ve(t,e){return O(t.name,e)}var E={Lru:"lru",Manual:"manual",Policy:"policy",Refresh:"refresh"},tt=E,H={Lru:"lru",Manual:"manual"};function J(){return {records:new Map,order:[],refreshNames:new Set}}function Q(t,e){let r=t.records.get(e);if(r==null)return t;let o=t.order.filter(s=>s!==e);o.push(e);let c=new Map(t.records);return c.set(e,{...r,updateTime:Date.now()}),{...t,records:c,order:o}}function rt(t,e){for(let r of t.refreshNames)if(O(e,r))return true;return false}function nt(t,e){let r=new Set(t.refreshNames);for(let o of [...r])O(e,o)&&r.delete(o);return {...t,refreshNames:r}}function Ye(t,e,r){let o=t.records.get(e.cacheKey),c=rt(t,e.name);if(o!=null&&!c){let p=Q(t,e.cacheKey);return {state:p,created:false,record:p.records.get(e.cacheKey)}}let s=Date.now(),u={cacheKey:e.cacheKey,name:e.name,id:e.id,parentCacheKey:e.parentCacheKey,phase:r?b.Active:b.Mounted,createTime:o?.createTime??s,updateTime:s,lastActiveTime:r?s:o?.lastActiveTime??0,activateCount:o?.activateCount??0},a=new Map(t.records);a.set(e.cacheKey,u);let y=t.order.filter(p=>p!==e.cacheKey);y.push(e.cacheKey);let d={...t,records:a,order:y};return c&&(d=nt(d,e.name)),{state:d,created:true,record:u}}function qe(t,e,r,o){let c=t.records.get(e);if(c==null)return {state:t,record:null,previousPhase:null};let s=Date.now(),u={...c,phase:r,updateTime:s,lastActiveTime:r===b.Active?s:c.lastActiveTime,activateCount:o?.bumpActivate===true?c.activateCount+1:c.activateCount},a=new Map(t.records);return a.set(e,u),{state:{...t,records:a},record:u,previousPhase:c.phase}}function ot(t,e){if(!t.records.has(e))return t;let r=new Map(t.records);return r.delete(e),{...t,records:r,order:t.order.filter(o=>o!==e)}}function se(t,e,r){let o=t,c=[],s=[];for(let u of e){let a=o.records.get(u);a!=null&&(c.push({cacheKey:u,name:a.name,reason:r}),s.push(a),o=ot(o,u));}return {state:o,evicted:c,removedRecords:s}}function ae(){return J()}function ze(t,e){let r=t.records.get(e);return r==null?J():{records:new Map([[e,r]]),order:[e],refreshNames:new Set(t.refreshNames)}}function le(t,e,r){let o=t,c=[],s=[];for(;o.order.length>e;){let u=o.order.find(y=>y!==r)??o.order[0];if(u==null)break;let a=se(o,[u],E.Lru);o=a.state,c.push(...a.evicted),s.push(...a.removedRecords);}return {state:o,evicted:c,removedRecords:s}}function X(t,e){return [...t.records.values()].filter(r=>Ve(r,e)).map(r=>r.cacheKey)}function ct(t,e){let r=new Set([e]),o=c=>{for(let s of t.records.values())s.parentCacheKey===c&&!r.has(s.cacheKey)&&(r.add(s.cacheKey),o(s.cacheKey));};return o(e),[...r]}function ue(t,e){let r=new Set;for(let o of X(t,e))for(let c of ct(t,o))r.add(c);return [...r]}function Z(t,e){let r=new Set(t.refreshNames);return r.add(e),{...t,refreshNames:r}}var T={maxEntries:5,neverCache:[],clearOnEnter:[],evictionMode:H.Lru},it={maxEntries:T.maxEntries,neverCache:[],clearOnEnter:[],evictionMode:H.Manual},st=T;function de(t){return {maxEntries:t?.maxEntries??T.maxEntries,neverCache:t?.neverCache??T.neverCache,clearOnEnter:t?.clearOnEnter??T.clearOnEnter,evictionMode:t?.evictionMode??T.evictionMode}}function j(t){return t.evictionMode===H.Manual}function pe(t,e){return e.some(r=>r===t)}var he=createContext(null),te=createContext(null),D=createContext(null);createContext(null);function R(){let t=useContext(he);if(t==null)throw new Error("AliveScope is required \u2014 wrap the tree with <AliveScope> or <PageCacheProvider>.");return t}function at(){return R()}function ye(){return useContext(te)}function re(){return useContext(D)}var ne=class{activateListeners=new Map;unactivateListeners=new Map;activateCleanups=new Map;runActivate(e){let r=this.activateListeners.get(e);if(r==null||r.size===0)return;let o=[];for(let c of r){let s=c();typeof s=="function"&&o.push(s);}o.length>0&&this.activateCleanups.set(e,o);}runUnactivate(e){let r=this.activateCleanups.get(e);if(r!=null){for(let c of r)c();this.activateCleanups.delete(e);}let o=this.unactivateListeners.get(e);if(o!=null)for(let c of o)c();}subscribeActivate(e,r,o){let c=this.activateListeners.get(e)??new Set;if(c.add(r),this.activateListeners.set(e,c),o){let s=r();if(typeof s=="function"){let u=this.activateCleanups.get(e)??[];u.push(s),this.activateCleanups.set(e,u);}}return ()=>{c.delete(r),c.size===0&&this.activateListeners.delete(e);}}subscribeUnactivate(e,r){let o=this.unactivateListeners.get(e)??new Set;return o.add(r),this.unactivateListeners.set(e,o),()=>{o.delete(r),o.size===0&&this.unactivateListeners.delete(e);}}purgeEntry(e,r){r&&this.runUnactivate(e),this.activateListeners.delete(e),this.unactivateListeners.delete(e),this.activateCleanups.delete(e);}purgeMany(e,r){for(let o of e)this.purgeEntry(o,r.has(o));}dispose(){for(let[e,r]of this.activateCleanups){for(let o of r)o();this.runUnactivate(e);}this.activateListeners.clear(),this.unactivateListeners.clear(),this.activateCleanups.clear();}};var oe=class{nodes=new Map;has(e){return this.nodes.has(e)}get(e){return this.nodes.get(e)??null}set(e,r){this.nodes.set(e,r);}delete(e){this.nodes.delete(e);}deleteMany(e){for(let r of e)this.nodes.delete(r);}keys(){return [...this.nodes.keys()]}clear(){this.nodes.clear();}};function ut(t,e){return {cacheKey:t.cacheKey,name:t.name,id:t.id,parentCacheKey:t.parentCacheKey,phase:t.phase,createTime:t.createTime,updateTime:t.updateTime,lastActiveTime:t.lastActiveTime,activateCount:t.activateCount,isActive:e}}function We({children:t,policy:e,onEvict:r,onLifecycle:o}){let c=useMemo(()=>de(e),[e]),s=useRef(r),u=useRef(o),a=useMemo(()=>new _,[]),y=useMemo(()=>new oe,[]),d=useMemo(()=>new ne,[]);useLayoutEffect(()=>{s.current=r,u.current=o;}),useEffect(()=>()=>{d.dispose(),y.clear(),a.dispose();},[a,d,y]);let[p,m]=useState(J),[g,M]=useState(()=>new Set),w=useRef(g);useLayoutEffect(()=>{w.current=g;});let S=useCallback(n=>{a.emit(n),u.current?.(n),n.type==="cache:evict"&&s.current?.({cacheKey:n.record.cacheKey,name:n.record.name,reason:n.reason});},[a]),P=useCallback((n,i,h)=>{if(n.length!==0){y.deleteMany(n),d.purgeMany(n,w.current),M(v=>{let f=new Set(v);for(let A of n)f.delete(A);return f});for(let v of i)S({type:"cache:evict",record:v,reason:h});}},[S,d,y]),Y=useCallback(n=>{d.runActivate(n);},[d]),q=useCallback(n=>{d.runUnactivate(n);},[d]),k=useCallback((n,i)=>{m(h=>{let v=qe(h,n,i?b.Active:b.Cached,{bumpActivate:i});return v.record==null?h:(S(i?{type:"cache:activate",record:v.record,previousPhase:v.previousPhase??b.Mounted}:{type:"cache:deactivate",record:v.record}),v.state)});},[S]),Ce=useCallback((n,i)=>{M(h=>{let v=h.has(n);if(i===v)return h;let f=new Set(h);return i?(f.add(n),queueMicrotask(()=>{k(n,true),Y(n);})):(f.delete(n),queueMicrotask(()=>{q(n),k(n,false);})),f});},[k,Y,q]),Ee=useCallback(n=>{if(n==null)return;let i=[],h=[],v=[],f=[];m(A=>{let C=A;if(pe(n,c.clearOnEnter)&&(i=[...A.records.keys()],h=[...A.records.values()],C=ae()),C.records.has(n)&&(C=Q(C,n)),!j(c)){let{state:N,removedRecords:I,evicted:ce}=le(C,c.maxEntries,n);return v=ce.map(B=>B.cacheKey),f=I,N}return C}),i.length>0&&(S({type:"policy:clear-on-enter",targetKey:n}),P(i,h,E.Policy)),v.length>0&&P(v,f,E.Lru),M(A=>{let C=new Set;for(let N of A)N!==n&&queueMicrotask(()=>{q(N),k(N,false);});return C.add(n),queueMicrotask(()=>{k(n,true),Y(n);}),C});},[k,S,P,c.clearOnEnter,c.maxEntries,Y,q]),z=useCallback(n=>!pe(n,c.neverCache),[c.neverCache]),$=useCallback((n,i,h)=>{if(!z(n.cacheKey))return;let v=[],f=[],A=false,C=null;m(N=>{let I=Ye(N,n,h);A=I.created,C=I.record,y.set(n.cacheKey,i);let ce=[...w.current][0]??n.cacheKey;if(!j(c)){let B=le(I.state,c.maxEntries,ce);return v=B.evicted.map(et=>et.cacheKey),f=B.removedRecords,B.state}return I.state}),A&&C!=null&&S({type:"cache:register",record:C}),!j(c)&&v.length>0&&P(v,f,E.Lru);},[S,P,y,c,z]),Re=useCallback((n,i)=>{$({cacheKey:n,name:n,parentCacheKey:null},i,w.current.has(n));},[$]),Ae=useCallback(n=>{m(i=>Q(i,n));},[]),x=useCallback((n,i)=>{let h=[],v=[];return m(f=>{let A=n(f);if(A.length===0)return f;let C=se(f,A,i);return h=[...A],v=C.removedRecords,C.state}),h.length===0?false:(P(h,v,i),true)},[P]),xe=useCallback(async n=>x(i=>X(i,n),E.Manual),[x]),Pe=useCallback(async n=>x(i=>ue(i,n),E.Manual),[x]),Se=useCallback(async n=>(m(i=>Z(i,n)),x(i=>X(i,n),E.Refresh),true),[x]),be=useCallback(async n=>(m(i=>Z(i,n)),x(i=>ue(i,n),E.Refresh),true),[x]),Le=useCallback(async n=>(m(i=>Z(i,n)),x(i=>i.records.has(n)?[n]:[],E.Refresh)),[x]),G=useCallback(async()=>{let n=[],i=[];return m(h=>(n=[...h.records.keys()],i=[...h.records.values()],ae())),P(n,i,E.Manual),true},[P]),Ke=useCallback(async n=>x(i=>i.records.has(n)?[n]:[],E.Manual),[x]),Me=useCallback(()=>{G();},[G]),Ne=useCallback(async n=>{let i=[],h=[];return m(v=>(i=[...v.records.keys()].filter(f=>f!==n),h=i.map(f=>v.records.get(f)).filter(f=>f!=null),ze(v,n))),i.length===0?false:(P(i,h,E.Manual),true)},[P]),Te=useCallback(n=>p.records.has(n),[p.records]),we=useCallback(n=>y.get(n),[y]),W=useCallback(()=>[...p.records.values()].map(n=>ut(n,g.has(n.cacheKey))),[g,p.records]),ke=useCallback(()=>W(),[W]),Ie=useCallback(()=>({entries:W(),activeKey:[...g][0]??null}),[g,W]),He=useCallback((n,i)=>d.subscribeActivate(n,i,g.has(n)),[g,d]),Ue=useCallback((n,i)=>d.subscribeUnactivate(n,i),[d]),Be=useCallback(n=>a.subscribe(n),[a]),_e=useCallback((n,i)=>a.subscribeType(n,i),[a]),Oe=useMemo(()=>[...p.records.keys()].filter(n=>y.has(n)).map(n=>[n,y.get(n)]),[y,p.records]),De=useMemo(()=>[...p.order],[p.order]),Fe=useMemo(()=>[...g][0]??null,[g]),je=useMemo(()=>({activeKey:Fe,activeKeys:g,cachedKeys:De,entries:Oe,policy:c,registerEntry:$,setNodeActive:Ce,getCachedNode:we,isCached:Te,shouldCache:z,subscribeActivate:He,subscribeUnactivate:Ue,subscribeLifecycle:Be,subscribeLifecycleType:_e,drop:xe,dropScope:Pe,refresh:Se,refreshScope:be,refreshEntry:Le,clear:G,getCachingNodes:ke,getSnapshot:Ie,ensureEntry:Re,touch:Ae,clearEntry:Ke,clearAll:Me,clearExcept:Ne,setActiveKey:Ee}),[Fe,g,De,G,Me,Ke,Ne,xe,Pe,Oe,Re,we,ke,Ie,Te,c,Se,Le,be,$,Ee,Ce,z,He,Be,_e,Ue,Ae]);return jsx(he.Provider,{value:je,children:t})}var dt=We;function F({cacheKey:t}){let{getCachedNode:e}=R();return e(t)}function U({isActive:t,inKeeper:e,routeKey:r,children:o}){return jsx("div",{className:vt(t&&"relative min-h-0 min-w-0",e&&"keep-alive-slot--keeper"),"data-keep-alive-route":r,"data-keep-alive-active":t?"true":"false","data-keep-alive-keeper":e?"true":"false","data-page-cache-route":r,"data-page-cache-active":t?"true":"false","data-page-cache-keeper":e?"true":"false","aria-hidden":t?void 0:true,inert:t?void 0:true,children:jsx(D.Provider,{value:r,children:o})})}function yt(t){return t==null?true:typeof t=="function"?t():t}function ft({cacheKey:t,children:e}){return jsx(D.Provider,{value:t,children:e})}function gt({name:t,id:e,when:r,children:o}){let c=useMemo(()=>ie(t,e),[e,t]),s=ye(),{registerEntry:u,setNodeActive:a,isCached:y,shouldCache:d}=R(),p=yt(r),m=y(c);return useLayoutEffect(()=>{a(c,p);},[c,a,p]),useLayoutEffect(()=>{!p||!d(c)||m||u({cacheKey:c,name:t,id:e,parentCacheKey:s},jsx(ft,{cacheKey:c,children:o}),true);},[c,m,o,e,t,s,u,d,p]),d(c)?m?jsx(te.Provider,{value:c,children:jsx(U,{isActive:p,inKeeper:!p,routeKey:c,children:jsx(F,{cacheKey:c})})}):p?jsx(te.Provider,{value:c,children:jsx(U,{isActive:true,inKeeper:false,routeKey:c,children:o})}):null:p?jsx(Fragment,{children:o}):null}function Ct(){let t=R(),{drop:e,dropScope:r,refresh:o,refreshScope:c,refreshEntry:s,clear:u,clearEntry:a,clearExcept:y,isCached:d,getCachingNodes:p,getSnapshot:m}=t;return {drop:e,dropScope:r,refresh:o,refreshScope:c,refreshEntry:s,clear:u,clearEntry:a,clearExcept:y,isCached:d,getCachingNodes:p,getSnapshot:m}}function Et(){let t=R();return {activeKey:t.activeKey,cachedKeys:t.cachedKeys,ensureEntry:t.ensureEntry,touch:t.touch,clear:t.clearEntry,clearAll:t.clearAll,clearExcept:t.clearExcept,isCached:t.isCached,shouldCache:t.shouldCache}}function Rt(t){let e=re(),{subscribeActivate:r}=R();useLayoutEffect(()=>{if(e!=null)return r(e,t)},[e,t,r]);}function At(t){let e=re(),{subscribeUnactivate:r}=R();useLayoutEffect(()=>{if(e!=null)return r(e,t)},[e,t,r]);}function xt(t,e){let{subscribeLifecycle:r,subscribeLifecycleType:o}=R();useLayoutEffect(()=>{if(typeof t=="function")return r(t);if(e!=null)return o(t,e)},[e,r,o,t]);}function Xe(t){return `${t.pathname}${t.search}`}var me=Xe;function bt({className:t}){let e=useLocation(),r=useOutlet(),o=me(e),{activeKey:c,entries:s,ensureEntry:u,setActiveKey:a,isCached:y,shouldCache:d}=R();if(useLayoutEffect(()=>{a(o);},[o,a]),useLayoutEffect(()=>{r==null||!d(o)||u(o,r);},[u,r,o,d]),!d(o))return jsx("div",{className:t,children:r});let p=c??o;return s.length>0?jsx("div",{className:t,"data-page-cache-root":true,children:s.map(([g])=>{let M=g===p,w=M&&g===o&&!y(o);return jsx(U,{isActive:M,routeKey:g,inKeeper:!M,children:w?r:jsx(F,{cacheKey:g})},g)})}):jsx("div",{className:t,children:r})}export{_ as AliveEventBus,F as AliveNodeShell,We as AliveScope,H as CacheEvictionMode,b as CachePhase,T as DEFAULT_CACHE_POLICY,st as DEFAULT_PAGE_CACHE_POLICY,it as DEFAULT_STACK_CACHE_POLICY,E as EvictReason,gt as KeepAlive,bt as KeepAliveOutlet,tt as PageCacheEvictReason,dt as PageCacheProvider,U as PageCacheSlot,me as createPageCacheRouteKey,Xe as createRouteCacheKey,O as matchesCacheName,de as mergeCachePolicy,ie as resolveCacheKey,Rt as useActivate,Ct as useAliveController,R as useAliveInternals,xt as useAliveLifecycle,ye as useAliveParentCacheKey,re as useAliveSelfCacheKey,Et as usePageCache,at as usePageCacheInternals,At as useUnactivate};
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { Location } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
className?: string;
|
|
6
|
+
};
|
|
7
|
+
/** React Router 7 adapter — drop-in replacement for `<Outlet />`. */
|
|
8
|
+
declare function KeepAliveOutlet({ className }: Props): react.JSX.Element;
|
|
9
|
+
|
|
10
|
+
/** Stable cache key from router location (pathname + search). */
|
|
11
|
+
declare function createRouteCacheKey(location: Pick<Location, 'pathname' | 'search'>): string;
|
|
12
|
+
/** @deprecated Use `createRouteCacheKey`. */
|
|
13
|
+
declare const createPageCacheRouteKey: typeof createRouteCacheKey;
|
|
14
|
+
|
|
15
|
+
export { KeepAliveOutlet, createPageCacheRouteKey, createRouteCacheKey };
|
package/dist/router.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {createContext,useLayoutEffect,useContext}from'react';import {useLocation,useOutlet}from'react-router-dom';import b from'clsx';import {jsx}from'react/jsx-runtime';var R=createContext(null);createContext(null);var y=createContext(null);createContext(null);function c(){let e=useContext(R);if(e==null)throw new Error("AliveScope is required \u2014 wrap the tree with <AliveScope> or <PageCacheProvider>.");return e}function h({cacheKey:e}){let{getCachedNode:r}=c();return r(e)}function g({isActive:e,inKeeper:r,routeKey:o,children:t}){return jsx("div",{className:b(e&&"relative min-h-0 min-w-0",r&&"keep-alive-slot--keeper"),"data-keep-alive-route":o,"data-keep-alive-active":e?"true":"false","data-keep-alive-keeper":r?"true":"false","data-page-cache-route":o,"data-page-cache-active":e?"true":"false","data-page-cache-keeper":r?"true":"false","aria-hidden":e?void 0:true,inert:e?void 0:true,children:jsx(y.Provider,{value:o,children:t})})}function m(e){return `${e.pathname}${e.search}`}var u=m;function N({className:e}){let r=useLocation(),o=useOutlet(),t=u(r),{activeKey:x,entries:p,ensureEntry:v,setActiveKey:d,isCached:A,shouldCache:l}=c();if(useLayoutEffect(()=>{d(t);},[t,d]),useLayoutEffect(()=>{o==null||!l(t)||v(t,o);},[v,o,t,l]),!l(t))return jsx("div",{className:e,children:o});let K=x??t;return p.length>0?jsx("div",{className:e,"data-page-cache-root":true,children:p.map(([n])=>{let s=n===K,E=s&&n===t&&!A(t);return jsx(g,{isActive:s,routeKey:n,inKeeper:!s,children:E?o:jsx(h,{cacheKey:n})},n)})}):jsx("div",{className:e,children:o})}export{N as KeepAliveOutlet,u as createPageCacheRouteKey,m as createRouteCacheKey};
|
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@osiris-smarttv/keep-alive",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Route & component keep-alive for React TV apps — AliveScope, KeepAlive, LRU cache, lifecycle events",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/OsirisTech-SmartTV/keep-alive.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/OsirisTech-SmartTV/keep-alive/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/OsirisTech-SmartTV/keep-alive#readme",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"react",
|
|
17
|
+
"smart-tv",
|
|
18
|
+
"keep-alive",
|
|
19
|
+
"page-cache",
|
|
20
|
+
"lru",
|
|
21
|
+
"react-router",
|
|
22
|
+
"osiris-smarttv"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"registry": "https://registry.npmjs.org/"
|
|
27
|
+
},
|
|
28
|
+
"sideEffects": [
|
|
29
|
+
"**/*.css"
|
|
30
|
+
],
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"module": "./dist/index.js",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./core": {
|
|
40
|
+
"types": "./dist/core.d.ts",
|
|
41
|
+
"import": "./dist/core.js"
|
|
42
|
+
},
|
|
43
|
+
"./router": {
|
|
44
|
+
"types": "./dist/router.d.ts",
|
|
45
|
+
"import": "./dist/router.js"
|
|
46
|
+
},
|
|
47
|
+
"./styles.css": "./dist/keep-alive.css",
|
|
48
|
+
"./package.json": "./package.json"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"README.md"
|
|
53
|
+
],
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": ">=18",
|
|
56
|
+
"react-dom": ">=18",
|
|
57
|
+
"react-router-dom": ">=6"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"react-router-dom": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"clsx": "^2.1.1"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
69
|
+
"@testing-library/dom": "^10.4.1",
|
|
70
|
+
"@testing-library/react": "^16.3.2",
|
|
71
|
+
"@testing-library/user-event": "^14.6.1",
|
|
72
|
+
"@types/react": "^19.2.14",
|
|
73
|
+
"@types/react-dom": "^19.2.3",
|
|
74
|
+
"jsdom": "^29.0.2",
|
|
75
|
+
"react": "^19.2.4",
|
|
76
|
+
"react-dom": "^19.2.4",
|
|
77
|
+
"react-router-dom": "^7.14.1",
|
|
78
|
+
"tsup": "^8.5.0",
|
|
79
|
+
"typescript": "~6.0.2",
|
|
80
|
+
"vitest": "^4.1.4"
|
|
81
|
+
},
|
|
82
|
+
"scripts": {
|
|
83
|
+
"clean": "node scripts/clean.mjs",
|
|
84
|
+
"build": "yarn build:js && yarn build:styles",
|
|
85
|
+
"build:js": "tsup",
|
|
86
|
+
"build:styles": "node scripts/copy-styles.mjs",
|
|
87
|
+
"dev": "tsup --watch",
|
|
88
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
89
|
+
"test": "vitest run",
|
|
90
|
+
"test:watch": "vitest",
|
|
91
|
+
"verify": "yarn typecheck && yarn test",
|
|
92
|
+
"publish:check": "yarn clean && yarn build && yarn verify && npm pack --dry-run",
|
|
93
|
+
"publish:dry-run": "npm publish --dry-run --access public",
|
|
94
|
+
"publish:manual": "npm publish --access public",
|
|
95
|
+
"prepublishOnly": "yarn clean && yarn build && yarn verify"
|
|
96
|
+
}
|
|
97
|
+
}
|