@soulcraft/sdk 2.2.0 → 2.4.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/dist/namespaces.d.ts +33 -1
- package/dist/namespaces.d.ts.map +1 -1
- package/dist/server/handlers/chat/conversations.d.ts +19 -0
- package/dist/server/handlers/chat/conversations.d.ts.map +1 -1
- package/dist/server/handlers/chat/conversations.js +55 -0
- package/dist/server/handlers/chat/conversations.js.map +1 -1
- package/dist/server/handlers/chat/engine.d.ts.map +1 -1
- package/dist/server/handlers/chat/engine.js +67 -6
- package/dist/server/handlers/chat/engine.js.map +1 -1
- package/dist/server/handlers/chat/index.d.ts +3 -1
- package/dist/server/handlers/chat/index.d.ts.map +1 -1
- package/dist/server/handlers/chat/index.js +3 -1
- package/dist/server/handlers/chat/index.js.map +1 -1
- package/dist/server/handlers/chat/resolve-ai-client.d.ts +75 -0
- package/dist/server/handlers/chat/resolve-ai-client.d.ts.map +1 -0
- package/dist/server/handlers/chat/resolve-ai-client.js +183 -0
- package/dist/server/handlers/chat/resolve-ai-client.js.map +1 -0
- package/dist/server/handlers/chat/types.d.ts +14 -2
- package/dist/server/handlers/chat/types.d.ts.map +1 -1
- package/dist/server/handlers/index.d.ts +2 -2
- package/dist/server/handlers/index.d.ts.map +1 -1
- package/dist/server/handlers/index.js +1 -1
- package/dist/server/handlers/index.js.map +1 -1
- package/dist/server/hono-router.d.ts +13 -2
- package/dist/server/hono-router.d.ts.map +1 -1
- package/dist/server/hono-router.js +13 -5
- package/dist/server/hono-router.js.map +1 -1
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/instance-pool.d.ts +28 -0
- package/dist/server/instance-pool.d.ts.map +1 -1
- package/dist/server/instance-pool.js +28 -0
- package/dist/server/instance-pool.js.map +1 -1
- package/dist/server/namespace-router.d.ts +1 -0
- package/dist/server/namespace-router.d.ts.map +1 -1
- package/dist/server/rpc-cache.d.ts +181 -0
- package/dist/server/rpc-cache.d.ts.map +1 -0
- package/dist/server/rpc-cache.js +314 -0
- package/dist/server/rpc-cache.js.map +1 -0
- package/docs/ADR-006-rpc-cache.md +132 -0
- package/package.json +1 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# ADR-006: RPC Response Cache — Namespace Router Cache Layer
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-03-18
|
|
5
|
+
**SDK version:** 2.4.0
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Brainy with Cortex + mmap-filesystem is already sub-millisecond for reads on the server
|
|
12
|
+
side (SQ8 HNSW distance = 0.27 μs, entity lookups = O(1) HashMap on mmap'd SSTables).
|
|
13
|
+
The value of an application-level cache is **not** avoiding Brainy reads — it's:
|
|
14
|
+
|
|
15
|
+
1. **Avoiding auth + dispatch + JSON serialization overhead** at high QPS (thousands of
|
|
16
|
+
anonymous Venue visitors or Workshop published-app users hitting the same tenant data)
|
|
17
|
+
2. **Singleflight deduplication** — concurrent identical requests share one execution
|
|
18
|
+
3. **HTTP cache headers** — `Cache-Control` + `ETag` on responses so browsers, CDNs, and
|
|
19
|
+
reverse proxies can participate
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Decision
|
|
24
|
+
|
|
25
|
+
### Layer: Namespace Router (server-side)
|
|
26
|
+
|
|
27
|
+
The cache wraps the `NamespaceRouter` via `createCachedDispatch()`. This means all
|
|
28
|
+
transports (HTTP, WebSocket, local) benefit automatically. The Hono router's
|
|
29
|
+
`SoulcraftRouterConfig` accepts an optional `cache` field for zero-effort integration.
|
|
30
|
+
|
|
31
|
+
### Invalidation: TTL + Scope-Flush on Write
|
|
32
|
+
|
|
33
|
+
Each cacheable method has a TTL (seconds). When any write method (`add`, `update`,
|
|
34
|
+
`delete`, `writeFile`, etc.) succeeds in a scope, **all** cached entries for that scope
|
|
35
|
+
are flushed. This is slightly aggressive but:
|
|
36
|
+
|
|
37
|
+
- Simple and correct — no stale reads after writes
|
|
38
|
+
- Acceptable for expected cache sizes (< 2000 entries per LRU)
|
|
39
|
+
- Avoids the complexity of tag-based invalidation
|
|
40
|
+
|
|
41
|
+
### Scale: Pluggable CacheProvider
|
|
42
|
+
|
|
43
|
+
The `CacheProvider` interface (`get`, `set`, `invalidateScope`, `delete`, `size`) allows
|
|
44
|
+
swapping the default in-process LRU for Redis/Memcached in multi-server deployments.
|
|
45
|
+
Ships with `LruCacheProvider` backed by `lru-cache` (already a dependency).
|
|
46
|
+
|
|
47
|
+
### Opt-in: Off by Default
|
|
48
|
+
|
|
49
|
+
Cache is disabled unless the product passes `cache` config. The `scopeKey` function
|
|
50
|
+
can also return `null` per-request to bypass caching selectively (e.g. Workshop skips
|
|
51
|
+
caching for workspace editing but caches published-app reads).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Cache Key Format
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
${scope}:${namespace}.${method}:${sha256(JSON.stringify(args)).slice(0,16)}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- **scope** — product-defined (workspace ID, tenant ID, `pub:${workspaceId}`, etc.)
|
|
62
|
+
- **namespace.method** — e.g. `brainy.find`, `graph.getData`
|
|
63
|
+
- **args hash** — 16-char truncated SHA-256 of the JSON-serialized args array
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Default Cacheable Methods
|
|
68
|
+
|
|
69
|
+
| Method | TTL |
|
|
70
|
+
|--------|-----|
|
|
71
|
+
| `brainy.find`, `brainy.get`, `brainy.getRelations` | 10s |
|
|
72
|
+
| `graph.getData`, `graph.getFocused`, `graph.getNeighbors` | 30s |
|
|
73
|
+
| `graph.getStats` | 60s |
|
|
74
|
+
| `search.query`, `search.unified` | 10s |
|
|
75
|
+
| `collections.list`, `collections.get`, `collections.getItems` | 30s |
|
|
76
|
+
| `config.get` | 5min |
|
|
77
|
+
| `brainy.vfs.readFile`, `brainy.vfs.readdir`, `brainy.vfs.stat` | 10s |
|
|
78
|
+
| `brainy.vfs.tree` | 30s |
|
|
79
|
+
| `workspace.list`, `workspace.get` | 30s |
|
|
80
|
+
|
|
81
|
+
Products can override entirely via `RpcCacheConfig.methods`.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Singleflight Deduplication
|
|
86
|
+
|
|
87
|
+
Follows the proven pattern from `src/modules/billing/portal-provider.ts`: a
|
|
88
|
+
`Map<string, Promise<DispatchResult>>` keyed by cache key, deleted in `finally`.
|
|
89
|
+
When multiple concurrent requests arrive for the same cache key, only one dispatches
|
|
90
|
+
to the inner router — all others await the same promise.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## HTTP Headers
|
|
95
|
+
|
|
96
|
+
Successful cached responses carry:
|
|
97
|
+
|
|
98
|
+
- `Cache-Control: public, max-age=${ttlSeconds}` — enables CDN/browser caching
|
|
99
|
+
- `ETag: "${sha256(body).slice(0,32)}"` — quoted, 32-char truncated hash
|
|
100
|
+
- `X-Cache: HIT | MISS` — observability
|
|
101
|
+
|
|
102
|
+
No `304 / If-None-Match` — POST requests don't conventionally use conditional responses.
|
|
103
|
+
The headers exist for CDN layers (Cloudflare Cache Rules can cache POST by header).
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## What the Cache Does NOT Do
|
|
108
|
+
|
|
109
|
+
- **Does not replace Brainy's mmap reads** — Brainy is already sub-ms
|
|
110
|
+
- **Does not cache streaming responses** — `stream: true` always bypasses
|
|
111
|
+
- **Does not cache error responses** — only successful results are stored
|
|
112
|
+
- **Does not use canonical JSON** — `JSON.stringify` is deterministic for same input
|
|
113
|
+
from JSON-parsed client requests; no normalization needed for v1
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Consequences
|
|
118
|
+
|
|
119
|
+
- Products get transparent read caching with a single config object
|
|
120
|
+
- Write-after-read correctness is guaranteed by scope-flush
|
|
121
|
+
- Singleflight prevents thundering herd on cold cache / TTL expiry
|
|
122
|
+
- CDN integration is possible via standard HTTP headers
|
|
123
|
+
- Multi-server deployments can implement `CacheProvider` for shared cache
|
|
124
|
+
- Metrics callback enables product-specific monitoring (hit rate, invalidation frequency)
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## References
|
|
129
|
+
|
|
130
|
+
- `src/server/rpc-cache.ts` — implementation
|
|
131
|
+
- `tests/server/rpc-cache.test.ts` — 17-test suite
|
|
132
|
+
- `src/modules/billing/portal-provider.ts:119-171` — singleflight pattern origin
|