react-redux-cache 0.21.0 → 0.22.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.
@@ -45,7 +45,7 @@ export const defaultGetCacheKey = (params) => {
45
45
  }
46
46
 
47
47
  export const applyEntityChanges = (entities, changes, options) => {
48
- var _a, _b, _c, _d
48
+ var _a, _b, _c
49
49
  if (changes.merge && changes.entities) {
50
50
  logWarn('applyEntityChanges', 'merge and entities should not be both set')
51
51
  }
@@ -53,6 +53,7 @@ export const applyEntityChanges = (entities, changes, options) => {
53
53
  if (!merge && !replace && !remove) {
54
54
  return undefined
55
55
  }
56
+ const mutable = options.mutableCollections
56
57
  const deepEqual = options.deepComparisonEnabled ? optionalUtils.deepEqual : undefined
57
58
  let result
58
59
  const objectWithAllTypenames = Object.assign(
@@ -95,37 +96,48 @@ export const applyEntityChanges = (entities, changes, options) => {
95
96
  logWarn('applyEntityChanges', 'merge, replace and remove changes have intersections for: ' + typename)
96
97
  }
97
98
  }
98
- const oldEntities = (_d = entities[typename]) !== null && _d !== void 0 ? _d : EMPTY_OBJECT
99
+ const oldEntities = entities[typename]
99
100
  let newEntities
100
101
  entitiesToRemove === null || entitiesToRemove === void 0
101
102
  ? void 0
102
103
  : entitiesToRemove.forEach((id) => {
103
- if (oldEntities[id]) {
104
+ if (oldEntities === null || oldEntities === void 0 ? void 0 : oldEntities[id]) {
104
105
  newEntities !== null && newEntities !== void 0
105
106
  ? newEntities
106
- : (newEntities = Object.assign({}, oldEntities))
107
+ : (newEntities = mutable ? oldEntities : Object.assign({}, oldEntities))
107
108
  delete newEntities[id]
108
109
  }
109
110
  })
110
111
  if (entitiesToReplace) {
111
112
  for (const id in entitiesToReplace) {
112
113
  const newEntity = entitiesToReplace[id]
113
- if (!(deepEqual === null || deepEqual === void 0 ? void 0 : deepEqual(oldEntities[id], newEntity))) {
114
+ if (
115
+ oldEntities === undefined ||
116
+ !(deepEqual === null || deepEqual === void 0 ? void 0 : deepEqual(oldEntities[id], newEntity))
117
+ ) {
114
118
  newEntities !== null && newEntities !== void 0
115
119
  ? newEntities
116
- : (newEntities = Object.assign({}, oldEntities))
120
+ : (newEntities = mutable
121
+ ? oldEntities !== null && oldEntities !== void 0
122
+ ? oldEntities
123
+ : {}
124
+ : Object.assign({}, oldEntities))
117
125
  newEntities[id] = newEntity
118
126
  }
119
127
  }
120
128
  }
121
129
  if (entitiesToMerge) {
122
130
  for (const id in entitiesToMerge) {
123
- const oldEntity = oldEntities[id]
131
+ const oldEntity = oldEntities === null || oldEntities === void 0 ? void 0 : oldEntities[id]
124
132
  const newEntity = Object.assign(Object.assign({}, oldEntity), entitiesToMerge[id])
125
133
  if (!(deepEqual === null || deepEqual === void 0 ? void 0 : deepEqual(oldEntity, newEntity))) {
126
134
  newEntities !== null && newEntities !== void 0
127
135
  ? newEntities
128
- : (newEntities = Object.assign({}, oldEntities))
136
+ : (newEntities = mutable
137
+ ? oldEntities !== null && oldEntities !== void 0
138
+ ? oldEntities
139
+ : {}
140
+ : Object.assign({}, oldEntities))
129
141
  newEntities[id] = newEntity
130
142
  }
131
143
  }
@@ -133,7 +145,15 @@ export const applyEntityChanges = (entities, changes, options) => {
133
145
  if (!newEntities) {
134
146
  continue
135
147
  }
136
- result !== null && result !== void 0 ? result : (result = Object.assign({}, entities))
148
+ if (mutable) {
149
+ incrementChangeKey(newEntities)
150
+ if (result === undefined) {
151
+ incrementChangeKey(entities)
152
+ result = entities
153
+ }
154
+ } else {
155
+ result !== null && result !== void 0 ? result : (result = Object.assign({}, entities))
156
+ }
137
157
  result[typename] = newEntities
138
158
  }
139
159
  options.logsEnabled &&
@@ -146,8 +166,8 @@ export const applyEntityChanges = (entities, changes, options) => {
146
166
  return result
147
167
  }
148
168
 
149
- export const isEmptyObject = (o) => {
150
- for (const _ in o) {
169
+ export const isEmptyObject = (obj) => {
170
+ for (const _ in obj) {
151
171
  return false
152
172
  }
153
173
  return true
@@ -177,3 +197,11 @@ export const FetchPolicy = {
177
197
  },
178
198
  Always: () => true,
179
199
  }
200
+
201
+ export const incrementChangeKey = (mutable) => {
202
+ if (mutable._changeKey === undefined) {
203
+ mutable._changeKey = 0
204
+ } else {
205
+ mutable._changeKey += 1
206
+ }
207
+ }
@@ -2,7 +2,6 @@ import type {
2
2
  Cache,
3
3
  CacheOptions,
4
4
  CacheState,
5
- Dict,
6
5
  EntitiesMap,
7
6
  EntityChanges,
8
7
  Globals,
@@ -127,7 +126,7 @@ export declare const withTypenames: <T extends Typenames = Typenames>() => {
127
126
  }
128
127
  type: `@rrc/${N}/mergeEntityChanges`
129
128
  }
130
- /** Invalidates query states. */
129
+ /** Sets expiresAt to Date.now(). */
131
130
  invalidateQuery: {
132
131
  <K extends keyof QP & keyof QR>(
133
132
  queries: {
@@ -255,9 +254,12 @@ export declare const withTypenames: <T extends Typenames = Typenames>() => {
255
254
  typename: TN
256
255
  ) => T[TN] | undefined
257
256
  /** Selects all entities. */
258
- selectEntities: (state: unknown) => EntitiesMap<T>
257
+ selectEntities: (state: unknown) => EntitiesMap<T> & import('./types').Mutable
259
258
  /** Selects all entities of provided typename. */
260
- selectEntitiesByTypename: <TN extends keyof T>(state: unknown, typename: TN) => EntitiesMap<T>[TN]
259
+ selectEntitiesByTypename: <TN extends keyof T>(
260
+ state: unknown,
261
+ typename: TN
262
+ ) => (EntitiesMap<T> & import('./types').Mutable)[TN]
261
263
  }
262
264
  hooks: {
263
265
  /** Returns memoized object with query and mutate functions. Memoization dependency is the store. */
@@ -331,6 +333,13 @@ export declare const withTypenames: <T extends Typenames = Typenames>() => {
331
333
  ]
332
334
  /** useSelector + selectEntityById. */
333
335
  useSelectEntityById: <TN extends keyof T>(id: Key | null | undefined, typename: TN) => T[TN] | undefined
336
+ /**
337
+ * useSelector + selectEntitiesByTypename. Also subscribes to collection's change key if `mutableCollections` enabled.
338
+ * @warning Subscribing to collections should be avoided.
339
+ * */
340
+ useEntitiesByTypename: <TN extends keyof T>(
341
+ typename: TN
342
+ ) => (EntitiesMap<T> & import('./types').Mutable)[TN]
334
343
  }
335
344
  utils: {
336
345
  /** Creates client by providing the store. Can be used when the store is a singleton - to not use a useClient hook for getting the client, but import it directly. */
@@ -481,7 +490,7 @@ export declare const createCache: <N extends string, QP, QR, MP, MR>(
481
490
  }
482
491
  type: `@rrc/${N}/mergeEntityChanges`
483
492
  }
484
- /** Invalidates query states. */
493
+ /** Sets expiresAt to Date.now(). */
485
494
  invalidateQuery: {
486
495
  <K extends keyof QP & keyof QR>(
487
496
  queries: {
@@ -616,9 +625,12 @@ export declare const createCache: <N extends string, QP, QR, MP, MR>(
616
625
  typename: TN
617
626
  ) => object | undefined
618
627
  /** Selects all entities. */
619
- selectEntities: (state: unknown) => EntitiesMap<Typenames>
628
+ selectEntities: (state: unknown) => EntitiesMap<Typenames> & import('./types').Mutable
620
629
  /** Selects all entities of provided typename. */
621
- selectEntitiesByTypename: <TN extends string>(state: unknown, typename: TN) => Dict<object> | undefined
630
+ selectEntitiesByTypename: <TN extends string>(
631
+ state: unknown,
632
+ typename: TN
633
+ ) => (EntitiesMap<Typenames> & import('./types').Mutable)[TN]
622
634
  }
623
635
  hooks: {
624
636
  /** Returns memoized object with query and mutate functions. Memoization dependency is the store. */
@@ -692,6 +704,13 @@ export declare const createCache: <N extends string, QP, QR, MP, MR>(
692
704
  ]
693
705
  /** useSelector + selectEntityById. */
694
706
  useSelectEntityById: <TN extends string>(id: Key | null | undefined, typename: TN) => object | undefined
707
+ /**
708
+ * useSelector + selectEntitiesByTypename. Also subscribes to collection's change key if `mutableCollections` enabled.
709
+ * @warning Subscribing to collections should be avoided.
710
+ * */
711
+ useEntitiesByTypename: <TN extends string>(
712
+ typename: TN
713
+ ) => (EntitiesMap<Typenames> & import('./types').Mutable)[TN]
695
714
  }
696
715
  utils: {
697
716
  /** Creates client by providing the store. Can be used when the store is a singleton - to not use a useClient hook for getting the client, but import it directly. */
@@ -720,7 +739,7 @@ export declare const createCache: <N extends string, QP, QR, MP, MR>(
720
739
  * Performs additional checks for intersections if `additionalValidation` option is `true`, and prints warnings if finds any issues.
721
740
  */
722
741
  applyEntityChanges: (
723
- entities: EntitiesMap<Typenames>,
742
+ entities: EntitiesMap<Typenames> & import('./types').Mutable,
724
743
  changes: EntityChanges<Typenames>
725
744
  ) => EntitiesMap<Typenames> | undefined
726
745
  }
@@ -12,6 +12,11 @@ export type Selectors<
12
12
  export declare const createSelectors: <N extends string, T extends Typenames, QP, QR, MP, MR>(
13
13
  cache: Cache<N, T, QP, QR, MP, MR>
14
14
  ) => {
15
+ selectEntityById: <TN extends keyof T>(
16
+ state: unknown,
17
+ id: Key | null | undefined,
18
+ typename: TN
19
+ ) => T[TN] | undefined
15
20
  selectQueryState: <QK extends keyof (QP & QR)>(
16
21
  state: unknown,
17
22
  query: QK,
@@ -67,14 +72,9 @@ export declare const createSelectors: <N extends string, T extends Typenames, QP
67
72
  state: unknown,
68
73
  mutation: MK
69
74
  ) => (MK extends keyof MP & keyof MR ? MP[MK] : never) | undefined
70
- selectEntityById: <TN extends keyof T>(
71
- state: unknown,
72
- id: Key | null | undefined,
73
- typename: TN
74
- ) => T[TN] | undefined
75
- selectEntities: (state: unknown) => import('./types').EntitiesMap<T>
75
+ selectEntities: (state: unknown) => import('./types').EntitiesMap<T> & import('./types').Mutable
76
76
  selectEntitiesByTypename: <TN extends keyof T>(
77
77
  state: unknown,
78
78
  typename: TN
79
- ) => import('./types').EntitiesMap<T>[TN]
79
+ ) => (import('./types').EntitiesMap<T> & import('./types').Mutable)[TN]
80
80
  }
@@ -3,7 +3,15 @@ import type {Selectors} from './createSelectors'
3
3
 
4
4
  export type Key = string | number | symbol
5
5
 
6
- export type Dict<T> = Record<Key, T>
6
+ export type Mutable = {
7
+ /**
8
+ * Used only when mutable cache enabled. Always incremented when collection changed by reducer to allow subscribe on changes.
9
+ * Should not be used for comparing different collections as supposed to be compared only with previously saved changeKey of the same collection.
10
+ */
11
+ _changeKey?: number
12
+ }
13
+
14
+ export type Dict<T> = Record<Key, T> & Mutable
7
15
 
8
16
  export type OptionalPartial<T, K extends keyof T> = Partial<{
9
17
  [A in K]: Partial<T[A]>
@@ -95,6 +103,13 @@ export type Globals<N extends string, T extends Typenames, QP, QR, MP, MR> = {
95
103
  }
96
104
 
97
105
  export type CacheOptions = {
106
+ /**
107
+ * BETA: Optimization that makes state collections mutable.
108
+ * Subscription to mutable collecitons will work only when subscribed to both collection and its change key - collections
109
+ * still can be replaced with the new ones instead of mutating e.g. when clearing state.
110
+ * @Default false
111
+ * */
112
+ mutableCollections: boolean
98
113
  /** Enables additional validation with logging to console.warn. Recommended to enable in dev/testing mode. @Default true in dev mode. */
99
114
  additionalValidation: boolean
100
115
  /** Enables debug logs. @Default false */
@@ -121,13 +136,13 @@ export type EntityIds<T extends Typenames> = {
121
136
  }
122
137
 
123
138
  export type CacheState<T extends Typenames, QP, QR, MP, MR> = {
124
- entities: EntitiesMap<T>
139
+ entities: EntitiesMap<T> & Mutable
125
140
  queries: {
126
141
  [QK in keyof (QP | QR)]: Dict<QueryState<T, QP[QK], QR[QK]> | undefined>
127
- }
142
+ } & Mutable
128
143
  mutations: {
129
144
  [MK in keyof (MP | MR)]: MutationState<T, MP[MK], MR[MK]>
130
- }
145
+ } & Mutable
131
146
  }
132
147
 
133
148
  export type QueryInfo<
@@ -3,6 +3,7 @@ import type {
3
3
  EntitiesMap,
4
4
  EntityChanges,
5
5
  Key,
6
+ Mutable,
6
7
  QueryState,
7
8
  QueryStateComparer,
8
9
  Typenames,
@@ -31,13 +32,13 @@ export declare const noop: () => void
31
32
  export declare const defaultGetCacheKey: <P = unknown>(params: P) => Key
32
33
 
33
34
  export declare const applyEntityChanges: <T extends Typenames>(
34
- entities: EntitiesMap<T>,
35
+ entities: EntitiesMap<T> & Mutable,
35
36
  changes: EntityChanges<T>,
36
37
  options: CacheOptions
37
38
  ) => EntitiesMap<T> | undefined
38
39
 
39
40
  /** Returns true if object has no keys. */
40
- export declare const isEmptyObject: (o: object) => boolean
41
+ export declare const isEmptyObject: (obj: object) => boolean
41
42
 
42
43
  /** Returns query state comparer that compares only provided fields. Used in implementation of `selectorComparer` option. */
43
44
  export declare const createStateComparer: <T extends Typenames = Typenames, Q = unknown, P = unknown>(
@@ -57,3 +58,5 @@ export declare const FetchPolicy: {
57
58
  /** Every fetch trigger. */
58
59
  Always: () => boolean
59
60
  }
61
+
62
+ export declare const incrementChangeKey: (mutable: Mutable) => void
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "react-redux-cache",
3
3
  "author": "Alexander Danilov",
4
4
  "license": "MIT",
5
- "version": "0.21.0",
5
+ "version": "0.22.0",
6
6
  "description": "Powerful data fetching and caching library for Redux and Zustand that supports normalization.",
7
7
  "main": "./dist/cjs/index.js",
8
8
  "module": "./dist/esm/index.js",
@@ -17,8 +17,8 @@
17
17
  "scripts": {
18
18
  "example": "(cd example && yarn --production && yarn dev)",
19
19
  "clean": "rm -rf dist",
20
- "lint": "yarn eslint",
21
- "lint-fix": "yarn eslint --fix",
20
+ "lint": "yarn eslint ./src",
21
+ "lint-fix": "yarn eslint --fix ./src",
22
22
  "lint-fix-dist": "yarn eslint --quiet --fix dist/ > /dev/null 2>&1 || true",
23
23
  "build-cjs": "tsc -p tsconfig.cjs.json && rm -rf dist/cjs/testing && rm -rf dist/cjs/__tests__",
24
24
  "build-esm": "tsc -p tsconfig.esm.json > /dev/null ; rm -rf dist/esm/testing && rm -rf dist/esm/__tests__",
@@ -31,7 +31,8 @@
31
31
  "publish-rc": "npm publish --tag rc",
32
32
  "remove-rc": "npm version <same-version-without-rc>",
33
33
  "prepublishOnly": "yarn build && yarn test",
34
- "generate-docs": "node --experimental-strip-types scripts/generate-docs.ts"
34
+ "generate-docs": "node --experimental-strip-types scripts/generate-docs.ts",
35
+ "benchmark": "NODE_ENV=production node -r ts-node/register --expose-gc benchmark.ts"
35
36
  },
36
37
  "peerDependencies": {
37
38
  "fast-deep-equal": "*",
@@ -72,6 +73,7 @@
72
73
  "redux": "4.2.1",
73
74
  "redux-logger": "3.0.6",
74
75
  "ts-jest": "29.1.0",
76
+ "ts-node": "10.9.2",
75
77
  "typescript": "5.6.3"
76
78
  },
77
79
  "files": [