-
-
Notifications
You must be signed in to change notification settings - Fork 9
fix: mock plugin blob stores #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -194,6 +194,7 @@ function registrationReturnValue(name, args, context) { | |
|
|
||
| function createRuntimeContext(options) { | ||
| const runtime = options.runtime ?? {}; | ||
| const blobStores = new Map(); | ||
| const syncKeyedStores = new Map(); | ||
| return { | ||
| ...runtime, | ||
|
|
@@ -204,6 +205,15 @@ function createRuntimeContext(options) { | |
| tts: runtime.tts ?? {}, | ||
| state: { | ||
| resolveStateDir: () => options.stateDir ?? process.cwd(), | ||
| openBlobStore(storeOptions) { | ||
| const existing = blobStores.get(storeOptions.namespace); | ||
| if (existing) { | ||
| return existing; | ||
| } | ||
| const store = createBlobStoreContext(storeOptions); | ||
| blobStores.set(storeOptions.namespace, store); | ||
| return store; | ||
| }, | ||
| openSyncKeyedStore({ namespace }) { | ||
| const existing = syncKeyedStores.get(namespace); | ||
| if (existing) { | ||
|
|
@@ -218,6 +228,85 @@ function createRuntimeContext(options) { | |
| }; | ||
| } | ||
|
|
||
| function createBlobStoreContext(options) { | ||
| const values = new Map(); | ||
| const entryInfo = (key, entry) => ({ | ||
| key, | ||
| metadata: entry.metadata, | ||
| sizeBytes: entry.bytes.byteLength, | ||
| createdAt: entry.createdAt, | ||
| ...(entry.expiresAt === undefined ? {} : { expiresAt: entry.expiresAt }), | ||
| }); | ||
| const read = (key) => { | ||
| const entry = values.get(key); | ||
| if (!entry) { | ||
| return undefined; | ||
| } | ||
| if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) { | ||
| return undefined; | ||
| } | ||
| return { ...entryInfo(key, entry), bytes: Uint8Array.from(entry.bytes) }; | ||
| }; | ||
| const register = async (key, bytes, metadata, registerOptions) => { | ||
| const createdAt = Date.now(); | ||
| const ttlMs = registerOptions?.ttlMs ?? options.defaultTtlMs; | ||
| values.set(key, { | ||
| bytes: Uint8Array.from(bytes), | ||
| metadata, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If a plugin mutates the metadata object after Useful? React with 👍 / 👎. |
||
| createdAt, | ||
| ...(ttlMs === undefined ? {} : { expiresAt: createdAt + ttlMs }), | ||
| }); | ||
| }; | ||
| return { | ||
| register, | ||
| async registerIfAbsent(key, bytes, metadata, registerOptions) { | ||
| if (values.has(key)) { | ||
| return false; | ||
| } | ||
| await register(key, bytes, metadata, registerOptions); | ||
| return true; | ||
| }, | ||
| async lookup(key) { | ||
| return read(key); | ||
| }, | ||
| async entries() { | ||
| return [...values.keys()].flatMap((key) => { | ||
| const entry = read(key); | ||
| if (!entry) { | ||
| return []; | ||
| } | ||
| const { bytes: _bytes, ...info } = entry; | ||
| return [info]; | ||
| }); | ||
| }, | ||
| async delete(key) { | ||
| return values.delete(key); | ||
| }, | ||
| async deleteExpiredKey(key) { | ||
| const entry = values.get(key); | ||
| if (!entry || entry.expiresAt === undefined || entry.expiresAt > Date.now()) { | ||
| return undefined; | ||
| } | ||
| values.delete(key); | ||
| return entryInfo(key, entry); | ||
| }, | ||
| async deleteExpired() { | ||
| const expired = []; | ||
| for (const [key, entry] of values) { | ||
| if (entry.expiresAt === undefined || entry.expiresAt > Date.now()) { | ||
| continue; | ||
| } | ||
| values.delete(key); | ||
| expired.push(entryInfo(key, entry)); | ||
| } | ||
| return expired; | ||
| }, | ||
| async clear() { | ||
| values.clear(); | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| function createSyncKeyedStoreContext() { | ||
| const values = new Map(); | ||
| return { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a plugin creates blobs during registration or synthetic probes, this mock stores every entry unconditionally, so blobs larger than
maxBytesPerEntryor writes that exceedmaxEntries/maxBytesPerNamespacestill succeed even though the real OpenClaw blob store rejects or evicts according to those options. That can make runtime capture report a trusted plugin as compatible when the same registration path would fail under the host runtime; please simulate the configured byte/row limits before inserting.Useful? React with 👍 / 👎.