Skip to content

Commit a213e17

Browse files
committed
Initial commit
0 parents  commit a213e17

File tree

101 files changed

+14516
-0
lines changed

Some content is hidden

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

101 files changed

+14516
-0
lines changed

.eslintrc.cjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = {
2+
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
3+
parser: "@typescript-eslint/parser",
4+
plugins: ["@typescript-eslint"],
5+
root: true,
6+
ignorePatterns: [
7+
"dist/*",
8+
"examples/*",
9+
"coverage/*",
10+
"__mocks__/*",
11+
"jest.config.js",
12+
"jest/*",
13+
],
14+
};

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
coverage
3+
dist

.vscode/extensions.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"recommendations": [
3+
"aaron-bond.better-comments",
4+
"redhat.fabric8-analytics",
5+
"dbaeumer.vscode-eslint",
6+
"orta.vscode-jest",
7+
"firsttris.vscode-jest-runner",
8+
"pmneo.tsimporter",
9+
"gamunu.vscode-yarn",
10+
"carlosjs23.vscode-yarn-script",
11+
"christian-kohler.npm-intellisense",
12+
"DavidAnson.vscode-markdownlint"
13+
]
14+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Timucin Besken
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
# React Native Async Cache
2+
3+
A simple, decorated, AsyncStorage-based and configurable cache written in Typescript.
4+
5+
# Install
6+
7+
## NPM
8+
9+
```bash
10+
$ npm install @timp4w/react-native-async-cache
11+
```
12+
13+
## Yarn
14+
15+
```bash
16+
$ yarn add @timp4w/react-native-async-cache
17+
```
18+
19+
# Setup
20+
21+
## React Native (JS)
22+
23+
```bash
24+
$ yarn add -D @babel/plugin-proposal-decorators
25+
$ yarn add reflect-metadata
26+
```
27+
28+
Then add to your `babel.config.js`
29+
30+
```javascript
31+
plugins: [['@babel/plugin-proposal-decorators', {legacy: true}]],
32+
```
33+
34+
And in the top of your `index.js` file:
35+
36+
```javascript
37+
import 'reflect-metadata';
38+
...
39+
```
40+
41+
## React Native (TS)
42+
43+
```bash
44+
$ yarn add reflect-metadata
45+
```
46+
47+
Then modify your `tsconfig.json` file to include the following:
48+
49+
```json
50+
{
51+
"compilerOptions": {
52+
"experimentalDecorators": true,
53+
"emitDecoratorMetadata": true
54+
}
55+
}
56+
```
57+
58+
And in the top of your `index.js` file:
59+
60+
```javascript
61+
import 'reflect-metadata';
62+
...
63+
```
64+
65+
# Cache Factory methods
66+
67+
In order to start you need to import `CacheFactory` in your index file
68+
69+
```typescript
70+
import { CacheFactory } from "@timp4w/react-native-async-cache";
71+
```
72+
73+
## `.setNamespace(namespace: string)`
74+
75+
Set a namespace for your this.cache.
76+
77+
The keys will be serialized with `${namespace}:${key}`
78+
79+
## `.setBackend(backend: StorageBackend) `
80+
81+
Select a backend to use for your this.cache.
82+
83+
By default a MemoryStorage is provided.
84+
85+
You can use any backend with the following signature:
86+
87+
```typescript
88+
type StorageBackend = {
89+
getItem: (key: string) => Promise<string | null>;
90+
setItem: (key: string, value: string) => Promise<void>;
91+
removeItem: (key: string) => Promise<void>;
92+
clear: () => Promise<void>;
93+
getAllKeys: () => Promise<readonly string[]>;
94+
multiRemove: (keys: string[]) => Promise<void>;
95+
};
96+
```
97+
98+
## `.attachStrategy(strategy: CacheStrategy) `
99+
100+
With this method you can attach a cach management strategy.
101+
102+
This library provides some basic strategies:
103+
104+
- LRUStrategy: When max entries are reached, remove the least recently used entry from the cache
105+
- ... more to come
106+
107+
You can also write your own strategy, by implementing the `CacheStrategy` interface (see the implementation of the LRU strategy)
108+
109+
```typescript
110+
class MyStrategy implements CacheStrategy {
111+
async onRead<T>(key: string, value: CachedItem<T>): Promise<void> {
112+
/* Do something when an item is read */
113+
}
114+
115+
onWrite<T>(_key: string, _value: CachedItem<T>): Promise<void> {
116+
/* Do something when an item is written */
117+
}
118+
119+
onEvict<T>(_key: string): Promise<void> {
120+
/* Do something when an item is evicted */
121+
}
122+
123+
onInit(_namespace: string, _keyConstructor: (key: string) => string): void {
124+
/* Do something on cache init */
125+
}
126+
}
127+
128+
export default new RemoveExpiredValueOnReadStrategy();
129+
```
130+
131+
## `.create(): void`
132+
133+
Initialize the cache
134+
135+
## `.getInstance(): Cache | undefined`
136+
137+
Returns the instance of the cache as a singleton.
138+
139+
## Example
140+
141+
```typescript
142+
const lruStrategy = new LRUStrategy();
143+
lruStrategy.setMaxEntries(5000);
144+
145+
CacheFactory.setNamespace("@MyAppCache")
146+
.setBackend(AsyncStorage)
147+
.attachStrategy(lruStrategy)
148+
.create();
149+
150+
const cache = CacheFactory.getInstance();
151+
```
152+
153+
# Decorators
154+
155+
## `@cached(key: string, ttl: number)`
156+
157+
Use this decorator above an async function to cache its value with the given TTL (in seconds).
158+
159+
## `@evictKey(key: string)`
160+
161+
Decorating a method with `evictKey` will evict the item stored with the given key.
162+
163+
You can also provide a pattern: `my-key:*` which will evict all items starting with that pattern, you **must use** this pattern if you want to evict all items where you used the `@cacheKey` decorator on the parameters.
164+
165+
In this example, calling `evictAll()` will evict all items cached for `cacheThis(...)` for all the arguments:
166+
167+
```typescript
168+
@cached('my-multi-item', 60)
169+
async cacheThis(@cacheKey a: number) {
170+
/* ... */
171+
}
172+
173+
@evictKey('my-multi-item:*')
174+
async evictAll() {
175+
/* ... */
176+
}
177+
```
178+
179+
## `@cacheKey`
180+
181+
You can decorate the parameters of your function with `@cacheKey` in order to serialize them into the key.
182+
183+
Useful if you want to cache the result given different parameters (see the note on `@evictKey()` for evicting the cached items if you use this decorator).
184+
185+
# APIs
186+
187+
You can also use the cache directly, it exposes the following APIs.
188+
189+
Just import the Cache Factory wherever you need it (after you called `.create()`) and call `.getInstance()`
190+
191+
```typescript
192+
import { CacheFactory } from "@timp4w/react-native-async-cache";
193+
194+
const cache = CacheFactory.getInstance();
195+
196+
cache?.write<string>("myKey", "myValue", 60); // Stores 'myValue' with a TTL of 60 seconds
197+
const myCachedString = cache?.read<string>("myKey"); // Read value
198+
cache?.evict("myKey"); // Remove value
199+
const cacheKeys: string[] = cache?.getAllKeys();
200+
cache?.flush(); // Flush the whole cache
201+
```
202+
203+
The methods that the strategies can hook to, have an extra `skipStrategy: boolean` flag, if you need to write / read items in the cache without causing an infinite loop in the strategy. You should ignore it and use the default value if you're not writing a strategy.
204+
205+
## Read
206+
207+
`public async read<T>(key: string, skipStrategy = false): Promise<T | null>`
208+
209+
## Write
210+
211+
`public async write<T>(key: string, value: T, ttl: number, skipStrategy = false): Promise<void>`
212+
213+
## Evict
214+
215+
`public async evict(key: string, skipStrategy = false): Promise<boolean>`
216+
217+
## Flush cache
218+
219+
`public async flush(): Promise<void>`
220+
221+
Flush all items in the cache.
222+
223+
## Get all keys
224+
225+
`public async getAllKeys(): Promise<string[] | undefined>`
226+
227+
Flush all keys stored in cache.
228+
229+
## Methods you would probably never use unless you write your strategy
230+
231+
### Create Storage Key
232+
233+
`public createStorageKey(key: string): string`
234+
235+
Returns the final storage key used by the cache.
236+
237+
### Serialize key
238+
239+
`public serializeItemKeys(key: string, additionalKeys: string[]): string`
240+
241+
Returns a serialized key.
242+
243+
### Is Expired?
244+
245+
`public isExpired(item: CachedItem<unknown>): boolean`
246+
247+
Test if an item is expired. You can use this in your custom strategy if you need to check for expiration.
248+
249+
# Example Usage
250+
251+
Your index file
252+
253+
```typescript
254+
...
255+
import 'reflect-metadata';
256+
import AsyncStorage from '@react-native-async-storage/async-storage';
257+
import {
258+
CacheFactory,
259+
LRUStrategy,
260+
} from ' @timp4w/react-native-async-cache';
261+
262+
...
263+
const lruStrategy = new LRUStrategy();
264+
lruStrategy.setMaxEntries(5000);
265+
266+
CacheFactory.setNamespace("@MyAppCache")
267+
.setBackend(AsyncStorage)
268+
.attachStrategy(lruStrategy)
269+
.create();
270+
271+
const cache = CacheFactory.getInstance();
272+
273+
```
274+
275+
In your classes
276+
277+
```typescript
278+
...
279+
import {
280+
cached,
281+
evictKey,
282+
cacheKey
283+
} from '@timp4w/react-native-async-cache';
284+
285+
class MyClass {
286+
@cached('cache-key', 1000)
287+
async cachedWork() {
288+
/* Do work */
289+
}
290+
291+
@evictKey('cache-key')
292+
async evictOtherCacheWork() {
293+
/* Do work */
294+
}
295+
296+
@cached('another-cache-key', 60)
297+
async anotherCachedWork(
298+
@cacheKey myParam1: string,
299+
@cacheKey myParam2: string
300+
) {
301+
/* Do work */
302+
}
303+
}
304+
305+
```
306+
307+
# ToDo
308+
309+
- Test performance
310+
- Apply feedback
311+
- Refactoring and renaming
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default from "@react-native-async-storage/async-storage/jest/async-storage-mock";

0 commit comments

Comments
 (0)