@selvajs/server 0.2.1 → 1.0.0-beta.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 +23 -16
- package/dist/compute/index.d.ts +0 -8
- package/dist/compute/index.d.ts.map +1 -1
- package/dist/compute/index.js +12 -9
- package/dist/compute/index.js.map +1 -1
- package/dist/compute/limits.d.ts +38 -54
- package/dist/compute/limits.d.ts.map +1 -1
- package/dist/compute/limits.js +39 -16
- package/dist/compute/limits.js.map +1 -1
- package/package.json +6 -12
- package/dist/compute/client-cache.d.ts +0 -135
- package/dist/compute/client-cache.d.ts.map +0 -1
- package/dist/compute/client-cache.js +0 -176
- package/dist/compute/client-cache.js.map +0 -1
- package/dist/compute/definition-byte-cache.d.ts +0 -85
- package/dist/compute/definition-byte-cache.d.ts.map +0 -1
- package/dist/compute/definition-byte-cache.js +0 -110
- package/dist/compute/definition-byte-cache.js.map +0 -1
- package/dist/compute/memory-solve-cache.d.ts +0 -58
- package/dist/compute/memory-solve-cache.d.ts.map +0 -1
- package/dist/compute/memory-solve-cache.js +0 -0
- package/dist/compute/memory-solve-cache.js.map +0 -1
- package/dist/compute/solve-cache-envelope.d.ts +0 -53
- package/dist/compute/solve-cache-envelope.d.ts.map +0 -1
- package/dist/compute/solve-cache-envelope.js +0 -66
- package/dist/compute/solve-cache-envelope.js.map +0 -1
- package/dist/compute/solve-cache-key.d.ts +0 -64
- package/dist/compute/solve-cache-key.d.ts.map +0 -1
- package/dist/compute/solve-cache-key.js +0 -51
- package/dist/compute/solve-cache-key.js.map +0 -1
- package/dist/compute/solve-cache-single-flight.d.ts +0 -42
- package/dist/compute/solve-cache-single-flight.d.ts.map +0 -1
- package/dist/compute/solve-cache-single-flight.js +0 -50
- package/dist/compute/solve-cache-single-flight.js.map +0 -1
- package/dist/compute/solve-pipeline.d.ts +0 -221
- package/dist/compute/solve-pipeline.d.ts.map +0 -1
- package/dist/compute/solve-pipeline.js +0 -419
- package/dist/compute/solve-pipeline.js.map +0 -1
- package/dist/compute/transform-input.d.ts +0 -20
- package/dist/compute/transform-input.d.ts.map +0 -1
- package/dist/compute/transform-input.js +0 -62
- package/dist/compute/transform-input.js.map +0 -1
- package/dist/index.d.ts +0 -27
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -27
- package/dist/index.js.map +0 -1
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { GrasshopperClient, enableDebugLogging } from '@selvajs/compute';
|
|
2
|
-
/** Derive the opaque cache identity from a resolved server. Identity is the `id`. */
|
|
3
|
-
export function serverIdentity(server) {
|
|
4
|
-
return server.id;
|
|
5
|
-
}
|
|
6
|
-
const DEFAULT_MAX_CACHED_CLIENTS = 16;
|
|
7
|
-
/**
|
|
8
|
-
* Build a per-server warm-client cache. Each cache owns its own LRU `Map` keyed
|
|
9
|
-
* on server `id`.
|
|
10
|
-
*/
|
|
11
|
-
export function createClientCache(config) {
|
|
12
|
-
const maxCachedClients = config.maxCachedClients ?? DEFAULT_MAX_CACHED_CLIENTS;
|
|
13
|
-
const cache = new Map();
|
|
14
|
-
if (config.debugVerbose)
|
|
15
|
-
enableDebugLogging();
|
|
16
|
-
const debugLog = (message) => {
|
|
17
|
-
if (config.debug)
|
|
18
|
-
config.onDebugLog?.(message);
|
|
19
|
-
};
|
|
20
|
-
async function build(server, definitionGuid) {
|
|
21
|
-
// Holders for per-request results from the client/scheduler callbacks; the
|
|
22
|
-
// caller reads them right after each solve resolves to build Server-Timing,
|
|
23
|
-
// using the seq counters to detect (and drop) ambiguous concurrent writes.
|
|
24
|
-
const rhinoTiming = { last: null, seq: 0 };
|
|
25
|
-
const solveMeta = { last: null, seq: 0 };
|
|
26
|
-
const clientConfig = {
|
|
27
|
-
serverUrl: server.serverUrl,
|
|
28
|
-
apiKey: server.apiKey,
|
|
29
|
-
// Lib-level debug is the verbose one (full payload logs); keep it on the
|
|
30
|
-
// verbose flag so the concise cache logs can run without the noise.
|
|
31
|
-
debug: config.debugVerbose,
|
|
32
|
-
// Ask Rhino.Compute to cache solve results and return them on identical
|
|
33
|
-
// repeats (same definition + inputs), skipping the solve.
|
|
34
|
-
cachesolve: config.cachesolve,
|
|
35
|
-
// Opt-in: also cache solves that reported GH errors.
|
|
36
|
-
cacheerroredsolves: config.cacheerroredsolves,
|
|
37
|
-
// DEBUG: the server's per-request timing breakdown, which exposes the two
|
|
38
|
-
// SERVER-side caches at once — definition cache (pointer reuse) via
|
|
39
|
-
// `decode`, solve cache (cachesolve) via `solve`. Flagged below so the
|
|
40
|
-
// contrast is obvious in the logs.
|
|
41
|
-
onServerTiming: (t) => {
|
|
42
|
-
const decode = t.decode ?? 0;
|
|
43
|
-
const solve = t.solve ?? 0;
|
|
44
|
-
const encode = t.encode ?? 0;
|
|
45
|
-
// Always recorded (cheap): the caller reads this after scheduler.solve.
|
|
46
|
-
rhinoTiming.seq += 1;
|
|
47
|
-
rhinoTiming.last = { decode, solve, encode };
|
|
48
|
-
if (!config.debug)
|
|
49
|
-
return;
|
|
50
|
-
// Heuristic thresholds: a genuine parse/solve is many ms; a cache hit
|
|
51
|
-
// is sub-millisecond. Only meaningful relative to the first (cold) solve.
|
|
52
|
-
const defCache = decode <= 1 ? 'HIT (definition reused, no re-upload/parse)' : 'miss (full parse)';
|
|
53
|
-
const solveCache = config.cachesolve && solve <= 1
|
|
54
|
-
? 'HIT (cached result, solve skipped)'
|
|
55
|
-
: config.cachesolve
|
|
56
|
-
? 'miss (solved fresh)'
|
|
57
|
-
: 'off';
|
|
58
|
-
debugLog(`[Compute/rhino-cache] decode=${decode}ms solve=${solve}ms encode=${encode}ms ` +
|
|
59
|
-
`| definition-cache: ${defCache} | solve-cache (cachesolve): ${solveCache}`);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
// `X-Selva-Definition` on the wire (ADR 0004 D2). Baked at client-create
|
|
63
|
-
// time because the scheduler has no per-request header hook — inert for a
|
|
64
|
-
// single-member "pool", useful in compute-side access logs immediately.
|
|
65
|
-
if (definitionGuid) {
|
|
66
|
-
clientConfig.headers = { 'X-Selva-Definition': definitionGuid };
|
|
67
|
-
}
|
|
68
|
-
const client = await GrasshopperClient.create(clientConfig);
|
|
69
|
-
const scheduler = client.createScheduler({
|
|
70
|
-
mode: 'queue',
|
|
71
|
-
// Queue mode defaults maxConcurrent to 1, which would serialize every
|
|
72
|
-
// solve on this server through a single slot while the compute VM's
|
|
73
|
-
// other compute.geometry children idle (audit B6) — always pass it.
|
|
74
|
-
maxConcurrent: config.maxConcurrentSolves,
|
|
75
|
-
// Backpressure (audit B7). The scheduler treats undefined as
|
|
76
|
-
// unbounded/no-deadline, so map our `0`-means-off convention to undefined
|
|
77
|
-
// rather than passing 0 (which would shed EVERY queued solve).
|
|
78
|
-
maxQueueDepth: config.maxQueueDepth || undefined,
|
|
79
|
-
queueWaitMs: config.queueWaitMs || undefined,
|
|
80
|
-
timeoutMs: config.maxSolveDurationMs,
|
|
81
|
-
// L1 response cache: count-capped AND byte-budgeted (audit C2) — entries
|
|
82
|
-
// can be as large as the response cap allows, so the count alone would
|
|
83
|
-
// let 20 × max-response-size pin gigabytes per warm client. `0` = off.
|
|
84
|
-
cache: config.responseCacheMaxBytes > 0
|
|
85
|
-
? { maxEntries: 20, ttlMs: 5 * 60_000, maxBytes: config.responseCacheMaxBytes }
|
|
86
|
-
: false,
|
|
87
|
-
// Solve large definitions by server cache-key (pointer) instead of
|
|
88
|
-
// re-uploading the full binary every solve. On a stale-pointer miss the
|
|
89
|
-
// client transparently re-uploads.
|
|
90
|
-
reuseServerDefinitionCache: config.reuseServerDefinitionCache,
|
|
91
|
-
// DEBUG: observe the in-process response cache. `fromCache === true` means
|
|
92
|
-
// this solve was served from the app's own cache — Rhino.Compute was never
|
|
93
|
-
// called. `false` means it went to the compute server.
|
|
94
|
-
onSettle: (_ctx, result) => {
|
|
95
|
-
// Count EVERY settle (success or error) so the caller's attribution
|
|
96
|
-
// guard sees all concurrent activity, not just successful writes.
|
|
97
|
-
solveMeta.seq += 1;
|
|
98
|
-
if (result.status !== 'success')
|
|
99
|
-
return;
|
|
100
|
-
// Always recorded (cheap): the caller surfaces these on Server-Timing.
|
|
101
|
-
solveMeta.last = {
|
|
102
|
-
fromCache: result.fromCache === true,
|
|
103
|
-
definitionReuploaded: result.definitionReuploaded
|
|
104
|
-
};
|
|
105
|
-
if (!config.debug)
|
|
106
|
-
return;
|
|
107
|
-
debugLog(`[Compute/selva-cache] ${result.fromCache ? 'HIT — served from cache (no compute call)' : 'miss — went to Rhino.Compute'} (${Math.round(result.durationMs)}ms)`);
|
|
108
|
-
// Definition-cache verdict for a real compute call (not a cache HIT).
|
|
109
|
-
if (!result.fromCache) {
|
|
110
|
-
debugLog(result.definitionReuploaded === true
|
|
111
|
-
? '[Compute/def-cache] miss — definition RE-UPLOADED (pointer was cold/stale)'
|
|
112
|
-
: result.definitionReuploaded === false
|
|
113
|
-
? '[Compute/def-cache] HIT — definition reused from server cache (no upload)'
|
|
114
|
-
: '[Compute/def-cache] n/a — reuse disabled or non-reusable definition');
|
|
115
|
-
}
|
|
116
|
-
// Surface real GH errors when present. Whether an errored solve is cached is
|
|
117
|
-
// config-gated by `cacheerroredsolves` (default false → errored solves are
|
|
118
|
-
// re-run each time; true → the error result is cached like any other, per R2).
|
|
119
|
-
if (!result.fromCache) {
|
|
120
|
-
const errs = result.response?.errors;
|
|
121
|
-
const errCount = Array.isArray(errs) ? errs.length : 0;
|
|
122
|
-
if (errCount > 0) {
|
|
123
|
-
const cacheNote = config.cacheerroredsolves
|
|
124
|
-
? 'cached (cacheerroredsolves on)'
|
|
125
|
-
: 'not cached (cacheerroredsolves off) — re-run until fixed';
|
|
126
|
-
debugLog(`[Compute/rhino-cache] ${errCount} GH error(s); ${cacheNote}. First: ${JSON.stringify(errs[0])}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
return { client, scheduler, rhinoTiming, solveMeta };
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
async getClient(server, opts) {
|
|
135
|
-
const key = server.id;
|
|
136
|
-
const existing = cache.get(key);
|
|
137
|
-
if (existing) {
|
|
138
|
-
// Refresh LRU position.
|
|
139
|
-
cache.delete(key);
|
|
140
|
-
cache.set(key, existing);
|
|
141
|
-
return existing;
|
|
142
|
-
}
|
|
143
|
-
// A build is a Rhino.Compute handshake — expensive, and repeated builds for
|
|
144
|
-
// the same id mean churn (LRU thrash or config-rotation evictions).
|
|
145
|
-
debugLog(`[Compute/client-cache] miss — building warm client for server ${key}`);
|
|
146
|
-
const entry = await build(server, opts?.definitionGuid);
|
|
147
|
-
// Evict the least-recently-used entry before inserting when at capacity.
|
|
148
|
-
if (cache.size >= maxCachedClients) {
|
|
149
|
-
const oldestKey = cache.keys().next().value;
|
|
150
|
-
if (oldestKey !== undefined) {
|
|
151
|
-
debugLog(`[Compute/client-cache] LRU evicted warm client for server ${oldestKey} (cap ${maxCachedClients})`);
|
|
152
|
-
cache.get(oldestKey)?.scheduler.dispose();
|
|
153
|
-
cache.delete(oldestKey);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
cache.set(key, entry);
|
|
157
|
-
return entry;
|
|
158
|
-
},
|
|
159
|
-
evict(id) {
|
|
160
|
-
const key = id;
|
|
161
|
-
const entry = cache.get(key);
|
|
162
|
-
if (entry) {
|
|
163
|
-
debugLog(`[Compute/client-cache] evicted warm client for server ${key} (config change)`);
|
|
164
|
-
entry.scheduler.dispose();
|
|
165
|
-
cache.delete(key);
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
disposeAll() {
|
|
169
|
-
debugLog(`[Compute/client-cache] disposing all ${cache.size} warm client(s)`);
|
|
170
|
-
for (const entry of cache.values())
|
|
171
|
-
entry.scheduler.dispose();
|
|
172
|
-
cache.clear();
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
//# sourceMappingURL=client-cache.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client-cache.js","sourceRoot":"","sources":["../../src/compute/client-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-process cache of `.gh` definition bytes, keyed by immutable **version id**.
|
|
3
|
-
*
|
|
4
|
-
* The solve path used to eagerly `storage.get(fileKey)` on every request — moving
|
|
5
|
-
* multi-MB definition bytes off disk/S3 into memory before the scheduler could
|
|
6
|
-
* decide it didn't need them (the compute server already holds the definition via
|
|
7
|
-
* the pointer/`reuseServerDefinitionCache` fast path). This cache turns that eager
|
|
8
|
-
* read into a lazy `DefinitionRef.load()`: the scheduler calls `load()` ONLY when
|
|
9
|
-
* an upload is unavoidable, and even then a warm entry serves the bytes without
|
|
10
|
-
* touching storage. A pointer-known re-solve therefore moves ZERO definition bytes.
|
|
11
|
-
*
|
|
12
|
-
* Keying discipline (the whole reason this module exists):
|
|
13
|
-
* - The key is the **version id**, an immutable UUID — never a `fileKey`, which
|
|
14
|
-
* a delete-latest-then-reupload can REUSE for different content (see the
|
|
15
|
-
* monotonic-version-number fix). A reused key would serve one version's bytes
|
|
16
|
-
* for another: silent cache poisoning with no diagnostic. Callers pass
|
|
17
|
-
* `version.id`, and the returned `DefinitionRef.key` is that same id, so the
|
|
18
|
-
* scheduler's own result/pointer cache keys on the same immutable identity.
|
|
19
|
-
*
|
|
20
|
-
* Eviction:
|
|
21
|
-
* - LRU by **total byte budget**, not entry count — definitions range from tens
|
|
22
|
-
* of KB to hundreds of MB, so an entry-count cap would either pin gigabytes or
|
|
23
|
-
* evict uselessly. `COMPUTE_DEFINITION_BYTE_CACHE_MB` sets the budget; `0`
|
|
24
|
-
* disables the cache (every `getOrLoad` calls `load` and caches nothing).
|
|
25
|
-
* - No TTL: version ids are immutable, so a cached entry can never go stale.
|
|
26
|
-
* - An entry larger than the whole budget is served but never retained (caching
|
|
27
|
-
* it would immediately evict everything including itself).
|
|
28
|
-
*/
|
|
29
|
-
/**
|
|
30
|
-
* Per-`getOrLoad` outcome, observable AFTER the scheduler has (or hasn't) called
|
|
31
|
-
* `load()`. Lets the solve pipeline emit a `def_bytes` Server-Timing verdict:
|
|
32
|
-
* - `load` never invoked → the scheduler served a pointer-known solve without
|
|
33
|
-
* bytes (`skipped`);
|
|
34
|
-
* - `load` invoked, `fromCache` true → warm byte-cache entry (`hit`);
|
|
35
|
-
* - `load` invoked, `fromCache` false → fell through to the loader (`miss`).
|
|
36
|
-
*/
|
|
37
|
-
export interface ByteRefOutcome {
|
|
38
|
-
/** True once `load()` has been called at least once on this ref. */
|
|
39
|
-
loaded: boolean;
|
|
40
|
-
/** When `loaded`, whether the bytes came from a warm cache entry. */
|
|
41
|
-
fromCache: boolean;
|
|
42
|
-
}
|
|
43
|
-
/** A definition reference shaped for `@selvajs/compute`'s `DefinitionRef`. */
|
|
44
|
-
export interface ByteCacheRef {
|
|
45
|
-
/** Immutable identity (a version UUID). Keys the scheduler's caches too. */
|
|
46
|
-
key: string;
|
|
47
|
-
/** Materialize the bytes — served from cache when warm, else via the loader. */
|
|
48
|
-
load: () => Promise<Uint8Array>;
|
|
49
|
-
/** Mutable outcome of this ref's `load()` (see {@link ByteRefOutcome}). */
|
|
50
|
-
outcome: ByteRefOutcome;
|
|
51
|
-
}
|
|
52
|
-
/** Hit/miss/eviction counters for observability (Server-Timing, admin debug). */
|
|
53
|
-
export interface ByteCacheStats {
|
|
54
|
-
/** `load()` served from a warm entry (no loader call). */
|
|
55
|
-
hits: number;
|
|
56
|
-
/** `load()` fell through to the loader (cold key, or caching disabled). */
|
|
57
|
-
misses: number;
|
|
58
|
-
/** Entries dropped by the byte-budget LRU. */
|
|
59
|
-
evictions: number;
|
|
60
|
-
/** Current retained entry count. */
|
|
61
|
-
entries: number;
|
|
62
|
-
/** Current retained bytes. */
|
|
63
|
-
bytes: number;
|
|
64
|
-
}
|
|
65
|
-
export interface DefinitionByteCache {
|
|
66
|
-
/**
|
|
67
|
-
* Return a `DefinitionRef` whose `load()` serves `versionId`'s bytes from the
|
|
68
|
-
* cache when warm, otherwise calls `load` (and caches the result). The
|
|
69
|
-
* `DefinitionRef` is cheap to build and moves no bytes until `load()` runs —
|
|
70
|
-
* so handing it to the scheduler for a pointer-known solve costs nothing.
|
|
71
|
-
*/
|
|
72
|
-
getOrLoad(versionId: string, load: () => Promise<Uint8Array>): ByteCacheRef;
|
|
73
|
-
/** Snapshot of the counters (see {@link ByteCacheStats}). */
|
|
74
|
-
stats(): ByteCacheStats;
|
|
75
|
-
/** Drop everything (test seam / eviction on definition delete if ever needed). */
|
|
76
|
-
clear(): void;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Build a definition-byte cache with a total-byte LRU budget.
|
|
80
|
-
*
|
|
81
|
-
* @param maxBytes total retained-byte budget; `0` (or negative) disables caching
|
|
82
|
-
* entirely — every `load()` calls the loader and nothing is retained.
|
|
83
|
-
*/
|
|
84
|
-
export declare function createDefinitionByteCache(maxBytes: number): DefinitionByteCache;
|
|
85
|
-
//# sourceMappingURL=definition-byte-cache.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"definition-byte-cache.d.ts","sourceRoot":"","sources":["../../src/compute/definition-byte-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC9B,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,SAAS,EAAE,OAAO,CAAC;CACnB;AAED,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC5B,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,2EAA2E;IAC3E,OAAO,EAAE,cAAc,CAAC;CACxB;AAED,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC9B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IACnC;;;;;OAKG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,YAAY,CAAC;IAC5E,6DAA6D;IAC7D,KAAK,IAAI,cAAc,CAAC;IACxB,kFAAkF;IAClF,KAAK,IAAI,IAAI,CAAC;CACd;AAMD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CA2E/E"}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-process cache of `.gh` definition bytes, keyed by immutable **version id**.
|
|
3
|
-
*
|
|
4
|
-
* The solve path used to eagerly `storage.get(fileKey)` on every request — moving
|
|
5
|
-
* multi-MB definition bytes off disk/S3 into memory before the scheduler could
|
|
6
|
-
* decide it didn't need them (the compute server already holds the definition via
|
|
7
|
-
* the pointer/`reuseServerDefinitionCache` fast path). This cache turns that eager
|
|
8
|
-
* read into a lazy `DefinitionRef.load()`: the scheduler calls `load()` ONLY when
|
|
9
|
-
* an upload is unavoidable, and even then a warm entry serves the bytes without
|
|
10
|
-
* touching storage. A pointer-known re-solve therefore moves ZERO definition bytes.
|
|
11
|
-
*
|
|
12
|
-
* Keying discipline (the whole reason this module exists):
|
|
13
|
-
* - The key is the **version id**, an immutable UUID — never a `fileKey`, which
|
|
14
|
-
* a delete-latest-then-reupload can REUSE for different content (see the
|
|
15
|
-
* monotonic-version-number fix). A reused key would serve one version's bytes
|
|
16
|
-
* for another: silent cache poisoning with no diagnostic. Callers pass
|
|
17
|
-
* `version.id`, and the returned `DefinitionRef.key` is that same id, so the
|
|
18
|
-
* scheduler's own result/pointer cache keys on the same immutable identity.
|
|
19
|
-
*
|
|
20
|
-
* Eviction:
|
|
21
|
-
* - LRU by **total byte budget**, not entry count — definitions range from tens
|
|
22
|
-
* of KB to hundreds of MB, so an entry-count cap would either pin gigabytes or
|
|
23
|
-
* evict uselessly. `COMPUTE_DEFINITION_BYTE_CACHE_MB` sets the budget; `0`
|
|
24
|
-
* disables the cache (every `getOrLoad` calls `load` and caches nothing).
|
|
25
|
-
* - No TTL: version ids are immutable, so a cached entry can never go stale.
|
|
26
|
-
* - An entry larger than the whole budget is served but never retained (caching
|
|
27
|
-
* it would immediately evict everything including itself).
|
|
28
|
-
*/
|
|
29
|
-
/**
|
|
30
|
-
* Build a definition-byte cache with a total-byte LRU budget.
|
|
31
|
-
*
|
|
32
|
-
* @param maxBytes total retained-byte budget; `0` (or negative) disables caching
|
|
33
|
-
* entirely — every `load()` calls the loader and nothing is retained.
|
|
34
|
-
*/
|
|
35
|
-
export function createDefinitionByteCache(maxBytes) {
|
|
36
|
-
const budget = Math.max(0, Math.floor(maxBytes));
|
|
37
|
-
// Map preserves insertion order → iteration order IS LRU order; re-inserting
|
|
38
|
-
// on access moves an entry to the most-recently-used (last) position.
|
|
39
|
-
const cache = new Map();
|
|
40
|
-
let retainedBytes = 0;
|
|
41
|
-
const stats = { hits: 0, misses: 0, evictions: 0 };
|
|
42
|
-
function touch(key, entry) {
|
|
43
|
-
cache.delete(key);
|
|
44
|
-
cache.set(key, entry);
|
|
45
|
-
}
|
|
46
|
-
function evictToFit() {
|
|
47
|
-
// Evict least-recently-used (first in insertion order) until within budget.
|
|
48
|
-
while (retainedBytes > budget && cache.size > 0) {
|
|
49
|
-
const oldestKey = cache.keys().next().value;
|
|
50
|
-
const oldest = cache.get(oldestKey);
|
|
51
|
-
cache.delete(oldestKey);
|
|
52
|
-
retainedBytes -= oldest.bytes.byteLength;
|
|
53
|
-
stats.evictions += 1;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function store(key, bytes) {
|
|
57
|
-
if (budget === 0)
|
|
58
|
-
return;
|
|
59
|
-
// An entry bigger than the whole budget is never retained — caching it would
|
|
60
|
-
// evict everything (including itself) on the next `evictToFit`. Serve it
|
|
61
|
-
// through, cache nothing.
|
|
62
|
-
if (bytes.byteLength > budget)
|
|
63
|
-
return;
|
|
64
|
-
const existing = cache.get(key);
|
|
65
|
-
if (existing)
|
|
66
|
-
retainedBytes -= existing.bytes.byteLength;
|
|
67
|
-
cache.set(key, { bytes });
|
|
68
|
-
retainedBytes += bytes.byteLength;
|
|
69
|
-
evictToFit();
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
getOrLoad(versionId, load) {
|
|
73
|
-
const outcome = { loaded: false, fromCache: false };
|
|
74
|
-
return {
|
|
75
|
-
key: versionId,
|
|
76
|
-
outcome,
|
|
77
|
-
load: async () => {
|
|
78
|
-
const hit = cache.get(versionId);
|
|
79
|
-
if (hit) {
|
|
80
|
-
touch(versionId, hit);
|
|
81
|
-
stats.hits += 1;
|
|
82
|
-
outcome.loaded = true;
|
|
83
|
-
outcome.fromCache = true;
|
|
84
|
-
return hit.bytes;
|
|
85
|
-
}
|
|
86
|
-
stats.misses += 1;
|
|
87
|
-
outcome.loaded = true;
|
|
88
|
-
outcome.fromCache = false;
|
|
89
|
-
const bytes = await load();
|
|
90
|
-
store(versionId, bytes);
|
|
91
|
-
return bytes;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
},
|
|
95
|
-
stats() {
|
|
96
|
-
return {
|
|
97
|
-
hits: stats.hits,
|
|
98
|
-
misses: stats.misses,
|
|
99
|
-
evictions: stats.evictions,
|
|
100
|
-
entries: cache.size,
|
|
101
|
-
bytes: retainedBytes
|
|
102
|
-
};
|
|
103
|
-
},
|
|
104
|
-
clear() {
|
|
105
|
-
cache.clear();
|
|
106
|
-
retainedBytes = 0;
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
//# sourceMappingURL=definition-byte-cache.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"definition-byte-cache.js","sourceRoot":"","sources":["../../src/compute/definition-byte-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory `ISolveResultCache` — the first durable L2 backend (H1).
|
|
3
|
-
*
|
|
4
|
-
* Two eviction dimensions, both required (see CACHING.md H1):
|
|
5
|
-
*
|
|
6
|
-
* 1. **Per-definition count quota, LRU-within-definition.** Each definition
|
|
7
|
-
* gets its own budget of remembered solves (`maxEntriesForDefinition`,
|
|
8
|
-
* passed at write time). A slider-heavy definition churns ONLY its own
|
|
9
|
-
* entries — it can never evict another definition's. This is what makes a
|
|
10
|
-
* wide-input-space definition harmless structurally instead of via an
|
|
11
|
-
* off-switch.
|
|
12
|
-
* 2. **Global byte backstop, global-LRU.** Entries range KB→100s of MB, so a
|
|
13
|
-
* count quota can't bound heap. `maxTotalBytes` evicts the globally
|
|
14
|
-
* least-recently-used entry across every definition until total retained
|
|
15
|
-
* bytes fit. Authors think in counts; operators think in bytes; both hold.
|
|
16
|
-
*
|
|
17
|
-
* The key is `(orgId, definitionId, versionId, inputKey)`. `definitionId` scopes
|
|
18
|
-
* the count quota; the full tuple addresses the entry. Version keying means a
|
|
19
|
-
* publish is a fresh keyspace and there is nothing to invalidate — entries just
|
|
20
|
-
* age out under the two budgets.
|
|
21
|
-
*
|
|
22
|
-
* Best-effort contract: this never throws. A `get` miss and a `set` drop are both
|
|
23
|
-
* normal. Correctness never depends on a hit.
|
|
24
|
-
*/
|
|
25
|
-
import type { ISolveResultCache } from '@selvajs/platform';
|
|
26
|
-
/** Observability counters for the admin/debug surface (parallels ByteCacheStats). */
|
|
27
|
-
export interface SolveCacheStats {
|
|
28
|
-
/** `get` served a live entry. */
|
|
29
|
-
hits: number;
|
|
30
|
-
/** `get` found nothing. */
|
|
31
|
-
misses: number;
|
|
32
|
-
/** `set` stored a new (or replaced) entry. */
|
|
33
|
-
writes: number;
|
|
34
|
-
/** Entries dropped by the per-definition count quota. */
|
|
35
|
-
quotaEvictions: number;
|
|
36
|
-
/** Entries dropped by the global byte backstop. */
|
|
37
|
-
byteEvictions: number;
|
|
38
|
-
/** Current retained entry count across all definitions. */
|
|
39
|
-
entries: number;
|
|
40
|
-
/** Current retained bytes across all definitions. */
|
|
41
|
-
bytes: number;
|
|
42
|
-
}
|
|
43
|
-
/** A memory backend with its own stats, for wiring + observability. */
|
|
44
|
-
export interface MemorySolveResultCache extends ISolveResultCache {
|
|
45
|
-
stats(): SolveCacheStats;
|
|
46
|
-
/** Drop everything (test seam). */
|
|
47
|
-
clear(): void;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Build an in-memory L2 solve cache.
|
|
51
|
-
*
|
|
52
|
-
* @param maxTotalBytes global retained-byte backstop; `0` (or negative) disables
|
|
53
|
-
* the backstop (per-definition counts are then the only bound — not recommended
|
|
54
|
-
* in-process). An entry larger than the whole backstop is stored-then-immediately
|
|
55
|
-
* evicted (served through, never retained) — same rule as the byte cache.
|
|
56
|
-
*/
|
|
57
|
-
export declare function createMemorySolveResultCache(maxTotalBytes: number): MemorySolveResultCache;
|
|
58
|
-
//# sourceMappingURL=memory-solve-cache.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory-solve-cache.d.ts","sourceRoot":"","sources":["../../src/compute/memory-solve-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EACX,iBAAiB,EAIjB,MAAM,mBAAmB,CAAC;AAE3B,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC/B,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;CACd;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAChE,KAAK,IAAI,eAAe,CAAC;IACzB,mCAAmC;IACnC,KAAK,IAAI,IAAI,CAAC;CACd;AAOD;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,GAAG,sBAAsB,CAoI1F"}
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory-solve-cache.js","sourceRoot":"","sources":["../../src/compute/memory-solve-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* L2 cache entry format (R6). An entry is the gzipped response body plus a tiny
|
|
3
|
-
* metadata header, so a hit is near-CPU-free: no `JSON.stringify`, no re-solve —
|
|
4
|
-
* gzip clients get the stored bytes verbatim, and the header carries just enough
|
|
5
|
-
* to rebuild the `ok` outcome (error/warning counts for the metric, uncompressed
|
|
6
|
-
* length for the `Content-Length` on a gunzip fallback).
|
|
7
|
-
*
|
|
8
|
-
* Wire layout (opaque to the `ISolveResultCache` backend, which sees only bytes):
|
|
9
|
-
*
|
|
10
|
-
* [ 4 bytes: header length N, big-endian uint32 ]
|
|
11
|
-
* [ N bytes: UTF-8 JSON EnvelopeHeader ]
|
|
12
|
-
* [ rest: gzipped JSON response body ]
|
|
13
|
-
*
|
|
14
|
-
* The `algo` echo (M3) is already stripped at the source in the package's
|
|
15
|
-
* `runSolve`, so the body never carries the base64 definition — entries stay
|
|
16
|
-
* small without a strip step here.
|
|
17
|
-
*/
|
|
18
|
-
/** Metadata stored beside the gzipped body. Small; parsed on every hit. */
|
|
19
|
-
export interface EnvelopeHeader {
|
|
20
|
-
/** Grasshopper runtime error count, for the solve metric. */
|
|
21
|
-
errorCount: number;
|
|
22
|
-
/** Grasshopper runtime warning count, for the solve metric. */
|
|
23
|
-
warningCount: number;
|
|
24
|
-
/** Uncompressed JSON body length in bytes (Content-Length for a gunzip fallback). */
|
|
25
|
-
serializedBytes: number;
|
|
26
|
-
/**
|
|
27
|
-
* SHA-256 hex of the canonical input preimage (H2 defense-in-depth). A hit
|
|
28
|
-
* verifies this equals the requesting key's hash before serving — a wide-hash
|
|
29
|
-
* collision that produced the same storage key still can't serve the wrong
|
|
30
|
-
* geometry.
|
|
31
|
-
*/
|
|
32
|
-
inputHash: string;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Encode a cache entry from the gzipped body + header. The body MUST already be
|
|
36
|
-
* gzipped (the pipeline gzips before storing so every hit serves compressed
|
|
37
|
-
* bytes; a rare non-gzip client gunzips on read).
|
|
38
|
-
*/
|
|
39
|
-
export declare function encodeSolveCacheEntry(header: EnvelopeHeader, gzippedBody: Uint8Array): Uint8Array;
|
|
40
|
-
/** A decoded entry: the parsed header + the still-gzipped body slice. */
|
|
41
|
-
export interface DecodedSolveCacheEntry {
|
|
42
|
-
header: EnvelopeHeader;
|
|
43
|
-
gzippedBody: Uint8Array;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Decode a cache entry. Returns `null` on any malformed input (truncated, bad
|
|
47
|
-
* length prefix, non-JSON header) — a corrupt entry is treated as a miss, never
|
|
48
|
-
* an error, matching the best-effort cache contract.
|
|
49
|
-
*/
|
|
50
|
-
export declare function decodeSolveCacheEntry(bytes: Uint8Array): DecodedSolveCacheEntry | null;
|
|
51
|
-
/** Gunzip a stored body back to the JSON string, for a non-gzip client (rare). */
|
|
52
|
-
export declare function gunzipEntryBody(gzippedBody: Uint8Array): string;
|
|
53
|
-
//# sourceMappingURL=solve-cache-envelope.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"solve-cache-envelope.d.ts","sourceRoot":"","sources":["../../src/compute/solve-cache-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC9B,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,GAAG,UAAU,CAUjG;AAED,yEAAyE;AACzE,MAAM,WAAW,sBAAsB;IACtC,MAAM,EAAE,cAAc,CAAC;IACvB,WAAW,EAAE,UAAU,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,GAAG,IAAI,CA2BtF;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,WAAW,EAAE,UAAU,GAAG,MAAM,CAE/D"}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* L2 cache entry format (R6). An entry is the gzipped response body plus a tiny
|
|
3
|
-
* metadata header, so a hit is near-CPU-free: no `JSON.stringify`, no re-solve —
|
|
4
|
-
* gzip clients get the stored bytes verbatim, and the header carries just enough
|
|
5
|
-
* to rebuild the `ok` outcome (error/warning counts for the metric, uncompressed
|
|
6
|
-
* length for the `Content-Length` on a gunzip fallback).
|
|
7
|
-
*
|
|
8
|
-
* Wire layout (opaque to the `ISolveResultCache` backend, which sees only bytes):
|
|
9
|
-
*
|
|
10
|
-
* [ 4 bytes: header length N, big-endian uint32 ]
|
|
11
|
-
* [ N bytes: UTF-8 JSON EnvelopeHeader ]
|
|
12
|
-
* [ rest: gzipped JSON response body ]
|
|
13
|
-
*
|
|
14
|
-
* The `algo` echo (M3) is already stripped at the source in the package's
|
|
15
|
-
* `runSolve`, so the body never carries the base64 definition — entries stay
|
|
16
|
-
* small without a strip step here.
|
|
17
|
-
*/
|
|
18
|
-
import { gunzipSync } from 'node:zlib';
|
|
19
|
-
/**
|
|
20
|
-
* Encode a cache entry from the gzipped body + header. The body MUST already be
|
|
21
|
-
* gzipped (the pipeline gzips before storing so every hit serves compressed
|
|
22
|
-
* bytes; a rare non-gzip client gunzips on read).
|
|
23
|
-
*/
|
|
24
|
-
export function encodeSolveCacheEntry(header, gzippedBody) {
|
|
25
|
-
const headerJson = Buffer.from(JSON.stringify(header), 'utf8');
|
|
26
|
-
const out = Buffer.allocUnsafe(4 + headerJson.byteLength + gzippedBody.byteLength);
|
|
27
|
-
out.writeUInt32BE(headerJson.byteLength, 0);
|
|
28
|
-
headerJson.copy(out, 4);
|
|
29
|
-
Buffer.from(gzippedBody.buffer, gzippedBody.byteOffset, gzippedBody.byteLength).copy(out, 4 + headerJson.byteLength);
|
|
30
|
-
return out;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Decode a cache entry. Returns `null` on any malformed input (truncated, bad
|
|
34
|
-
* length prefix, non-JSON header) — a corrupt entry is treated as a miss, never
|
|
35
|
-
* an error, matching the best-effort cache contract.
|
|
36
|
-
*/
|
|
37
|
-
export function decodeSolveCacheEntry(bytes) {
|
|
38
|
-
if (bytes.byteLength < 4)
|
|
39
|
-
return null;
|
|
40
|
-
const buf = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
41
|
-
const headerLen = buf.readUInt32BE(0);
|
|
42
|
-
const headerEnd = 4 + headerLen;
|
|
43
|
-
if (headerEnd > buf.byteLength)
|
|
44
|
-
return null;
|
|
45
|
-
let header;
|
|
46
|
-
try {
|
|
47
|
-
header = JSON.parse(buf.toString('utf8', 4, headerEnd));
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
if (typeof header?.errorCount !== 'number' ||
|
|
53
|
-
typeof header?.warningCount !== 'number' ||
|
|
54
|
-
typeof header?.serializedBytes !== 'number' ||
|
|
55
|
-
typeof header?.inputHash !== 'string') {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
// Slice (view, no copy) of the gzipped remainder.
|
|
59
|
-
const gzippedBody = new Uint8Array(bytes.buffer, bytes.byteOffset + headerEnd, bytes.byteLength - headerEnd);
|
|
60
|
-
return { header, gzippedBody };
|
|
61
|
-
}
|
|
62
|
-
/** Gunzip a stored body back to the JSON string, for a non-gzip client (rare). */
|
|
63
|
-
export function gunzipEntryBody(gzippedBody) {
|
|
64
|
-
return gunzipSync(gzippedBody).toString('utf8');
|
|
65
|
-
}
|
|
66
|
-
//# sourceMappingURL=solve-cache-envelope.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"solve-cache-envelope.js","sourceRoot":"","sources":["../../src/compute/solve-cache-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAmBvC;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAsB,EAAE,WAAuB;IACpF,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,UAAU,CAAC,UAAU,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACnF,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC5C,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACxB,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,UAAU,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC,IAAI,CACnF,GAAG,EACH,CAAC,GAAG,UAAU,CAAC,UAAU,CACzB,CAAC;IACF,OAAO,GAAG,CAAC;AACZ,CAAC;AAQD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAiB;IACtD,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1E,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,CAAC,GAAG,SAAS,CAAC;IAChC,IAAI,SAAS,GAAG,GAAG,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC5C,IAAI,MAAsB,CAAC;IAC3B,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,SAAS,CAAC,CAAmB,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;IACD,IACC,OAAO,MAAM,EAAE,UAAU,KAAK,QAAQ;QACtC,OAAO,MAAM,EAAE,YAAY,KAAK,QAAQ;QACxC,OAAO,MAAM,EAAE,eAAe,KAAK,QAAQ;QAC3C,OAAO,MAAM,EAAE,SAAS,KAAK,QAAQ,EACpC,CAAC;QACF,OAAO,IAAI,CAAC;IACb,CAAC;IACD,kDAAkD;IAClD,MAAM,WAAW,GAAG,IAAI,UAAU,CACjC,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,UAAU,GAAG,SAAS,EAC5B,KAAK,CAAC,UAAU,GAAG,SAAS,CAC5B,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AAChC,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,WAAuB;IACtD,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACjD,CAAC"}
|