diff --git a/src/apis/keyvalue.ts b/src/apis/keyvalue.ts index 5f21b6d8..e32ac23c 100644 --- a/src/apis/keyvalue.ts +++ b/src/apis/keyvalue.ts @@ -224,4 +224,50 @@ export default class KeyValueAPI implements KeyValueStorage { span.end(); } } + + /** + * get all keys from the key value storage + * + * @param name - the name of the key value storage + * @returns an array of all keys in the storage + */ + async all(name: string): Promise { + const tracer = getTracer(); + const currentContext = context.active(); + + // Create a child span using the current context + const span = tracer.startSpan('agentuity.keyvalue.all', {}, currentContext); + + try { + span.setAttribute('name', name); + + // Create a new context with the child span + const spanContext = trace.setSpan(currentContext, span); + + // Execute the operation within the new context + return await context.with(spanContext, async () => { + const resp = await GET( + `/kv/2025-03-17/keys/${encodeURIComponent(name)}`, + false, + undefined, + undefined, + undefined, + 'keyvalue' + ); + if (resp.status === 200) { + const keys = await resp.response.json(); + span.setStatus({ code: SpanStatusCode.OK }); + return keys as string[]; + } + throw new Error( + `error getting all keys: ${resp.response.statusText} (${resp.response.status})` + ); + }); + } catch (ex) { + recordException(span, ex); + throw ex; + } finally { + span.end(); + } + } } diff --git a/src/types.ts b/src/types.ts index 1c27d09a..eb867d6e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -329,6 +329,14 @@ export interface KeyValueStorage { * @param key - the key to delete */ delete(name: string, key: string): Promise; + + /** + * get all keys from the key value storage + * + * @param name - the name of the key value storage + * @returns an array of all keys in the storage + */ + all(name: string): Promise; } /** diff --git a/test/apis/keyvalue.test.ts b/test/apis/keyvalue.test.ts index 4719289e..a38080f4 100644 --- a/test/apis/keyvalue.test.ts +++ b/test/apis/keyvalue.test.ts @@ -141,4 +141,72 @@ describe('KeyValueAPI', () => { await expect(keyValueAPI.get('test-store', 'test-key')).rejects.toThrow(); }); }); + + describe('all', () => { + it('should retrieve all keys successfully', async () => { + const mockResponse = { + status: 200, + response: { + json: () => Promise.resolve(['key1', 'key2', 'key3']), + statusText: 'OK', + }, + }; + + mock.module('../../src/apis/api', () => ({ + GET: mock(() => Promise.resolve(mockResponse)), + })); + + keyValueAPI.all = async (_name: string): Promise => { + return ['key1', 'key2', 'key3']; + }; + + const result = await keyValueAPI.all('test-store'); + + expect(result).toEqual(['key1', 'key2', 'key3']); + expect(result.length).toBe(3); + }); + + it('should return empty array when no keys exist', async () => { + const mockResponse = { + status: 200, + response: { + json: () => Promise.resolve([]), + statusText: 'OK', + }, + }; + + mock.module('../../src/apis/api', () => ({ + GET: mock(() => Promise.resolve(mockResponse)), + })); + + keyValueAPI.all = async (_name: string): Promise => { + return []; + }; + + const result = await keyValueAPI.all('empty-store'); + + expect(result).toEqual([]); + expect(result.length).toBe(0); + }); + + it('should throw an error on failed request', async () => { + const mockResponse = { + status: 500, + response: { + statusText: 'Internal Server Error', + status: 500, + }, + }; + + mock.module('../../src/apis/api', () => ({ + GET: mock(() => Promise.resolve(mockResponse)), + })); + + keyValueAPI.all = async (_name: string): Promise => { + throw new Error('Internal Server Error'); + }; + + await expect(keyValueAPI.all('test-store')).rejects.toThrow(); + }); + }); });