@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,46 @@
1
+ export interface PubSubMessage {
2
+ data: string;
3
+ topic: string;
4
+ timestamp: number;
5
+ }
6
+
7
+ export interface RawEnvelope {
8
+ type?: string;
9
+ data: string; // base64-encoded
10
+ timestamp: number;
11
+ topic: string;
12
+ member_id?: string;
13
+ meta?: Record<string, unknown>;
14
+ }
15
+
16
+ export interface PresenceMember {
17
+ memberId: string;
18
+ joinedAt: number;
19
+ meta?: Record<string, unknown>;
20
+ }
21
+
22
+ export interface PresenceResponse {
23
+ topic: string;
24
+ members: PresenceMember[];
25
+ count: number;
26
+ }
27
+
28
+ export interface PresenceOptions {
29
+ enabled: boolean;
30
+ memberId: string;
31
+ meta?: Record<string, unknown>;
32
+ onJoin?: (member: PresenceMember) => void;
33
+ onLeave?: (member: PresenceMember) => void;
34
+ }
35
+
36
+ export interface SubscribeOptions {
37
+ onMessage?: MessageHandler;
38
+ onError?: ErrorHandler;
39
+ onClose?: CloseHandler;
40
+ presence?: PresenceOptions;
41
+ }
42
+
43
+ export type MessageHandler = (message: PubSubMessage) => void;
44
+ export type ErrorHandler = (error: Error) => void;
45
+ export type CloseHandler = (code: number, reason: string) => void;
46
+
@@ -0,0 +1,272 @@
1
+ import { HttpClient } from "../core/http";
2
+
3
+ export interface StorageUploadResponse {
4
+ cid: string;
5
+ name: string;
6
+ size: number;
7
+ }
8
+
9
+ export interface StoragePinRequest {
10
+ cid: string;
11
+ name?: string;
12
+ }
13
+
14
+ export interface StoragePinResponse {
15
+ cid: string;
16
+ name: string;
17
+ }
18
+
19
+ export interface StorageStatus {
20
+ cid: string;
21
+ name: string;
22
+ status: string; // "pinned", "pinning", "queued", "unpinned", "error"
23
+ replication_min: number;
24
+ replication_max: number;
25
+ replication_factor: number;
26
+ peers: string[];
27
+ error?: string;
28
+ }
29
+
30
+ export class StorageClient {
31
+ private httpClient: HttpClient;
32
+
33
+ constructor(httpClient: HttpClient) {
34
+ this.httpClient = httpClient;
35
+ }
36
+
37
+ /**
38
+ * Upload content to IPFS and optionally pin it.
39
+ * Supports both File objects (browser) and Buffer/ReadableStream (Node.js).
40
+ *
41
+ * @param file - File to upload (File, Blob, or Buffer)
42
+ * @param name - Optional filename
43
+ * @param options - Optional upload options
44
+ * @param options.pin - Whether to pin the content (default: true). Pinning happens asynchronously on the backend.
45
+ * @returns Upload result with CID
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * // Browser
50
+ * const fileInput = document.querySelector('input[type="file"]');
51
+ * const file = fileInput.files[0];
52
+ * const result = await client.storage.upload(file, file.name);
53
+ * console.log(result.cid);
54
+ *
55
+ * // Node.js
56
+ * const fs = require('fs');
57
+ * const fileBuffer = fs.readFileSync('image.jpg');
58
+ * const result = await client.storage.upload(fileBuffer, 'image.jpg', { pin: true });
59
+ * ```
60
+ */
61
+ async upload(
62
+ file: File | Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>,
63
+ name?: string,
64
+ options?: {
65
+ pin?: boolean;
66
+ }
67
+ ): Promise<StorageUploadResponse> {
68
+ // Create FormData for multipart upload
69
+ const formData = new FormData();
70
+
71
+ // Handle different input types
72
+ if (file instanceof File) {
73
+ formData.append("file", file);
74
+ } else if (file instanceof Blob) {
75
+ formData.append("file", file, name);
76
+ } else if (file instanceof ArrayBuffer) {
77
+ const blob = new Blob([file]);
78
+ formData.append("file", blob, name);
79
+ } else if (file instanceof Uint8Array) {
80
+ // Convert Uint8Array to ArrayBuffer for Blob constructor
81
+ const buffer = file.buffer.slice(
82
+ file.byteOffset,
83
+ file.byteOffset + file.byteLength
84
+ ) as ArrayBuffer;
85
+ const blob = new Blob([buffer], { type: "application/octet-stream" });
86
+ formData.append("file", blob, name);
87
+ } else if (file instanceof ReadableStream) {
88
+ // For ReadableStream, we need to read it into a blob first
89
+ // This is a limitation - in practice, pass File/Blob/Buffer
90
+ const chunks: ArrayBuffer[] = [];
91
+ const reader = file.getReader();
92
+ while (true) {
93
+ const { done, value } = await reader.read();
94
+ if (done) break;
95
+ const buffer = value.buffer.slice(
96
+ value.byteOffset,
97
+ value.byteOffset + value.byteLength
98
+ ) as ArrayBuffer;
99
+ chunks.push(buffer);
100
+ }
101
+ const blob = new Blob(chunks);
102
+ formData.append("file", blob, name);
103
+ } else {
104
+ throw new Error(
105
+ "Unsupported file type. Use File, Blob, ArrayBuffer, Uint8Array, or ReadableStream."
106
+ );
107
+ }
108
+
109
+ // Add pin flag (default: true)
110
+ const shouldPin = options?.pin !== false; // Default to true
111
+ formData.append("pin", shouldPin ? "true" : "false");
112
+
113
+ return this.httpClient.uploadFile<StorageUploadResponse>(
114
+ "/v1/storage/upload",
115
+ formData,
116
+ { timeout: 300000 } // 5 minute timeout for large files
117
+ );
118
+ }
119
+
120
+ /**
121
+ * Pin an existing CID
122
+ *
123
+ * @param cid - Content ID to pin
124
+ * @param name - Optional name for the pin
125
+ * @returns Pin result
126
+ */
127
+ async pin(cid: string, name?: string): Promise<StoragePinResponse> {
128
+ return this.httpClient.post<StoragePinResponse>("/v1/storage/pin", {
129
+ cid,
130
+ name,
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Get the pin status for a CID
136
+ *
137
+ * @param cid - Content ID to check
138
+ * @returns Pin status information
139
+ */
140
+ async status(cid: string): Promise<StorageStatus> {
141
+ return this.httpClient.get<StorageStatus>(`/v1/storage/status/${cid}`);
142
+ }
143
+
144
+ /**
145
+ * Retrieve content from IPFS by CID
146
+ *
147
+ * @param cid - Content ID to retrieve
148
+ * @returns ReadableStream of the content
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * const stream = await client.storage.get(cid);
153
+ * const reader = stream.getReader();
154
+ * while (true) {
155
+ * const { done, value } = await reader.read();
156
+ * if (done) break;
157
+ * // Process chunk
158
+ * }
159
+ * ```
160
+ */
161
+ async get(cid: string): Promise<ReadableStream<Uint8Array>> {
162
+ // Retry logic for content retrieval - content may not be immediately available
163
+ // after upload due to eventual consistency in IPFS Cluster
164
+ // IPFS Cluster pins can take 2-3+ seconds to complete across all nodes
165
+ const maxAttempts = 8;
166
+ let lastError: Error | null = null;
167
+
168
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
169
+ try {
170
+ const response = await this.httpClient.getBinary(
171
+ `/v1/storage/get/${cid}`
172
+ );
173
+
174
+ if (!response.body) {
175
+ throw new Error("Response body is null");
176
+ }
177
+
178
+ return response.body;
179
+ } catch (error: any) {
180
+ lastError = error;
181
+
182
+ // Check if this is a 404 error (content not found)
183
+ const isNotFound =
184
+ error?.httpStatus === 404 ||
185
+ error?.message?.includes("not found") ||
186
+ error?.message?.includes("404");
187
+
188
+ // If it's not a 404 error, or this is the last attempt, give up
189
+ if (!isNotFound || attempt === maxAttempts) {
190
+ throw error;
191
+ }
192
+
193
+ // Wait before retrying with bounded exponential backoff
194
+ // Max 3 seconds per retry to fit within 30s test timeout
195
+ // Total: 1s + 2s + 3s + 3s + 3s + 3s + 3s + 3s = 21 seconds
196
+ const backoffMs = Math.min(attempt * 1000, 3000);
197
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
198
+ }
199
+ }
200
+
201
+ // This should never be reached, but TypeScript needs it
202
+ throw lastError || new Error("Failed to retrieve content");
203
+ }
204
+
205
+ /**
206
+ * Retrieve content from IPFS by CID and return the full Response object
207
+ * Useful when you need access to response headers (e.g., content-length)
208
+ *
209
+ * @param cid - Content ID to retrieve
210
+ * @returns Response object with body stream and headers
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * const response = await client.storage.getBinary(cid);
215
+ * const contentLength = response.headers.get('content-length');
216
+ * const reader = response.body.getReader();
217
+ * // ... read stream
218
+ * ```
219
+ */
220
+ async getBinary(cid: string): Promise<Response> {
221
+ // Retry logic for content retrieval - content may not be immediately available
222
+ // after upload due to eventual consistency in IPFS Cluster
223
+ // IPFS Cluster pins can take 2-3+ seconds to complete across all nodes
224
+ const maxAttempts = 8;
225
+ let lastError: Error | null = null;
226
+
227
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
228
+ try {
229
+ const response = await this.httpClient.getBinary(
230
+ `/v1/storage/get/${cid}`
231
+ );
232
+
233
+ if (!response) {
234
+ throw new Error("Response is null");
235
+ }
236
+
237
+ return response;
238
+ } catch (error: any) {
239
+ lastError = error;
240
+
241
+ // Check if this is a 404 error (content not found)
242
+ const isNotFound =
243
+ error?.httpStatus === 404 ||
244
+ error?.message?.includes("not found") ||
245
+ error?.message?.includes("404");
246
+
247
+ // If it's not a 404 error, or this is the last attempt, give up
248
+ if (!isNotFound || attempt === maxAttempts) {
249
+ throw error;
250
+ }
251
+
252
+ // Wait before retrying with bounded exponential backoff
253
+ // Max 3 seconds per retry to fit within 30s test timeout
254
+ // Total: 1s + 2s + 3s + 3s + 3s + 3s + 3s + 3s = 21 seconds
255
+ const backoffMs = Math.min(attempt * 1000, 3000);
256
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
257
+ }
258
+ }
259
+
260
+ // This should never be reached, but TypeScript needs it
261
+ throw lastError || new Error("Failed to retrieve content");
262
+ }
263
+
264
+ /**
265
+ * Unpin a CID
266
+ *
267
+ * @param cid - Content ID to unpin
268
+ */
269
+ async unpin(cid: string): Promise<void> {
270
+ await this.httpClient.delete(`/v1/storage/unpin/${cid}`);
271
+ }
272
+ }
@@ -0,0 +1,7 @@
1
+ export { StorageClient } from "./client";
2
+ export type {
3
+ StorageUploadResponse,
4
+ StoragePinRequest,
5
+ StoragePinResponse,
6
+ StorageStatus,
7
+ } from "./client";
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Base64 Codec for cross-platform encoding/decoding
3
+ * Works in both Node.js and browser environments
4
+ */
5
+ export class Base64Codec {
6
+ /**
7
+ * Encode string or Uint8Array to base64
8
+ */
9
+ static encode(input: string | Uint8Array): string {
10
+ if (typeof input === "string") {
11
+ return this.encodeString(input);
12
+ }
13
+ return this.encodeBytes(input);
14
+ }
15
+
16
+ /**
17
+ * Encode string to base64
18
+ */
19
+ static encodeString(str: string): string {
20
+ if (this.isNode()) {
21
+ return Buffer.from(str).toString("base64");
22
+ }
23
+ // Browser
24
+ return btoa(
25
+ encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) =>
26
+ String.fromCharCode(parseInt(p1, 16))
27
+ )
28
+ );
29
+ }
30
+
31
+ /**
32
+ * Encode Uint8Array to base64
33
+ */
34
+ static encodeBytes(bytes: Uint8Array): string {
35
+ if (this.isNode()) {
36
+ return Buffer.from(bytes).toString("base64");
37
+ }
38
+ // Browser
39
+ let binary = "";
40
+ for (let i = 0; i < bytes.length; i++) {
41
+ binary += String.fromCharCode(bytes[i]);
42
+ }
43
+ return btoa(binary);
44
+ }
45
+
46
+ /**
47
+ * Decode base64 to string
48
+ */
49
+ static decode(b64: string): string {
50
+ if (this.isNode()) {
51
+ return Buffer.from(b64, "base64").toString("utf-8");
52
+ }
53
+ // Browser
54
+ const binary = atob(b64);
55
+ const bytes = new Uint8Array(binary.length);
56
+ for (let i = 0; i < binary.length; i++) {
57
+ bytes[i] = binary.charCodeAt(i);
58
+ }
59
+ return new TextDecoder().decode(bytes);
60
+ }
61
+
62
+ /**
63
+ * Check if running in Node.js environment
64
+ */
65
+ private static isNode(): boolean {
66
+ return typeof Buffer !== "undefined";
67
+ }
68
+ }
@@ -0,0 +1,3 @@
1
+ export { Base64Codec } from "./codec";
2
+ export { retryWithBackoff, type RetryConfig } from "./retry";
3
+ export { Platform } from "./platform";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Platform detection utilities
3
+ * Helps determine runtime environment (Node.js vs Browser)
4
+ */
5
+ export const Platform = {
6
+ /**
7
+ * Check if running in Node.js
8
+ */
9
+ isNode: (): boolean => {
10
+ return typeof process !== "undefined" && !!process.versions?.node;
11
+ },
12
+
13
+ /**
14
+ * Check if running in browser
15
+ */
16
+ isBrowser: (): boolean => {
17
+ return typeof window !== "undefined";
18
+ },
19
+
20
+ /**
21
+ * Check if localStorage is available
22
+ */
23
+ hasLocalStorage: (): boolean => {
24
+ try {
25
+ return typeof localStorage !== "undefined" && localStorage !== null;
26
+ } catch {
27
+ return false;
28
+ }
29
+ },
30
+
31
+ /**
32
+ * Check if Buffer is available (Node.js)
33
+ */
34
+ hasBuffer: (): boolean => {
35
+ return typeof Buffer !== "undefined";
36
+ },
37
+
38
+ /**
39
+ * Check if btoa/atob are available (Browser)
40
+ */
41
+ hasBase64: (): boolean => {
42
+ return typeof btoa !== "undefined" && typeof atob !== "undefined";
43
+ },
44
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Retry configuration
3
+ */
4
+ export interface RetryConfig {
5
+ /**
6
+ * Maximum number of retry attempts
7
+ */
8
+ maxAttempts: number;
9
+
10
+ /**
11
+ * Function to calculate backoff delay in milliseconds
12
+ */
13
+ backoffMs: (attempt: number) => number;
14
+
15
+ /**
16
+ * Function to determine if error should trigger retry
17
+ */
18
+ shouldRetry: (error: any) => boolean;
19
+ }
20
+
21
+ /**
22
+ * Retry an operation with exponential backoff
23
+ * @param operation - The async operation to retry
24
+ * @param config - Retry configuration
25
+ * @returns Promise resolving to operation result
26
+ * @throws Last error if all retries exhausted
27
+ */
28
+ export async function retryWithBackoff<T>(
29
+ operation: () => Promise<T>,
30
+ config: RetryConfig
31
+ ): Promise<T> {
32
+ let lastError: Error | null = null;
33
+
34
+ for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
35
+ try {
36
+ return await operation();
37
+ } catch (error: any) {
38
+ lastError = error instanceof Error ? error : new Error(String(error));
39
+
40
+ // Check if we should retry this error
41
+ if (!config.shouldRetry(error)) {
42
+ throw error;
43
+ }
44
+
45
+ // If this was the last attempt, throw
46
+ if (attempt === config.maxAttempts) {
47
+ throw error;
48
+ }
49
+
50
+ // Wait before next attempt
51
+ const delay = config.backoffMs(attempt);
52
+ await new Promise((resolve) => setTimeout(resolve, delay));
53
+ }
54
+ }
55
+
56
+ // Fallback (should never reach here)
57
+ throw lastError || new Error("Retry failed");
58
+ }
@@ -0,0 +1,98 @@
1
+ import { GuardianClient } from './transport/guardian';
2
+ import type { GuardianEndpoint } from './transport/types';
3
+
4
+ /**
5
+ * Handles challenge-response authentication with guardian nodes.
6
+ * Caches session tokens per guardian endpoint.
7
+ *
8
+ * Auth flow:
9
+ * 1. POST /v2/vault/auth/challenge with identity → get {nonce, created_ns, tag}
10
+ * 2. POST /v2/vault/auth/session with identity + challenge fields → get session token
11
+ * 3. Use session token as X-Session-Token header for V2 requests
12
+ *
13
+ * The session token format is: `<identity_hex>:<expiry_ns>:<tag_hex>`
14
+ */
15
+ export class AuthClient {
16
+ private sessions = new Map<string, { token: string; expiryNs: number }>();
17
+ private identityHex: string;
18
+ private timeoutMs: number;
19
+
20
+ constructor(identityHex: string, timeoutMs = 10_000) {
21
+ this.identityHex = identityHex;
22
+ this.timeoutMs = timeoutMs;
23
+ }
24
+
25
+ /**
26
+ * Authenticate with a guardian and cache the session token.
27
+ * Returns a GuardianClient with the session token set.
28
+ */
29
+ async authenticate(endpoint: GuardianEndpoint): Promise<GuardianClient> {
30
+ const key = `${endpoint.address}:${endpoint.port}`;
31
+ const cached = this.sessions.get(key);
32
+
33
+ // Check if we have a valid cached session (with 30s safety margin)
34
+ if (cached) {
35
+ const nowNs = Date.now() * 1_000_000;
36
+ if (cached.expiryNs > nowNs + 30_000_000_000) {
37
+ const client = new GuardianClient(endpoint, this.timeoutMs);
38
+ client.setSessionToken(cached.token);
39
+ return client;
40
+ }
41
+ // Expired, remove
42
+ this.sessions.delete(key);
43
+ }
44
+
45
+ const client = new GuardianClient(endpoint, this.timeoutMs);
46
+
47
+ // Step 1: Request challenge
48
+ const challenge = await client.requestChallenge(this.identityHex);
49
+
50
+ // Step 2: Exchange for session
51
+ const session = await client.createSession(
52
+ this.identityHex,
53
+ challenge.nonce,
54
+ challenge.created_ns,
55
+ challenge.tag,
56
+ );
57
+
58
+ // Build token string: identity:expiry_ns:tag
59
+ const token = `${session.identity}:${session.expiry_ns}:${session.tag}`;
60
+ client.setSessionToken(token);
61
+
62
+ // Cache
63
+ this.sessions.set(key, { token, expiryNs: session.expiry_ns });
64
+
65
+ return client;
66
+ }
67
+
68
+ /**
69
+ * Authenticate with multiple guardians in parallel.
70
+ * Returns authenticated GuardianClients for all that succeed.
71
+ */
72
+ async authenticateAll(endpoints: GuardianEndpoint[]): Promise<{ client: GuardianClient; endpoint: GuardianEndpoint }[]> {
73
+ const results = await Promise.allSettled(
74
+ endpoints.map(async (ep) => {
75
+ const client = await this.authenticate(ep);
76
+ return { client, endpoint: ep };
77
+ }),
78
+ );
79
+
80
+ const authenticated: { client: GuardianClient; endpoint: GuardianEndpoint }[] = [];
81
+ for (const r of results) {
82
+ if (r.status === 'fulfilled') {
83
+ authenticated.push(r.value);
84
+ }
85
+ }
86
+ return authenticated;
87
+ }
88
+
89
+ /** Clear all cached sessions. */
90
+ clearSessions(): void {
91
+ this.sessions.clear();
92
+ }
93
+
94
+ /** Get the identity hex string. */
95
+ getIdentityHex(): string {
96
+ return this.identityHex;
97
+ }
98
+ }