Skip to content

Commit 1df829e

Browse files
authored
enhance: Add garbage collection based on expiry time (#3343)
1 parent b3d23ec commit 1df829e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+975
-159
lines changed

.changeset/rare-hairs-enjoy.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@data-client/core': patch
3+
---
4+
5+
Add initManager()

.changeset/silly-hotels-shout.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@data-client/react': patch
3+
---
4+
5+
Add gcPolicy option to DataProvider and prepareStore
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@data-client/react': patch
3+
'@data-client/core': patch
4+
---
5+
6+
Add GCPolicy

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ jobs:
239239
240240
workflows:
241241
version: 2
242-
all-tests:
242+
validation:
243243
jobs:
244244
- setup
245245
- unit_tests:

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"packages/**/index.d.ts": true,
4949
"packages/*/lib": true,
5050
"packages/*/legacy": true,
51+
"packages/*/native": true,
5152
"yarn.lock": true,
5253
"**/versioned_docs": true,
5354
"**/*_versioned_docs": true,

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
"@react-navigation/native": "^7.0.0",
6767
"@react-navigation/native-stack": "^7.0.0",
6868
"@testing-library/dom": "^10.4.0",
69+
"@testing-library/jest-dom": "^6.6.3",
70+
"@testing-library/jest-native": "^5.4.3",
6971
"@testing-library/react": "16.1.0",
7072
"@testing-library/react-hooks": "8.0.1",
7173
"@testing-library/react-native": "13.0.0",

packages/core/src/actions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
Denormalize,
33
EndpointInterface,
4+
EntityPath,
45
Queryable,
56
ResolveType,
67
UnknownError,
@@ -148,7 +149,7 @@ export interface ResetAction {
148149
/* GC */
149150
export interface GCAction {
150151
type: typeof GC;
151-
entities: [string, string][];
152+
entities: EntityPath[];
152153
endpoints: string[];
153154
}
154155

packages/core/src/controller/Controller.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
} from './actions/index.js';
3636
import ensurePojo from './ensurePojo.js';
3737
import type { EndpointUpdateFunction } from './types.js';
38+
import type { GCInterface } from '../state/GCPolicy.js';
39+
import { ImmortalGCPolicy } from '../state/GCPolicy.js';
3840
import { initialState } from '../state/reducer/createReducer.js';
3941
import selectMeta from '../state/selectMeta.js';
4042
import type { ActionTypes, State } from '../types.js';
@@ -46,6 +48,7 @@ interface ConstructorProps<D extends GenericDispatch = DataClientDispatch> {
4648
dispatch?: D;
4749
getState?: () => State<unknown>;
4850
memo?: Pick<MemoCache, 'denormalize' | 'query' | 'buildQueryKey'>;
51+
gcPolicy?: GCInterface;
4952
}
5053

5154
const unsetDispatch = (action: unknown): Promise<void> => {
@@ -89,14 +92,21 @@ export default class Controller<
8992
'denormalize' | 'query' | 'buildQueryKey'
9093
>;
9194

95+
/**
96+
* Handles garbage collection
97+
*/
98+
declare readonly gcPolicy: GCInterface;
99+
92100
constructor({
93101
dispatch = unsetDispatch as any,
94102
getState = unsetState,
95103
memo = new MemoCache(),
104+
gcPolicy = new ImmortalGCPolicy(),
96105
}: ConstructorProps<D> = {}) {
97106
this.dispatch = dispatch;
98107
this.getState = getState;
99108
this.memo = memo;
109+
this.gcPolicy = gcPolicy;
100110
}
101111

102112
/*************** Action Dispatchers ***************/
@@ -389,6 +399,7 @@ export default class Controller<
389399
data: DenormalizeNullable<E['schema']>;
390400
expiryStatus: ExpiryStatus;
391401
expiresAt: number;
402+
countRef: () => () => void;
392403
};
393404

394405
getResponse<
@@ -403,6 +414,7 @@ export default class Controller<
403414
data: DenormalizeNullable<E['schema']>;
404415
expiryStatus: ExpiryStatus;
405416
expiresAt: number;
417+
countRef: () => () => void;
406418
};
407419

408420
getResponse(
@@ -412,6 +424,7 @@ export default class Controller<
412424
data: unknown;
413425
expiryStatus: ExpiryStatus;
414426
expiresAt: number;
427+
countRef: () => () => void;
415428
} {
416429
const state = rest[rest.length - 1] as State<unknown>;
417430
// this is typescript generics breaking
@@ -446,12 +459,14 @@ export default class Controller<
446459
data: input as any,
447460
expiryStatus: ExpiryStatus.Valid,
448461
expiresAt: Infinity,
462+
countRef: () => () => undefined,
449463
};
450464
}
451465

452466
let isInvalid = false;
453467
if (shouldQuery) {
454468
isInvalid = !validateQueryKey(input);
469+
// endpoint without entities
455470
} else if (!schema || !schemaHasEntity(schema)) {
456471
return {
457472
data: cacheEndpoints,
@@ -460,6 +475,7 @@ export default class Controller<
460475
: cacheEndpoints && !endpoint.invalidIfStale ? ExpiryStatus.Valid
461476
: ExpiryStatus.InvalidIfStale,
462477
expiresAt: expiresAt || 0,
478+
countRef: this.gcPolicy.createCountRef({ key }),
463479
};
464480
}
465481

@@ -477,6 +493,7 @@ export default class Controller<
477493

478494
return this.getSchemaResponse(
479495
data,
496+
key,
480497
paths,
481498
state.entityMeta,
482499
expiresAt,
@@ -507,6 +524,7 @@ export default class Controller<
507524

508525
private getSchemaResponse<T>(
509526
data: T,
527+
key: string,
510528
paths: EntityPath[],
511529
entityMeta: State<unknown>['entityMeta'],
512530
expiresAt: number,
@@ -516,6 +534,7 @@ export default class Controller<
516534
data: T;
517535
expiryStatus: ExpiryStatus;
518536
expiresAt: number;
537+
countRef: () => () => void;
519538
} {
520539
const invalidDenormalize = typeof data === 'symbol';
521540

@@ -533,7 +552,12 @@ export default class Controller<
533552
: invalidDenormalize || invalidIfStale ? ExpiryStatus.InvalidIfStale
534553
: ExpiryStatus.Valid;
535554

536-
return { data, expiryStatus, expiresAt };
555+
return {
556+
data,
557+
expiryStatus,
558+
expiresAt,
559+
countRef: this.gcPolicy.createCountRef({ key, paths }),
560+
};
537561
}
538562
}
539563

packages/core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ export {
2929
default as NetworkManager,
3030
ResetError,
3131
} from './manager/NetworkManager.js';
32+
export * from './state/GCPolicy.js';
3233
export {
3334
default as createReducer,
3435
initialState,
3536
} from './state/reducer/createReducer.js';
3637
export { default as applyManager } from './manager/applyManager.js';
38+
export { default as initManager } from './manager/initManager.js';
3739

3840
export { default as Controller } from './controller/Controller.js';
3941
export type {

packages/core/src/manager/SubscriptionManager.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { SUBSCRIBE, UNSUBSCRIBE } from '../actionTypes.js';
22
import Controller from '../controller/Controller.js';
33
import type {
44
Manager,
5-
MiddlewareAPI,
65
Middleware,
76
UnsubscribeAction,
87
SubscribeAction,

0 commit comments

Comments
 (0)