redux-sparse-kit 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -151,7 +151,8 @@ const ordersPage = defineSegment({
151
151
 
152
152
  A dependency has to be defined before the segments that list it, so the
153
153
  dependency graph cannot have cycles. Two different segments cannot use the same
154
- key in one store at the same time.
154
+ key in one store at the same time, and a segment with a reducer cannot use a key
155
+ of the static state: acquiring it throws.
155
156
 
156
157
  ### Typing epics
157
158
 
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ const SparseActionTypes = {
14
14
  };
15
15
  const emptyKeys = /* @__PURE__ */ new Set();
16
16
  const defaultIdleTimeout = 6e4;
17
+ const probeActionType = "@@sparse/PROBE";
17
18
  const whileIdleValues = [
18
19
  "keep-running",
19
20
  "pause-epics",
@@ -40,7 +41,23 @@ function sparseEnhancer(options = {}) {
40
41
  const idleEntries = /* @__PURE__ */ new Set();
41
42
  let evictingKeys = emptyKeys;
42
43
  const slices = /* @__PURE__ */ new Map();
43
- const isDynamicKey = (key) => slices.has(key) || evictingKeys.has(key) || require_segment.isSegmentKey(key);
44
+ let staticKeys = emptyKeys;
45
+ const learnStaticKeys = (staticState) => {
46
+ let count = 0;
47
+ for (const key in staticState) {
48
+ count++;
49
+ if (!staticKeys.has(key)) {
50
+ staticKeys = new Set(Object.keys(staticState));
51
+ return;
52
+ }
53
+ }
54
+ if (count !== staticKeys.size) staticKeys = new Set(Object.keys(staticState));
55
+ };
56
+ /** Asks a root reducer for its keys before it reduces a real state. */
57
+ const probeStaticKeys = (root) => {
58
+ learnStaticKeys(root(void 0, { type: probeActionType }));
59
+ };
60
+ const isDynamicKey = (key) => !staticKeys.has(key) && (slices.has(key) || evictingKeys.has(key) || require_segment.isSegmentKey(key));
44
61
  const wrapReducer = (root) => (state, action) => {
45
62
  var _staticState;
46
63
  const current = state !== null && state !== void 0 ? state : {};
@@ -50,12 +67,15 @@ function sparseEnhancer(options = {}) {
50
67
  hasDynamicKeys = true;
51
68
  break;
52
69
  }
70
+ const pendingSlices = [];
53
71
  if (hasDynamicKeys) {
54
72
  staticState = {};
55
73
  for (const key in current) if (!isDynamicKey(key)) staticState[key] = current[key];
74
+ else if (!slices.has(key) && !evictingKeys.has(key)) pendingSlices.push(key);
56
75
  }
57
76
  const input = state === void 0 ? void 0 : (_staticState = staticState) !== null && _staticState !== void 0 ? _staticState : current;
58
77
  const nextStatic = root(input, action);
78
+ if (nextStatic !== input) learnStaticKeys(nextStatic);
59
79
  let changed = nextStatic !== input || evictingKeys.size > 0;
60
80
  const values = [];
61
81
  for (const [key, entry] of slices) {
@@ -66,11 +86,12 @@ function sparseEnhancer(options = {}) {
66
86
  }
67
87
  if (!changed) return current;
68
88
  const next = { ...nextStatic };
69
- for (const key in current) if (!slices.has(key) && !evictingKeys.has(key) && require_segment.isSegmentKey(key)) next[key] = current[key];
89
+ for (const key of pendingSlices) next[key] = current[key];
70
90
  let index = 0;
71
91
  for (const key of slices.keys()) next[key] = values[index++];
72
92
  return next;
73
93
  };
94
+ probeStaticKeys(rootReducer);
74
95
  const store = createStore(wrapReducer(rootReducer), preloadedState);
75
96
  let batchDepth = 0;
76
97
  const pendingInject = [];
@@ -142,6 +163,7 @@ function sparseEnhancer(options = {}) {
142
163
  const other = (_seen$get = seen.get(segment.key)) !== null && _seen$get !== void 0 ? _seen$get : (_entries$get = entries.get(segment.key)) === null || _entries$get === void 0 ? void 0 : _entries$get.segment;
143
164
  if (other === segment) return;
144
165
  if (other !== void 0) throw new Error(`Two different segments use the key "${segment.key}"`);
166
+ if (segment.reducer && staticKeys.has(segment.key)) throw new Error(`Segment "${segment.key}" uses the key of a slice of the static state`);
145
167
  if (segment.epics.length > 0 && epicRunner === void 0) throw new Error(`Segment "${segment.key}" has epics, but sparseEnhancer was created without an epicRunner`);
146
168
  seen.set(segment.key, segment);
147
169
  for (const dep of segment.deps) assertCompatible(dep, seen);
@@ -320,6 +342,7 @@ function sparseEnhancer(options = {}) {
320
342
  return {
321
343
  ...store,
322
344
  replaceReducer(nextReducer) {
345
+ probeStaticKeys(nextReducer);
323
346
  store.replaceReducer(wrapReducer(nextReducer));
324
347
  },
325
348
  sparse
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["isSegmentKey"],"sources":["../src/sparseEnhancer.ts"],"sourcesContent":["import type { Action, Reducer, StoreEnhancer, StoreEnhancerStoreCreator } from 'redux';\nimport type { AnySegment, SegmentEpic, WhileIdle } from './segment';\nimport { isSegmentKey } from './segment';\n\n/**\n * Actions the sparse store dispatches. Reducers outside the segments can\n * handle {@linkcode SparseActionTypes.EVICT} to drop data that refers to an\n * evicted segment.\n */\nexport const SparseActionTypes = {\n /** Segment slices with the keys in `payload.keys` were added to the state. */\n INJECT: '@@sparse/INJECT',\n /** Segment slices with the keys in `payload.keys` are removed by this action. */\n EVICT: '@@sparse/EVICT',\n} as const;\n\nexport interface SparseAction extends Action<\n (typeof SparseActionTypes)[keyof typeof SparseActionTypes]\n> {\n payload: { keys: readonly string[] };\n}\n\nexport type SparseEvent =\n | { type: 'inject'; keys: readonly string[] }\n | { type: 'evict'; keys: readonly string[] }\n /** A segment is no longer held and waits for its idle timeout. */\n | { type: 'idle'; key: string }\n /** An idle segment is held again before it was evicted. */\n | { type: 'revive'; key: string };\n\nexport interface SparseStats {\n /** Keys of present segments that something holds. */\n active: string[];\n /** Keys of present segments waiting for their idle timeout. */\n idle: string[];\n}\n\nexport interface SparseController {\n /**\n * Holds the given segments, injecting the ones that are not present yet\n * together with their dependencies. Returns a function that releases the\n * hold; calling it more than once has no further effect.\n */\n acquire(...segments: readonly AnySegment[]): () => void;\n /** Whether a segment with this key is present in the store. */\n has(segmentOrKey: AnySegment | string): boolean;\n /**\n * Runs `fn`, deferring injections and evictions it causes until it returns,\n * so that they reach the store as a single action of each kind.\n */\n batch<T>(fn: () => T): T;\n /** Listens to segments being injected, going idle, revived and evicted. */\n subscribe(listener: (event: SparseEvent) => void): () => void;\n /** The keys of the present segments, by state. */\n stats(): SparseStats;\n}\n\nexport interface SparseStoreExt {\n readonly sparse: SparseController;\n}\n\n/**\n * Starts segment epics. `redux-sparse-kit/observable` provides one for\n * redux-observable.\n */\nexport interface EpicRunner {\n /** Starts `epic` and returns a function that stops it. */\n run(epic: SegmentEpic, segment: AnySegment): () => void;\n}\n\nexport interface SparseOptions {\n /** Required as soon as a held segment has epics. */\n epicRunner?: EpicRunner;\n /**\n * How long a segment nothing holds stays present before it is evicted, in\n * milliseconds. `0` evicts it right away, `Infinity` never. Segments can\n * override it. Defaults to one minute.\n */\n idleTimeout?: number;\n /**\n * What an idle segment does until it is evicted. Segments can override it.\n * Defaults to `keep-running`.\n */\n whileIdle?: WhileIdle;\n /**\n * How many idle segments may wait for their idle timeout at once. Past it,\n * the ones idle the longest are evicted right away. Defaults to `Infinity`.\n */\n maxIdle?: number;\n /**\n * Stops the idle clock while the page is hidden, so that time spent in a\n * background tab does not count towards the idle timeout. Defaults to `true`.\n */\n pauseWhenHidden?: boolean;\n}\n\ninterface Entry {\n readonly segment: AnySegment;\n /** Holds by `acquire` callers and by present segments that depend on it. */\n holders: number;\n /** Stops the segment's running epics. */\n stops: (() => void)[];\n /** Fires once the segment has been idle for its idle timeout. */\n timer: ReturnType<typeof setTimeout> | undefined;\n /** Idle time left before it expires; `Infinity` when it never does. */\n remaining: number;\n /** When the running timer was started. */\n startedAt: number;\n /** Idle for its whole idle timeout, so evicted by the next sweep. */\n expired: boolean;\n /** Idle with its epics stopped. */\n paused: boolean;\n /** Idle with its reducer skipped. */\n frozen: boolean;\n}\n\ntype AnyReducer = Reducer<any, Action, any>;\n\nconst emptyKeys: ReadonlySet<string> = new Set();\n\nconst defaultIdleTimeout = 60_000;\n\nconst whileIdleValues: readonly WhileIdle[] = ['keep-running', 'pause-epics', 'freeze'];\n\n// Longer delays overflow `setTimeout` and would fire right away.\nconst maxTimerDelay = 2 ** 31 - 1;\n\nconst scheduleIdle = (callback: () => void) => {\n if (typeof requestIdleCallback === 'function') {\n requestIdleCallback(callback, { timeout: 1000 });\n } else {\n setTimeout(callback, 0);\n }\n};\n\n/**\n * A store enhancer that lets segments be injected into and evicted from a\n * running store. The root reducer passed to `createStore` keeps owning the\n * static state; segment slices live next to it under their keys.\n */\nexport function sparseEnhancer(options: SparseOptions = {}): StoreEnhancer<SparseStoreExt> {\n const {\n epicRunner,\n idleTimeout = defaultIdleTimeout,\n whileIdle = 'keep-running',\n maxIdle = Infinity,\n pauseWhenHidden = true,\n } = options;\n\n const isHidden = () =>\n pauseWhenHidden && typeof document !== 'undefined' && document.visibilityState === 'hidden';\n\n if (!(idleTimeout >= 0)) {\n throw new RangeError('The idle timeout must be a non-negative number');\n }\n\n if (!(maxIdle >= 0)) {\n throw new RangeError('maxIdle must be a non-negative number');\n }\n\n if (!whileIdleValues.includes(whileIdle)) {\n throw new RangeError(`whileIdle must be one of ${whileIdleValues.join(', ')}`);\n }\n\n const enhancer =\n (createStore: StoreEnhancerStoreCreator) =>\n (rootReducer: AnyReducer, preloadedState?: unknown) => {\n const entries = new Map<string, Entry>();\n // Idle entries, the one idle the longest first.\n const idleEntries = new Set<Entry>();\n // Keys being removed by the EVICT action currently being reduced.\n let evictingKeys: ReadonlySet<string> = emptyKeys;\n\n // Injected segments that own a slice of the state, by key: only those\n // with a reducer. They keep reducing until the action that evicts them.\n const slices = new Map<string, Entry>();\n\n const isDynamicKey = (key: string) =>\n slices.has(key) || evictingKeys.has(key) || isSegmentKey(key);\n\n const wrapReducer =\n (root: AnyReducer): AnyReducer =>\n (state, action) => {\n const current = (state ?? {}) as Record<string, unknown>;\n\n let staticState: Record<string, unknown> | undefined;\n let hasDynamicKeys = false;\n\n for (const key in current) {\n if (isDynamicKey(key)) {\n hasDynamicKeys = true;\n break;\n }\n }\n\n if (hasDynamicKeys) {\n staticState = {};\n for (const key in current) {\n if (!isDynamicKey(key)) {\n staticState[key] = current[key];\n }\n }\n }\n\n const input = state === undefined ? undefined : (staticState ?? current);\n const nextStatic = root(input, action) as Record<string, unknown>;\n\n let changed = nextStatic !== input || evictingKeys.size > 0;\n const values: unknown[] = [];\n\n for (const [key, entry] of slices) {\n const previous = current[key];\n const value =\n entry.frozen && key in current ? previous : entry.segment.reducer!(previous, action);\n values.push(value);\n if (value !== previous || !(key in current)) {\n changed = true;\n }\n }\n\n if (!changed) {\n return current;\n }\n\n const next: Record<string, unknown> = { ...nextStatic };\n\n for (const key in current) {\n if (!slices.has(key) && !evictingKeys.has(key) && isSegmentKey(key)) {\n // A slice of a segment that is not present yet, e.g. from a\n // preloaded state: kept so the segment starts from it.\n next[key] = current[key];\n }\n }\n\n let index = 0;\n for (const key of slices.keys()) {\n next[key] = values[index++];\n }\n\n return next;\n };\n\n const store = createStore(wrapReducer(rootReducer), preloadedState);\n\n let batchDepth = 0;\n const pendingInject: Entry[] = [];\n const pendingEvict: Entry[] = [];\n const listeners = new Set<(event: SparseEvent) => void>();\n\n const emit = (event: SparseEvent) => {\n for (const listener of listeners) {\n listener(event);\n }\n };\n\n const dispatchSparse = (type: SparseAction['type'], keys: readonly string[]) => {\n const action: SparseAction = { type, payload: { keys } };\n store.dispatch(action);\n };\n\n const flush = () => {\n if (batchDepth > 0) {\n return;\n }\n\n if (pendingInject.length > 0) {\n const injected = pendingInject.splice(0);\n const keys = injected.map((entry) => entry.segment.key);\n const sliceKeys: string[] = [];\n for (const entry of injected) {\n if (entry.segment.reducer) {\n slices.set(entry.segment.key, entry);\n sliceKeys.push(entry.segment.key);\n }\n }\n if (sliceKeys.length > 0) {\n dispatchSparse(SparseActionTypes.INJECT, sliceKeys);\n }\n // Started once their slices are in the state, deps first.\n for (const entry of injected) {\n if (!entry.paused) {\n startEpics(entry);\n }\n }\n emit({ type: 'inject', keys });\n }\n\n if (pendingEvict.length > 0) {\n const evicted = pendingEvict.splice(0);\n const keys = evicted.map((entry) => entry.segment.key);\n // Stopped while their slices are still in the state, dependents first.\n for (const entry of evicted) {\n stopEpics(entry);\n }\n const sliceKeys = keys.filter((key) => slices.delete(key));\n if (sliceKeys.length > 0) {\n evictingKeys = new Set(sliceKeys);\n try {\n dispatchSparse(SparseActionTypes.EVICT, sliceKeys);\n } finally {\n evictingKeys = emptyKeys;\n }\n }\n emit({ type: 'evict', keys });\n }\n };\n\n const startEpics = (entry: Entry) => {\n for (const epic of entry.segment.epics) {\n entry.stops.push(epicRunner!.run(epic, entry.segment));\n }\n };\n\n const stopEpics = (entry: Entry) => {\n for (const stop of entry.stops.splice(0)) {\n stop();\n }\n };\n\n const batch = <T>(fn: () => T): T => {\n batchDepth++;\n try {\n return fn();\n } finally {\n batchDepth--;\n flush();\n }\n };\n\n const assertCompatible = (segment: AnySegment, seen: Map<string, AnySegment>) => {\n const other = seen.get(segment.key) ?? entries.get(segment.key)?.segment;\n\n if (other === segment) {\n return;\n }\n\n if (other !== undefined) {\n throw new Error(`Two different segments use the key \"${segment.key}\"`);\n }\n\n if (segment.epics.length > 0 && epicRunner === undefined) {\n throw new Error(\n `Segment \"${segment.key}\" has epics, but sparseEnhancer was created without an epicRunner`,\n );\n }\n\n seen.set(segment.key, segment);\n\n for (const dep of segment.deps) {\n assertCompatible(dep, seen);\n }\n };\n\n const hold = (segment: AnySegment) => {\n const existing = entries.get(segment.key);\n\n if (existing) {\n if (existing.holders === 0) {\n revive(existing);\n }\n existing.holders++;\n return;\n }\n\n const evictIndex = pendingEvict.findIndex((entry) => entry.segment === segment);\n\n if (evictIndex !== -1) {\n // Released and held again before the eviction reached the store:\n // its slice is still there, so it only takes its holds back.\n const [entry] = pendingEvict.splice(evictIndex, 1);\n entry!.holders = 1;\n entries.set(segment.key, entry!);\n for (const dep of segment.deps) {\n hold(dep);\n }\n return;\n }\n\n const entry: Entry = {\n segment,\n holders: 1,\n stops: [],\n timer: undefined,\n remaining: Infinity,\n startedAt: 0,\n expired: false,\n paused: false,\n frozen: false,\n };\n entries.set(segment.key, entry);\n\n // Dependencies first, so they are injected before their dependents.\n for (const dep of segment.deps) {\n hold(dep);\n }\n\n pendingInject.push(entry);\n };\n\n /**\n * Drops a hold. A segment left without holders goes idle, unless it was\n * only held by a segment being evicted: then it is evicted with it.\n */\n const unhold = (segment: AnySegment, cascade = false) => {\n // Only reached for segments this store holds: a release undoes exactly\n // the holds its acquire took.\n const entry = entries.get(segment.key)!;\n\n entry.holders--;\n\n if (entry.holders > 0) {\n return;\n }\n\n const timeout = segment.idleTimeout ?? idleTimeout;\n\n if (cascade || timeout === 0) {\n evict(entry);\n } else {\n becomeIdle(entry, timeout);\n }\n };\n\n const becomeIdle = (entry: Entry, timeout: number) => {\n const policy = entry.segment.whileIdle ?? whileIdle;\n\n if (policy !== 'keep-running') {\n entry.paused = true;\n entry.frozen = policy === 'freeze';\n stopEpics(entry);\n }\n\n entry.remaining = timeout > maxTimerDelay ? Infinity : timeout;\n if (!isHidden()) {\n startTimer(entry);\n }\n idleEntries.add(entry);\n watchVisibility();\n emit({ type: 'idle', key: entry.segment.key });\n\n while (idleEntries.size > maxIdle) {\n evict(idleEntries.values().next().value!);\n }\n };\n\n const startTimer = (entry: Entry) => {\n if (entry.remaining !== Infinity) {\n entry.startedAt = Date.now();\n entry.timer = setTimeout(() => {\n entry.timer = undefined;\n entry.expired = true;\n scheduleSweep();\n }, entry.remaining);\n }\n };\n\n const pauseTimers = () => {\n for (const entry of idleEntries) {\n if (entry.timer !== undefined) {\n clearTimeout(entry.timer);\n entry.timer = undefined;\n entry.remaining = Math.max(0, entry.remaining - (Date.now() - entry.startedAt));\n }\n }\n };\n\n const resumeTimers = () => {\n for (const entry of idleEntries) {\n if (entry.timer === undefined && !entry.expired) {\n startTimer(entry);\n }\n }\n };\n\n const onVisibilityChange = () => {\n if (isHidden()) {\n pauseTimers();\n } else {\n resumeTimers();\n }\n };\n\n let watchingVisibility = false;\n\n /** Listens to the page visibility only while there are idle segments. */\n const watchVisibility = () => {\n const watch = idleEntries.size > 0;\n\n if (watch !== watchingVisibility && pauseWhenHidden && typeof document !== 'undefined') {\n watchingVisibility = watch;\n if (watch) {\n document.addEventListener('visibilitychange', onVisibilityChange);\n } else {\n document.removeEventListener('visibilitychange', onVisibilityChange);\n }\n }\n };\n\n const revive = (entry: Entry) => {\n idleEntries.delete(entry);\n watchVisibility();\n clearTimeout(entry.timer);\n entry.timer = undefined;\n entry.expired = false;\n entry.frozen = false;\n\n if (entry.paused) {\n entry.paused = false;\n // Not injected yet: its epics start once it is.\n if (!pendingInject.includes(entry)) {\n startEpics(entry);\n }\n }\n\n emit({ type: 'revive', key: entry.segment.key });\n };\n\n const evict = (entry: Entry) => {\n idleEntries.delete(entry);\n watchVisibility();\n clearTimeout(entry.timer);\n entries.delete(entry.segment.key);\n\n const pendingIndex = pendingInject.indexOf(entry);\n if (pendingIndex !== -1) {\n // Released before it reached the store: nothing to evict.\n pendingInject.splice(pendingIndex, 1);\n } else {\n pendingEvict.push(entry);\n }\n\n // Dependents first, so they are evicted before their dependencies.\n for (const dep of entry.segment.deps) {\n unhold(dep, true);\n }\n };\n\n let sweepScheduled = false;\n\n /** Evicts every expired segment with a single action. */\n const sweep = () => {\n sweepScheduled = false;\n batch(() => {\n for (const entry of entries.values()) {\n if (entry.expired) {\n evict(entry);\n }\n }\n });\n };\n\n const scheduleSweep = () => {\n if (!sweepScheduled) {\n sweepScheduled = true;\n scheduleIdle(sweep);\n }\n };\n\n const sparse: SparseController = {\n acquire(...segments) {\n // Checked up front so that a refused acquire leaves no holds behind.\n const seen = new Map<string, AnySegment>();\n for (const segment of segments) {\n assertCompatible(segment, seen);\n }\n\n batch(() => {\n for (const segment of segments) {\n hold(segment);\n }\n });\n\n let released = false;\n\n return () => {\n if (released) {\n return;\n }\n released = true;\n\n batch(() => {\n for (const segment of segments) {\n unhold(segment);\n }\n });\n };\n },\n\n has(segmentOrKey) {\n const key = typeof segmentOrKey === 'string' ? segmentOrKey : segmentOrKey.key;\n return entries.has(key) && !pendingInject.includes(entries.get(key)!);\n },\n\n stats() {\n const stats: SparseStats = { active: [], idle: [] };\n for (const [key, entry] of entries) {\n if (!pendingInject.includes(entry)) {\n (entry.holders > 0 ? stats.active : stats.idle).push(key);\n }\n }\n return stats;\n },\n\n batch,\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n\n return {\n ...store,\n replaceReducer(nextReducer: AnyReducer) {\n store.replaceReducer(wrapReducer(nextReducer));\n },\n sparse,\n };\n };\n\n return enhancer as StoreEnhancer<SparseStoreExt>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["isSegmentKey"],"sources":["../src/sparseEnhancer.ts"],"sourcesContent":["import type { Action, Reducer, StoreEnhancer, StoreEnhancerStoreCreator } from 'redux';\nimport type { AnySegment, SegmentEpic, WhileIdle } from './segment';\nimport { isSegmentKey } from './segment';\n\n/**\n * Actions the sparse store dispatches. Reducers outside the segments can\n * handle {@linkcode SparseActionTypes.EVICT} to drop data that refers to an\n * evicted segment.\n */\nexport const SparseActionTypes = {\n /** Segment slices with the keys in `payload.keys` were added to the state. */\n INJECT: '@@sparse/INJECT',\n /** Segment slices with the keys in `payload.keys` are removed by this action. */\n EVICT: '@@sparse/EVICT',\n} as const;\n\nexport interface SparseAction extends Action<\n (typeof SparseActionTypes)[keyof typeof SparseActionTypes]\n> {\n payload: { keys: readonly string[] };\n}\n\nexport type SparseEvent =\n | { type: 'inject'; keys: readonly string[] }\n | { type: 'evict'; keys: readonly string[] }\n /** A segment is no longer held and waits for its idle timeout. */\n | { type: 'idle'; key: string }\n /** An idle segment is held again before it was evicted. */\n | { type: 'revive'; key: string };\n\nexport interface SparseStats {\n /** Keys of present segments that something holds. */\n active: string[];\n /** Keys of present segments waiting for their idle timeout. */\n idle: string[];\n}\n\nexport interface SparseController {\n /**\n * Holds the given segments, injecting the ones that are not present yet\n * together with their dependencies. Returns a function that releases the\n * hold; calling it more than once has no further effect.\n */\n acquire(...segments: readonly AnySegment[]): () => void;\n /** Whether a segment with this key is present in the store. */\n has(segmentOrKey: AnySegment | string): boolean;\n /**\n * Runs `fn`, deferring injections and evictions it causes until it returns,\n * so that they reach the store as a single action of each kind.\n */\n batch<T>(fn: () => T): T;\n /** Listens to segments being injected, going idle, revived and evicted. */\n subscribe(listener: (event: SparseEvent) => void): () => void;\n /** The keys of the present segments, by state. */\n stats(): SparseStats;\n}\n\nexport interface SparseStoreExt {\n readonly sparse: SparseController;\n}\n\n/**\n * Starts segment epics. `redux-sparse-kit/observable` provides one for\n * redux-observable.\n */\nexport interface EpicRunner {\n /** Starts `epic` and returns a function that stops it. */\n run(epic: SegmentEpic, segment: AnySegment): () => void;\n}\n\nexport interface SparseOptions {\n /** Required as soon as a held segment has epics. */\n epicRunner?: EpicRunner;\n /**\n * How long a segment nothing holds stays present before it is evicted, in\n * milliseconds. `0` evicts it right away, `Infinity` never. Segments can\n * override it. Defaults to one minute.\n */\n idleTimeout?: number;\n /**\n * What an idle segment does until it is evicted. Segments can override it.\n * Defaults to `keep-running`.\n */\n whileIdle?: WhileIdle;\n /**\n * How many idle segments may wait for their idle timeout at once. Past it,\n * the ones idle the longest are evicted right away. Defaults to `Infinity`.\n */\n maxIdle?: number;\n /**\n * Stops the idle clock while the page is hidden, so that time spent in a\n * background tab does not count towards the idle timeout. Defaults to `true`.\n */\n pauseWhenHidden?: boolean;\n}\n\ninterface Entry {\n readonly segment: AnySegment;\n /** Holds by `acquire` callers and by present segments that depend on it. */\n holders: number;\n /** Stops the segment's running epics. */\n stops: (() => void)[];\n /** Fires once the segment has been idle for its idle timeout. */\n timer: ReturnType<typeof setTimeout> | undefined;\n /** Idle time left before it expires; `Infinity` when it never does. */\n remaining: number;\n /** When the running timer was started. */\n startedAt: number;\n /** Idle for its whole idle timeout, so evicted by the next sweep. */\n expired: boolean;\n /** Idle with its epics stopped. */\n paused: boolean;\n /** Idle with its reducer skipped. */\n frozen: boolean;\n}\n\ntype AnyReducer = Reducer<any, Action, any>;\n\nconst emptyKeys: ReadonlySet<string> = new Set();\n\nconst defaultIdleTimeout = 60_000;\n\n// Reduced only by the root reducer, to learn the keys of the static state.\nconst probeActionType = '@@sparse/PROBE';\n\nconst whileIdleValues: readonly WhileIdle[] = ['keep-running', 'pause-epics', 'freeze'];\n\n// Longer delays overflow `setTimeout` and would fire right away.\nconst maxTimerDelay = 2 ** 31 - 1;\n\nconst scheduleIdle = (callback: () => void) => {\n if (typeof requestIdleCallback === 'function') {\n requestIdleCallback(callback, { timeout: 1000 });\n } else {\n setTimeout(callback, 0);\n }\n};\n\n/**\n * A store enhancer that lets segments be injected into and evicted from a\n * running store. The root reducer passed to `createStore` keeps owning the\n * static state; segment slices live next to it under their keys.\n */\nexport function sparseEnhancer(options: SparseOptions = {}): StoreEnhancer<SparseStoreExt> {\n const {\n epicRunner,\n idleTimeout = defaultIdleTimeout,\n whileIdle = 'keep-running',\n maxIdle = Infinity,\n pauseWhenHidden = true,\n } = options;\n\n const isHidden = () =>\n pauseWhenHidden && typeof document !== 'undefined' && document.visibilityState === 'hidden';\n\n if (!(idleTimeout >= 0)) {\n throw new RangeError('The idle timeout must be a non-negative number');\n }\n\n if (!(maxIdle >= 0)) {\n throw new RangeError('maxIdle must be a non-negative number');\n }\n\n if (!whileIdleValues.includes(whileIdle)) {\n throw new RangeError(`whileIdle must be one of ${whileIdleValues.join(', ')}`);\n }\n\n const enhancer =\n (createStore: StoreEnhancerStoreCreator) =>\n (rootReducer: AnyReducer, preloadedState?: unknown) => {\n const entries = new Map<string, Entry>();\n // Idle entries, the one idle the longest first.\n const idleEntries = new Set<Entry>();\n // Keys being removed by the EVICT action currently being reduced.\n let evictingKeys: ReadonlySet<string> = emptyKeys;\n\n // Injected segments that own a slice of the state, by key: only those\n // with a reducer. They keep reducing until the action that evicts them.\n const slices = new Map<string, Entry>();\n\n // Keys of the static state: whatever the root reducer returns. Never\n // treated as segment slices, even when a segment uses the same key.\n let staticKeys: ReadonlySet<string> = emptyKeys;\n\n const learnStaticKeys = (staticState: object) => {\n let count = 0;\n for (const key in staticState) {\n count++;\n if (!staticKeys.has(key)) {\n staticKeys = new Set(Object.keys(staticState));\n return;\n }\n }\n if (count !== staticKeys.size) {\n staticKeys = new Set(Object.keys(staticState));\n }\n };\n\n /** Asks a root reducer for its keys before it reduces a real state. */\n const probeStaticKeys = (root: AnyReducer) => {\n learnStaticKeys(root(undefined, { type: probeActionType }) as object);\n };\n\n const isDynamicKey = (key: string) =>\n !staticKeys.has(key) && (slices.has(key) || evictingKeys.has(key) || isSegmentKey(key));\n\n const wrapReducer =\n (root: AnyReducer): AnyReducer =>\n (state, action) => {\n const current = (state ?? {}) as Record<string, unknown>;\n\n let staticState: Record<string, unknown> | undefined;\n let hasDynamicKeys = false;\n\n for (const key in current) {\n if (isDynamicKey(key)) {\n hasDynamicKeys = true;\n break;\n }\n }\n\n // Slices of segments that are not present yet, e.g. from a preloaded\n // state: kept so that the segments start from them.\n const pendingSlices: string[] = [];\n\n if (hasDynamicKeys) {\n staticState = {};\n for (const key in current) {\n if (!isDynamicKey(key)) {\n staticState[key] = current[key];\n } else if (!slices.has(key) && !evictingKeys.has(key)) {\n pendingSlices.push(key);\n }\n }\n }\n\n const input = state === undefined ? undefined : (staticState ?? current);\n const nextStatic = root(input, action) as Record<string, unknown>;\n\n if (nextStatic !== input) {\n learnStaticKeys(nextStatic);\n }\n\n let changed = nextStatic !== input || evictingKeys.size > 0;\n const values: unknown[] = [];\n\n for (const [key, entry] of slices) {\n const previous = current[key];\n const value =\n entry.frozen && key in current ? previous : entry.segment.reducer!(previous, action);\n values.push(value);\n if (value !== previous || !(key in current)) {\n changed = true;\n }\n }\n\n if (!changed) {\n return current;\n }\n\n const next: Record<string, unknown> = { ...nextStatic };\n\n for (const key of pendingSlices) {\n next[key] = current[key];\n }\n\n let index = 0;\n for (const key of slices.keys()) {\n next[key] = values[index++];\n }\n\n return next;\n };\n\n probeStaticKeys(rootReducer);\n const store = createStore(wrapReducer(rootReducer), preloadedState);\n\n let batchDepth = 0;\n const pendingInject: Entry[] = [];\n const pendingEvict: Entry[] = [];\n const listeners = new Set<(event: SparseEvent) => void>();\n\n const emit = (event: SparseEvent) => {\n for (const listener of listeners) {\n listener(event);\n }\n };\n\n const dispatchSparse = (type: SparseAction['type'], keys: readonly string[]) => {\n const action: SparseAction = { type, payload: { keys } };\n store.dispatch(action);\n };\n\n const flush = () => {\n if (batchDepth > 0) {\n return;\n }\n\n if (pendingInject.length > 0) {\n const injected = pendingInject.splice(0);\n const keys = injected.map((entry) => entry.segment.key);\n const sliceKeys: string[] = [];\n for (const entry of injected) {\n if (entry.segment.reducer) {\n slices.set(entry.segment.key, entry);\n sliceKeys.push(entry.segment.key);\n }\n }\n if (sliceKeys.length > 0) {\n dispatchSparse(SparseActionTypes.INJECT, sliceKeys);\n }\n // Started once their slices are in the state, deps first.\n for (const entry of injected) {\n if (!entry.paused) {\n startEpics(entry);\n }\n }\n emit({ type: 'inject', keys });\n }\n\n if (pendingEvict.length > 0) {\n const evicted = pendingEvict.splice(0);\n const keys = evicted.map((entry) => entry.segment.key);\n // Stopped while their slices are still in the state, dependents first.\n for (const entry of evicted) {\n stopEpics(entry);\n }\n const sliceKeys = keys.filter((key) => slices.delete(key));\n if (sliceKeys.length > 0) {\n evictingKeys = new Set(sliceKeys);\n try {\n dispatchSparse(SparseActionTypes.EVICT, sliceKeys);\n } finally {\n evictingKeys = emptyKeys;\n }\n }\n emit({ type: 'evict', keys });\n }\n };\n\n const startEpics = (entry: Entry) => {\n for (const epic of entry.segment.epics) {\n entry.stops.push(epicRunner!.run(epic, entry.segment));\n }\n };\n\n const stopEpics = (entry: Entry) => {\n for (const stop of entry.stops.splice(0)) {\n stop();\n }\n };\n\n const batch = <T>(fn: () => T): T => {\n batchDepth++;\n try {\n return fn();\n } finally {\n batchDepth--;\n flush();\n }\n };\n\n const assertCompatible = (segment: AnySegment, seen: Map<string, AnySegment>) => {\n const other = seen.get(segment.key) ?? entries.get(segment.key)?.segment;\n\n if (other === segment) {\n return;\n }\n\n if (other !== undefined) {\n throw new Error(`Two different segments use the key \"${segment.key}\"`);\n }\n\n if (segment.reducer && staticKeys.has(segment.key)) {\n throw new Error(`Segment \"${segment.key}\" uses the key of a slice of the static state`);\n }\n\n if (segment.epics.length > 0 && epicRunner === undefined) {\n throw new Error(\n `Segment \"${segment.key}\" has epics, but sparseEnhancer was created without an epicRunner`,\n );\n }\n\n seen.set(segment.key, segment);\n\n for (const dep of segment.deps) {\n assertCompatible(dep, seen);\n }\n };\n\n const hold = (segment: AnySegment) => {\n const existing = entries.get(segment.key);\n\n if (existing) {\n if (existing.holders === 0) {\n revive(existing);\n }\n existing.holders++;\n return;\n }\n\n const evictIndex = pendingEvict.findIndex((entry) => entry.segment === segment);\n\n if (evictIndex !== -1) {\n // Released and held again before the eviction reached the store:\n // its slice is still there, so it only takes its holds back.\n const [entry] = pendingEvict.splice(evictIndex, 1);\n entry!.holders = 1;\n entries.set(segment.key, entry!);\n for (const dep of segment.deps) {\n hold(dep);\n }\n return;\n }\n\n const entry: Entry = {\n segment,\n holders: 1,\n stops: [],\n timer: undefined,\n remaining: Infinity,\n startedAt: 0,\n expired: false,\n paused: false,\n frozen: false,\n };\n entries.set(segment.key, entry);\n\n // Dependencies first, so they are injected before their dependents.\n for (const dep of segment.deps) {\n hold(dep);\n }\n\n pendingInject.push(entry);\n };\n\n /**\n * Drops a hold. A segment left without holders goes idle, unless it was\n * only held by a segment being evicted: then it is evicted with it.\n */\n const unhold = (segment: AnySegment, cascade = false) => {\n // Only reached for segments this store holds: a release undoes exactly\n // the holds its acquire took.\n const entry = entries.get(segment.key)!;\n\n entry.holders--;\n\n if (entry.holders > 0) {\n return;\n }\n\n const timeout = segment.idleTimeout ?? idleTimeout;\n\n if (cascade || timeout === 0) {\n evict(entry);\n } else {\n becomeIdle(entry, timeout);\n }\n };\n\n const becomeIdle = (entry: Entry, timeout: number) => {\n const policy = entry.segment.whileIdle ?? whileIdle;\n\n if (policy !== 'keep-running') {\n entry.paused = true;\n entry.frozen = policy === 'freeze';\n stopEpics(entry);\n }\n\n entry.remaining = timeout > maxTimerDelay ? Infinity : timeout;\n if (!isHidden()) {\n startTimer(entry);\n }\n idleEntries.add(entry);\n watchVisibility();\n emit({ type: 'idle', key: entry.segment.key });\n\n while (idleEntries.size > maxIdle) {\n evict(idleEntries.values().next().value!);\n }\n };\n\n const startTimer = (entry: Entry) => {\n if (entry.remaining !== Infinity) {\n entry.startedAt = Date.now();\n entry.timer = setTimeout(() => {\n entry.timer = undefined;\n entry.expired = true;\n scheduleSweep();\n }, entry.remaining);\n }\n };\n\n const pauseTimers = () => {\n for (const entry of idleEntries) {\n if (entry.timer !== undefined) {\n clearTimeout(entry.timer);\n entry.timer = undefined;\n entry.remaining = Math.max(0, entry.remaining - (Date.now() - entry.startedAt));\n }\n }\n };\n\n const resumeTimers = () => {\n for (const entry of idleEntries) {\n if (entry.timer === undefined && !entry.expired) {\n startTimer(entry);\n }\n }\n };\n\n const onVisibilityChange = () => {\n if (isHidden()) {\n pauseTimers();\n } else {\n resumeTimers();\n }\n };\n\n let watchingVisibility = false;\n\n /** Listens to the page visibility only while there are idle segments. */\n const watchVisibility = () => {\n const watch = idleEntries.size > 0;\n\n if (watch !== watchingVisibility && pauseWhenHidden && typeof document !== 'undefined') {\n watchingVisibility = watch;\n if (watch) {\n document.addEventListener('visibilitychange', onVisibilityChange);\n } else {\n document.removeEventListener('visibilitychange', onVisibilityChange);\n }\n }\n };\n\n const revive = (entry: Entry) => {\n idleEntries.delete(entry);\n watchVisibility();\n clearTimeout(entry.timer);\n entry.timer = undefined;\n entry.expired = false;\n entry.frozen = false;\n\n if (entry.paused) {\n entry.paused = false;\n // Not injected yet: its epics start once it is.\n if (!pendingInject.includes(entry)) {\n startEpics(entry);\n }\n }\n\n emit({ type: 'revive', key: entry.segment.key });\n };\n\n const evict = (entry: Entry) => {\n idleEntries.delete(entry);\n watchVisibility();\n clearTimeout(entry.timer);\n entries.delete(entry.segment.key);\n\n const pendingIndex = pendingInject.indexOf(entry);\n if (pendingIndex !== -1) {\n // Released before it reached the store: nothing to evict.\n pendingInject.splice(pendingIndex, 1);\n } else {\n pendingEvict.push(entry);\n }\n\n // Dependents first, so they are evicted before their dependencies.\n for (const dep of entry.segment.deps) {\n unhold(dep, true);\n }\n };\n\n let sweepScheduled = false;\n\n /** Evicts every expired segment with a single action. */\n const sweep = () => {\n sweepScheduled = false;\n batch(() => {\n for (const entry of entries.values()) {\n if (entry.expired) {\n evict(entry);\n }\n }\n });\n };\n\n const scheduleSweep = () => {\n if (!sweepScheduled) {\n sweepScheduled = true;\n scheduleIdle(sweep);\n }\n };\n\n const sparse: SparseController = {\n acquire(...segments) {\n // Checked up front so that a refused acquire leaves no holds behind.\n const seen = new Map<string, AnySegment>();\n for (const segment of segments) {\n assertCompatible(segment, seen);\n }\n\n batch(() => {\n for (const segment of segments) {\n hold(segment);\n }\n });\n\n let released = false;\n\n return () => {\n if (released) {\n return;\n }\n released = true;\n\n batch(() => {\n for (const segment of segments) {\n unhold(segment);\n }\n });\n };\n },\n\n has(segmentOrKey) {\n const key = typeof segmentOrKey === 'string' ? segmentOrKey : segmentOrKey.key;\n return entries.has(key) && !pendingInject.includes(entries.get(key)!);\n },\n\n stats() {\n const stats: SparseStats = { active: [], idle: [] };\n for (const [key, entry] of entries) {\n if (!pendingInject.includes(entry)) {\n (entry.holders > 0 ? stats.active : stats.idle).push(key);\n }\n }\n return stats;\n },\n\n batch,\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n\n return {\n ...store,\n replaceReducer(nextReducer: AnyReducer) {\n probeStaticKeys(nextReducer);\n store.replaceReducer(wrapReducer(nextReducer));\n },\n sparse,\n };\n };\n\n return enhancer as StoreEnhancer<SparseStoreExt>;\n}\n"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -13,6 +13,7 @@ const SparseActionTypes = {
13
13
  };
14
14
  const emptyKeys = /* @__PURE__ */ new Set();
15
15
  const defaultIdleTimeout = 6e4;
16
+ const probeActionType = "@@sparse/PROBE";
16
17
  const whileIdleValues = [
17
18
  "keep-running",
18
19
  "pause-epics",
@@ -39,7 +40,23 @@ function sparseEnhancer(options = {}) {
39
40
  const idleEntries = /* @__PURE__ */ new Set();
40
41
  let evictingKeys = emptyKeys;
41
42
  const slices = /* @__PURE__ */ new Map();
42
- const isDynamicKey = (key) => slices.has(key) || evictingKeys.has(key) || isSegmentKey(key);
43
+ let staticKeys = emptyKeys;
44
+ const learnStaticKeys = (staticState) => {
45
+ let count = 0;
46
+ for (const key in staticState) {
47
+ count++;
48
+ if (!staticKeys.has(key)) {
49
+ staticKeys = new Set(Object.keys(staticState));
50
+ return;
51
+ }
52
+ }
53
+ if (count !== staticKeys.size) staticKeys = new Set(Object.keys(staticState));
54
+ };
55
+ /** Asks a root reducer for its keys before it reduces a real state. */
56
+ const probeStaticKeys = (root) => {
57
+ learnStaticKeys(root(void 0, { type: probeActionType }));
58
+ };
59
+ const isDynamicKey = (key) => !staticKeys.has(key) && (slices.has(key) || evictingKeys.has(key) || isSegmentKey(key));
43
60
  const wrapReducer = (root) => (state, action) => {
44
61
  var _staticState;
45
62
  const current = state !== null && state !== void 0 ? state : {};
@@ -49,12 +66,15 @@ function sparseEnhancer(options = {}) {
49
66
  hasDynamicKeys = true;
50
67
  break;
51
68
  }
69
+ const pendingSlices = [];
52
70
  if (hasDynamicKeys) {
53
71
  staticState = {};
54
72
  for (const key in current) if (!isDynamicKey(key)) staticState[key] = current[key];
73
+ else if (!slices.has(key) && !evictingKeys.has(key)) pendingSlices.push(key);
55
74
  }
56
75
  const input = state === void 0 ? void 0 : (_staticState = staticState) !== null && _staticState !== void 0 ? _staticState : current;
57
76
  const nextStatic = root(input, action);
77
+ if (nextStatic !== input) learnStaticKeys(nextStatic);
58
78
  let changed = nextStatic !== input || evictingKeys.size > 0;
59
79
  const values = [];
60
80
  for (const [key, entry] of slices) {
@@ -65,11 +85,12 @@ function sparseEnhancer(options = {}) {
65
85
  }
66
86
  if (!changed) return current;
67
87
  const next = { ...nextStatic };
68
- for (const key in current) if (!slices.has(key) && !evictingKeys.has(key) && isSegmentKey(key)) next[key] = current[key];
88
+ for (const key of pendingSlices) next[key] = current[key];
69
89
  let index = 0;
70
90
  for (const key of slices.keys()) next[key] = values[index++];
71
91
  return next;
72
92
  };
93
+ probeStaticKeys(rootReducer);
73
94
  const store = createStore(wrapReducer(rootReducer), preloadedState);
74
95
  let batchDepth = 0;
75
96
  const pendingInject = [];
@@ -141,6 +162,7 @@ function sparseEnhancer(options = {}) {
141
162
  const other = (_seen$get = seen.get(segment.key)) !== null && _seen$get !== void 0 ? _seen$get : (_entries$get = entries.get(segment.key)) === null || _entries$get === void 0 ? void 0 : _entries$get.segment;
142
163
  if (other === segment) return;
143
164
  if (other !== void 0) throw new Error(`Two different segments use the key "${segment.key}"`);
165
+ if (segment.reducer && staticKeys.has(segment.key)) throw new Error(`Segment "${segment.key}" uses the key of a slice of the static state`);
144
166
  if (segment.epics.length > 0 && epicRunner === void 0) throw new Error(`Segment "${segment.key}" has epics, but sparseEnhancer was created without an epicRunner`);
145
167
  seen.set(segment.key, segment);
146
168
  for (const dep of segment.deps) assertCompatible(dep, seen);
@@ -319,6 +341,7 @@ function sparseEnhancer(options = {}) {
319
341
  return {
320
342
  ...store,
321
343
  replaceReducer(nextReducer) {
344
+ probeStaticKeys(nextReducer);
322
345
  store.replaceReducer(wrapReducer(nextReducer));
323
346
  },
324
347
  sparse
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/sparseEnhancer.ts"],"sourcesContent":["import type { Action, Reducer, StoreEnhancer, StoreEnhancerStoreCreator } from 'redux';\nimport type { AnySegment, SegmentEpic, WhileIdle } from './segment';\nimport { isSegmentKey } from './segment';\n\n/**\n * Actions the sparse store dispatches. Reducers outside the segments can\n * handle {@linkcode SparseActionTypes.EVICT} to drop data that refers to an\n * evicted segment.\n */\nexport const SparseActionTypes = {\n /** Segment slices with the keys in `payload.keys` were added to the state. */\n INJECT: '@@sparse/INJECT',\n /** Segment slices with the keys in `payload.keys` are removed by this action. */\n EVICT: '@@sparse/EVICT',\n} as const;\n\nexport interface SparseAction extends Action<\n (typeof SparseActionTypes)[keyof typeof SparseActionTypes]\n> {\n payload: { keys: readonly string[] };\n}\n\nexport type SparseEvent =\n | { type: 'inject'; keys: readonly string[] }\n | { type: 'evict'; keys: readonly string[] }\n /** A segment is no longer held and waits for its idle timeout. */\n | { type: 'idle'; key: string }\n /** An idle segment is held again before it was evicted. */\n | { type: 'revive'; key: string };\n\nexport interface SparseStats {\n /** Keys of present segments that something holds. */\n active: string[];\n /** Keys of present segments waiting for their idle timeout. */\n idle: string[];\n}\n\nexport interface SparseController {\n /**\n * Holds the given segments, injecting the ones that are not present yet\n * together with their dependencies. Returns a function that releases the\n * hold; calling it more than once has no further effect.\n */\n acquire(...segments: readonly AnySegment[]): () => void;\n /** Whether a segment with this key is present in the store. */\n has(segmentOrKey: AnySegment | string): boolean;\n /**\n * Runs `fn`, deferring injections and evictions it causes until it returns,\n * so that they reach the store as a single action of each kind.\n */\n batch<T>(fn: () => T): T;\n /** Listens to segments being injected, going idle, revived and evicted. */\n subscribe(listener: (event: SparseEvent) => void): () => void;\n /** The keys of the present segments, by state. */\n stats(): SparseStats;\n}\n\nexport interface SparseStoreExt {\n readonly sparse: SparseController;\n}\n\n/**\n * Starts segment epics. `redux-sparse-kit/observable` provides one for\n * redux-observable.\n */\nexport interface EpicRunner {\n /** Starts `epic` and returns a function that stops it. */\n run(epic: SegmentEpic, segment: AnySegment): () => void;\n}\n\nexport interface SparseOptions {\n /** Required as soon as a held segment has epics. */\n epicRunner?: EpicRunner;\n /**\n * How long a segment nothing holds stays present before it is evicted, in\n * milliseconds. `0` evicts it right away, `Infinity` never. Segments can\n * override it. Defaults to one minute.\n */\n idleTimeout?: number;\n /**\n * What an idle segment does until it is evicted. Segments can override it.\n * Defaults to `keep-running`.\n */\n whileIdle?: WhileIdle;\n /**\n * How many idle segments may wait for their idle timeout at once. Past it,\n * the ones idle the longest are evicted right away. Defaults to `Infinity`.\n */\n maxIdle?: number;\n /**\n * Stops the idle clock while the page is hidden, so that time spent in a\n * background tab does not count towards the idle timeout. Defaults to `true`.\n */\n pauseWhenHidden?: boolean;\n}\n\ninterface Entry {\n readonly segment: AnySegment;\n /** Holds by `acquire` callers and by present segments that depend on it. */\n holders: number;\n /** Stops the segment's running epics. */\n stops: (() => void)[];\n /** Fires once the segment has been idle for its idle timeout. */\n timer: ReturnType<typeof setTimeout> | undefined;\n /** Idle time left before it expires; `Infinity` when it never does. */\n remaining: number;\n /** When the running timer was started. */\n startedAt: number;\n /** Idle for its whole idle timeout, so evicted by the next sweep. */\n expired: boolean;\n /** Idle with its epics stopped. */\n paused: boolean;\n /** Idle with its reducer skipped. */\n frozen: boolean;\n}\n\ntype AnyReducer = Reducer<any, Action, any>;\n\nconst emptyKeys: ReadonlySet<string> = new Set();\n\nconst defaultIdleTimeout = 60_000;\n\nconst whileIdleValues: readonly WhileIdle[] = ['keep-running', 'pause-epics', 'freeze'];\n\n// Longer delays overflow `setTimeout` and would fire right away.\nconst maxTimerDelay = 2 ** 31 - 1;\n\nconst scheduleIdle = (callback: () => void) => {\n if (typeof requestIdleCallback === 'function') {\n requestIdleCallback(callback, { timeout: 1000 });\n } else {\n setTimeout(callback, 0);\n }\n};\n\n/**\n * A store enhancer that lets segments be injected into and evicted from a\n * running store. The root reducer passed to `createStore` keeps owning the\n * static state; segment slices live next to it under their keys.\n */\nexport function sparseEnhancer(options: SparseOptions = {}): StoreEnhancer<SparseStoreExt> {\n const {\n epicRunner,\n idleTimeout = defaultIdleTimeout,\n whileIdle = 'keep-running',\n maxIdle = Infinity,\n pauseWhenHidden = true,\n } = options;\n\n const isHidden = () =>\n pauseWhenHidden && typeof document !== 'undefined' && document.visibilityState === 'hidden';\n\n if (!(idleTimeout >= 0)) {\n throw new RangeError('The idle timeout must be a non-negative number');\n }\n\n if (!(maxIdle >= 0)) {\n throw new RangeError('maxIdle must be a non-negative number');\n }\n\n if (!whileIdleValues.includes(whileIdle)) {\n throw new RangeError(`whileIdle must be one of ${whileIdleValues.join(', ')}`);\n }\n\n const enhancer =\n (createStore: StoreEnhancerStoreCreator) =>\n (rootReducer: AnyReducer, preloadedState?: unknown) => {\n const entries = new Map<string, Entry>();\n // Idle entries, the one idle the longest first.\n const idleEntries = new Set<Entry>();\n // Keys being removed by the EVICT action currently being reduced.\n let evictingKeys: ReadonlySet<string> = emptyKeys;\n\n // Injected segments that own a slice of the state, by key: only those\n // with a reducer. They keep reducing until the action that evicts them.\n const slices = new Map<string, Entry>();\n\n const isDynamicKey = (key: string) =>\n slices.has(key) || evictingKeys.has(key) || isSegmentKey(key);\n\n const wrapReducer =\n (root: AnyReducer): AnyReducer =>\n (state, action) => {\n const current = (state ?? {}) as Record<string, unknown>;\n\n let staticState: Record<string, unknown> | undefined;\n let hasDynamicKeys = false;\n\n for (const key in current) {\n if (isDynamicKey(key)) {\n hasDynamicKeys = true;\n break;\n }\n }\n\n if (hasDynamicKeys) {\n staticState = {};\n for (const key in current) {\n if (!isDynamicKey(key)) {\n staticState[key] = current[key];\n }\n }\n }\n\n const input = state === undefined ? undefined : (staticState ?? current);\n const nextStatic = root(input, action) as Record<string, unknown>;\n\n let changed = nextStatic !== input || evictingKeys.size > 0;\n const values: unknown[] = [];\n\n for (const [key, entry] of slices) {\n const previous = current[key];\n const value =\n entry.frozen && key in current ? previous : entry.segment.reducer!(previous, action);\n values.push(value);\n if (value !== previous || !(key in current)) {\n changed = true;\n }\n }\n\n if (!changed) {\n return current;\n }\n\n const next: Record<string, unknown> = { ...nextStatic };\n\n for (const key in current) {\n if (!slices.has(key) && !evictingKeys.has(key) && isSegmentKey(key)) {\n // A slice of a segment that is not present yet, e.g. from a\n // preloaded state: kept so the segment starts from it.\n next[key] = current[key];\n }\n }\n\n let index = 0;\n for (const key of slices.keys()) {\n next[key] = values[index++];\n }\n\n return next;\n };\n\n const store = createStore(wrapReducer(rootReducer), preloadedState);\n\n let batchDepth = 0;\n const pendingInject: Entry[] = [];\n const pendingEvict: Entry[] = [];\n const listeners = new Set<(event: SparseEvent) => void>();\n\n const emit = (event: SparseEvent) => {\n for (const listener of listeners) {\n listener(event);\n }\n };\n\n const dispatchSparse = (type: SparseAction['type'], keys: readonly string[]) => {\n const action: SparseAction = { type, payload: { keys } };\n store.dispatch(action);\n };\n\n const flush = () => {\n if (batchDepth > 0) {\n return;\n }\n\n if (pendingInject.length > 0) {\n const injected = pendingInject.splice(0);\n const keys = injected.map((entry) => entry.segment.key);\n const sliceKeys: string[] = [];\n for (const entry of injected) {\n if (entry.segment.reducer) {\n slices.set(entry.segment.key, entry);\n sliceKeys.push(entry.segment.key);\n }\n }\n if (sliceKeys.length > 0) {\n dispatchSparse(SparseActionTypes.INJECT, sliceKeys);\n }\n // Started once their slices are in the state, deps first.\n for (const entry of injected) {\n if (!entry.paused) {\n startEpics(entry);\n }\n }\n emit({ type: 'inject', keys });\n }\n\n if (pendingEvict.length > 0) {\n const evicted = pendingEvict.splice(0);\n const keys = evicted.map((entry) => entry.segment.key);\n // Stopped while their slices are still in the state, dependents first.\n for (const entry of evicted) {\n stopEpics(entry);\n }\n const sliceKeys = keys.filter((key) => slices.delete(key));\n if (sliceKeys.length > 0) {\n evictingKeys = new Set(sliceKeys);\n try {\n dispatchSparse(SparseActionTypes.EVICT, sliceKeys);\n } finally {\n evictingKeys = emptyKeys;\n }\n }\n emit({ type: 'evict', keys });\n }\n };\n\n const startEpics = (entry: Entry) => {\n for (const epic of entry.segment.epics) {\n entry.stops.push(epicRunner!.run(epic, entry.segment));\n }\n };\n\n const stopEpics = (entry: Entry) => {\n for (const stop of entry.stops.splice(0)) {\n stop();\n }\n };\n\n const batch = <T>(fn: () => T): T => {\n batchDepth++;\n try {\n return fn();\n } finally {\n batchDepth--;\n flush();\n }\n };\n\n const assertCompatible = (segment: AnySegment, seen: Map<string, AnySegment>) => {\n const other = seen.get(segment.key) ?? entries.get(segment.key)?.segment;\n\n if (other === segment) {\n return;\n }\n\n if (other !== undefined) {\n throw new Error(`Two different segments use the key \"${segment.key}\"`);\n }\n\n if (segment.epics.length > 0 && epicRunner === undefined) {\n throw new Error(\n `Segment \"${segment.key}\" has epics, but sparseEnhancer was created without an epicRunner`,\n );\n }\n\n seen.set(segment.key, segment);\n\n for (const dep of segment.deps) {\n assertCompatible(dep, seen);\n }\n };\n\n const hold = (segment: AnySegment) => {\n const existing = entries.get(segment.key);\n\n if (existing) {\n if (existing.holders === 0) {\n revive(existing);\n }\n existing.holders++;\n return;\n }\n\n const evictIndex = pendingEvict.findIndex((entry) => entry.segment === segment);\n\n if (evictIndex !== -1) {\n // Released and held again before the eviction reached the store:\n // its slice is still there, so it only takes its holds back.\n const [entry] = pendingEvict.splice(evictIndex, 1);\n entry!.holders = 1;\n entries.set(segment.key, entry!);\n for (const dep of segment.deps) {\n hold(dep);\n }\n return;\n }\n\n const entry: Entry = {\n segment,\n holders: 1,\n stops: [],\n timer: undefined,\n remaining: Infinity,\n startedAt: 0,\n expired: false,\n paused: false,\n frozen: false,\n };\n entries.set(segment.key, entry);\n\n // Dependencies first, so they are injected before their dependents.\n for (const dep of segment.deps) {\n hold(dep);\n }\n\n pendingInject.push(entry);\n };\n\n /**\n * Drops a hold. A segment left without holders goes idle, unless it was\n * only held by a segment being evicted: then it is evicted with it.\n */\n const unhold = (segment: AnySegment, cascade = false) => {\n // Only reached for segments this store holds: a release undoes exactly\n // the holds its acquire took.\n const entry = entries.get(segment.key)!;\n\n entry.holders--;\n\n if (entry.holders > 0) {\n return;\n }\n\n const timeout = segment.idleTimeout ?? idleTimeout;\n\n if (cascade || timeout === 0) {\n evict(entry);\n } else {\n becomeIdle(entry, timeout);\n }\n };\n\n const becomeIdle = (entry: Entry, timeout: number) => {\n const policy = entry.segment.whileIdle ?? whileIdle;\n\n if (policy !== 'keep-running') {\n entry.paused = true;\n entry.frozen = policy === 'freeze';\n stopEpics(entry);\n }\n\n entry.remaining = timeout > maxTimerDelay ? Infinity : timeout;\n if (!isHidden()) {\n startTimer(entry);\n }\n idleEntries.add(entry);\n watchVisibility();\n emit({ type: 'idle', key: entry.segment.key });\n\n while (idleEntries.size > maxIdle) {\n evict(idleEntries.values().next().value!);\n }\n };\n\n const startTimer = (entry: Entry) => {\n if (entry.remaining !== Infinity) {\n entry.startedAt = Date.now();\n entry.timer = setTimeout(() => {\n entry.timer = undefined;\n entry.expired = true;\n scheduleSweep();\n }, entry.remaining);\n }\n };\n\n const pauseTimers = () => {\n for (const entry of idleEntries) {\n if (entry.timer !== undefined) {\n clearTimeout(entry.timer);\n entry.timer = undefined;\n entry.remaining = Math.max(0, entry.remaining - (Date.now() - entry.startedAt));\n }\n }\n };\n\n const resumeTimers = () => {\n for (const entry of idleEntries) {\n if (entry.timer === undefined && !entry.expired) {\n startTimer(entry);\n }\n }\n };\n\n const onVisibilityChange = () => {\n if (isHidden()) {\n pauseTimers();\n } else {\n resumeTimers();\n }\n };\n\n let watchingVisibility = false;\n\n /** Listens to the page visibility only while there are idle segments. */\n const watchVisibility = () => {\n const watch = idleEntries.size > 0;\n\n if (watch !== watchingVisibility && pauseWhenHidden && typeof document !== 'undefined') {\n watchingVisibility = watch;\n if (watch) {\n document.addEventListener('visibilitychange', onVisibilityChange);\n } else {\n document.removeEventListener('visibilitychange', onVisibilityChange);\n }\n }\n };\n\n const revive = (entry: Entry) => {\n idleEntries.delete(entry);\n watchVisibility();\n clearTimeout(entry.timer);\n entry.timer = undefined;\n entry.expired = false;\n entry.frozen = false;\n\n if (entry.paused) {\n entry.paused = false;\n // Not injected yet: its epics start once it is.\n if (!pendingInject.includes(entry)) {\n startEpics(entry);\n }\n }\n\n emit({ type: 'revive', key: entry.segment.key });\n };\n\n const evict = (entry: Entry) => {\n idleEntries.delete(entry);\n watchVisibility();\n clearTimeout(entry.timer);\n entries.delete(entry.segment.key);\n\n const pendingIndex = pendingInject.indexOf(entry);\n if (pendingIndex !== -1) {\n // Released before it reached the store: nothing to evict.\n pendingInject.splice(pendingIndex, 1);\n } else {\n pendingEvict.push(entry);\n }\n\n // Dependents first, so they are evicted before their dependencies.\n for (const dep of entry.segment.deps) {\n unhold(dep, true);\n }\n };\n\n let sweepScheduled = false;\n\n /** Evicts every expired segment with a single action. */\n const sweep = () => {\n sweepScheduled = false;\n batch(() => {\n for (const entry of entries.values()) {\n if (entry.expired) {\n evict(entry);\n }\n }\n });\n };\n\n const scheduleSweep = () => {\n if (!sweepScheduled) {\n sweepScheduled = true;\n scheduleIdle(sweep);\n }\n };\n\n const sparse: SparseController = {\n acquire(...segments) {\n // Checked up front so that a refused acquire leaves no holds behind.\n const seen = new Map<string, AnySegment>();\n for (const segment of segments) {\n assertCompatible(segment, seen);\n }\n\n batch(() => {\n for (const segment of segments) {\n hold(segment);\n }\n });\n\n let released = false;\n\n return () => {\n if (released) {\n return;\n }\n released = true;\n\n batch(() => {\n for (const segment of segments) {\n unhold(segment);\n }\n });\n };\n },\n\n has(segmentOrKey) {\n const key = typeof segmentOrKey === 'string' ? segmentOrKey : segmentOrKey.key;\n return entries.has(key) && !pendingInject.includes(entries.get(key)!);\n },\n\n stats() {\n const stats: SparseStats = { active: [], idle: [] };\n for (const [key, entry] of entries) {\n if (!pendingInject.includes(entry)) {\n (entry.holders > 0 ? stats.active : stats.idle).push(key);\n }\n }\n return stats;\n },\n\n batch,\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n\n return {\n ...store,\n replaceReducer(nextReducer: AnyReducer) {\n store.replaceReducer(wrapReducer(nextReducer));\n },\n sparse,\n };\n };\n\n return enhancer as StoreEnhancer<SparseStoreExt>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/sparseEnhancer.ts"],"sourcesContent":["import type { Action, Reducer, StoreEnhancer, StoreEnhancerStoreCreator } from 'redux';\nimport type { AnySegment, SegmentEpic, WhileIdle } from './segment';\nimport { isSegmentKey } from './segment';\n\n/**\n * Actions the sparse store dispatches. Reducers outside the segments can\n * handle {@linkcode SparseActionTypes.EVICT} to drop data that refers to an\n * evicted segment.\n */\nexport const SparseActionTypes = {\n /** Segment slices with the keys in `payload.keys` were added to the state. */\n INJECT: '@@sparse/INJECT',\n /** Segment slices with the keys in `payload.keys` are removed by this action. */\n EVICT: '@@sparse/EVICT',\n} as const;\n\nexport interface SparseAction extends Action<\n (typeof SparseActionTypes)[keyof typeof SparseActionTypes]\n> {\n payload: { keys: readonly string[] };\n}\n\nexport type SparseEvent =\n | { type: 'inject'; keys: readonly string[] }\n | { type: 'evict'; keys: readonly string[] }\n /** A segment is no longer held and waits for its idle timeout. */\n | { type: 'idle'; key: string }\n /** An idle segment is held again before it was evicted. */\n | { type: 'revive'; key: string };\n\nexport interface SparseStats {\n /** Keys of present segments that something holds. */\n active: string[];\n /** Keys of present segments waiting for their idle timeout. */\n idle: string[];\n}\n\nexport interface SparseController {\n /**\n * Holds the given segments, injecting the ones that are not present yet\n * together with their dependencies. Returns a function that releases the\n * hold; calling it more than once has no further effect.\n */\n acquire(...segments: readonly AnySegment[]): () => void;\n /** Whether a segment with this key is present in the store. */\n has(segmentOrKey: AnySegment | string): boolean;\n /**\n * Runs `fn`, deferring injections and evictions it causes until it returns,\n * so that they reach the store as a single action of each kind.\n */\n batch<T>(fn: () => T): T;\n /** Listens to segments being injected, going idle, revived and evicted. */\n subscribe(listener: (event: SparseEvent) => void): () => void;\n /** The keys of the present segments, by state. */\n stats(): SparseStats;\n}\n\nexport interface SparseStoreExt {\n readonly sparse: SparseController;\n}\n\n/**\n * Starts segment epics. `redux-sparse-kit/observable` provides one for\n * redux-observable.\n */\nexport interface EpicRunner {\n /** Starts `epic` and returns a function that stops it. */\n run(epic: SegmentEpic, segment: AnySegment): () => void;\n}\n\nexport interface SparseOptions {\n /** Required as soon as a held segment has epics. */\n epicRunner?: EpicRunner;\n /**\n * How long a segment nothing holds stays present before it is evicted, in\n * milliseconds. `0` evicts it right away, `Infinity` never. Segments can\n * override it. Defaults to one minute.\n */\n idleTimeout?: number;\n /**\n * What an idle segment does until it is evicted. Segments can override it.\n * Defaults to `keep-running`.\n */\n whileIdle?: WhileIdle;\n /**\n * How many idle segments may wait for their idle timeout at once. Past it,\n * the ones idle the longest are evicted right away. Defaults to `Infinity`.\n */\n maxIdle?: number;\n /**\n * Stops the idle clock while the page is hidden, so that time spent in a\n * background tab does not count towards the idle timeout. Defaults to `true`.\n */\n pauseWhenHidden?: boolean;\n}\n\ninterface Entry {\n readonly segment: AnySegment;\n /** Holds by `acquire` callers and by present segments that depend on it. */\n holders: number;\n /** Stops the segment's running epics. */\n stops: (() => void)[];\n /** Fires once the segment has been idle for its idle timeout. */\n timer: ReturnType<typeof setTimeout> | undefined;\n /** Idle time left before it expires; `Infinity` when it never does. */\n remaining: number;\n /** When the running timer was started. */\n startedAt: number;\n /** Idle for its whole idle timeout, so evicted by the next sweep. */\n expired: boolean;\n /** Idle with its epics stopped. */\n paused: boolean;\n /** Idle with its reducer skipped. */\n frozen: boolean;\n}\n\ntype AnyReducer = Reducer<any, Action, any>;\n\nconst emptyKeys: ReadonlySet<string> = new Set();\n\nconst defaultIdleTimeout = 60_000;\n\n// Reduced only by the root reducer, to learn the keys of the static state.\nconst probeActionType = '@@sparse/PROBE';\n\nconst whileIdleValues: readonly WhileIdle[] = ['keep-running', 'pause-epics', 'freeze'];\n\n// Longer delays overflow `setTimeout` and would fire right away.\nconst maxTimerDelay = 2 ** 31 - 1;\n\nconst scheduleIdle = (callback: () => void) => {\n if (typeof requestIdleCallback === 'function') {\n requestIdleCallback(callback, { timeout: 1000 });\n } else {\n setTimeout(callback, 0);\n }\n};\n\n/**\n * A store enhancer that lets segments be injected into and evicted from a\n * running store. The root reducer passed to `createStore` keeps owning the\n * static state; segment slices live next to it under their keys.\n */\nexport function sparseEnhancer(options: SparseOptions = {}): StoreEnhancer<SparseStoreExt> {\n const {\n epicRunner,\n idleTimeout = defaultIdleTimeout,\n whileIdle = 'keep-running',\n maxIdle = Infinity,\n pauseWhenHidden = true,\n } = options;\n\n const isHidden = () =>\n pauseWhenHidden && typeof document !== 'undefined' && document.visibilityState === 'hidden';\n\n if (!(idleTimeout >= 0)) {\n throw new RangeError('The idle timeout must be a non-negative number');\n }\n\n if (!(maxIdle >= 0)) {\n throw new RangeError('maxIdle must be a non-negative number');\n }\n\n if (!whileIdleValues.includes(whileIdle)) {\n throw new RangeError(`whileIdle must be one of ${whileIdleValues.join(', ')}`);\n }\n\n const enhancer =\n (createStore: StoreEnhancerStoreCreator) =>\n (rootReducer: AnyReducer, preloadedState?: unknown) => {\n const entries = new Map<string, Entry>();\n // Idle entries, the one idle the longest first.\n const idleEntries = new Set<Entry>();\n // Keys being removed by the EVICT action currently being reduced.\n let evictingKeys: ReadonlySet<string> = emptyKeys;\n\n // Injected segments that own a slice of the state, by key: only those\n // with a reducer. They keep reducing until the action that evicts them.\n const slices = new Map<string, Entry>();\n\n // Keys of the static state: whatever the root reducer returns. Never\n // treated as segment slices, even when a segment uses the same key.\n let staticKeys: ReadonlySet<string> = emptyKeys;\n\n const learnStaticKeys = (staticState: object) => {\n let count = 0;\n for (const key in staticState) {\n count++;\n if (!staticKeys.has(key)) {\n staticKeys = new Set(Object.keys(staticState));\n return;\n }\n }\n if (count !== staticKeys.size) {\n staticKeys = new Set(Object.keys(staticState));\n }\n };\n\n /** Asks a root reducer for its keys before it reduces a real state. */\n const probeStaticKeys = (root: AnyReducer) => {\n learnStaticKeys(root(undefined, { type: probeActionType }) as object);\n };\n\n const isDynamicKey = (key: string) =>\n !staticKeys.has(key) && (slices.has(key) || evictingKeys.has(key) || isSegmentKey(key));\n\n const wrapReducer =\n (root: AnyReducer): AnyReducer =>\n (state, action) => {\n const current = (state ?? {}) as Record<string, unknown>;\n\n let staticState: Record<string, unknown> | undefined;\n let hasDynamicKeys = false;\n\n for (const key in current) {\n if (isDynamicKey(key)) {\n hasDynamicKeys = true;\n break;\n }\n }\n\n // Slices of segments that are not present yet, e.g. from a preloaded\n // state: kept so that the segments start from them.\n const pendingSlices: string[] = [];\n\n if (hasDynamicKeys) {\n staticState = {};\n for (const key in current) {\n if (!isDynamicKey(key)) {\n staticState[key] = current[key];\n } else if (!slices.has(key) && !evictingKeys.has(key)) {\n pendingSlices.push(key);\n }\n }\n }\n\n const input = state === undefined ? undefined : (staticState ?? current);\n const nextStatic = root(input, action) as Record<string, unknown>;\n\n if (nextStatic !== input) {\n learnStaticKeys(nextStatic);\n }\n\n let changed = nextStatic !== input || evictingKeys.size > 0;\n const values: unknown[] = [];\n\n for (const [key, entry] of slices) {\n const previous = current[key];\n const value =\n entry.frozen && key in current ? previous : entry.segment.reducer!(previous, action);\n values.push(value);\n if (value !== previous || !(key in current)) {\n changed = true;\n }\n }\n\n if (!changed) {\n return current;\n }\n\n const next: Record<string, unknown> = { ...nextStatic };\n\n for (const key of pendingSlices) {\n next[key] = current[key];\n }\n\n let index = 0;\n for (const key of slices.keys()) {\n next[key] = values[index++];\n }\n\n return next;\n };\n\n probeStaticKeys(rootReducer);\n const store = createStore(wrapReducer(rootReducer), preloadedState);\n\n let batchDepth = 0;\n const pendingInject: Entry[] = [];\n const pendingEvict: Entry[] = [];\n const listeners = new Set<(event: SparseEvent) => void>();\n\n const emit = (event: SparseEvent) => {\n for (const listener of listeners) {\n listener(event);\n }\n };\n\n const dispatchSparse = (type: SparseAction['type'], keys: readonly string[]) => {\n const action: SparseAction = { type, payload: { keys } };\n store.dispatch(action);\n };\n\n const flush = () => {\n if (batchDepth > 0) {\n return;\n }\n\n if (pendingInject.length > 0) {\n const injected = pendingInject.splice(0);\n const keys = injected.map((entry) => entry.segment.key);\n const sliceKeys: string[] = [];\n for (const entry of injected) {\n if (entry.segment.reducer) {\n slices.set(entry.segment.key, entry);\n sliceKeys.push(entry.segment.key);\n }\n }\n if (sliceKeys.length > 0) {\n dispatchSparse(SparseActionTypes.INJECT, sliceKeys);\n }\n // Started once their slices are in the state, deps first.\n for (const entry of injected) {\n if (!entry.paused) {\n startEpics(entry);\n }\n }\n emit({ type: 'inject', keys });\n }\n\n if (pendingEvict.length > 0) {\n const evicted = pendingEvict.splice(0);\n const keys = evicted.map((entry) => entry.segment.key);\n // Stopped while their slices are still in the state, dependents first.\n for (const entry of evicted) {\n stopEpics(entry);\n }\n const sliceKeys = keys.filter((key) => slices.delete(key));\n if (sliceKeys.length > 0) {\n evictingKeys = new Set(sliceKeys);\n try {\n dispatchSparse(SparseActionTypes.EVICT, sliceKeys);\n } finally {\n evictingKeys = emptyKeys;\n }\n }\n emit({ type: 'evict', keys });\n }\n };\n\n const startEpics = (entry: Entry) => {\n for (const epic of entry.segment.epics) {\n entry.stops.push(epicRunner!.run(epic, entry.segment));\n }\n };\n\n const stopEpics = (entry: Entry) => {\n for (const stop of entry.stops.splice(0)) {\n stop();\n }\n };\n\n const batch = <T>(fn: () => T): T => {\n batchDepth++;\n try {\n return fn();\n } finally {\n batchDepth--;\n flush();\n }\n };\n\n const assertCompatible = (segment: AnySegment, seen: Map<string, AnySegment>) => {\n const other = seen.get(segment.key) ?? entries.get(segment.key)?.segment;\n\n if (other === segment) {\n return;\n }\n\n if (other !== undefined) {\n throw new Error(`Two different segments use the key \"${segment.key}\"`);\n }\n\n if (segment.reducer && staticKeys.has(segment.key)) {\n throw new Error(`Segment \"${segment.key}\" uses the key of a slice of the static state`);\n }\n\n if (segment.epics.length > 0 && epicRunner === undefined) {\n throw new Error(\n `Segment \"${segment.key}\" has epics, but sparseEnhancer was created without an epicRunner`,\n );\n }\n\n seen.set(segment.key, segment);\n\n for (const dep of segment.deps) {\n assertCompatible(dep, seen);\n }\n };\n\n const hold = (segment: AnySegment) => {\n const existing = entries.get(segment.key);\n\n if (existing) {\n if (existing.holders === 0) {\n revive(existing);\n }\n existing.holders++;\n return;\n }\n\n const evictIndex = pendingEvict.findIndex((entry) => entry.segment === segment);\n\n if (evictIndex !== -1) {\n // Released and held again before the eviction reached the store:\n // its slice is still there, so it only takes its holds back.\n const [entry] = pendingEvict.splice(evictIndex, 1);\n entry!.holders = 1;\n entries.set(segment.key, entry!);\n for (const dep of segment.deps) {\n hold(dep);\n }\n return;\n }\n\n const entry: Entry = {\n segment,\n holders: 1,\n stops: [],\n timer: undefined,\n remaining: Infinity,\n startedAt: 0,\n expired: false,\n paused: false,\n frozen: false,\n };\n entries.set(segment.key, entry);\n\n // Dependencies first, so they are injected before their dependents.\n for (const dep of segment.deps) {\n hold(dep);\n }\n\n pendingInject.push(entry);\n };\n\n /**\n * Drops a hold. A segment left without holders goes idle, unless it was\n * only held by a segment being evicted: then it is evicted with it.\n */\n const unhold = (segment: AnySegment, cascade = false) => {\n // Only reached for segments this store holds: a release undoes exactly\n // the holds its acquire took.\n const entry = entries.get(segment.key)!;\n\n entry.holders--;\n\n if (entry.holders > 0) {\n return;\n }\n\n const timeout = segment.idleTimeout ?? idleTimeout;\n\n if (cascade || timeout === 0) {\n evict(entry);\n } else {\n becomeIdle(entry, timeout);\n }\n };\n\n const becomeIdle = (entry: Entry, timeout: number) => {\n const policy = entry.segment.whileIdle ?? whileIdle;\n\n if (policy !== 'keep-running') {\n entry.paused = true;\n entry.frozen = policy === 'freeze';\n stopEpics(entry);\n }\n\n entry.remaining = timeout > maxTimerDelay ? Infinity : timeout;\n if (!isHidden()) {\n startTimer(entry);\n }\n idleEntries.add(entry);\n watchVisibility();\n emit({ type: 'idle', key: entry.segment.key });\n\n while (idleEntries.size > maxIdle) {\n evict(idleEntries.values().next().value!);\n }\n };\n\n const startTimer = (entry: Entry) => {\n if (entry.remaining !== Infinity) {\n entry.startedAt = Date.now();\n entry.timer = setTimeout(() => {\n entry.timer = undefined;\n entry.expired = true;\n scheduleSweep();\n }, entry.remaining);\n }\n };\n\n const pauseTimers = () => {\n for (const entry of idleEntries) {\n if (entry.timer !== undefined) {\n clearTimeout(entry.timer);\n entry.timer = undefined;\n entry.remaining = Math.max(0, entry.remaining - (Date.now() - entry.startedAt));\n }\n }\n };\n\n const resumeTimers = () => {\n for (const entry of idleEntries) {\n if (entry.timer === undefined && !entry.expired) {\n startTimer(entry);\n }\n }\n };\n\n const onVisibilityChange = () => {\n if (isHidden()) {\n pauseTimers();\n } else {\n resumeTimers();\n }\n };\n\n let watchingVisibility = false;\n\n /** Listens to the page visibility only while there are idle segments. */\n const watchVisibility = () => {\n const watch = idleEntries.size > 0;\n\n if (watch !== watchingVisibility && pauseWhenHidden && typeof document !== 'undefined') {\n watchingVisibility = watch;\n if (watch) {\n document.addEventListener('visibilitychange', onVisibilityChange);\n } else {\n document.removeEventListener('visibilitychange', onVisibilityChange);\n }\n }\n };\n\n const revive = (entry: Entry) => {\n idleEntries.delete(entry);\n watchVisibility();\n clearTimeout(entry.timer);\n entry.timer = undefined;\n entry.expired = false;\n entry.frozen = false;\n\n if (entry.paused) {\n entry.paused = false;\n // Not injected yet: its epics start once it is.\n if (!pendingInject.includes(entry)) {\n startEpics(entry);\n }\n }\n\n emit({ type: 'revive', key: entry.segment.key });\n };\n\n const evict = (entry: Entry) => {\n idleEntries.delete(entry);\n watchVisibility();\n clearTimeout(entry.timer);\n entries.delete(entry.segment.key);\n\n const pendingIndex = pendingInject.indexOf(entry);\n if (pendingIndex !== -1) {\n // Released before it reached the store: nothing to evict.\n pendingInject.splice(pendingIndex, 1);\n } else {\n pendingEvict.push(entry);\n }\n\n // Dependents first, so they are evicted before their dependencies.\n for (const dep of entry.segment.deps) {\n unhold(dep, true);\n }\n };\n\n let sweepScheduled = false;\n\n /** Evicts every expired segment with a single action. */\n const sweep = () => {\n sweepScheduled = false;\n batch(() => {\n for (const entry of entries.values()) {\n if (entry.expired) {\n evict(entry);\n }\n }\n });\n };\n\n const scheduleSweep = () => {\n if (!sweepScheduled) {\n sweepScheduled = true;\n scheduleIdle(sweep);\n }\n };\n\n const sparse: SparseController = {\n acquire(...segments) {\n // Checked up front so that a refused acquire leaves no holds behind.\n const seen = new Map<string, AnySegment>();\n for (const segment of segments) {\n assertCompatible(segment, seen);\n }\n\n batch(() => {\n for (const segment of segments) {\n hold(segment);\n }\n });\n\n let released = false;\n\n return () => {\n if (released) {\n return;\n }\n released = true;\n\n batch(() => {\n for (const segment of segments) {\n unhold(segment);\n }\n });\n };\n },\n\n has(segmentOrKey) {\n const key = typeof segmentOrKey === 'string' ? segmentOrKey : segmentOrKey.key;\n return entries.has(key) && !pendingInject.includes(entries.get(key)!);\n },\n\n stats() {\n const stats: SparseStats = { active: [], idle: [] };\n for (const [key, entry] of entries) {\n if (!pendingInject.includes(entry)) {\n (entry.holders > 0 ? stats.active : stats.idle).push(key);\n }\n }\n return stats;\n },\n\n batch,\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n\n return {\n ...store,\n replaceReducer(nextReducer: AnyReducer) {\n probeStaticKeys(nextReducer);\n store.replaceReducer(wrapReducer(nextReducer));\n },\n sparse,\n };\n };\n\n return enhancer as StoreEnhancer<SparseStoreExt>;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redux-sparse-kit",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Dynamic reducers and epics for large Redux stores: only mounted features run on dispatch",
5
5
  "keywords": [
6
6
  "code-splitting",