@gravito/stasis 3.1.0 → 3.1.1
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 +41 -80
- package/README.zh-TW.md +40 -79
- package/dist/index.cjs +7 -3
- package/dist/index.js +7 -3
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,115 +1,76 @@
|
|
|
1
1
|
# @gravito/stasis 🧊
|
|
2
2
|
|
|
3
|
-
> High-performance
|
|
3
|
+
> High-performance Cache and State Management Orbit for Gravito.
|
|
4
4
|
|
|
5
|
-
`@gravito/stasis`
|
|
5
|
+
`@gravito/stasis` wraps complex caching logic into an elegant and powerful API, ensuring your application handles high concurrency and massive datasets with ease.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
---
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **⚡ Flexible Caching (SWR)**: Stale-While-Revalidate support to serve data fast while refreshing in the background.
|
|
13
|
-
- **🚦 Integrated Rate Limiting**: Throttling mechanism built directly on top of your cache infrastructure.
|
|
14
|
-
- **🏷️ Cache Tagging**: Group related items for bulk invalidation (supported in Memory driver).
|
|
15
|
-
- **🪝 Hook System**: Lifecycle events for monitoring cache hits, misses, and writes.
|
|
9
|
+
## 📖 Quick Index
|
|
10
|
+
* [**Architecture Deep Dive**](./docs/architecture.md) — Understand the mechanics of hybrid caching and predictive engines.
|
|
11
|
+
* [**Observability & Protection**](./docs/observability.md) — How to prevent OOM and monitor cache performance.
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🌟 Core Capabilities
|
|
16
|
+
* 🚀 **Unified API**: Seamlessly switch between Memory, Redis, File, and other storage drivers.
|
|
17
|
+
* 🏗️ **Tiered Cache (Hybrid)**: Combine local Memory with distributed Redis for extreme read speeds.
|
|
18
|
+
* 🔒 **Distributed Locks**: Atomic cross-instance concurrency control.
|
|
19
|
+
* 🚦 **Rate Limiting**: Built-in traffic throttling on top of your cache infrastructure.
|
|
20
|
+
* 🧠 **Smart Pre-warming**: Access path prediction and automated pre-fetching powered by Markov Chains.
|
|
18
21
|
|
|
22
|
+
## 📦 Installation
|
|
19
23
|
```bash
|
|
20
24
|
bun add @gravito/stasis
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
## 🚀 Quick Start
|
|
24
|
-
|
|
25
|
-
### 1. Register the Orbit
|
|
27
|
+
## 🚀 5-Minute Quick Start
|
|
26
28
|
|
|
29
|
+
### 1. Configure the Orbit
|
|
27
30
|
```typescript
|
|
28
|
-
import {
|
|
31
|
+
import { defineConfig } from '@gravito/core'
|
|
29
32
|
import { OrbitStasis } from '@gravito/stasis'
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
export default defineConfig({
|
|
32
35
|
config: {
|
|
33
36
|
cache: {
|
|
34
|
-
default: '
|
|
37
|
+
default: 'tiered', // default to tiered caching
|
|
35
38
|
stores: {
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
local: { driver: 'memory', maxItems: 1000 },
|
|
40
|
+
remote: { driver: 'redis', connection: 'default' },
|
|
41
|
+
tiered: { driver: 'tiered', local: 'local', remote: 'remote' }
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
},
|
|
41
45
|
orbits: [new OrbitStasis()]
|
|
42
46
|
})
|
|
43
|
-
|
|
44
|
-
const core = await PlanetCore.boot(config)
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
### 2. Basic Caching
|
|
48
|
-
|
|
49
|
+
### 2. Basic Caching Example
|
|
49
50
|
```typescript
|
|
50
|
-
const cache = core.container.make('cache')
|
|
51
|
+
const cache = core.container.make('cache');
|
|
51
52
|
|
|
52
|
-
//
|
|
53
|
-
await cache.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const users = await cache.remember('users:all', 300, async () => {
|
|
57
|
-
return await db.users.findMany()
|
|
58
|
-
})
|
|
53
|
+
// 💡 Classic "Remember" pattern
|
|
54
|
+
const news = await cache.remember('news:today', 3600, () => {
|
|
55
|
+
return await db.news.latest();
|
|
56
|
+
});
|
|
59
57
|
```
|
|
60
58
|
|
|
61
|
-
|
|
59
|
+
---
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
const lock = cache.lock('process-invoice:123', 10)
|
|
65
|
-
|
|
66
|
-
if (await lock.get()) {
|
|
67
|
-
try {
|
|
68
|
-
// Perform critical task...
|
|
69
|
-
} finally {
|
|
70
|
-
await lock.release()
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
```
|
|
61
|
+
## 🛠️ Drivers Overview
|
|
74
62
|
|
|
75
|
-
|
|
63
|
+
| Driver Name | Tier | Best For |
|
|
64
|
+
| :--- | :--- | :--- |
|
|
65
|
+
| **Memory** | L1 | Local hotspots, LRU restricted |
|
|
66
|
+
| **Redis** | L2 | Distributed sharing, Atomic locks |
|
|
67
|
+
| **Tiered** | Hybrid | **Recommended**: Balance of speed and consistency |
|
|
68
|
+
| **Predictive**| Smart | Scenarios with clear access patterns |
|
|
69
|
+
| **File** | Persistent | Simple local persistence |
|
|
76
70
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
const limiter = cache.limiter()
|
|
81
|
-
|
|
82
|
-
if (await limiter.tooManyAttempts('login:127.0.0.1', 5)) {
|
|
83
|
-
const seconds = await limiter.availableIn('login:127.0.0.1')
|
|
84
|
-
throw new Error(`Too many attempts. Try again in ${seconds}s.`)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
await limiter.hit('login:127.0.0.1', 60) // Decay in 60s
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## 🛠️ Supported Drivers
|
|
91
|
-
|
|
92
|
-
| Driver | Best For | Features |
|
|
93
|
-
|---|---|---|
|
|
94
|
-
| **Memory** | Local dev & Small apps | Fast, Tags, LRU |
|
|
95
|
-
| **Redis** | Distributed production | Multi-node, Locks, Persistent |
|
|
96
|
-
| **File** | Simple persistence | No external deps |
|
|
97
|
-
| **Null** | Testing / Disabling cache | No-op |
|
|
98
|
-
|
|
99
|
-
## 🧩 API Reference
|
|
100
|
-
|
|
101
|
-
### `CacheManager`
|
|
102
|
-
- `cache.get(key, default?)`: Retrieve an item.
|
|
103
|
-
- `cache.put(key, value, ttl?)`: Store an item.
|
|
104
|
-
- `cache.remember(key, ttl, callback)`: Get or execute callback and store.
|
|
105
|
-
- `cache.flexible(key, ttl, stale, callback)`: Stale-While-Revalidate.
|
|
106
|
-
- `cache.increment / decrement`: Atomic numeric updates.
|
|
107
|
-
- `cache.tags(['tag1']).flush()`: Invalidate by tag.
|
|
71
|
+
---
|
|
108
72
|
|
|
109
73
|
## 🤝 Contributing
|
|
74
|
+
We welcome any optimization suggestions! Please see our [Contributing Guide](../../CONTRIBUTING.md).
|
|
110
75
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
## 📄 License
|
|
114
|
-
|
|
115
|
-
MIT © Carl Lee
|
|
76
|
+
MIT © Carl Lee
|
package/README.zh-TW.md
CHANGED
|
@@ -1,115 +1,76 @@
|
|
|
1
1
|
# @gravito/stasis 🧊
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Gravito 的高效能快取與狀態管理 Orbit。
|
|
4
4
|
|
|
5
|
-
`@gravito/stasis`
|
|
5
|
+
`@gravito/stasis` 將複雜的快取邏輯封裝成優雅且強大的 API,讓你的應用在處理高併發與海量數據時依然游刃有餘。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
---
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- **⚡ 彈性快取 (SWR)**:支援 Stale-While-Revalidate 模式,在背景更新數據的同時快速響應請求。
|
|
13
|
-
- **🚦 整合式速率限制**:直接在快取基礎設施上構建的流量節流機制。
|
|
14
|
-
- **🏷️ 快取標籤 (Tagging)**:支援將相關項目分組,以便進行批量刪除(Memory 驅動支援)。
|
|
15
|
-
- **🪝 Hook 系統**:提供生命週期事件,用於監控快取命中 (Hit)、未命中 (Miss) 與寫入。
|
|
9
|
+
## 📖 快速索引
|
|
10
|
+
* [**技術架構深探**](./docs/architecture.zh-TW.md) — 了解混合式快取與預測機制的運作原理。
|
|
11
|
+
* [**可觀察性與資源保護**](./docs/observability.zh-TW.md) — 如何防止 OOM 並監控快取效能。
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🌟 核心能力
|
|
16
|
+
* 🚀 **統一 API**:無縫切換 Memory, Redis, File 等不同存儲驅動。
|
|
17
|
+
* 🏗️ **混合快取 (Tiered)**:結合本地 Memory 與分散式 Redis,實現極限讀取速度。
|
|
18
|
+
* 🔒 **分散式鎖**:基於原子操作的跨實例並發控制。
|
|
19
|
+
* 🚦 **流量節流**:內建基於快取基礎設施的 Rate Limiting。
|
|
20
|
+
* 🧠 **智慧預熱**:基於馬可夫鏈的存取路徑預測與自動讀取。
|
|
18
21
|
|
|
22
|
+
## 📦 安裝
|
|
19
23
|
```bash
|
|
20
24
|
bun add @gravito/stasis
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
## 🚀
|
|
24
|
-
|
|
25
|
-
### 1. 註冊 Orbit
|
|
27
|
+
## 🚀 5 分鐘快速上手
|
|
26
28
|
|
|
29
|
+
### 1. 配置 Orbit
|
|
27
30
|
```typescript
|
|
28
|
-
import {
|
|
31
|
+
import { defineConfig } from '@gravito/core'
|
|
29
32
|
import { OrbitStasis } from '@gravito/stasis'
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
export default defineConfig({
|
|
32
35
|
config: {
|
|
33
36
|
cache: {
|
|
34
|
-
default: '
|
|
37
|
+
default: 'tiered', // 預設使用分層快取
|
|
35
38
|
stores: {
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
local: { driver: 'memory', maxItems: 1000 },
|
|
40
|
+
remote: { driver: 'redis', connection: 'default' },
|
|
41
|
+
tiered: { driver: 'tiered', local: 'local', remote: 'remote' }
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
},
|
|
41
45
|
orbits: [new OrbitStasis()]
|
|
42
46
|
})
|
|
43
|
-
|
|
44
|
-
const core = await PlanetCore.boot(config)
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
### 2.
|
|
48
|
-
|
|
49
|
+
### 2. 資料存取範例
|
|
49
50
|
```typescript
|
|
50
|
-
const cache = core.container.make('cache')
|
|
51
|
+
const cache = core.container.make('cache');
|
|
51
52
|
|
|
52
|
-
//
|
|
53
|
-
await cache.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const users = await cache.remember('users:all', 300, async () => {
|
|
57
|
-
return await db.users.findMany()
|
|
58
|
-
})
|
|
53
|
+
// 💡 經典的「讀取或存儲」模式
|
|
54
|
+
const news = await cache.remember('news:today', 3600, () => {
|
|
55
|
+
return await db.news.latest();
|
|
56
|
+
});
|
|
59
57
|
```
|
|
60
58
|
|
|
61
|
-
|
|
59
|
+
---
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
const lock = cache.lock('process-invoice:123', 10)
|
|
65
|
-
|
|
66
|
-
if (await lock.get()) {
|
|
67
|
-
try {
|
|
68
|
-
// 執行關鍵任務...
|
|
69
|
-
} finally {
|
|
70
|
-
await lock.release()
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
```
|
|
61
|
+
## 🛠️ 驅動程式一覽
|
|
74
62
|
|
|
75
|
-
|
|
63
|
+
| 驅動名稱 | 性質 | 適用場景 |
|
|
64
|
+
| :--- | :--- | :--- |
|
|
65
|
+
| **Memory** | L1 | 本地熱點、LRU 限制 |
|
|
66
|
+
| **Redis** | L2 | 分散式共用、原子性鎖 |
|
|
67
|
+
| **Tiered** | Hybrid | **推薦**:平衡效能與一致性 |
|
|
68
|
+
| **Predictive**| Smart | 具有明顯路徑規律的存取場景 |
|
|
69
|
+
| **File** | Persistent | 簡單的本地持久化 |
|
|
76
70
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
const limiter = cache.limiter()
|
|
81
|
-
|
|
82
|
-
if (await limiter.tooManyAttempts('login:127.0.0.1', 5)) {
|
|
83
|
-
const seconds = await limiter.availableIn('login:127.0.0.1')
|
|
84
|
-
throw new Error(`嘗試次數過多。請在 ${seconds} 秒後重試。`)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
await limiter.hit('login:127.0.0.1', 60) // 60 秒後衰減
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## 🛠️ 支援的驅動程式 (Drivers)
|
|
91
|
-
|
|
92
|
-
| 驅動名稱 | 適用場景 | 核心功能 |
|
|
93
|
-
|---|---|---|
|
|
94
|
-
| **Memory** | 本地開發與小型應用 | 極速、標籤支援、LRU |
|
|
95
|
-
| **Redis** | 分佈式生產環境 | 多節點共享、鎖定、持久化 |
|
|
96
|
-
| **File** | 簡單的持久化需求 | 無外部依賴 |
|
|
97
|
-
| **Null** | 測試或停用快取 | 不執行任何操作 |
|
|
98
|
-
|
|
99
|
-
## 🧩 API 參考
|
|
100
|
-
|
|
101
|
-
### `CacheManager`
|
|
102
|
-
- `cache.get(key, default?)`:讀取項目。
|
|
103
|
-
- `cache.put(key, value, ttl?)`:存儲項目。
|
|
104
|
-
- `cache.remember(key, ttl, callback)`:讀取或執行回調並存儲。
|
|
105
|
-
- `cache.flexible(key, ttl, stale, callback)`:Stale-While-Revalidate 模式。
|
|
106
|
-
- `cache.increment / decrement`:原子性的數值更新。
|
|
107
|
-
- `cache.tags(['tag1']).flush()`:按標籤清除快取。
|
|
71
|
+
---
|
|
108
72
|
|
|
109
73
|
## 🤝 參與貢獻
|
|
110
|
-
|
|
111
|
-
我們歡迎任何形式的貢獻!詳細資訊請參閱 [貢獻指南](../../CONTRIBUTING.md)。
|
|
112
|
-
|
|
113
|
-
## 📄 開源授權
|
|
74
|
+
我們歡迎任何優化建議!請參閱 [貢獻指南](../../CONTRIBUTING.md)。
|
|
114
75
|
|
|
115
76
|
MIT © Carl Lee
|
package/dist/index.cjs
CHANGED
|
@@ -99,7 +99,7 @@ function isExpired(expiresAt, now = Date.now()) {
|
|
|
99
99
|
if (expiresAt === void 0) {
|
|
100
100
|
return false;
|
|
101
101
|
}
|
|
102
|
-
return now
|
|
102
|
+
return now >= expiresAt;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// src/CacheRepository.ts
|
|
@@ -1460,14 +1460,18 @@ var MarkovPredictor = class {
|
|
|
1460
1460
|
minKey = k;
|
|
1461
1461
|
}
|
|
1462
1462
|
}
|
|
1463
|
-
if (minKey)
|
|
1463
|
+
if (minKey) {
|
|
1464
|
+
edges.delete(minKey);
|
|
1465
|
+
}
|
|
1464
1466
|
}
|
|
1465
1467
|
}
|
|
1466
1468
|
this.lastKey = key;
|
|
1467
1469
|
}
|
|
1468
1470
|
predict(key) {
|
|
1469
1471
|
const edges = this.transitions.get(key);
|
|
1470
|
-
if (!edges)
|
|
1472
|
+
if (!edges) {
|
|
1473
|
+
return [];
|
|
1474
|
+
}
|
|
1471
1475
|
return Array.from(edges.entries()).sort((a, b) => b[1] - a[1]).map((entry) => entry[0]);
|
|
1472
1476
|
}
|
|
1473
1477
|
reset() {
|
package/dist/index.js
CHANGED
|
@@ -43,7 +43,7 @@ function isExpired(expiresAt, now = Date.now()) {
|
|
|
43
43
|
if (expiresAt === void 0) {
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
|
-
return now
|
|
46
|
+
return now >= expiresAt;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// src/CacheRepository.ts
|
|
@@ -1404,14 +1404,18 @@ var MarkovPredictor = class {
|
|
|
1404
1404
|
minKey = k;
|
|
1405
1405
|
}
|
|
1406
1406
|
}
|
|
1407
|
-
if (minKey)
|
|
1407
|
+
if (minKey) {
|
|
1408
|
+
edges.delete(minKey);
|
|
1409
|
+
}
|
|
1408
1410
|
}
|
|
1409
1411
|
}
|
|
1410
1412
|
this.lastKey = key;
|
|
1411
1413
|
}
|
|
1412
1414
|
predict(key) {
|
|
1413
1415
|
const edges = this.transitions.get(key);
|
|
1414
|
-
if (!edges)
|
|
1416
|
+
if (!edges) {
|
|
1417
|
+
return [];
|
|
1418
|
+
}
|
|
1415
1419
|
return Array.from(edges.entries()).sort((a, b) => b[1] - a[1]).map((entry) => entry[0]);
|
|
1416
1420
|
}
|
|
1417
1421
|
reset() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravito/stasis",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"test:coverage": "bun test --timeout=10000 --coverage --coverage-reporter=lcov --coverage-dir coverage && bun run --bun scripts/check-coverage.ts",
|
|
28
28
|
"test:ci": "bun test --timeout=10000 --coverage --coverage-reporter=lcov --coverage-dir coverage && bun run --bun scripts/check-coverage.ts",
|
|
29
29
|
"typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck",
|
|
30
|
-
"test:unit": "bun test tests/ --timeout=10000",
|
|
30
|
+
"test:unit": "bun test $(find tests -name '*.test.ts' ! -name '*.integration.test.ts' 2>/dev/null | tr '\\n' ' ') --timeout=10000",
|
|
31
31
|
"test:integration": "test $(find tests -name '*.integration.test.ts' 2>/dev/null | wc -l) -gt 0 && find tests -name '*.integration.test.ts' -print0 | xargs -0 bun test --timeout=10000 || echo 'No integration tests found'"
|
|
32
32
|
},
|
|
33
33
|
"keywords": [
|
|
@@ -40,13 +40,13 @@
|
|
|
40
40
|
"author": "Carl Lee <carllee0520@gmail.com>",
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@gravito/core": "
|
|
44
|
-
"@gravito/plasma": "
|
|
43
|
+
"@gravito/core": "^1.6.1",
|
|
44
|
+
"@gravito/plasma": "^1.0.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@gravito/plasma": "
|
|
48
|
-
"@gravito/core": "
|
|
49
|
-
"@gravito/photon": "
|
|
47
|
+
"@gravito/plasma": "^1.0.0",
|
|
48
|
+
"@gravito/core": "^1.6.1",
|
|
49
|
+
"@gravito/photon": "^1.0.1",
|
|
50
50
|
"bun-types": "latest",
|
|
51
51
|
"tsup": "^8.5.1",
|
|
52
52
|
"typescript": "^5.9.3"
|