@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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +665 -0
  3. package/dist/index.d.ts +1334 -0
  4. package/dist/index.js +2553 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +82 -0
  7. package/src/auth/client.ts +276 -0
  8. package/src/auth/index.ts +3 -0
  9. package/src/auth/types.ts +62 -0
  10. package/src/cache/client.ts +203 -0
  11. package/src/cache/index.ts +14 -0
  12. package/src/core/http.ts +541 -0
  13. package/src/core/index.ts +10 -0
  14. package/src/core/interfaces/IAuthStrategy.ts +28 -0
  15. package/src/core/interfaces/IHttpTransport.ts +73 -0
  16. package/src/core/interfaces/IRetryPolicy.ts +20 -0
  17. package/src/core/interfaces/IWebSocketClient.ts +60 -0
  18. package/src/core/interfaces/index.ts +4 -0
  19. package/src/core/transport/AuthHeaderStrategy.ts +108 -0
  20. package/src/core/transport/RequestLogger.ts +116 -0
  21. package/src/core/transport/RequestRetryPolicy.ts +53 -0
  22. package/src/core/transport/TLSConfiguration.ts +53 -0
  23. package/src/core/transport/index.ts +4 -0
  24. package/src/core/ws.ts +246 -0
  25. package/src/db/client.ts +126 -0
  26. package/src/db/index.ts +13 -0
  27. package/src/db/qb.ts +111 -0
  28. package/src/db/repository.ts +128 -0
  29. package/src/db/types.ts +67 -0
  30. package/src/errors.ts +38 -0
  31. package/src/functions/client.ts +62 -0
  32. package/src/functions/index.ts +2 -0
  33. package/src/functions/types.ts +21 -0
  34. package/src/index.ts +201 -0
  35. package/src/network/client.ts +119 -0
  36. package/src/network/index.ts +7 -0
  37. package/src/pubsub/client.ts +361 -0
  38. package/src/pubsub/index.ts +12 -0
  39. package/src/pubsub/types.ts +46 -0
  40. package/src/storage/client.ts +272 -0
  41. package/src/storage/index.ts +7 -0
  42. package/src/utils/codec.ts +68 -0
  43. package/src/utils/index.ts +3 -0
  44. package/src/utils/platform.ts +44 -0
  45. package/src/utils/retry.ts +58 -0
  46. package/src/vault/auth.ts +98 -0
  47. package/src/vault/client.ts +197 -0
  48. package/src/vault/crypto/aes.ts +271 -0
  49. package/src/vault/crypto/hkdf.ts +42 -0
  50. package/src/vault/crypto/index.ts +27 -0
  51. package/src/vault/crypto/shamir.ts +173 -0
  52. package/src/vault/index.ts +65 -0
  53. package/src/vault/quorum.ts +16 -0
  54. package/src/vault/transport/fanout.ts +94 -0
  55. package/src/vault/transport/guardian.ts +285 -0
  56. package/src/vault/transport/index.ts +19 -0
  57. package/src/vault/transport/types.ts +101 -0
  58. 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,3 @@
1
+ export { AuthClient } from "./client";
2
+ export type { AuthConfig, WhoAmI, StorageAdapter } from "./types";
3
+ export { MemoryStorage, LocalStorageAdapter } from "./types";
@@ -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";