cache-entanglement
Version:
Manage caches that are dependent on each other efficiently.
243 lines (177 loc) ⢠6.98 kB
Markdown
# cache-entanglement
[](https://www.jsdelivr.com/package/npm/cache-entanglement)

Efficiently manage interconnected caches with automatic dependency tracking and updates.
## ⨠Features
* Declare dependencies between caches
* Automatic invalidation and updates
* Sync and async cache creation functions
* Key-based namespace resolution
* Cache capacity and LRU (Least Recently Used) management
## š Quick Start
```typescript
import { CacheEntanglementSync } from 'cache-entanglement'
const name = new CacheEntanglementSync((key, state, value: string) => value)
const age = new CacheEntanglementSync((key, state, value: number) => value)
const user = new CacheEntanglementSync((key, state) => {
const { name, age } = state
return { name: name.raw, age: age.raw }
}, {
dependencies: { name, age },
})
name.cache('john', 'John')
age.cache('john', 20)
user.cache('john/user')
user.get('john/user').raw // { name: 'John', age: 20 }
age.update('john', 21)
user.get('john/user').raw // { name: 'John', age: 21 }
```
## š¦ Installation
### Node.js
```bash
npm i cache-entanglement
```
```typescript
// CommonJS
const { CacheEntanglementSync, CacheEntanglementAsync } = require('cache-entanglement')
// ESM
import { CacheEntanglementSync, CacheEntanglementAsync } from 'cache-entanglement'
```
### Browser (ESM)
```typescript
import { CacheEntanglementSync, CacheEntanglementAsync } from 'https://cdn.jsdelivr.net/npm/cache-entanglement@1.x.x/+esm'
```
## š How It Works
### Key Naming Convention for Dependencies
Keys must reflect their dependency path. For example:
```typescript
const company = new CacheEntanglementSync((key, state, name: string) => name)
const employee = new CacheEntanglementSync((key, { company }, name: string) => {
return { name, companyName: company.raw }
}, {
dependencies: { company },
})
company.cache('github', 'GitHub')
employee.cache('github/john', 'John')
```
By naming the employee key as `github/john`, it indicates dependency on the `github` key from the `company` cache. Updates to `company:github` automatically propagate.
You can continue chaining dependencies:
```typescript
const card = new CacheEntanglementSync((key, { employee }, tel: string) => ({
...employee.clone(),
tel,
}), {
dependencies: { employee },
})
card.cache('github/john/card', 'xxx-xxxx-xxxx')
```
### Async Cache Example
```typescript
class FileManager {
constructor() {
this.content = new CacheEntanglementAsync(async (key, state, path: string) => {
return await fs.readFile(path)
})
}
async getContent(path: string) {
return await this.content.cache(`key:${path}`, path)
}
}
```
## š Handling Dependencies
```typescript
const articleComments = new CacheEntanglementSync((key, state, comments: string[]) => comments)
const articleContent = new CacheEntanglementSync((key, state, content: string) => content)
const article = new CacheEntanglementSync((key, state) => {
return {
articleComments: state.articleComments.raw,
articleContent: state.articleContent.raw,
}
}, {
dependencies: { articleComments, articleContent },
})
function postArticle(content: string) {
const id = uuid()
articleComments.cache(id, [])
articleContent.cache(id, content)
article.cache(id)
}
function addComment(id: string, comment: string) {
if (!articleComments.exists(id)) throw new Error(`Missing article: ${id}`)
const comments = articleComments.get(id).clone('array-shallow-copy')
comments.push(comment)
articleComments.update(id, comments)
}
```
## š§ Cache Capacity & LRU (Least Recently Used)
Instances manage cache values using a **Least Recently Used (LRU)** strategy. If the number of cached items exceeds the `capacity`, the oldest accessed entries are automatically removed.
```typescript
const cache = new CacheEntanglementSync((key, state, value: string) => value, {
capacity: 500 // Limit to 500 entries (Default: 100)
})
cache.cache('my-key', 'hello')
```
This approach ensures stable memory usage and prevents the "infinite growth" problem, providing a better runtime experience than time-based expiration.
## š Migration Guide (lifespan ā capacity)
The `lifespan` option has been removed to improve reliability and prevent unexpected process hangs caused by `setTimeout`.
### Before
```typescript
const cache = new CacheEntanglementSync(getter, { lifespan: '5m' })
```
### After
Use the `capacity` option to limit memory usage based on the number of entries.
```typescript
const cache = new CacheEntanglementSync(getter, { capacity: 100 })
```
> [!IMPORTANT]
> Since entries no longer "expire" based on time, they will remain in memory until the `capacity` is reached or they are manually deleted.
## šŖ beforeUpdateHook
A hook you can use to pre-assign dependencies from within the parent:
```typescript
const user = new CacheEntanglementSync((key, state, _name, _age) => {
return {
name: state.name.clone(),
age: state.age.clone(),
}
}, {
dependencies: { name, age },
beforeUpdateHook: (key, dependencyKey, _name, _age) => {
name.cache(key, _name)
age.cache(key, _age)
}
})
user.cache('john', 'John', 20)
```
ā ļø Avoid using `.update()` within `beforeUpdateHook` to prevent recursion.
## š§© Utility Classes
The package also exports utility classes that are used internally but can be useful for general purposes.
### `LRUMap`
A Map-like data structure that implements the Least Recently Used (LRU) eviction policy. Once the capacity is reached, the least recently accessed item is removed.
```typescript
import { LRUMap } from 'cache-entanglement'
const cache = new LRUMap<string, string>(100) // capacity: 100
cache.set('key', 'value')
console.log(cache.get('key')) // 'value'
```
### `InvertedWeakMap`
A Map that holds weak references to its **values** rather than its keys. This is useful when you want to cache objects but allow them to be garbage collected if there are no other references to them.
```typescript
import { InvertedWeakMap } from 'cache-entanglement'
const map = new InvertedWeakMap<string, object>()
let obj: object | null = { data: 'hello' }
map.set('obj1', obj)
console.log(map.get('obj1')) // { data: 'hello' }
obj = null // Now the object can be garbage collected
// After GC, map.get('obj1') will return undefined
```
## š§© TypeScript Usage
```typescript
import { CacheEntanglementSync } from 'cache-entanglement'
class MyClass {
private readonly _myCache = new CacheEntanglementSync((key, state) => {
// your logic
})
}
```
## License
MIT