Update package version to 0.3.0 and introduce CacheClient with caching functionality

- Added CacheClient to manage cache operations including get, put, delete, and scan.
- Updated createClient function to include cache client.
- Added new types and interfaces for cache requests and responses.
- Implemented comprehensive tests for cache functionality, covering health checks, value storage, retrieval, deletion, and scanning.
This commit is contained in:
anonpenguin23 2025-11-03 15:28:30 +02:00
parent 272c6b872c
commit e30e81d0c9
4 changed files with 300 additions and 2 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@debros/network-ts-sdk",
"version": "0.2.5",
"version": "0.3.0",
"description": "TypeScript SDK for DeBros Network Gateway",
"type": "module",
"main": "./dist/index.js",

114
src/cache/client.ts vendored Normal file
View File

@ -0,0 +1,114 @@
import { HttpClient } from "../core/http";
export interface CacheGetRequest {
dmap: string;
key: string;
}
export interface CacheGetResponse {
key: string;
value: any;
dmap: string;
}
export interface CachePutRequest {
dmap: string;
key: string;
value: any;
ttl?: string; // Duration string like "1h", "30m"
}
export interface CachePutResponse {
status: string;
key: string;
dmap: string;
}
export interface CacheDeleteRequest {
dmap: string;
key: string;
}
export interface CacheDeleteResponse {
status: string;
key: string;
dmap: string;
}
export interface CacheScanRequest {
dmap: string;
match?: string; // Optional regex pattern
}
export interface CacheScanResponse {
keys: string[];
count: number;
dmap: string;
}
export interface CacheHealthResponse {
status: string;
service: string;
}
export class CacheClient {
private httpClient: HttpClient;
constructor(httpClient: HttpClient) {
this.httpClient = httpClient;
}
/**
* Check cache service health
*/
async health(): Promise<CacheHealthResponse> {
return this.httpClient.get("/v1/cache/health");
}
/**
* Get a value from cache
*/
async get(dmap: string, key: string): Promise<CacheGetResponse> {
return this.httpClient.post<CacheGetResponse>("/v1/cache/get", {
dmap,
key,
});
}
/**
* Put a value into cache
*/
async put(
dmap: string,
key: string,
value: any,
ttl?: string
): Promise<CachePutResponse> {
return this.httpClient.post<CachePutResponse>("/v1/cache/put", {
dmap,
key,
value,
ttl,
});
}
/**
* Delete a value from cache
*/
async delete(dmap: string, key: string): Promise<CacheDeleteResponse> {
return this.httpClient.post<CacheDeleteResponse>("/v1/cache/delete", {
dmap,
key,
});
}
/**
* Scan keys in a distributed map, optionally matching a regex pattern
*/
async scan(dmap: string, match?: string): Promise<CacheScanResponse> {
return this.httpClient.post<CacheScanResponse>("/v1/cache/scan", {
dmap,
match,
});
}
}

View File

@ -3,6 +3,7 @@ import { AuthClient } from "./auth/client";
import { DBClient } from "./db/client";
import { PubSubClient } from "./pubsub/client";
import { NetworkClient } from "./network/client";
import { CacheClient } from "./cache/client";
import { WSClientConfig } from "./core/ws";
import {
StorageAdapter,
@ -23,6 +24,7 @@ export interface Client {
db: DBClient;
pubsub: PubSubClient;
network: NetworkClient;
cache: CacheClient;
}
export function createClient(config: ClientConfig): Client {
@ -52,16 +54,17 @@ export function createClient(config: ClientConfig): Client {
wsURL,
});
const network = new NetworkClient(httpClient);
const cache = new CacheClient(httpClient);
return {
auth,
db,
pubsub,
network,
cache,
};
}
// Re-exports
export { HttpClient } from "./core/http";
export { WSClient } from "./core/ws";
export { AuthClient } from "./auth/client";
@ -70,6 +73,7 @@ export { QueryBuilder } from "./db/qb";
export { Repository } from "./db/repository";
export { PubSubClient, Subscription } from "./pubsub/client";
export { NetworkClient } from "./network/client";
export { CacheClient } from "./cache/client";
export { SDKError } from "./errors";
export { MemoryStorage, LocalStorageAdapter } from "./auth/types";
export type { StorageAdapter, AuthConfig, WhoAmI } from "./auth/types";
@ -86,3 +90,14 @@ export type {
ProxyRequest,
ProxyResponse,
} from "./network/client";
export type {
CacheGetRequest,
CacheGetResponse,
CachePutRequest,
CachePutResponse,
CacheDeleteRequest,
CacheDeleteResponse,
CacheScanRequest,
CacheScanResponse,
CacheHealthResponse,
} from "./cache/client";

