Compare commits

..

No commits in common. "06d58fe85b1be5752e2fd52acff2e9d519c91f4b" and "51f7c433c7cfc034339531ea3217919a8e804198" have entirely different histories.

4 changed files with 30 additions and 143 deletions

99
src/cache/client.ts vendored
View File

@ -1,5 +1,4 @@
import { HttpClient } from "../core/http"; import { HttpClient } from "../core/http";
import { SDKError } from "../errors";
export interface CacheGetRequest { export interface CacheGetRequest {
dmap: string; dmap: string;
@ -36,19 +35,6 @@ export interface CacheDeleteResponse {
dmap: string; dmap: string;
} }
export interface CacheMultiGetRequest {
dmap: string;
keys: string[];
}
export interface CacheMultiGetResponse {
results: Array<{
key: string;
value: any;
}>;
dmap: string;
}
export interface CacheScanRequest { export interface CacheScanRequest {
dmap: string; dmap: string;
match?: string; // Optional regex pattern match?: string; // Optional regex pattern
@ -81,27 +67,12 @@ export class CacheClient {
/** /**
* Get a value from cache * Get a value from cache
* Returns null if the key is not found (cache miss/expired), which is normal behavior
*/ */
async get(dmap: string, key: string): Promise<CacheGetResponse | null> { async get(dmap: string, key: string): Promise<CacheGetResponse> {
try { return this.httpClient.post<CacheGetResponse>("/v1/cache/get", {
return await this.httpClient.post<CacheGetResponse>("/v1/cache/get", { dmap,
dmap, key,
key, });
});
} catch (error) {
// Cache misses (404 or "key not found" messages) are normal behavior - return null instead of throwing
if (
error instanceof SDKError &&
(error.httpStatus === 404 ||
(error.httpStatus === 500 &&
error.message?.toLowerCase().includes("key not found")))
) {
return null;
}
// Re-throw other errors (network issues, server errors, etc.)
throw error;
}
} }
/** /**
@ -131,66 +102,6 @@ export class CacheClient {
}); });
} }
/**
* Get multiple values from cache in a single request
* Returns a map of key -> value (or null if not found)
* Gracefully handles 404 errors (endpoint not implemented) by returning empty results
*/
async multiGet(
dmap: string,
keys: string[]
): Promise<Map<string, any | null>> {
try {
if (keys.length === 0) {
return new Map();
}
const response = await this.httpClient.post<CacheMultiGetResponse>(
"/v1/cache/mget",
{
dmap,
keys,
}
);
// Convert array to Map
const resultMap = new Map<string, any | null>();
// First, mark all keys as null (cache miss)
keys.forEach((key) => {
resultMap.set(key, null);
});
// Then, update with found values
if (response.results) {
response.results.forEach(({ key, value }) => {
resultMap.set(key, value);
});
}
return resultMap;
} catch (error) {
// Handle 404 errors silently (endpoint not implemented on backend)
// This is expected behavior when the backend doesn't support multiGet yet
if (error instanceof SDKError && error.httpStatus === 404) {
// Return map with all nulls silently - caller can fall back to individual gets
const resultMap = new Map<string, any | null>();
keys.forEach((key) => {
resultMap.set(key, null);
});
return resultMap;
}
// Log and return empty results for other errors
const resultMap = new Map<string, any | null>();
keys.forEach((key) => {
resultMap.set(key, null);
});
console.error(`[CacheClient] Error in multiGet for ${dmap}:`, error);
return resultMap;
}
}
/** /**
* Scan keys in a distributed map, optionally matching a regex pattern * Scan keys in a distributed map, optionally matching a regex pattern
*/ */

View File

@ -152,13 +152,11 @@ export class HttpClient {
} catch (error) { } catch (error) {
const duration = performance.now() - startTime; const duration = performance.now() - startTime;
if (typeof console !== "undefined") { if (typeof console !== "undefined") {
// Cache "key not found" (404 or error message) is expected behavior - don't log as error // Cache "key not found" (404) is expected behavior - don't log as error
const isCacheGetNotFound = const isCacheGetNotFound =
path === "/v1/cache/get" && path === "/v1/cache/get" &&
error instanceof SDKError && error instanceof SDKError &&
(error.httpStatus === 404 || error.httpStatus === 404;
(error.httpStatus === 500 &&
error.message?.toLowerCase().includes("key not found")));
// "Not found" (404) for blocked_users is expected behavior - don't log as error // "Not found" (404) for blocked_users is expected behavior - don't log as error
// This happens when checking if users are blocked (most users aren't blocked) // This happens when checking if users are blocked (most users aren't blocked)
@ -179,31 +177,8 @@ export class HttpClient {
} }
})(); })();
// "Not found" (404) for conversation_participants is expected behavior - don't log as error if (isCacheGetNotFound || isBlockedUsersNotFound) {
// This happens when checking if a user is a participant (e.g., on first group join) // Log cache miss or non-blocked status as debug/info, not error
const isConversationParticipantNotFound =
path === "/v1/rqlite/find-one" &&
error instanceof SDKError &&
error.httpStatus === 404 &&
options.body &&
(() => {
try {
const body =
typeof options.body === "string"
? JSON.parse(options.body)
: options.body;
return body.table === "conversation_participants";
} catch {
return false;
}
})();
if (
isCacheGetNotFound ||
isBlockedUsersNotFound ||
isConversationParticipantNotFound
) {
// Log cache miss, non-blocked status, or non-participant status as debug/info, not error
// These are expected behaviors // These are expected behaviors
} else { } else {
console.error( console.error(

View File

@ -102,8 +102,6 @@ export type {
CachePutResponse, CachePutResponse,
CacheDeleteRequest, CacheDeleteRequest,
CacheDeleteResponse, CacheDeleteResponse,
CacheMultiGetRequest,
CacheMultiGetResponse,
CacheScanRequest, CacheScanRequest,
CacheScanResponse, CacheScanResponse,
CacheHealthResponse, CacheHealthResponse,

View File

@ -42,10 +42,9 @@ describe("Cache", () => {
// Get value // Get value
const getResult = await client.cache.get(testDMap, testKey); const getResult = await client.cache.get(testDMap, testKey);
expect(getResult).not.toBeNull(); expect(getResult.key).toBe(testKey);
expect(getResult!.key).toBe(testKey); expect(getResult.value).toBe(testValue);
expect(getResult!.value).toBe(testValue); expect(getResult.dmap).toBe(testDMap);
expect(getResult!.dmap).toBe(testDMap);
}); });
it("should put and get complex objects", async () => { it("should put and get complex objects", async () => {
@ -62,10 +61,9 @@ describe("Cache", () => {
// Get object // Get object
const getResult = await client.cache.get(testDMap, testKey); const getResult = await client.cache.get(testDMap, testKey);
expect(getResult).not.toBeNull(); expect(getResult.value).toBeDefined();
expect(getResult!.value).toBeDefined(); expect(getResult.value.name).toBe(testValue.name);
expect(getResult!.value.name).toBe(testValue.name); expect(getResult.value.age).toBe(testValue.age);
expect(getResult!.value.age).toBe(testValue.age);
}); });
it("should put value with TTL", async () => { it("should put value with TTL", async () => {
@ -84,8 +82,7 @@ describe("Cache", () => {
// Verify value exists // Verify value exists
const getResult = await client.cache.get(testDMap, testKey); const getResult = await client.cache.get(testDMap, testKey);
expect(getResult).not.toBeNull(); expect(getResult.value).toBe(testValue);
expect(getResult!.value).toBe(testValue);
}); });
it("should delete a value", async () => { it("should delete a value", async () => {
@ -98,17 +95,20 @@ describe("Cache", () => {
// Verify it exists // Verify it exists
const before = await client.cache.get(testDMap, testKey); const before = await client.cache.get(testDMap, testKey);
expect(before).not.toBeNull(); expect(before.value).toBe(testValue);
expect(before!.value).toBe(testValue);
// Delete value // Delete value
const deleteResult = await client.cache.delete(testDMap, testKey); const deleteResult = await client.cache.delete(testDMap, testKey);
expect(deleteResult.status).toBe("ok"); expect(deleteResult.status).toBe("ok");
expect(deleteResult.key).toBe(testKey); expect(deleteResult.key).toBe(testKey);
// Verify it's deleted (should return null, not throw) // Verify it's deleted
const after = await client.cache.get(testDMap, testKey); try {
expect(after).toBeNull(); await client.cache.get(testDMap, testKey);
expect.fail("Expected get to fail after delete");
} catch (err: any) {
expect(err.message).toBeDefined();
}
}); });
it("should scan keys", async () => { it("should scan keys", async () => {
@ -148,9 +148,12 @@ describe("Cache", () => {
const client = await createTestClient(); const client = await createTestClient();
const nonExistentKey = "non-existent-key"; const nonExistentKey = "non-existent-key";
// Cache misses should return null, not throw an error try {
const result = await client.cache.get(testDMap, nonExistentKey); await client.cache.get(testDMap, nonExistentKey);
expect(result).toBeNull(); expect.fail("Expected get to fail for non-existent key");
} catch (err: any) {
expect(err.message).toBeDefined();
}
}); });
it("should handle empty dmap name", async () => { it("should handle empty dmap name", async () => {