@expo/entity-secondary-cache-local-memory 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/build/LocalMemorySecondaryEntityCache.d.ts +12 -0
- package/build/LocalMemorySecondaryEntityCache.js +18 -0
- package/build/LocalMemorySecondaryEntityCache.js.map +1 -0
- package/build/__tests__/LocalMemorySecondaryEntityCache-test.d.ts +16 -0
- package/build/__tests__/LocalMemorySecondaryEntityCache-test.js +124 -0
- package/build/__tests__/LocalMemorySecondaryEntityCache-test.js.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +14 -0
- package/build/index.js.map +1 -0
- package/package.json +38 -0
- package/src/LocalMemorySecondaryEntityCache.ts +21 -0
- package/src/__tests__/LocalMemorySecondaryEntityCache-test.ts +228 -0
- package/src/index.ts +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# `@expo/entity-secondary-cache-local-memory`
|
|
2
|
+
|
|
3
|
+
Cross-request [LRU](https://github.com/isaacs/node-lru-cache) secondary cache for `@expo/entity`. Use
|
|
4
|
+
this cache with caution - it is nonstandard. The cache is shared between requests in the node process.
|
|
5
|
+
|
|
6
|
+
[Documentation](https://expo.github.io/entity/modules/_expo_secondary_cache_local_memory.html)
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
1. Create a concrete implementation of abstract class `EntitySecondaryCacheLoader`, in this example `TestEntitySecondaryCacheLoader`. The underlying data can come from anywhere, but an entity is constructed from the data and then authorized for the viewer.
|
|
11
|
+
2. Create an instance of your `EntitySecondaryCacheLoader`, passing in a `LocalMemorySecondaryEntityCache`.
|
|
12
|
+
```typescript
|
|
13
|
+
const secondaryCacheLoader = new TestSecondaryLocalMemoryCacheLoader(
|
|
14
|
+
new LocalMemorySecondaryEntityCache(
|
|
15
|
+
GenericLocalMemoryCacher.createLRUCache<LocalMemoryTestEntityFields>({})
|
|
16
|
+
),
|
|
17
|
+
LocalMemoryTestEntity.loader(viewerContext)
|
|
18
|
+
);
|
|
19
|
+
```
|
|
20
|
+
3. Load entities through it:
|
|
21
|
+
```typescript
|
|
22
|
+
const loadParams = { id: createdEntity.getID() };
|
|
23
|
+
const results = await secondaryCacheLoader.loadManyAsync([loadParams]);
|
|
24
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GenericSecondaryEntityCache } from '@expo/entity';
|
|
2
|
+
import { LocalMemoryCache } from '@expo/entity-cache-adapter-local-memory';
|
|
3
|
+
/**
|
|
4
|
+
* A local memory {@link GenericSecondaryEntityCache}.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
*
|
|
8
|
+
* TLoadParams must be JSON stringifyable.
|
|
9
|
+
*/
|
|
10
|
+
export default class LocalMemorySecondaryEntityCache<TFields, TLoadParams> extends GenericSecondaryEntityCache<TFields, TLoadParams> {
|
|
11
|
+
constructor(localMemoryCache: LocalMemoryCache<TFields>);
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const entity_1 = require("@expo/entity");
|
|
4
|
+
const entity_cache_adapter_local_memory_1 = require("@expo/entity-cache-adapter-local-memory");
|
|
5
|
+
/**
|
|
6
|
+
* A local memory {@link GenericSecondaryEntityCache}.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
*
|
|
10
|
+
* TLoadParams must be JSON stringifyable.
|
|
11
|
+
*/
|
|
12
|
+
class LocalMemorySecondaryEntityCache extends entity_1.GenericSecondaryEntityCache {
|
|
13
|
+
constructor(localMemoryCache) {
|
|
14
|
+
super(new entity_cache_adapter_local_memory_1.GenericLocalMemoryCacher(localMemoryCache), (params) => JSON.stringify(params));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.default = LocalMemorySecondaryEntityCache;
|
|
18
|
+
//# sourceMappingURL=LocalMemorySecondaryEntityCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalMemorySecondaryEntityCache.js","sourceRoot":"","sources":["../src/LocalMemorySecondaryEntityCache.ts"],"names":[],"mappings":";;AAAA,yCAA2D;AAC3D,+FAGiD;AAEjD;;;;;;GAMG;AACH,MAAqB,+BAGnB,SAAQ,oCAAiD;IACzD,YAAY,gBAA2C;QACrD,KAAK,CAAC,IAAI,4DAAwB,CAAC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5F,CAAC;CACF;AAPD,kDAOC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ViewerContext, IEntityMetricsAdapter, EntityCompanionProvider, AlwaysAllowPrivacyPolicyRule, EntityPrivacyPolicy, EntityConfiguration, EntityCompanionDefinition, Entity } from '@expo/entity';
|
|
2
|
+
export declare type LocalMemoryTestEntityFields = {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
};
|
|
6
|
+
export default class LocalMemoryTestEntity extends Entity<LocalMemoryTestEntityFields, string, ViewerContext> {
|
|
7
|
+
static getCompanionDefinition(): EntityCompanionDefinition<LocalMemoryTestEntityFields, string, ViewerContext, LocalMemoryTestEntity, LocalMemoryTestEntityPrivacyPolicy>;
|
|
8
|
+
}
|
|
9
|
+
export declare class LocalMemoryTestEntityPrivacyPolicy extends EntityPrivacyPolicy<LocalMemoryTestEntityFields, string, ViewerContext, LocalMemoryTestEntity> {
|
|
10
|
+
protected readonly createRules: AlwaysAllowPrivacyPolicyRule<LocalMemoryTestEntityFields, string, ViewerContext, LocalMemoryTestEntity, keyof LocalMemoryTestEntityFields>[];
|
|
11
|
+
protected readonly readRules: AlwaysAllowPrivacyPolicyRule<LocalMemoryTestEntityFields, string, ViewerContext, LocalMemoryTestEntity, keyof LocalMemoryTestEntityFields>[];
|
|
12
|
+
protected readonly updateRules: AlwaysAllowPrivacyPolicyRule<LocalMemoryTestEntityFields, string, ViewerContext, LocalMemoryTestEntity, keyof LocalMemoryTestEntityFields>[];
|
|
13
|
+
protected readonly deleteRules: AlwaysAllowPrivacyPolicyRule<LocalMemoryTestEntityFields, string, ViewerContext, LocalMemoryTestEntity, keyof LocalMemoryTestEntityFields>[];
|
|
14
|
+
}
|
|
15
|
+
export declare const localMemoryTestEntityConfiguration: EntityConfiguration<LocalMemoryTestEntityFields>;
|
|
16
|
+
export declare const createTestEntityCompanionProvider: (metricsAdapter?: IEntityMetricsAdapter) => EntityCompanionProvider;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createTestEntityCompanionProvider = exports.localMemoryTestEntityConfiguration = exports.LocalMemoryTestEntityPrivacyPolicy = void 0;
|
|
7
|
+
const entity_1 = require("@expo/entity");
|
|
8
|
+
const entity_cache_adapter_local_memory_1 = require("@expo/entity-cache-adapter-local-memory");
|
|
9
|
+
const nullthrows_1 = __importDefault(require("nullthrows"));
|
|
10
|
+
const LocalMemorySecondaryEntityCache_1 = __importDefault(require("../LocalMemorySecondaryEntityCache"));
|
|
11
|
+
class LocalMemoryTestEntity extends entity_1.Entity {
|
|
12
|
+
static getCompanionDefinition() {
|
|
13
|
+
return localMemoryTestEntityCompanionDefinition;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.default = LocalMemoryTestEntity;
|
|
17
|
+
class LocalMemoryTestEntityPrivacyPolicy extends entity_1.EntityPrivacyPolicy {
|
|
18
|
+
constructor() {
|
|
19
|
+
super(...arguments);
|
|
20
|
+
this.createRules = [
|
|
21
|
+
new entity_1.AlwaysAllowPrivacyPolicyRule(),
|
|
22
|
+
];
|
|
23
|
+
this.readRules = [
|
|
24
|
+
new entity_1.AlwaysAllowPrivacyPolicyRule(),
|
|
25
|
+
];
|
|
26
|
+
this.updateRules = [
|
|
27
|
+
new entity_1.AlwaysAllowPrivacyPolicyRule(),
|
|
28
|
+
];
|
|
29
|
+
this.deleteRules = [
|
|
30
|
+
new entity_1.AlwaysAllowPrivacyPolicyRule(),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.LocalMemoryTestEntityPrivacyPolicy = LocalMemoryTestEntityPrivacyPolicy;
|
|
35
|
+
exports.localMemoryTestEntityConfiguration = new entity_1.EntityConfiguration({
|
|
36
|
+
idField: 'id',
|
|
37
|
+
tableName: 'local_memory_test_entities',
|
|
38
|
+
schema: {
|
|
39
|
+
id: new entity_1.UUIDField({
|
|
40
|
+
columnName: 'id',
|
|
41
|
+
}),
|
|
42
|
+
name: new entity_1.StringField({
|
|
43
|
+
columnName: 'name',
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
databaseAdapterFlavor: 'postgres',
|
|
47
|
+
cacheAdapterFlavor: 'local-memory',
|
|
48
|
+
});
|
|
49
|
+
const localMemoryTestEntityCompanionDefinition = new entity_1.EntityCompanionDefinition({
|
|
50
|
+
entityClass: LocalMemoryTestEntity,
|
|
51
|
+
entityConfiguration: exports.localMemoryTestEntityConfiguration,
|
|
52
|
+
privacyPolicyClass: LocalMemoryTestEntityPrivacyPolicy,
|
|
53
|
+
});
|
|
54
|
+
const createTestEntityCompanionProvider = (metricsAdapter = new entity_1.NoOpEntityMetricsAdapter()) => {
|
|
55
|
+
return new entity_1.EntityCompanionProvider(metricsAdapter, new Map([
|
|
56
|
+
[
|
|
57
|
+
'postgres',
|
|
58
|
+
{
|
|
59
|
+
adapterProvider: new entity_1.StubDatabaseAdapterProvider(),
|
|
60
|
+
queryContextProvider: entity_1.StubQueryContextProvider,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
]), new Map([
|
|
64
|
+
[
|
|
65
|
+
'local-memory',
|
|
66
|
+
{
|
|
67
|
+
cacheAdapterProvider: entity_cache_adapter_local_memory_1.LocalMemoryCacheAdapterProvider.getProvider(),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
]));
|
|
71
|
+
};
|
|
72
|
+
exports.createTestEntityCompanionProvider = createTestEntityCompanionProvider;
|
|
73
|
+
class TestViewerContext extends entity_1.ViewerContext {
|
|
74
|
+
}
|
|
75
|
+
const FAKE_ID = 'fake';
|
|
76
|
+
class TestSecondaryLocalMemoryCacheLoader extends entity_1.EntitySecondaryCacheLoader {
|
|
77
|
+
constructor() {
|
|
78
|
+
super(...arguments);
|
|
79
|
+
this.databaseLoadCount = 0;
|
|
80
|
+
}
|
|
81
|
+
async fetchObjectsFromDatabaseAsync(loadParamsArray) {
|
|
82
|
+
this.databaseLoadCount += loadParamsArray.length;
|
|
83
|
+
const emptyMap = new Map(loadParamsArray.map((p) => [p, null]));
|
|
84
|
+
return await (0, entity_1.mapMapAsync)(emptyMap, async (_value, loadParams) => {
|
|
85
|
+
if (loadParams.id === FAKE_ID) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return (0, nullthrows_1.default)((await this.entityLoader
|
|
89
|
+
.enforcing()
|
|
90
|
+
.loadManyByFieldEqualityConjunctionAsync([
|
|
91
|
+
{ fieldName: 'id', fieldValue: loadParams.id },
|
|
92
|
+
]))[0]).getAllFields();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
describe(LocalMemorySecondaryEntityCache_1.default, () => {
|
|
97
|
+
it('Loads through secondary loader, caches, and invalidates', async () => {
|
|
98
|
+
const viewerContext = new TestViewerContext((0, exports.createTestEntityCompanionProvider)());
|
|
99
|
+
const createdEntity = await LocalMemoryTestEntity.creator(viewerContext)
|
|
100
|
+
.setField('name', 'wat')
|
|
101
|
+
.enforceCreateAsync();
|
|
102
|
+
const secondaryCacheLoader = new TestSecondaryLocalMemoryCacheLoader(new LocalMemorySecondaryEntityCache_1.default(entity_cache_adapter_local_memory_1.GenericLocalMemoryCacher.createLRUCache({})), LocalMemoryTestEntity.loader(viewerContext));
|
|
103
|
+
const loadParams = { id: createdEntity.getID() };
|
|
104
|
+
const results = await secondaryCacheLoader.loadManyAsync([loadParams]);
|
|
105
|
+
expect((0, nullthrows_1.default)(results.get(loadParams)).enforceValue().getID()).toEqual(createdEntity.getID());
|
|
106
|
+
expect(secondaryCacheLoader.databaseLoadCount).toEqual(1);
|
|
107
|
+
const results2 = await secondaryCacheLoader.loadManyAsync([loadParams]);
|
|
108
|
+
expect((0, nullthrows_1.default)(results2.get(loadParams)).enforceValue().getID()).toEqual(createdEntity.getID());
|
|
109
|
+
expect(secondaryCacheLoader.databaseLoadCount).toEqual(1);
|
|
110
|
+
await secondaryCacheLoader.invalidateManyAsync([loadParams]);
|
|
111
|
+
const results3 = await secondaryCacheLoader.loadManyAsync([loadParams]);
|
|
112
|
+
expect((0, nullthrows_1.default)(results3.get(loadParams)).enforceValue().getID()).toEqual(createdEntity.getID());
|
|
113
|
+
expect(secondaryCacheLoader.databaseLoadCount).toEqual(2);
|
|
114
|
+
});
|
|
115
|
+
it('correctly handles uncached and unfetchable load params', async () => {
|
|
116
|
+
const viewerContext = new TestViewerContext((0, exports.createTestEntityCompanionProvider)());
|
|
117
|
+
const secondaryCacheLoader = new TestSecondaryLocalMemoryCacheLoader(new LocalMemorySecondaryEntityCache_1.default(entity_cache_adapter_local_memory_1.GenericLocalMemoryCacher.createLRUCache({})), LocalMemoryTestEntity.loader(viewerContext));
|
|
118
|
+
const loadParams = { id: FAKE_ID };
|
|
119
|
+
const results = await secondaryCacheLoader.loadManyAsync([loadParams]);
|
|
120
|
+
expect(results.size).toBe(1);
|
|
121
|
+
expect(results.get(loadParams)).toBe(null);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=LocalMemorySecondaryEntityCache-test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalMemorySecondaryEntityCache-test.js","sourceRoot":"","sources":["../../src/__tests__/LocalMemorySecondaryEntityCache-test.ts"],"names":[],"mappings":";;;;;;AAAA,yCAgBsB;AACtB,+FAGiD;AACjD,4DAAoC;AAEpC,yGAAiF;AAOjF,MAAqB,qBAAsB,SAAQ,eAIlD;IACC,MAAM,CAAC,sBAAsB;QAO3B,OAAO,wCAAwC,CAAC;IAClD,CAAC;CACF;AAdD,wCAcC;AAED,MAAa,kCAAmC,SAAQ,4BAKvD;IALD;;QAM8B,gBAAW,GAAG;YACxC,IAAI,qCAA4B,EAK7B;SACJ,CAAC;QAC0B,cAAS,GAAG;YACtC,IAAI,qCAA4B,EAK7B;SACJ,CAAC;QAC0B,gBAAW,GAAG;YACxC,IAAI,qCAA4B,EAK7B;SACJ,CAAC;QAC0B,gBAAW,GAAG;YACxC,IAAI,qCAA4B,EAK7B;SACJ,CAAC;IACJ,CAAC;CAAA;AAtCD,gFAsCC;AAEY,QAAA,kCAAkC,GAC7C,IAAI,4BAAmB,CAA8B;IACnD,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,4BAA4B;IACvC,MAAM,EAAE;QACN,EAAE,EAAE,IAAI,kBAAS,CAAC;YAChB,UAAU,EAAE,IAAI;SACjB,CAAC;QACF,IAAI,EAAE,IAAI,oBAAW,CAAC;YACpB,UAAU,EAAE,MAAM;SACnB,CAAC;KACH;IACD,qBAAqB,EAAE,UAAU;IACjC,kBAAkB,EAAE,cAAc;CACnC,CAAC,CAAC;AAEL,MAAM,wCAAwC,GAAG,IAAI,kCAAyB,CAAC;IAC7E,WAAW,EAAE,qBAAqB;IAClC,mBAAmB,EAAE,0CAAkC;IACvD,kBAAkB,EAAE,kCAAkC;CACvD,CAAC,CAAC;AAEI,MAAM,iCAAiC,GAAG,CAC/C,iBAAwC,IAAI,iCAAwB,EAAE,EAC7C,EAAE;IAC3B,OAAO,IAAI,gCAAuB,CAChC,cAAc,EACd,IAAI,GAAG,CAAC;QACN;YACE,UAAU;YACV;gBACE,eAAe,EAAE,IAAI,oCAA2B,EAAE;gBAClD,oBAAoB,EAAE,iCAAwB;aAC/C;SACF;KACF,CAAC,EACF,IAAI,GAAG,CAAC;QACN;YACE,cAAc;YACd;gBACE,oBAAoB,EAAE,mEAA+B,CAAC,WAAW,EAAE;aACpE;SACF;KACF,CAAC,CACH,CAAC;AACJ,CAAC,CAAC;AAvBW,QAAA,iCAAiC,qCAuB5C;AAEF,MAAM,iBAAkB,SAAQ,sBAAa;CAAG;AAIhD,MAAM,OAAO,GAAG,MAAM,CAAC;AAEvB,MAAM,mCAAoC,SAAQ,mCAOjD;IAPD;;QAQS,sBAAiB,GAAG,CAAC,CAAC;IAuB/B,CAAC;IArBW,KAAK,CAAC,6BAA6B,CAC3C,eAAoD;QAEpD,IAAI,CAAC,iBAAiB,IAAI,eAAe,CAAC,MAAM,CAAC;QAEjD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAChE,OAAO,MAAM,IAAA,oBAAW,EAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE;YAC9D,IAAI,UAAU,CAAC,EAAE,KAAK,OAAO,EAAE;gBAC7B,OAAO,IAAI,CAAC;aACb;YACD,OAAO,IAAA,oBAAU,EACf,CACE,MAAM,IAAI,CAAC,YAAY;iBACpB,SAAS,EAAE;iBACX,uCAAuC,CAAC;gBACvC,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE;aAC/C,CAAC,CACL,CAAC,CAAC,CAAC,CACL,CAAC,YAAY,EAAE,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,QAAQ,CAAC,yCAA+B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,aAAa,GAAG,IAAI,iBAAiB,CAAC,IAAA,yCAAiC,GAAE,CAAC,CAAC;QAEjF,MAAM,aAAa,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,aAAa,CAAC;aACrE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;aACvB,kBAAkB,EAAE,CAAC;QAExB,MAAM,oBAAoB,GAAG,IAAI,mCAAmC,CAClE,IAAI,yCAA+B,CACjC,4DAAwB,CAAC,cAAc,CAA8B,EAAE,CAAC,CACzE,EACD,qBAAqB,CAAC,MAAM,CAAC,aAAa,CAAC,CAC5C,CAAC;QAEF,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,aAAa,CAAC,KAAK,EAAE,EAAE,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,aAAa,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,IAAA,oBAAU,EAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CACxE,aAAa,CAAC,KAAK,EAAE,CACtB,CAAC;QAEF,MAAM,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAE1D,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,aAAa,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QACxE,MAAM,CAAC,IAAA,oBAAU,EAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CACzE,aAAa,CAAC,KAAK,EAAE,CACtB,CAAC;QAEF,MAAM,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAE1D,MAAM,oBAAoB,CAAC,mBAAmB,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QAE7D,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,aAAa,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QACxE,MAAM,CAAC,IAAA,oBAAU,EAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CACzE,aAAa,CAAC,KAAK,EAAE,CACtB,CAAC;QAEF,MAAM,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,aAAa,GAAG,IAAI,iBAAiB,CAAC,IAAA,yCAAiC,GAAE,CAAC,CAAC;QAEjF,MAAM,oBAAoB,GAAG,IAAI,mCAAmC,CAClE,IAAI,yCAA+B,CACjC,4DAAwB,CAAC,cAAc,CAA8B,EAAE,CAAC,CACzE,EACD,qBAAqB,CAAC,MAAM,CAAC,aAAa,CAAC,CAC5C,CAAC;QAEF,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,aAAa,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable tsdoc/syntax */
|
|
3
|
+
/**
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
* @module @expo/entity-secondary-cache-local-memory
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.LocalMemorySecondaryEntityCache = void 0;
|
|
12
|
+
var LocalMemorySecondaryEntityCache_1 = require("./LocalMemorySecondaryEntityCache");
|
|
13
|
+
Object.defineProperty(exports, "LocalMemorySecondaryEntityCache", { enumerable: true, get: function () { return __importDefault(LocalMemorySecondaryEntityCache_1).default; } });
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,iCAAiC;AACjC;;;GAGG;;;;;;AAEH,qFAA+F;AAAtF,mKAAA,OAAO,OAAmC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@expo/entity-secondary-cache-local-memory",
|
|
3
|
+
"version": "0.25.0",
|
|
4
|
+
"description": "Local memory secondary cache for @expo/entity",
|
|
5
|
+
"files": [
|
|
6
|
+
"build",
|
|
7
|
+
"src"
|
|
8
|
+
],
|
|
9
|
+
"main": "build/index.js",
|
|
10
|
+
"types": "build/index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"tsc": "tsc",
|
|
13
|
+
"clean": "rm -rf build coverage coverage-integration",
|
|
14
|
+
"lint": "eslint src",
|
|
15
|
+
"lint-fix": "eslint src --fix",
|
|
16
|
+
"test": "jest --rootDir . --config ../../resources/jest.config.js --passWithNoTests",
|
|
17
|
+
"integration": "../../resources/run-with-docker yarn integration-no-setup",
|
|
18
|
+
"integration-no-setup": "jest --config ../../resources/jest-integration.config.js --rootDir . --runInBand --passWithNoTests",
|
|
19
|
+
"barrelsby": "barrelsby --directory src --location top --exclude tests__ --singleQuotes --exportDefault --delete"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=12"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"entity"
|
|
26
|
+
],
|
|
27
|
+
"author": "Expo",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@expo/entity": "*",
|
|
31
|
+
"@expo/entity-cache-adapter-local-memory": "*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@expo/entity": "^0.25.0",
|
|
35
|
+
"@expo/entity-cache-adapter-local-memory": "^0.25.0",
|
|
36
|
+
"nullthrows": "^1.1.1"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { GenericSecondaryEntityCache } from '@expo/entity';
|
|
2
|
+
import {
|
|
3
|
+
GenericLocalMemoryCacher,
|
|
4
|
+
LocalMemoryCache,
|
|
5
|
+
} from '@expo/entity-cache-adapter-local-memory';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A local memory {@link GenericSecondaryEntityCache}.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
*
|
|
12
|
+
* TLoadParams must be JSON stringifyable.
|
|
13
|
+
*/
|
|
14
|
+
export default class LocalMemorySecondaryEntityCache<
|
|
15
|
+
TFields,
|
|
16
|
+
TLoadParams
|
|
17
|
+
> extends GenericSecondaryEntityCache<TFields, TLoadParams> {
|
|
18
|
+
constructor(localMemoryCache: LocalMemoryCache<TFields>) {
|
|
19
|
+
super(new GenericLocalMemoryCacher(localMemoryCache), (params) => JSON.stringify(params));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EntitySecondaryCacheLoader,
|
|
3
|
+
mapMapAsync,
|
|
4
|
+
ViewerContext,
|
|
5
|
+
NoOpEntityMetricsAdapter,
|
|
6
|
+
IEntityMetricsAdapter,
|
|
7
|
+
EntityCompanionProvider,
|
|
8
|
+
StubQueryContextProvider,
|
|
9
|
+
StubDatabaseAdapterProvider,
|
|
10
|
+
AlwaysAllowPrivacyPolicyRule,
|
|
11
|
+
EntityPrivacyPolicy,
|
|
12
|
+
UUIDField,
|
|
13
|
+
StringField,
|
|
14
|
+
EntityConfiguration,
|
|
15
|
+
EntityCompanionDefinition,
|
|
16
|
+
Entity,
|
|
17
|
+
} from '@expo/entity';
|
|
18
|
+
import {
|
|
19
|
+
GenericLocalMemoryCacher,
|
|
20
|
+
LocalMemoryCacheAdapterProvider,
|
|
21
|
+
} from '@expo/entity-cache-adapter-local-memory';
|
|
22
|
+
import nullthrows from 'nullthrows';
|
|
23
|
+
|
|
24
|
+
import LocalMemorySecondaryEntityCache from '../LocalMemorySecondaryEntityCache';
|
|
25
|
+
|
|
26
|
+
export type LocalMemoryTestEntityFields = {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default class LocalMemoryTestEntity extends Entity<
|
|
32
|
+
LocalMemoryTestEntityFields,
|
|
33
|
+
string,
|
|
34
|
+
ViewerContext
|
|
35
|
+
> {
|
|
36
|
+
static getCompanionDefinition(): EntityCompanionDefinition<
|
|
37
|
+
LocalMemoryTestEntityFields,
|
|
38
|
+
string,
|
|
39
|
+
ViewerContext,
|
|
40
|
+
LocalMemoryTestEntity,
|
|
41
|
+
LocalMemoryTestEntityPrivacyPolicy
|
|
42
|
+
> {
|
|
43
|
+
return localMemoryTestEntityCompanionDefinition;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class LocalMemoryTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
|
|
48
|
+
LocalMemoryTestEntityFields,
|
|
49
|
+
string,
|
|
50
|
+
ViewerContext,
|
|
51
|
+
LocalMemoryTestEntity
|
|
52
|
+
> {
|
|
53
|
+
protected override readonly createRules = [
|
|
54
|
+
new AlwaysAllowPrivacyPolicyRule<
|
|
55
|
+
LocalMemoryTestEntityFields,
|
|
56
|
+
string,
|
|
57
|
+
ViewerContext,
|
|
58
|
+
LocalMemoryTestEntity
|
|
59
|
+
>(),
|
|
60
|
+
];
|
|
61
|
+
protected override readonly readRules = [
|
|
62
|
+
new AlwaysAllowPrivacyPolicyRule<
|
|
63
|
+
LocalMemoryTestEntityFields,
|
|
64
|
+
string,
|
|
65
|
+
ViewerContext,
|
|
66
|
+
LocalMemoryTestEntity
|
|
67
|
+
>(),
|
|
68
|
+
];
|
|
69
|
+
protected override readonly updateRules = [
|
|
70
|
+
new AlwaysAllowPrivacyPolicyRule<
|
|
71
|
+
LocalMemoryTestEntityFields,
|
|
72
|
+
string,
|
|
73
|
+
ViewerContext,
|
|
74
|
+
LocalMemoryTestEntity
|
|
75
|
+
>(),
|
|
76
|
+
];
|
|
77
|
+
protected override readonly deleteRules = [
|
|
78
|
+
new AlwaysAllowPrivacyPolicyRule<
|
|
79
|
+
LocalMemoryTestEntityFields,
|
|
80
|
+
string,
|
|
81
|
+
ViewerContext,
|
|
82
|
+
LocalMemoryTestEntity
|
|
83
|
+
>(),
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const localMemoryTestEntityConfiguration =
|
|
88
|
+
new EntityConfiguration<LocalMemoryTestEntityFields>({
|
|
89
|
+
idField: 'id',
|
|
90
|
+
tableName: 'local_memory_test_entities',
|
|
91
|
+
schema: {
|
|
92
|
+
id: new UUIDField({
|
|
93
|
+
columnName: 'id',
|
|
94
|
+
}),
|
|
95
|
+
name: new StringField({
|
|
96
|
+
columnName: 'name',
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
databaseAdapterFlavor: 'postgres',
|
|
100
|
+
cacheAdapterFlavor: 'local-memory',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const localMemoryTestEntityCompanionDefinition = new EntityCompanionDefinition({
|
|
104
|
+
entityClass: LocalMemoryTestEntity,
|
|
105
|
+
entityConfiguration: localMemoryTestEntityConfiguration,
|
|
106
|
+
privacyPolicyClass: LocalMemoryTestEntityPrivacyPolicy,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export const createTestEntityCompanionProvider = (
|
|
110
|
+
metricsAdapter: IEntityMetricsAdapter = new NoOpEntityMetricsAdapter()
|
|
111
|
+
): EntityCompanionProvider => {
|
|
112
|
+
return new EntityCompanionProvider(
|
|
113
|
+
metricsAdapter,
|
|
114
|
+
new Map([
|
|
115
|
+
[
|
|
116
|
+
'postgres',
|
|
117
|
+
{
|
|
118
|
+
adapterProvider: new StubDatabaseAdapterProvider(),
|
|
119
|
+
queryContextProvider: StubQueryContextProvider,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
]),
|
|
123
|
+
new Map([
|
|
124
|
+
[
|
|
125
|
+
'local-memory',
|
|
126
|
+
{
|
|
127
|
+
cacheAdapterProvider: LocalMemoryCacheAdapterProvider.getProvider(),
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
])
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
class TestViewerContext extends ViewerContext {}
|
|
135
|
+
|
|
136
|
+
type TestLoadParams = { id: string };
|
|
137
|
+
|
|
138
|
+
const FAKE_ID = 'fake';
|
|
139
|
+
|
|
140
|
+
class TestSecondaryLocalMemoryCacheLoader extends EntitySecondaryCacheLoader<
|
|
141
|
+
TestLoadParams,
|
|
142
|
+
LocalMemoryTestEntityFields,
|
|
143
|
+
string,
|
|
144
|
+
TestViewerContext,
|
|
145
|
+
LocalMemoryTestEntity,
|
|
146
|
+
LocalMemoryTestEntityPrivacyPolicy
|
|
147
|
+
> {
|
|
148
|
+
public databaseLoadCount = 0;
|
|
149
|
+
|
|
150
|
+
protected async fetchObjectsFromDatabaseAsync(
|
|
151
|
+
loadParamsArray: readonly Readonly<TestLoadParams>[]
|
|
152
|
+
): Promise<ReadonlyMap<Readonly<TestLoadParams>, Readonly<LocalMemoryTestEntityFields> | null>> {
|
|
153
|
+
this.databaseLoadCount += loadParamsArray.length;
|
|
154
|
+
|
|
155
|
+
const emptyMap = new Map(loadParamsArray.map((p) => [p, null]));
|
|
156
|
+
return await mapMapAsync(emptyMap, async (_value, loadParams) => {
|
|
157
|
+
if (loadParams.id === FAKE_ID) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return nullthrows(
|
|
161
|
+
(
|
|
162
|
+
await this.entityLoader
|
|
163
|
+
.enforcing()
|
|
164
|
+
.loadManyByFieldEqualityConjunctionAsync([
|
|
165
|
+
{ fieldName: 'id', fieldValue: loadParams.id },
|
|
166
|
+
])
|
|
167
|
+
)[0]
|
|
168
|
+
).getAllFields();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
describe(LocalMemorySecondaryEntityCache, () => {
|
|
174
|
+
it('Loads through secondary loader, caches, and invalidates', async () => {
|
|
175
|
+
const viewerContext = new TestViewerContext(createTestEntityCompanionProvider());
|
|
176
|
+
|
|
177
|
+
const createdEntity = await LocalMemoryTestEntity.creator(viewerContext)
|
|
178
|
+
.setField('name', 'wat')
|
|
179
|
+
.enforceCreateAsync();
|
|
180
|
+
|
|
181
|
+
const secondaryCacheLoader = new TestSecondaryLocalMemoryCacheLoader(
|
|
182
|
+
new LocalMemorySecondaryEntityCache(
|
|
183
|
+
GenericLocalMemoryCacher.createLRUCache<LocalMemoryTestEntityFields>({})
|
|
184
|
+
),
|
|
185
|
+
LocalMemoryTestEntity.loader(viewerContext)
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const loadParams = { id: createdEntity.getID() };
|
|
189
|
+
const results = await secondaryCacheLoader.loadManyAsync([loadParams]);
|
|
190
|
+
expect(nullthrows(results.get(loadParams)).enforceValue().getID()).toEqual(
|
|
191
|
+
createdEntity.getID()
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(secondaryCacheLoader.databaseLoadCount).toEqual(1);
|
|
195
|
+
|
|
196
|
+
const results2 = await secondaryCacheLoader.loadManyAsync([loadParams]);
|
|
197
|
+
expect(nullthrows(results2.get(loadParams)).enforceValue().getID()).toEqual(
|
|
198
|
+
createdEntity.getID()
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(secondaryCacheLoader.databaseLoadCount).toEqual(1);
|
|
202
|
+
|
|
203
|
+
await secondaryCacheLoader.invalidateManyAsync([loadParams]);
|
|
204
|
+
|
|
205
|
+
const results3 = await secondaryCacheLoader.loadManyAsync([loadParams]);
|
|
206
|
+
expect(nullthrows(results3.get(loadParams)).enforceValue().getID()).toEqual(
|
|
207
|
+
createdEntity.getID()
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(secondaryCacheLoader.databaseLoadCount).toEqual(2);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('correctly handles uncached and unfetchable load params', async () => {
|
|
214
|
+
const viewerContext = new TestViewerContext(createTestEntityCompanionProvider());
|
|
215
|
+
|
|
216
|
+
const secondaryCacheLoader = new TestSecondaryLocalMemoryCacheLoader(
|
|
217
|
+
new LocalMemorySecondaryEntityCache(
|
|
218
|
+
GenericLocalMemoryCacher.createLRUCache<LocalMemoryTestEntityFields>({})
|
|
219
|
+
),
|
|
220
|
+
LocalMemoryTestEntity.loader(viewerContext)
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const loadParams = { id: FAKE_ID };
|
|
224
|
+
const results = await secondaryCacheLoader.loadManyAsync([loadParams]);
|
|
225
|
+
expect(results.size).toBe(1);
|
|
226
|
+
expect(results.get(loadParams)).toBe(null);
|
|
227
|
+
});
|
|
228
|
+
});
|