169
tests/e2e/cache.test.ts Normal file
View File

@ -0,0 +1,169 @@
import { describe, it, expect, beforeEach } from "vitest";
import { createTestClient, skipIfNoGateway } from "./setup";
describe("Cache", () => {
if (skipIfNoGateway()) {
console.log("Skipping cache tests - gateway not available");
return;
}
const testDMap = "test-cache";
beforeEach(async () => {
// Clean up test keys before each test
const client = await createTestClient();
try {
const keys = await client.cache.scan(testDMap);
for (const key of keys.keys) {
await client.cache.delete(testDMap, key);
}
} catch (err) {
// Ignore errors during cleanup
}
});
it("should check cache health", async () => {
const client = await createTestClient();
const health = await client.cache.health();
expect(health.status).toBe("ok");
expect(health.service).toBe("olric");
});
it("should put and get a value", async () => {
const client = await createTestClient();
const testKey = "test-key-1";
const testValue = "test-value-1";
// Put value
const putResult = await client.cache.put(testDMap, testKey, testValue);
expect(putResult.status).toBe("ok");
expect(putResult.key).toBe(testKey);
expect(putResult.dmap).toBe(testDMap);
// Get value
const getResult = await client.cache.get(testDMap, testKey);
expect(getResult.key).toBe(testKey);
expect(getResult.value).toBe(testValue);
expect(getResult.dmap).toBe(testDMap);
});
it("should put and get complex objects", async () => {
const client = await createTestClient();
const testKey = "test-key-2";
const testValue = {
name: "John",
age: 30,
tags: ["developer", "golang"],
};
// Put object
await client.cache.put(testDMap, testKey, testValue);
// Get object
const getResult = await client.cache.get(testDMap, testKey);
expect(getResult.value).toBeDefined();
expect(getResult.value.name).toBe(testValue.name);
expect(getResult.value.age).toBe(testValue.age);
});
it("should put value with TTL", async () => {
const client = await createTestClient();
const testKey = "test-key-ttl";
const testValue = "ttl-value";
// Put with TTL
const putResult = await client.cache.put(
testDMap,
testKey,
testValue,
"5m"
);
expect(putResult.status).toBe("ok");
// Verify value exists
const getResult = await client.cache.get(testDMap, testKey);
expect(getResult.value).toBe(testValue);
});
it("should delete a value", async () => {
const client = await createTestClient();
const testKey = "test-key-delete";
const testValue = "delete-me";
// Put value
await client.cache.put(testDMap, testKey, testValue);
// Verify it exists
const before = await client.cache.get(testDMap, testKey);
expect(before.value).toBe(testValue);
// Delete value
const deleteResult = await client.cache.delete(testDMap, testKey);
expect(deleteResult.status).toBe("ok");
expect(deleteResult.key).toBe(testKey);
// Verify it's deleted
try {
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 () => {
const client = await createTestClient();
// Put multiple keys
await client.cache.put(testDMap, "key-1", "value-1");
await client.cache.put(testDMap, "key-2", "value-2");
await client.cache.put(testDMap, "key-3", "value-3");
// Scan all keys
const scanResult = await client.cache.scan(testDMap);
expect(scanResult.count).toBeGreaterThanOrEqual(3);
expect(scanResult.keys).toContain("key-1");
expect(scanResult.keys).toContain("key-2");
expect(scanResult.keys).toContain("key-3");
expect(scanResult.dmap).toBe(testDMap);
});
it("should scan keys with regex match", async () => {
const client = await createTestClient();
// Put keys with different patterns
await client.cache.put(testDMap, "user-1", "value-1");
await client.cache.put(testDMap, "user-2", "value-2");
await client.cache.put(testDMap, "session-1", "value-3");
// Scan with regex match
const scanResult = await client.cache.scan(testDMap, "^user-");
expect(scanResult.count).toBeGreaterThanOrEqual(2);
expect(scanResult.keys).toContain("user-1");
expect(scanResult.keys).toContain("user-2");
expect(scanResult.keys).not.toContain("session-1");
});
it("should handle non-existent key gracefully", async () => {
const client = await createTestClient();
const nonExistentKey = "non-existent-key";
try {
await client.cache.get(testDMap, nonExistentKey);
expect.fail("Expected get to fail for non-existent key");
} catch (err: any) {
expect(err.message).toBeDefined();
}
});
it("should handle empty dmap name", async () => {
const client = await createTestClient();
try {
await client.cache.get("", "test-key");
expect.fail("Expected get to fail with empty dmap");
} catch (err: any) {
expect(err.message).toBeDefined();
}
});
});