mirror of
https://github.com/DeBrosOfficial/network-ts-sdk.git
synced 2025-12-12 18:28:50 +00:00
Compare commits
3 Commits
51f7c433c7
...
06d58fe85b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06d58fe85b | ||
|
|
2cdb78ee1d | ||
|
|
cee0cd62a9 |
99
src/cache/client.ts
vendored
99
src/cache/client.ts
vendored
@ -1,4 +1,5 @@
|
|||||||
import { HttpClient } from "../core/http";
|
import { HttpClient } from "../core/http";
|
||||||
|
import { SDKError } from "../errors";
|
||||||
|
|
||||||
export interface CacheGetRequest {
|
export interface CacheGetRequest {
|
||||||
dmap: string;
|
dmap: string;
|
||||||
@ -35,6 +36,19 @@ 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
|
||||||
@ -67,12 +81,27 @@ 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> {
|
async get(dmap: string, key: string): Promise<CacheGetResponse | null> {
|
||||||
return this.httpClient.post<CacheGetResponse>("/v1/cache/get", {
|
try {
|
||||||
dmap,
|
return await this.httpClient.post<CacheGetResponse>("/v1/cache/get", {
|
||||||
key,
|
dmap,
|
||||||
});
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,6 +131,66 @@ 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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -152,11 +152,13 @@ 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) is expected behavior - don't log as error
|
// Cache "key not found" (404 or error message) 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)
|
||||||
@ -177,8 +179,31 @@ export class HttpClient {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (isCacheGetNotFound || isBlockedUsersNotFound) {
|
// "Not found" (404) for conversation_participants is expected behavior - don't log as error
|
||||||
// Log cache miss or non-blocked status as debug/info, not error
|
// This happens when checking if a user is a participant (e.g., on first group join)
|
||||||
|
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(
|
||||||
|
|||||||
@ -102,6 +102,8 @@ export type {
|
|||||||
CachePutResponse,
|
CachePutResponse,
|
||||||
CacheDeleteRequest,
|
CacheDeleteRequest,
|
||||||
CacheDeleteResponse,
|
CacheDeleteResponse,
|
||||||
|
CacheMultiGetRequest,
|
||||||
|
CacheMultiGetResponse,
|
||||||
CacheScanRequest,
|
CacheScanRequest,
|
||||||
CacheScanResponse,
|
CacheScanResponse,
|
||||||
CacheHealthResponse,
|
CacheHealthResponse,
|
||||||
|
|||||||
@ -42,9 +42,10 @@ describe("Cache", () => {
|
|||||||
|
|
||||||
// Get value
|
// Get value
|
||||||
const getResult = await client.cache.get(testDMap, testKey);
|
const getResult = await client.cache.get(testDMap, testKey);
|
||||||
expect(getResult.key).toBe(testKey);
|
expect(getResult).not.toBeNull();
|
||||||
expect(getResult.value).toBe(testValue);
|
expect(getResult!.key).toBe(testKey);
|
||||||
expect(getResult.dmap).toBe(testDMap);
|
expect(getResult!.value).toBe(testValue);
|
||||||
|
expect(getResult!.dmap).toBe(testDMap);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should put and get complex objects", async () => {
|
it("should put and get complex objects", async () => {
|
||||||
@ -61,9 +62,10 @@ describe("Cache", () => {
|
|||||||
|
|
||||||
// Get object
|
// Get object
|
||||||
const getResult = await client.cache.get(testDMap, testKey);
|
const getResult = await client.cache.get(testDMap, testKey);
|
||||||
expect(getResult.value).toBeDefined();
|
expect(getResult).not.toBeNull();
|
||||||
expect(getResult.value.name).toBe(testValue.name);
|
expect(getResult!.value).toBeDefined();
|
||||||
expect(getResult.value.age).toBe(testValue.age);
|
expect(getResult!.value.name).toBe(testValue.name);
|
||||||
|
expect(getResult!.value.age).toBe(testValue.age);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should put value with TTL", async () => {
|
it("should put value with TTL", async () => {
|
||||||
@ -82,7 +84,8 @@ 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.value).toBe(testValue);
|
expect(getResult).not.toBeNull();
|
||||||
|
expect(getResult!.value).toBe(testValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should delete a value", async () => {
|
it("should delete a value", async () => {
|
||||||
@ -95,20 +98,17 @@ 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.value).toBe(testValue);
|
expect(before).not.toBeNull();
|
||||||
|
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
|
// Verify it's deleted (should return null, not throw)
|
||||||
try {
|
const after = await client.cache.get(testDMap, testKey);
|
||||||
await client.cache.get(testDMap, testKey);
|
expect(after).toBeNull();
|
||||||
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,12 +148,9 @@ describe("Cache", () => {
|
|||||||
const client = await createTestClient();
|
const client = await createTestClient();
|
||||||
const nonExistentKey = "non-existent-key";
|
const nonExistentKey = "non-existent-key";
|
||||||
|
|
||||||
try {
|
// Cache misses should return null, not throw an error
|
||||||
await client.cache.get(testDMap, nonExistentKey);
|
const result = await client.cache.get(testDMap, nonExistentKey);
|
||||||
expect.fail("Expected get to fail for non-existent key");
|
expect(result).toBeNull();
|
||||||
} catch (err: any) {
|
|
||||||
expect(err.message).toBeDefined();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle empty dmap name", async () => {
|
it("should handle empty dmap name", async () => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user