@debros/orama 0.122.4-nightly
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +665 -0
- package/dist/index.d.ts +1334 -0
- package/dist/index.js +2553 -0
- package/dist/index.js.map +1 -0
- package/package.json +82 -0
- package/src/auth/client.ts +276 -0
- package/src/auth/index.ts +3 -0
- package/src/auth/types.ts +62 -0
- package/src/cache/client.ts +203 -0
- package/src/cache/index.ts +14 -0
- package/src/core/http.ts +541 -0
- package/src/core/index.ts +10 -0
- package/src/core/interfaces/IAuthStrategy.ts +28 -0
- package/src/core/interfaces/IHttpTransport.ts +73 -0
- package/src/core/interfaces/IRetryPolicy.ts +20 -0
- package/src/core/interfaces/IWebSocketClient.ts +60 -0
- package/src/core/interfaces/index.ts +4 -0
- package/src/core/transport/AuthHeaderStrategy.ts +108 -0
- package/src/core/transport/RequestLogger.ts +116 -0
- package/src/core/transport/RequestRetryPolicy.ts +53 -0
- package/src/core/transport/TLSConfiguration.ts +53 -0
- package/src/core/transport/index.ts +4 -0
- package/src/core/ws.ts +246 -0
- package/src/db/client.ts +126 -0
- package/src/db/index.ts +13 -0
- package/src/db/qb.ts +111 -0
- package/src/db/repository.ts +128 -0
- package/src/db/types.ts +67 -0
- package/src/errors.ts +38 -0
- package/src/functions/client.ts +62 -0
- package/src/functions/index.ts +2 -0
- package/src/functions/types.ts +21 -0
- package/src/index.ts +201 -0
- package/src/network/client.ts +119 -0
- package/src/network/index.ts +7 -0
- package/src/pubsub/client.ts +361 -0
- package/src/pubsub/index.ts +12 -0
- package/src/pubsub/types.ts +46 -0
- package/src/storage/client.ts +272 -0
- package/src/storage/index.ts +7 -0
- package/src/utils/codec.ts +68 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/platform.ts +44 -0
- package/src/utils/retry.ts +58 -0
- package/src/vault/auth.ts +98 -0
- package/src/vault/client.ts +197 -0
- package/src/vault/crypto/aes.ts +271 -0
- package/src/vault/crypto/hkdf.ts +42 -0
- package/src/vault/crypto/index.ts +27 -0
- package/src/vault/crypto/shamir.ts +173 -0
- package/src/vault/index.ts +65 -0
- package/src/vault/quorum.ts +16 -0
- package/src/vault/transport/fanout.ts +94 -0
- package/src/vault/transport/guardian.ts +285 -0
- package/src/vault/transport/index.ts +19 -0
- package/src/vault/transport/types.ts +101 -0
- package/src/vault/types.ts +62 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { HttpClient } from "../core/http";
|
|
2
|
+
import { AuthConfig, WhoAmI, StorageAdapter, MemoryStorage } from "./types";
|
|
3
|
+
|
|
4
|
+
export class AuthClient {
|
|
5
|
+
private httpClient: HttpClient;
|
|
6
|
+
private storage: StorageAdapter;
|
|
7
|
+
private currentApiKey?: string;
|
|
8
|
+
private currentJwt?: string;
|
|
9
|
+
|
|
10
|
+
constructor(config: {
|
|
11
|
+
httpClient: HttpClient;
|
|
12
|
+
storage?: StorageAdapter;
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
jwt?: string;
|
|
15
|
+
}) {
|
|
16
|
+
this.httpClient = config.httpClient;
|
|
17
|
+
this.storage = config.storage ?? new MemoryStorage();
|
|
18
|
+
this.currentApiKey = config.apiKey;
|
|
19
|
+
this.currentJwt = config.jwt;
|
|
20
|
+
|
|
21
|
+
if (this.currentApiKey) {
|
|
22
|
+
this.httpClient.setApiKey(this.currentApiKey);
|
|
23
|
+
}
|
|
24
|
+
if (this.currentJwt) {
|
|
25
|
+
this.httpClient.setJwt(this.currentJwt);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setApiKey(apiKey: string) {
|
|
30
|
+
this.currentApiKey = apiKey;
|
|
31
|
+
// Don't clear JWT - it will be cleared explicitly on logout
|
|
32
|
+
this.httpClient.setApiKey(apiKey);
|
|
33
|
+
this.storage.set("apiKey", apiKey);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setJwt(jwt: string) {
|
|
37
|
+
this.currentJwt = jwt;
|
|
38
|
+
// Don't clear API key - keep it as fallback for after logout
|
|
39
|
+
this.httpClient.setJwt(jwt);
|
|
40
|
+
this.storage.set("jwt", jwt);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getToken(): string | undefined {
|
|
44
|
+
return this.httpClient.getToken();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async whoami(): Promise<WhoAmI> {
|
|
48
|
+
try {
|
|
49
|
+
const response = await this.httpClient.get<WhoAmI>("/v1/auth/whoami");
|
|
50
|
+
return response;
|
|
51
|
+
} catch {
|
|
52
|
+
return { authenticated: false };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Exchange a stored refresh token for a fresh access token.
|
|
58
|
+
*
|
|
59
|
+
* Pulls the refresh token (and the namespace it was issued for) out of
|
|
60
|
+
* storage — both are persisted by `verify()` after a successful wallet
|
|
61
|
+
* sign-in. The gateway returns a new access token and may rotate the
|
|
62
|
+
* refresh token; we persist the rotated one if present.
|
|
63
|
+
*
|
|
64
|
+
* Bug #239: previously this method (a) sent no body and (b) read the
|
|
65
|
+
* wrong response field, so the call always 400-ed AND silently wrote
|
|
66
|
+
* `undefined` as the in-memory JWT. Both issues fixed.
|
|
67
|
+
*/
|
|
68
|
+
async refresh(): Promise<string> {
|
|
69
|
+
const refreshToken = await this.storage.get("refreshToken");
|
|
70
|
+
if (!refreshToken) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"refresh failed: no refresh token in storage — call verify() first"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const namespace = (await this.storage.get("namespace")) ?? "default";
|
|
76
|
+
|
|
77
|
+
const response = await this.httpClient.post<{
|
|
78
|
+
access_token: string;
|
|
79
|
+
refresh_token?: string;
|
|
80
|
+
expires_in?: number;
|
|
81
|
+
subject?: string;
|
|
82
|
+
namespace?: string;
|
|
83
|
+
token_type?: string;
|
|
84
|
+
}>("/v1/auth/refresh", { refresh_token: refreshToken, namespace });
|
|
85
|
+
|
|
86
|
+
if (!response?.access_token) {
|
|
87
|
+
throw new Error("refresh failed: server returned no access_token");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.setJwt(response.access_token);
|
|
91
|
+
|
|
92
|
+
// Rotate the stored refresh token if the server returned a new one
|
|
93
|
+
// (rqlite-side gateway currently echoes the same token; future versions
|
|
94
|
+
// may rotate, so handle both shapes).
|
|
95
|
+
if (response.refresh_token && response.refresh_token !== refreshToken) {
|
|
96
|
+
await this.storage.set("refreshToken", response.refresh_token);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return response.access_token;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Logout user and clear JWT, but preserve API key
|
|
104
|
+
* Use this for user logout in apps where API key is app-level credential
|
|
105
|
+
*/
|
|
106
|
+
async logoutUser(): Promise<void> {
|
|
107
|
+
// Attempt server-side logout if using JWT
|
|
108
|
+
if (this.currentJwt) {
|
|
109
|
+
try {
|
|
110
|
+
await this.httpClient.post("/v1/auth/logout", { all: true });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
// Log warning but don't fail - local cleanup is more important
|
|
113
|
+
console.warn(
|
|
114
|
+
"Server-side logout failed, continuing with local cleanup:",
|
|
115
|
+
error
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Clear JWT only, preserve API key
|
|
121
|
+
this.currentJwt = undefined;
|
|
122
|
+
this.httpClient.setJwt(undefined);
|
|
123
|
+
await this.storage.set("jwt", ""); // Clear JWT from storage
|
|
124
|
+
|
|
125
|
+
// Ensure API key is loaded and set as active auth method
|
|
126
|
+
if (!this.currentApiKey) {
|
|
127
|
+
// Try to load from storage
|
|
128
|
+
const storedApiKey = await this.storage.get("apiKey");
|
|
129
|
+
if (storedApiKey) {
|
|
130
|
+
this.currentApiKey = storedApiKey;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Restore API key as the active auth method
|
|
135
|
+
if (this.currentApiKey) {
|
|
136
|
+
this.httpClient.setApiKey(this.currentApiKey);
|
|
137
|
+
console.log("[Auth] API key restored after user logout");
|
|
138
|
+
} else {
|
|
139
|
+
console.warn("[Auth] No API key available after logout");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Full logout - clears both JWT and API key
|
|
145
|
+
* Use this to completely reset authentication state
|
|
146
|
+
*/
|
|
147
|
+
async logout(): Promise<void> {
|
|
148
|
+
// Only attempt server-side logout if using JWT
|
|
149
|
+
// API keys don't support server-side logout with all=true
|
|
150
|
+
if (this.currentJwt) {
|
|
151
|
+
try {
|
|
152
|
+
await this.httpClient.post("/v1/auth/logout", { all: true });
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// Log warning but don't fail - local cleanup is more important
|
|
155
|
+
console.warn(
|
|
156
|
+
"Server-side logout failed, continuing with local cleanup:",
|
|
157
|
+
error
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Always clear local state
|
|
163
|
+
this.currentApiKey = undefined;
|
|
164
|
+
this.currentJwt = undefined;
|
|
165
|
+
this.httpClient.setApiKey(undefined);
|
|
166
|
+
this.httpClient.setJwt(undefined);
|
|
167
|
+
await this.storage.clear();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async clear(): Promise<void> {
|
|
171
|
+
this.currentApiKey = undefined;
|
|
172
|
+
this.currentJwt = undefined;
|
|
173
|
+
this.httpClient.setApiKey(undefined);
|
|
174
|
+
this.httpClient.setJwt(undefined);
|
|
175
|
+
await this.storage.clear();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Request a challenge nonce for wallet authentication
|
|
180
|
+
*/
|
|
181
|
+
async challenge(params: {
|
|
182
|
+
wallet: string;
|
|
183
|
+
purpose?: string;
|
|
184
|
+
namespace?: string;
|
|
185
|
+
}): Promise<{
|
|
186
|
+
nonce: string;
|
|
187
|
+
wallet: string;
|
|
188
|
+
namespace: string;
|
|
189
|
+
expires_at: string;
|
|
190
|
+
}> {
|
|
191
|
+
const response = await this.httpClient.post("/v1/auth/challenge", {
|
|
192
|
+
wallet: params.wallet,
|
|
193
|
+
purpose: params.purpose || "authentication",
|
|
194
|
+
namespace: params.namespace || "default",
|
|
195
|
+
});
|
|
196
|
+
return response;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Verify wallet signature and get JWT token
|
|
201
|
+
*/
|
|
202
|
+
async verify(params: {
|
|
203
|
+
wallet: string;
|
|
204
|
+
nonce: string;
|
|
205
|
+
signature: string;
|
|
206
|
+
namespace?: string;
|
|
207
|
+
chain_type?: "ETH" | "SOL";
|
|
208
|
+
}): Promise<{
|
|
209
|
+
access_token: string;
|
|
210
|
+
refresh_token?: string;
|
|
211
|
+
subject: string;
|
|
212
|
+
namespace: string;
|
|
213
|
+
api_key?: string;
|
|
214
|
+
expires_in?: number;
|
|
215
|
+
token_type?: string;
|
|
216
|
+
}> {
|
|
217
|
+
const response = await this.httpClient.post("/v1/auth/verify", {
|
|
218
|
+
wallet: params.wallet,
|
|
219
|
+
nonce: params.nonce,
|
|
220
|
+
signature: params.signature,
|
|
221
|
+
namespace: params.namespace || "default",
|
|
222
|
+
chain_type: params.chain_type || "ETH",
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Persist JWT
|
|
226
|
+
this.setJwt(response.access_token);
|
|
227
|
+
|
|
228
|
+
// Persist API key if server provided it (created in verifyHandler)
|
|
229
|
+
if ((response as any).api_key) {
|
|
230
|
+
this.setApiKey((response as any).api_key);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Persist refresh token if present (optional, for silent renewal)
|
|
234
|
+
if ((response as any).refresh_token) {
|
|
235
|
+
await this.storage.set("refreshToken", (response as any).refresh_token);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Persist the namespace this JWT was issued for so refresh() can
|
|
239
|
+
// include it in the refresh request body (the gateway scopes refresh
|
|
240
|
+
// tokens to the issuing namespace). Bug #239 — without this, refresh
|
|
241
|
+
// would default to "default" and fail for namespace-scoped sessions.
|
|
242
|
+
const issuedNamespace =
|
|
243
|
+
(response as any).namespace || params.namespace || "default";
|
|
244
|
+
await this.storage.set("namespace", issuedNamespace);
|
|
245
|
+
|
|
246
|
+
return response as any;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get API key for wallet (creates namespace ownership)
|
|
251
|
+
*/
|
|
252
|
+
async getApiKey(params: {
|
|
253
|
+
wallet: string;
|
|
254
|
+
nonce: string;
|
|
255
|
+
signature: string;
|
|
256
|
+
namespace?: string;
|
|
257
|
+
chain_type?: "ETH" | "SOL";
|
|
258
|
+
}): Promise<{
|
|
259
|
+
api_key: string;
|
|
260
|
+
namespace: string;
|
|
261
|
+
wallet: string;
|
|
262
|
+
}> {
|
|
263
|
+
const response = await this.httpClient.post("/v1/auth/api-key", {
|
|
264
|
+
wallet: params.wallet,
|
|
265
|
+
nonce: params.nonce,
|
|
266
|
+
signature: params.signature,
|
|
267
|
+
namespace: params.namespace || "default",
|
|
268
|
+
chain_type: params.chain_type || "ETH",
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Automatically set the API key
|
|
272
|
+
this.setApiKey(response.api_key);
|
|
273
|
+
|
|
274
|
+
return response;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface AuthConfig {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
jwt?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface WhoAmI {
|
|
7
|
+
address?: string;
|
|
8
|
+
namespace?: string;
|
|
9
|
+
authenticated: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface StorageAdapter {
|
|
13
|
+
get(key: string): Promise<string | null>;
|
|
14
|
+
set(key: string, value: string): Promise<void>;
|
|
15
|
+
clear(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class MemoryStorage implements StorageAdapter {
|
|
19
|
+
private storage: Map<string, string> = new Map();
|
|
20
|
+
|
|
21
|
+
async get(key: string): Promise<string | null> {
|
|
22
|
+
return this.storage.get(key) ?? null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async set(key: string, value: string): Promise<void> {
|
|
26
|
+
this.storage.set(key, value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async clear(): Promise<void> {
|
|
30
|
+
this.storage.clear();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class LocalStorageAdapter implements StorageAdapter {
|
|
35
|
+
private prefix = "@network/sdk:";
|
|
36
|
+
|
|
37
|
+
async get(key: string): Promise<string | null> {
|
|
38
|
+
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
39
|
+
return globalThis.localStorage.getItem(this.prefix + key);
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async set(key: string, value: string): Promise<void> {
|
|
45
|
+
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
46
|
+
globalThis.localStorage.setItem(this.prefix + key, value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async clear(): Promise<void> {
|
|
51
|
+
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
52
|
+
const keysToDelete: string[] = [];
|
|
53
|
+
for (let i = 0; i < globalThis.localStorage.length; i++) {
|
|
54
|
+
const key = globalThis.localStorage.key(i);
|
|
55
|
+
if (key?.startsWith(this.prefix)) {
|
|
56
|
+
keysToDelete.push(key);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
keysToDelete.forEach((key) => globalThis.localStorage.removeItem(key));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { HttpClient } from "../core/http";
|
|
2
|
+
import { SDKError } from "../errors";
|
|
3
|
+
|
|
4
|
+
export interface CacheGetRequest {
|
|
5
|
+
dmap: string;
|
|
6
|
+
key: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CacheGetResponse {
|
|
10
|
+
key: string;
|
|
11
|
+
value: any;
|
|
12
|
+
dmap: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CachePutRequest {
|
|
16
|
+
dmap: string;
|
|
17
|
+
key: string;
|
|
18
|
+
value: any;
|
|
19
|
+
ttl?: string; // Duration string like "1h", "30m"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CachePutResponse {
|
|
23
|
+
status: string;
|
|
24
|
+
key: string;
|
|
25
|
+
dmap: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CacheDeleteRequest {
|
|
29
|
+
dmap: string;
|
|
30
|
+
key: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CacheDeleteResponse {
|
|
34
|
+
status: string;
|
|
35
|
+
key: string;
|
|
36
|
+
dmap: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CacheMultiGetRequest {
|
|
40
|
+
dmap: string;
|
|
41
|
+
keys: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CacheMultiGetResponse {
|
|
45
|
+
results: Array<{
|
|
46
|
+
key: string;
|
|
47
|
+
value: any;
|
|
48
|
+
}>;
|
|
49
|
+
dmap: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CacheScanRequest {
|
|
53
|
+
dmap: string;
|
|
54
|
+
match?: string; // Optional regex pattern
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface CacheScanResponse {
|
|
58
|
+
keys: string[];
|
|
59
|
+
count: number;
|
|
60
|
+
dmap: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CacheHealthResponse {
|
|
64
|
+
status: string;
|
|
65
|
+
service: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class CacheClient {
|
|
69
|
+
private httpClient: HttpClient;
|
|
70
|
+
|
|
71
|
+
constructor(httpClient: HttpClient) {
|
|
72
|
+
this.httpClient = httpClient;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check cache service health
|
|
77
|
+
*/
|
|
78
|
+
async health(): Promise<CacheHealthResponse> {
|
|
79
|
+
return this.httpClient.get("/v1/cache/health");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get a value from cache
|
|
84
|
+
* Returns null if the key is not found (cache miss/expired), which is normal behavior
|
|
85
|
+
*/
|
|
86
|
+
async get(dmap: string, key: string): Promise<CacheGetResponse | null> {
|
|
87
|
+
try {
|
|
88
|
+
return await this.httpClient.post<CacheGetResponse>("/v1/cache/get", {
|
|
89
|
+
dmap,
|
|
90
|
+
key,
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
// Cache misses (404 or "key not found" messages) are normal behavior - return null instead of throwing
|
|
94
|
+
if (
|
|
95
|
+
error instanceof SDKError &&
|
|
96
|
+
(error.httpStatus === 404 ||
|
|
97
|
+
(error.httpStatus === 500 &&
|
|
98
|
+
error.message?.toLowerCase().includes("key not found")))
|
|
99
|
+
) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
// Re-throw other errors (network issues, server errors, etc.)
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Put a value into cache
|
|
109
|
+
*/
|
|
110
|
+
async put(
|
|
111
|
+
dmap: string,
|
|
112
|
+
key: string,
|
|
113
|
+
value: any,
|
|
114
|
+
ttl?: string
|
|
115
|
+
): Promise<CachePutResponse> {
|
|
116
|
+
return this.httpClient.post<CachePutResponse>("/v1/cache/put", {
|
|
117
|
+
dmap,
|
|
118
|
+
key,
|
|
119
|
+
value,
|
|
120
|
+
ttl,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Delete a value from cache
|
|
126
|
+
*/
|
|
127
|
+
async delete(dmap: string, key: string): Promise<CacheDeleteResponse> {
|
|
128
|
+
return this.httpClient.post<CacheDeleteResponse>("/v1/cache/delete", {
|
|
129
|
+
dmap,
|
|
130
|
+
key,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get multiple values from cache in a single request
|
|
136
|
+
* Returns a map of key -> value (or null if not found)
|
|
137
|
+
* Gracefully handles 404 errors (endpoint not implemented) by returning empty results
|
|
138
|
+
*/
|
|
139
|
+
async multiGet(
|
|
140
|
+
dmap: string,
|
|
141
|
+
keys: string[]
|
|
142
|
+
): Promise<Map<string, any | null>> {
|
|
143
|
+
try {
|
|
144
|
+
if (keys.length === 0) {
|
|
145
|
+
return new Map();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const response = await this.httpClient.post<CacheMultiGetResponse>(
|
|
149
|
+
"/v1/cache/mget",
|
|
150
|
+
{
|
|
151
|
+
dmap,
|
|
152
|
+
keys,
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Convert array to Map
|
|
157
|
+
const resultMap = new Map<string, any | null>();
|
|
158
|
+
|
|
159
|
+
// First, mark all keys as null (cache miss)
|
|
160
|
+
keys.forEach((key) => {
|
|
161
|
+
resultMap.set(key, null);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Then, update with found values
|
|
165
|
+
if (response.results) {
|
|
166
|
+
response.results.forEach(({ key, value }) => {
|
|
167
|
+
resultMap.set(key, value);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return resultMap;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
// Handle 404 errors silently (endpoint not implemented on backend)
|
|
174
|
+
// This is expected behavior when the backend doesn't support multiGet yet
|
|
175
|
+
if (error instanceof SDKError && error.httpStatus === 404) {
|
|
176
|
+
// Return map with all nulls silently - caller can fall back to individual gets
|
|
177
|
+
const resultMap = new Map<string, any | null>();
|
|
178
|
+
keys.forEach((key) => {
|
|
179
|
+
resultMap.set(key, null);
|
|
180
|
+
});
|
|
181
|
+
return resultMap;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Log and return empty results for other errors
|
|
185
|
+
const resultMap = new Map<string, any | null>();
|
|
186
|
+
keys.forEach((key) => {
|
|
187
|
+
resultMap.set(key, null);
|
|
188
|
+
});
|
|
189
|
+
console.error(`[CacheClient] Error in multiGet for ${dmap}:`, error);
|
|
190
|
+
return resultMap;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Scan keys in a distributed map, optionally matching a regex pattern
|
|
196
|
+
*/
|
|
197
|
+
async scan(dmap: string, match?: string): Promise<CacheScanResponse> {
|
|
198
|
+
return this.httpClient.post<CacheScanResponse>("/v1/cache/scan", {
|
|
199
|
+
dmap,
|
|
200
|
+
match,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { CacheClient } from "./client";
|
|
2
|
+
export type {
|
|
3
|
+
CacheGetRequest,
|
|
4
|
+
CacheGetResponse,
|
|
5
|
+
CachePutRequest,
|
|
6
|
+
CachePutResponse,
|
|
7
|
+
CacheDeleteRequest,
|
|
8
|
+
CacheDeleteResponse,
|
|
9
|
+
CacheMultiGetRequest,
|
|
10
|
+
CacheMultiGetResponse,
|
|
11
|
+
CacheScanRequest,
|
|
12
|
+
CacheScanResponse,
|
|
13
|
+
CacheHealthResponse,
|
|
14
|
+
} from "./client";
|