@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
|
@@ -55,6 +55,34 @@
|
|
|
55
55
|
* const brain = await pool.forUser(session.user.emailHash, workspaceId)
|
|
56
56
|
* const results = await brain.find({ query: 'inventory items' })
|
|
57
57
|
* ```
|
|
58
|
+
*
|
|
59
|
+
* @example Singleton pattern — single fixed instance with pool benefits
|
|
60
|
+
*
|
|
61
|
+
* For a "platform brain" or other single-instance use case that still needs
|
|
62
|
+
* mutex deduplication, Cortex loading, and proper `close()` on shutdown,
|
|
63
|
+
* use `per-scope` with `maxInstances: 1` and a fixed scope key:
|
|
64
|
+
*
|
|
65
|
+
* ```typescript
|
|
66
|
+
* import { BrainyInstancePool } from '@soulcraft/sdk/server'
|
|
67
|
+
*
|
|
68
|
+
* // One pool for the single platform-wide brain
|
|
69
|
+
* const platformPool = new BrainyInstancePool({
|
|
70
|
+
* storage: 'mmap-filesystem',
|
|
71
|
+
* dataPath: '/mnt/brainy-data/platform',
|
|
72
|
+
* strategy: 'per-scope',
|
|
73
|
+
* maxInstances: 1,
|
|
74
|
+
* })
|
|
75
|
+
*
|
|
76
|
+
* // Always use the same fixed key — the pool handles init-once and dedup
|
|
77
|
+
* const brain = await platformPool.forScope('platform', async (dataPath) => {
|
|
78
|
+
* const brain = await Brainy.create({ storage: 'mmap-filesystem', dataPath })
|
|
79
|
+
* // Run any one-time setup (migrations, seed data, etc.)
|
|
80
|
+
* return brain
|
|
81
|
+
* })
|
|
82
|
+
*
|
|
83
|
+
* // Shutdown flushes and closes the single instance
|
|
84
|
+
* await platformPool.shutdown()
|
|
85
|
+
* ```
|
|
58
86
|
*/
|
|
59
87
|
import { createHash } from 'crypto';
|
|
60
88
|
import fs from 'fs/promises';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance-pool.js","sourceRoot":"","sources":["../../src/server/instance-pool.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"instance-pool.js","sourceRoot":"","sources":["../../src/server/instance-pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"namespace-router.d.ts","sourceRoot":"","sources":["../../src/server/namespace-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAErB,MAAM,WAAW,CAAA;AAMlB;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAA;IAChB,uEAAuE;IACvE,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;IACxB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAA;IACjB,qBAAqB;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,oEAAoE;IACpE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAMD;;;;;;;GAOG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC,CAAA;AAErF;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,8BAA8B;IAC9B,IAAI,EAAE,WAAW,CAAA;IACjB,oBAAoB;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAA;IACb,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAA;AAM9E;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;OAQG;IACH,YAAY,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAEtD;;;;;;;;OAQG;IACH,YAAY,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAElE;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,CACV,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EAAE,EACf,IAAI,EAAE,WAAW,KACd,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAE/B;;;OAGG;IACH,SAAS,EAAE,kBAAkB,CAAA;IAE7B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;CAChC;AAMD,gDAAgD;AAChD,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"namespace-router.d.ts","sourceRoot":"","sources":["../../src/server/namespace-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module server/rpc-cache
|
|
3
|
+
* @description Application-level RPC response cache for the Soulcraft namespace router.
|
|
4
|
+
*
|
|
5
|
+
* Wraps an existing {@link NamespaceRouter} with transparent caching that:
|
|
6
|
+
* - Avoids auth + dispatch + JSON serialization overhead at high QPS
|
|
7
|
+
* - Deduplicates concurrent identical requests via singleflight
|
|
8
|
+
* - Attaches `Cache-Control`, `ETag`, and `X-Cache` headers for CDN/browser participation
|
|
9
|
+
*
|
|
10
|
+
* The cache operates at the scope level (tenant, published-app, etc.). All entries for a
|
|
11
|
+
* scope are flushed when any write method executes in that scope — slightly aggressive but
|
|
12
|
+
* simple and correct for the expected cache sizes (<2000 entries per LRU).
|
|
13
|
+
*
|
|
14
|
+
* Off by default. Products opt in by passing `cache` config to `createSoulcraftRouter()`.
|
|
15
|
+
*
|
|
16
|
+
* @example Venue — per-tenant caching for anonymous visitors
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const sdkRouter = createSoulcraftRouter({
|
|
19
|
+
* resolveBrain: (ctx) => pool.forTenant(ctx.workspaceId),
|
|
20
|
+
* authenticate: (ctx) => verifySession(ctx.request),
|
|
21
|
+
* providers: { graph: graphHandler, search: searchHandler },
|
|
22
|
+
* cache: {
|
|
23
|
+
* scopeKey: (ctx) => ctx.workspaceId || null,
|
|
24
|
+
* },
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
import type { NamespaceRouter, RequestContext } from './namespace-router.js';
|
|
29
|
+
/**
|
|
30
|
+
* Pluggable cache storage abstraction.
|
|
31
|
+
*
|
|
32
|
+
* Ships with {@link LruCacheProvider} for single-process deployments.
|
|
33
|
+
* Implement this interface for Redis, Memcached, or other shared stores.
|
|
34
|
+
*/
|
|
35
|
+
export interface CacheProvider {
|
|
36
|
+
/** Retrieve a cached entry by key. Returns `undefined` on miss. */
|
|
37
|
+
get(key: string): CacheEntry | undefined;
|
|
38
|
+
/** Store an entry. */
|
|
39
|
+
set(key: string, entry: CacheEntry): void;
|
|
40
|
+
/** Invalidate all entries whose key starts with the given scope prefix. */
|
|
41
|
+
invalidateScope(scopePrefix: string): void;
|
|
42
|
+
/** Delete a single entry. */
|
|
43
|
+
delete(key: string): void;
|
|
44
|
+
/** Number of entries currently stored. */
|
|
45
|
+
size(): number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A cached RPC response with its ETag and expiration timestamp.
|
|
49
|
+
*/
|
|
50
|
+
export interface CacheEntry {
|
|
51
|
+
/** JSON-serialized response body. */
|
|
52
|
+
body: string;
|
|
53
|
+
/** Quoted ETag value (truncated SHA-256 of body). */
|
|
54
|
+
etag: string;
|
|
55
|
+
/** Unix timestamp (ms) when this entry expires. */
|
|
56
|
+
expiresAt: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Product-facing cache configuration passed to `createSoulcraftRouter()`.
|
|
60
|
+
*/
|
|
61
|
+
export interface RpcCacheConfig {
|
|
62
|
+
/**
|
|
63
|
+
* Extracts the cache scope key from a request context.
|
|
64
|
+
*
|
|
65
|
+
* Return `null` to skip caching for this request entirely (e.g. workspace
|
|
66
|
+
* editing in Workshop should not be cached).
|
|
67
|
+
*
|
|
68
|
+
* @param ctx - The request context with user and workspace info.
|
|
69
|
+
* @returns A scope key string, or `null` to bypass the cache.
|
|
70
|
+
*/
|
|
71
|
+
scopeKey: (ctx: RequestContext) => string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Map of `namespace.method` → TTL in seconds for cacheable methods.
|
|
74
|
+
*
|
|
75
|
+
* @default DEFAULT_CACHEABLE_METHODS
|
|
76
|
+
*/
|
|
77
|
+
methods?: Record<string, number>;
|
|
78
|
+
/**
|
|
79
|
+
* Enable singleflight deduplication of concurrent identical requests.
|
|
80
|
+
*
|
|
81
|
+
* @default true
|
|
82
|
+
*/
|
|
83
|
+
singleflight?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Optional metrics callback fired on cache events.
|
|
86
|
+
*/
|
|
87
|
+
onMetrics?: (event: CacheMetricsEvent) => void;
|
|
88
|
+
/**
|
|
89
|
+
* Custom cache provider. Defaults to {@link LruCacheProvider} with 2000 entries.
|
|
90
|
+
*/
|
|
91
|
+
provider?: CacheProvider;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Metrics event emitted by the cache layer.
|
|
95
|
+
*/
|
|
96
|
+
export interface CacheMetricsEvent {
|
|
97
|
+
/** Event type. */
|
|
98
|
+
type: 'hit' | 'miss' | 'singleflight' | 'invalidate';
|
|
99
|
+
/** The scope key for this request. */
|
|
100
|
+
scope: string;
|
|
101
|
+
/** The target namespace. */
|
|
102
|
+
ns: string;
|
|
103
|
+
/** The method name. */
|
|
104
|
+
method: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Default method→TTL map for cacheable read methods.
|
|
108
|
+
*
|
|
109
|
+
* Products can override this entirely via `RpcCacheConfig.methods`, or use
|
|
110
|
+
* these defaults which cover the most common high-QPS read patterns.
|
|
111
|
+
*/
|
|
112
|
+
export declare const DEFAULT_CACHEABLE_METHODS: Record<string, number>;
|
|
113
|
+
/**
|
|
114
|
+
* In-process LRU cache provider backed by `lru-cache`.
|
|
115
|
+
*
|
|
116
|
+
* Suitable for single-process deployments. For multi-server deployments,
|
|
117
|
+
* implement {@link CacheProvider} with a shared store (Redis, etc.).
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* const provider = new LruCacheProvider({ maxEntries: 5000 })
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export declare class LruCacheProvider implements CacheProvider {
|
|
125
|
+
private readonly _lru;
|
|
126
|
+
/**
|
|
127
|
+
* @param options - Provider options.
|
|
128
|
+
* @param options.maxEntries - Maximum number of cached entries. Defaults to 2000.
|
|
129
|
+
*/
|
|
130
|
+
constructor(options?: {
|
|
131
|
+
maxEntries?: number;
|
|
132
|
+
});
|
|
133
|
+
/**
|
|
134
|
+
* @param key - Cache key.
|
|
135
|
+
* @returns The cached entry, or `undefined` on miss.
|
|
136
|
+
*/
|
|
137
|
+
get(key: string): CacheEntry | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* @param key - Cache key.
|
|
140
|
+
* @param entry - The entry to store.
|
|
141
|
+
*/
|
|
142
|
+
set(key: string, entry: CacheEntry): void;
|
|
143
|
+
/**
|
|
144
|
+
* Invalidate all entries whose key starts with the given scope prefix.
|
|
145
|
+
*
|
|
146
|
+
* Iterates all keys in the LRU — acceptable for cache sizes ≤ 2000.
|
|
147
|
+
*
|
|
148
|
+
* @param scopePrefix - The scope prefix to match (e.g. `"tenant-123:"`).
|
|
149
|
+
*/
|
|
150
|
+
invalidateScope(scopePrefix: string): void;
|
|
151
|
+
/**
|
|
152
|
+
* @param key - Cache key to delete.
|
|
153
|
+
*/
|
|
154
|
+
delete(key: string): void;
|
|
155
|
+
/**
|
|
156
|
+
* @returns The number of entries currently in the cache.
|
|
157
|
+
*/
|
|
158
|
+
size(): number;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Wraps a {@link NamespaceRouter} with transparent response caching.
|
|
162
|
+
*
|
|
163
|
+
* The returned router has the same `dispatch()` signature as the inner router.
|
|
164
|
+
* Cacheable read methods are served from cache when fresh; write methods flush
|
|
165
|
+
* all cached entries for the current scope.
|
|
166
|
+
*
|
|
167
|
+
* @param innerRouter - The namespace router to wrap.
|
|
168
|
+
* @param config - Cache configuration.
|
|
169
|
+
* @returns A new {@link NamespaceRouter} with caching applied.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* const router = createNamespaceRouter(routerConfig)
|
|
174
|
+
* const cachedRouter = createCachedDispatch(router, {
|
|
175
|
+
* scopeKey: (ctx) => ctx.workspaceId || null,
|
|
176
|
+
* })
|
|
177
|
+
* const result = await cachedRouter.dispatch(rpc, request)
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
export declare function createCachedDispatch(innerRouter: NamespaceRouter, config: RpcCacheConfig): NamespaceRouter;
|
|
181
|
+
//# sourceMappingURL=rpc-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-cache.d.ts","sourceRoot":"","sources":["../../src/server/rpc-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAEf,MAAM,uBAAuB,CAAA;AAM9B;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;IACxC,sBAAsB;IACtB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACzC,2EAA2E;IAC3E,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1C,6BAA6B;IAC7B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,0CAA0C;IAC1C,IAAI,IAAI,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,MAAM,GAAG,IAAI,CAAA;IAEhD;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEhC;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IAEtB;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAE9C;;OAEG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAA;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,kBAAkB;IAClB,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,cAAc,GAAG,YAAY,CAAA;IACpD,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,4BAA4B;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAA;CACf;AAMD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAiC5D,CAAA;AAmCD;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,YAAW,aAAa;IACpD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA8B;IAEnD;;;OAGG;gBACS,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;IAM7C;;;OAGG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIxC;;;OAGG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAIzC;;;;;;OAMG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAQ1C;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB;;OAEG;IACH,IAAI,IAAI,MAAM;CAGf;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,eAAe,EAC5B,MAAM,EAAE,cAAc,GACrB,eAAe,CA+HjB"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module server/rpc-cache
|
|
3
|
+
* @description Application-level RPC response cache for the Soulcraft namespace router.
|
|
4
|
+
*
|
|
5
|
+
* Wraps an existing {@link NamespaceRouter} with transparent caching that:
|
|
6
|
+
* - Avoids auth + dispatch + JSON serialization overhead at high QPS
|
|
7
|
+
* - Deduplicates concurrent identical requests via singleflight
|
|
8
|
+
* - Attaches `Cache-Control`, `ETag`, and `X-Cache` headers for CDN/browser participation
|
|
9
|
+
*
|
|
10
|
+
* The cache operates at the scope level (tenant, published-app, etc.). All entries for a
|
|
11
|
+
* scope are flushed when any write method executes in that scope — slightly aggressive but
|
|
12
|
+
* simple and correct for the expected cache sizes (<2000 entries per LRU).
|
|
13
|
+
*
|
|
14
|
+
* Off by default. Products opt in by passing `cache` config to `createSoulcraftRouter()`.
|
|
15
|
+
*
|
|
16
|
+
* @example Venue — per-tenant caching for anonymous visitors
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const sdkRouter = createSoulcraftRouter({
|
|
19
|
+
* resolveBrain: (ctx) => pool.forTenant(ctx.workspaceId),
|
|
20
|
+
* authenticate: (ctx) => verifySession(ctx.request),
|
|
21
|
+
* providers: { graph: graphHandler, search: searchHandler },
|
|
22
|
+
* cache: {
|
|
23
|
+
* scopeKey: (ctx) => ctx.workspaceId || null,
|
|
24
|
+
* },
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
import { createHash } from 'crypto';
|
|
29
|
+
import { LRUCache } from 'lru-cache';
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
// Default cacheable methods — namespace.method → TTL in seconds
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
/**
|
|
34
|
+
* Default method→TTL map for cacheable read methods.
|
|
35
|
+
*
|
|
36
|
+
* Products can override this entirely via `RpcCacheConfig.methods`, or use
|
|
37
|
+
* these defaults which cover the most common high-QPS read patterns.
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_CACHEABLE_METHODS = {
|
|
40
|
+
// Brainy entity reads
|
|
41
|
+
'brainy.find': 10,
|
|
42
|
+
'brainy.get': 10,
|
|
43
|
+
'brainy.getRelations': 10,
|
|
44
|
+
// Graph namespace
|
|
45
|
+
'graph.getData': 30,
|
|
46
|
+
'graph.getFocused': 30,
|
|
47
|
+
'graph.getNeighbors': 30,
|
|
48
|
+
'graph.getStats': 60,
|
|
49
|
+
// Search namespace
|
|
50
|
+
'search.query': 10,
|
|
51
|
+
'search.unified': 10,
|
|
52
|
+
// Collections reads
|
|
53
|
+
'collections.list': 30,
|
|
54
|
+
'collections.get': 30,
|
|
55
|
+
'collections.getItems': 30,
|
|
56
|
+
// Config
|
|
57
|
+
'config.get': 300,
|
|
58
|
+
// VFS reads
|
|
59
|
+
'brainy.vfs.readFile': 10,
|
|
60
|
+
'brainy.vfs.readdir': 10,
|
|
61
|
+
'brainy.vfs.stat': 10,
|
|
62
|
+
'brainy.vfs.tree': 30,
|
|
63
|
+
// Workspace reads
|
|
64
|
+
'workspace.list': 30,
|
|
65
|
+
'workspace.get': 30,
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Methods that invalidate all cached entries for the current scope.
|
|
69
|
+
*
|
|
70
|
+
* Any successful dispatch of these methods triggers a full scope flush.
|
|
71
|
+
*/
|
|
72
|
+
const SCOPE_INVALIDATING_METHODS = new Set([
|
|
73
|
+
'add',
|
|
74
|
+
'update',
|
|
75
|
+
'delete',
|
|
76
|
+
'relate',
|
|
77
|
+
'unrelate',
|
|
78
|
+
'writeFile',
|
|
79
|
+
'mkdir',
|
|
80
|
+
'rmdir',
|
|
81
|
+
'unlink',
|
|
82
|
+
'rename',
|
|
83
|
+
'clear',
|
|
84
|
+
'save',
|
|
85
|
+
'restore',
|
|
86
|
+
'import',
|
|
87
|
+
'create',
|
|
88
|
+
'remove',
|
|
89
|
+
'set',
|
|
90
|
+
'put',
|
|
91
|
+
'patch',
|
|
92
|
+
'move',
|
|
93
|
+
'copy',
|
|
94
|
+
]);
|
|
95
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
// LRU cache provider
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
/**
|
|
99
|
+
* In-process LRU cache provider backed by `lru-cache`.
|
|
100
|
+
*
|
|
101
|
+
* Suitable for single-process deployments. For multi-server deployments,
|
|
102
|
+
* implement {@link CacheProvider} with a shared store (Redis, etc.).
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const provider = new LruCacheProvider({ maxEntries: 5000 })
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export class LruCacheProvider {
|
|
110
|
+
_lru;
|
|
111
|
+
/**
|
|
112
|
+
* @param options - Provider options.
|
|
113
|
+
* @param options.maxEntries - Maximum number of cached entries. Defaults to 2000.
|
|
114
|
+
*/
|
|
115
|
+
constructor(options) {
|
|
116
|
+
this._lru = new LRUCache({
|
|
117
|
+
max: options?.maxEntries ?? 2000,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* @param key - Cache key.
|
|
122
|
+
* @returns The cached entry, or `undefined` on miss.
|
|
123
|
+
*/
|
|
124
|
+
get(key) {
|
|
125
|
+
return this._lru.get(key);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* @param key - Cache key.
|
|
129
|
+
* @param entry - The entry to store.
|
|
130
|
+
*/
|
|
131
|
+
set(key, entry) {
|
|
132
|
+
this._lru.set(key, entry);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Invalidate all entries whose key starts with the given scope prefix.
|
|
136
|
+
*
|
|
137
|
+
* Iterates all keys in the LRU — acceptable for cache sizes ≤ 2000.
|
|
138
|
+
*
|
|
139
|
+
* @param scopePrefix - The scope prefix to match (e.g. `"tenant-123:"`).
|
|
140
|
+
*/
|
|
141
|
+
invalidateScope(scopePrefix) {
|
|
142
|
+
for (const key of this._lru.keys()) {
|
|
143
|
+
if (key.startsWith(scopePrefix)) {
|
|
144
|
+
this._lru.delete(key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* @param key - Cache key to delete.
|
|
150
|
+
*/
|
|
151
|
+
delete(key) {
|
|
152
|
+
this._lru.delete(key);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* @returns The number of entries currently in the cache.
|
|
156
|
+
*/
|
|
157
|
+
size() {
|
|
158
|
+
return this._lru.size;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
162
|
+
// Core: createCachedDispatch
|
|
163
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
164
|
+
/**
|
|
165
|
+
* Wraps a {@link NamespaceRouter} with transparent response caching.
|
|
166
|
+
*
|
|
167
|
+
* The returned router has the same `dispatch()` signature as the inner router.
|
|
168
|
+
* Cacheable read methods are served from cache when fresh; write methods flush
|
|
169
|
+
* all cached entries for the current scope.
|
|
170
|
+
*
|
|
171
|
+
* @param innerRouter - The namespace router to wrap.
|
|
172
|
+
* @param config - Cache configuration.
|
|
173
|
+
* @returns A new {@link NamespaceRouter} with caching applied.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* const router = createNamespaceRouter(routerConfig)
|
|
178
|
+
* const cachedRouter = createCachedDispatch(router, {
|
|
179
|
+
* scopeKey: (ctx) => ctx.workspaceId || null,
|
|
180
|
+
* })
|
|
181
|
+
* const result = await cachedRouter.dispatch(rpc, request)
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
export function createCachedDispatch(innerRouter, config) {
|
|
185
|
+
const provider = config.provider ?? new LruCacheProvider();
|
|
186
|
+
const methods = config.methods ?? DEFAULT_CACHEABLE_METHODS;
|
|
187
|
+
const enableSingleflight = config.singleflight ?? true;
|
|
188
|
+
const onMetrics = config.onMetrics;
|
|
189
|
+
/** In-flight singleflight promises, keyed by cache key. */
|
|
190
|
+
const inflight = new Map();
|
|
191
|
+
return {
|
|
192
|
+
async dispatch(rpc, requestOrCtx) {
|
|
193
|
+
// ── Resolve scope ────────────────────────────────────────────────
|
|
194
|
+
const ctx = _buildMinimalContext(requestOrCtx);
|
|
195
|
+
const scope = config.scopeKey(ctx);
|
|
196
|
+
// No scope → pass through entirely (caching disabled for this request).
|
|
197
|
+
if (scope === null) {
|
|
198
|
+
return innerRouter.dispatch(rpc, requestOrCtx);
|
|
199
|
+
}
|
|
200
|
+
const { ns, method } = rpc;
|
|
201
|
+
const methodKey = `${ns}.${method}`;
|
|
202
|
+
// ── Write methods → delegate then flush scope ────────────────────
|
|
203
|
+
if (SCOPE_INVALIDATING_METHODS.has(method)) {
|
|
204
|
+
const result = await innerRouter.dispatch(rpc, requestOrCtx);
|
|
205
|
+
// Only flush on successful writes.
|
|
206
|
+
if (result.type === 'response' && !result.response.error) {
|
|
207
|
+
const scopePrefix = `${scope}:`;
|
|
208
|
+
provider.invalidateScope(scopePrefix);
|
|
209
|
+
onMetrics?.({ type: 'invalidate', scope, ns, method });
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
// ── Non-cacheable method → pass through ──────────────────────────
|
|
214
|
+
const ttlSeconds = methods[methodKey];
|
|
215
|
+
if (ttlSeconds === undefined) {
|
|
216
|
+
return innerRouter.dispatch(rpc, requestOrCtx);
|
|
217
|
+
}
|
|
218
|
+
// ── Streaming requests are never cached ──────────────────────────
|
|
219
|
+
if (rpc.stream) {
|
|
220
|
+
return innerRouter.dispatch(rpc, requestOrCtx);
|
|
221
|
+
}
|
|
222
|
+
// ── Build cache key ──────────────────────────────────────────────
|
|
223
|
+
const argsHash = createHash('sha256')
|
|
224
|
+
.update(JSON.stringify(rpc.args))
|
|
225
|
+
.digest('hex')
|
|
226
|
+
.slice(0, 16);
|
|
227
|
+
const cacheKey = `${scope}:${methodKey}:${argsHash}`;
|
|
228
|
+
// ── Check cache ──────────────────────────────────────────────────
|
|
229
|
+
const cached = provider.get(cacheKey);
|
|
230
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
231
|
+
onMetrics?.({ type: 'hit', scope, ns, method });
|
|
232
|
+
const response = JSON.parse(cached.body);
|
|
233
|
+
return {
|
|
234
|
+
type: 'response',
|
|
235
|
+
response,
|
|
236
|
+
headers: {
|
|
237
|
+
'Cache-Control': `public, max-age=${ttlSeconds}`,
|
|
238
|
+
'ETag': cached.etag,
|
|
239
|
+
'X-Cache': 'HIT',
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// ── Singleflight deduplication ───────────────────────────────────
|
|
244
|
+
if (enableSingleflight) {
|
|
245
|
+
const existing = inflight.get(cacheKey);
|
|
246
|
+
if (existing) {
|
|
247
|
+
onMetrics?.({ type: 'singleflight', scope, ns, method });
|
|
248
|
+
return existing;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// ── Miss: dispatch and cache ─────────────────────────────────────
|
|
252
|
+
onMetrics?.({ type: 'miss', scope, ns, method });
|
|
253
|
+
const promise = (async () => {
|
|
254
|
+
const result = await innerRouter.dispatch(rpc, requestOrCtx);
|
|
255
|
+
// Only cache successful non-streaming responses.
|
|
256
|
+
if (result.type === 'response' && !result.response.error) {
|
|
257
|
+
const body = JSON.stringify(result.response);
|
|
258
|
+
const etag = `"${createHash('sha256').update(body).digest('hex').slice(0, 32)}"`;
|
|
259
|
+
const entry = {
|
|
260
|
+
body,
|
|
261
|
+
etag,
|
|
262
|
+
expiresAt: Date.now() + ttlSeconds * 1000,
|
|
263
|
+
};
|
|
264
|
+
provider.set(cacheKey, entry);
|
|
265
|
+
return {
|
|
266
|
+
type: 'response',
|
|
267
|
+
response: result.response,
|
|
268
|
+
headers: {
|
|
269
|
+
'Cache-Control': `public, max-age=${ttlSeconds}`,
|
|
270
|
+
'ETag': etag,
|
|
271
|
+
'X-Cache': 'MISS',
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
})();
|
|
277
|
+
if (enableSingleflight) {
|
|
278
|
+
inflight.set(cacheKey, promise);
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
return await promise;
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
if (enableSingleflight) {
|
|
285
|
+
inflight.delete(cacheKey);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
292
|
+
// Internal helpers
|
|
293
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
294
|
+
/**
|
|
295
|
+
* Builds a minimal {@link RequestContext} for scope key extraction.
|
|
296
|
+
*
|
|
297
|
+
* The scope key function only needs the request and workspace ID — it does
|
|
298
|
+
* not need the full authenticated context.
|
|
299
|
+
*
|
|
300
|
+
* @param requestOrCtx - Raw HTTP request or pre-authenticated context.
|
|
301
|
+
* @returns A minimal request context.
|
|
302
|
+
*/
|
|
303
|
+
function _buildMinimalContext(requestOrCtx) {
|
|
304
|
+
if (requestOrCtx instanceof Request) {
|
|
305
|
+
const workspaceId = requestOrCtx.headers.get('x-workspace-id') ?? '';
|
|
306
|
+
return { request: requestOrCtx, user: null, workspaceId };
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
request: requestOrCtx.request,
|
|
310
|
+
user: requestOrCtx.user,
|
|
311
|
+
workspaceId: requestOrCtx.workspaceId,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=rpc-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-cache.js","sourceRoot":"","sources":["../../src/server/rpc-cache.ts"],"names":[],"mappings":"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"}
|