@debros/network-ts-sdk 0.2.5 → 0.3.2

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/dist/index.js CHANGED
@@ -34,14 +34,6 @@ var HttpClient = class {
34
34
  }
35
35
  setApiKey(apiKey) {
36
36
  this.apiKey = apiKey;
37
- if (typeof console !== "undefined") {
38
- console.log(
39
- "[HttpClient] API key set:",
40
- !!apiKey,
41
- "JWT still present:",
42
- !!this.jwt
43
- );
44
- }
45
37
  }
46
38
  setJwt(jwt) {
47
39
  this.jwt = jwt;
@@ -59,12 +51,21 @@ var HttpClient = class {
59
51
  const isDbOperation = path.includes("/v1/rqlite/");
60
52
  const isPubSubOperation = path.includes("/v1/pubsub/");
61
53
  const isProxyOperation = path.includes("/v1/proxy/");
62
- if (isDbOperation || isPubSubOperation || isProxyOperation) {
54
+ const isCacheOperation = path.includes("/v1/cache/");
55
+ const isAuthOperation = path.includes("/v1/auth/");
56
+ if (isDbOperation || isPubSubOperation || isProxyOperation || isCacheOperation) {
63
57
  if (this.apiKey) {
64
58
  headers["X-API-Key"] = this.apiKey;
65
59
  } else if (this.jwt) {
66
60
  headers["Authorization"] = `Bearer ${this.jwt}`;
67
61
  }
62
+ } else if (isAuthOperation) {
63
+ if (this.apiKey) {
64
+ headers["X-API-Key"] = this.apiKey;
65
+ }
66
+ if (this.jwt) {
67
+ headers["Authorization"] = `Bearer ${this.jwt}`;
68
+ }
68
69
  } else {
69
70
  if (this.jwt) {
70
71
  headers["Authorization"] = `Bearer ${this.jwt}`;
@@ -82,6 +83,7 @@ var HttpClient = class {
82
83
  return this.apiKey;
83
84
  }
84
85
  async request(method, path, options = {}) {
86
+ const startTime = performance.now();
85
87
  const url = new URL(this.baseURL + path);
86
88
  if (options.query) {
87
89
  Object.entries(options.query).forEach(([key, value]) => {
@@ -93,14 +95,6 @@ var HttpClient = class {
93
95
  ...this.getAuthHeaders(path),
94
96
  ...options.headers
95
97
  };
96
- if (typeof console !== "undefined" && (path.includes("/db/") || path.includes("/query") || path.includes("/auth/") || path.includes("/pubsub/") || path.includes("/proxy/"))) {
97
- console.log("[HttpClient] Request headers for", path, {
98
- hasAuth: !!headers["Authorization"],
99
- hasApiKey: !!headers["X-API-Key"],
100
- authPrefix: headers["Authorization"] ? headers["Authorization"].substring(0, 20) : "none",
101
- apiKeyPrefix: headers["X-API-Key"] ? headers["X-API-Key"].substring(0, 20) : "none"
102
- });
103
- }
104
98
  const controller = new AbortController();
105
99
  const requestTimeout = options.timeout ?? this.timeout;
106
100
  const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
@@ -112,13 +106,86 @@ var HttpClient = class {
112
106
  if (options.body !== void 0) {
113
107
  fetchOptions.body = JSON.stringify(options.body);
114
108
  }
109
+ const isRqliteOperation = path.includes("/v1/rqlite/");
110
+ let queryDetails = null;
111
+ if (isRqliteOperation && options.body) {
112
+ try {
113
+ const body = typeof options.body === "string" ? JSON.parse(options.body) : options.body;
114
+ if (body.sql) {
115
+ queryDetails = `SQL: ${body.sql}`;
116
+ if (body.args && body.args.length > 0) {
117
+ queryDetails += ` | Args: [${body.args.map((a) => typeof a === "string" ? `"${a}"` : a).join(", ")}]`;
118
+ }
119
+ } else if (body.table) {
120
+ queryDetails = `Table: ${body.table}`;
121
+ if (body.criteria && Object.keys(body.criteria).length > 0) {
122
+ queryDetails += ` | Criteria: ${JSON.stringify(body.criteria)}`;
123
+ }
124
+ if (body.options) {
125
+ queryDetails += ` | Options: ${JSON.stringify(body.options)}`;
126
+ }
127
+ if (body.select) {
128
+ queryDetails += ` | Select: ${JSON.stringify(body.select)}`;
129
+ }
130
+ if (body.where) {
131
+ queryDetails += ` | Where: ${JSON.stringify(body.where)}`;
132
+ }
133
+ if (body.limit) {
134
+ queryDetails += ` | Limit: ${body.limit}`;
135
+ }
136
+ if (body.offset) {
137
+ queryDetails += ` | Offset: ${body.offset}`;
138
+ }
139
+ }
140
+ } catch (e) {
141
+ }
142
+ }
115
143
  try {
116
- return await this.requestWithRetry(url.toString(), fetchOptions);
144
+ const result = await this.requestWithRetry(
145
+ url.toString(),
146
+ fetchOptions,
147
+ 0,
148
+ startTime
149
+ );
150
+ const duration = performance.now() - startTime;
151
+ if (typeof console !== "undefined") {
152
+ const logMessage = `[HttpClient] ${method} ${path} completed in ${duration.toFixed(
153
+ 2
154
+ )}ms`;
155
+ if (queryDetails) {
156
+ console.log(logMessage);
157
+ console.log(`[HttpClient] ${queryDetails}`);
158
+ } else {
159
+ console.log(logMessage);
160
+ }
161
+ }
162
+ return result;
163
+ } catch (error) {
164
+ const duration = performance.now() - startTime;
165
+ if (typeof console !== "undefined") {
166
+ const is404FindOne = path === "/v1/rqlite/find-one" && error instanceof SDKError && error.httpStatus === 404;
167
+ if (is404FindOne) {
168
+ console.warn(
169
+ `[HttpClient] ${method} ${path} returned 404 after ${duration.toFixed(
170
+ 2
171
+ )}ms (expected for optional lookups)`
172
+ );
173
+ } else {
174
+ const errorMessage = `[HttpClient] ${method} ${path} failed after ${duration.toFixed(
175
+ 2
176
+ )}ms:`;
177
+ console.error(errorMessage, error);
178
+ if (queryDetails) {
179
+ console.error(`[HttpClient] ${queryDetails}`);
180
+ }
181
+ }
182
+ }
183
+ throw error;
117
184
  } finally {
118
185
  clearTimeout(timeoutId);
119
186
  }
120
187
  }
121
- async requestWithRetry(url, options, attempt = 0) {
188
+ async requestWithRetry(url, options, attempt = 0, startTime) {
122
189
  try {
123
190
  const response = await this.fetch(url, options);
124
191
  if (!response.ok) {
@@ -140,7 +207,7 @@ var HttpClient = class {
140
207
  await new Promise(
141
208
  (resolve) => setTimeout(resolve, this.retryDelayMs * (attempt + 1))
142
209
  );
143
- return this.requestWithRetry(url, options, attempt + 1);
210
+ return this.requestWithRetry(url, options, attempt + 1, startTime);
144
211
  }
145
212
  throw error;
146
213
  }
@@ -157,6 +224,90 @@ var HttpClient = class {
157
224
  async delete(path, options) {
158
225
  return this.request("DELETE", path, options);
159
226
  }
227
+ /**
228
+ * Upload a file using multipart/form-data
229
+ * This is a special method for file uploads that bypasses JSON serialization
230
+ */
231
+ async uploadFile(path, formData, options) {
232
+ const startTime = performance.now();
233
+ const url = new URL(this.baseURL + path);
234
+ const headers = {
235
+ ...this.getAuthHeaders(path)
236
+ // Don't set Content-Type - browser will set it with boundary
237
+ };
238
+ const controller = new AbortController();
239
+ const requestTimeout = options?.timeout ?? this.timeout * 5;
240
+ const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
241
+ const fetchOptions = {
242
+ method: "POST",
243
+ headers,
244
+ body: formData,
245
+ signal: controller.signal
246
+ };
247
+ try {
248
+ const result = await this.requestWithRetry(
249
+ url.toString(),
250
+ fetchOptions,
251
+ 0,
252
+ startTime
253
+ );
254
+ const duration = performance.now() - startTime;
255
+ if (typeof console !== "undefined") {
256
+ console.log(
257
+ `[HttpClient] POST ${path} (upload) completed in ${duration.toFixed(
258
+ 2
259
+ )}ms`
260
+ );
261
+ }
262
+ return result;
263
+ } catch (error) {
264
+ const duration = performance.now() - startTime;
265
+ if (typeof console !== "undefined") {
266
+ console.error(
267
+ `[HttpClient] POST ${path} (upload) failed after ${duration.toFixed(
268
+ 2
269
+ )}ms:`,
270
+ error
271
+ );
272
+ }
273
+ throw error;
274
+ } finally {
275
+ clearTimeout(timeoutId);
276
+ }
277
+ }
278
+ /**
279
+ * Get a binary response (returns Response object for streaming)
280
+ */
281
+ async getBinary(path) {
282
+ const url = new URL(this.baseURL + path);
283
+ const headers = {
284
+ ...this.getAuthHeaders(path)
285
+ };
286
+ const controller = new AbortController();
287
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout * 5);
288
+ const fetchOptions = {
289
+ method: "GET",
290
+ headers,
291
+ signal: controller.signal
292
+ };
293
+ try {
294
+ const response = await this.fetch(url.toString(), fetchOptions);
295
+ if (!response.ok) {
296
+ clearTimeout(timeoutId);
297
+ const error = await response.json().catch(() => ({
298
+ error: response.statusText
299
+ }));
300
+ throw SDKError.fromResponse(response.status, error);
301
+ }
302
+ return response;
303
+ } catch (error) {
304
+ clearTimeout(timeoutId);
305
+ if (error instanceof SDKError) {
306
+ throw error;
307
+ }
308
+ throw error;
309
+ }
310
+ }
160
311
  getToken() {
161
312
  return this.getAuthToken();
162
313
  }
@@ -522,7 +673,9 @@ var Repository = class {
522
673
  buildInsertSql(entity) {
523
674
  const columns = Object.keys(entity).filter((k) => entity[k] !== void 0);
524
675
  const placeholders = columns.map(() => "?").join(", ");
525
- return `INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders})`;
676
+ return `INSERT INTO ${this.tableName} (${columns.join(
677
+ ", "
678
+ )}) VALUES (${placeholders})`;
526
679
  }
527
680
  buildInsertArgs(entity) {
528
681
  return Object.entries(entity).filter(([, v]) => v !== void 0).map(([, v]) => v);
@@ -1062,6 +1215,293 @@ var NetworkClient = class {
1062
1215
  }
1063
1216
  };
1064
1217
 
1218
+ // src/cache/client.ts
1219
+ var CacheClient = class {
1220
+ constructor(httpClient) {
1221
+ this.httpClient = httpClient;
1222
+ }
1223
+ /**
1224
+ * Check cache service health
1225
+ */
1226
+ async health() {
1227
+ return this.httpClient.get("/v1/cache/health");
1228
+ }
1229
+ /**
1230
+ * Get a value from cache
1231
+ * Returns null if the key is not found (cache miss/expired), which is normal behavior
1232
+ */
1233
+ async get(dmap, key) {
1234
+ try {
1235
+ return await this.httpClient.post("/v1/cache/get", {
1236
+ dmap,
1237
+ key
1238
+ });
1239
+ } catch (error) {
1240
+ if (error instanceof SDKError && (error.httpStatus === 404 || error.httpStatus === 500 && error.message?.toLowerCase().includes("key not found"))) {
1241
+ return null;
1242
+ }
1243
+ throw error;
1244
+ }
1245
+ }
1246
+ /**
1247
+ * Put a value into cache
1248
+ */
1249
+ async put(dmap, key, value, ttl) {
1250
+ return this.httpClient.post("/v1/cache/put", {
1251
+ dmap,
1252
+ key,
1253
+ value,
1254
+ ttl
1255
+ });
1256
+ }
1257
+ /**
1258
+ * Delete a value from cache
1259
+ */
1260
+ async delete(dmap, key) {
1261
+ return this.httpClient.post("/v1/cache/delete", {
1262
+ dmap,
1263
+ key
1264
+ });
1265
+ }
1266
+ /**
1267
+ * Get multiple values from cache in a single request
1268
+ * Returns a map of key -> value (or null if not found)
1269
+ * Gracefully handles 404 errors (endpoint not implemented) by returning empty results
1270
+ */
1271
+ async multiGet(dmap, keys) {
1272
+ try {
1273
+ if (keys.length === 0) {
1274
+ return /* @__PURE__ */ new Map();
1275
+ }
1276
+ const response = await this.httpClient.post(
1277
+ "/v1/cache/mget",
1278
+ {
1279
+ dmap,
1280
+ keys
1281
+ }
1282
+ );
1283
+ const resultMap = /* @__PURE__ */ new Map();
1284
+ keys.forEach((key) => {
1285
+ resultMap.set(key, null);
1286
+ });
1287
+ if (response.results) {
1288
+ response.results.forEach(({ key, value }) => {
1289
+ resultMap.set(key, value);
1290
+ });
1291
+ }
1292
+ return resultMap;
1293
+ } catch (error) {
1294
+ if (error instanceof SDKError && error.httpStatus === 404) {
1295
+ const resultMap2 = /* @__PURE__ */ new Map();
1296
+ keys.forEach((key) => {
1297
+ resultMap2.set(key, null);
1298
+ });
1299
+ return resultMap2;
1300
+ }
1301
+ const resultMap = /* @__PURE__ */ new Map();
1302
+ keys.forEach((key) => {
1303
+ resultMap.set(key, null);
1304
+ });
1305
+ console.error(`[CacheClient] Error in multiGet for ${dmap}:`, error);
1306
+ return resultMap;
1307
+ }
1308
+ }
1309
+ /**
1310
+ * Scan keys in a distributed map, optionally matching a regex pattern
1311
+ */
1312
+ async scan(dmap, match) {
1313
+ return this.httpClient.post("/v1/cache/scan", {
1314
+ dmap,
1315
+ match
1316
+ });
1317
+ }
1318
+ };
1319
+
1320
+ // src/storage/client.ts
1321
+ var StorageClient = class {
1322
+ constructor(httpClient) {
1323
+ this.httpClient = httpClient;
1324
+ }
1325
+ /**
1326
+ * Upload content to IPFS and optionally pin it.
1327
+ * Supports both File objects (browser) and Buffer/ReadableStream (Node.js).
1328
+ *
1329
+ * @param file - File to upload (File, Blob, or Buffer)
1330
+ * @param name - Optional filename
1331
+ * @param options - Optional upload options
1332
+ * @param options.pin - Whether to pin the content (default: true). Pinning happens asynchronously on the backend.
1333
+ * @returns Upload result with CID
1334
+ *
1335
+ * @example
1336
+ * ```ts
1337
+ * // Browser
1338
+ * const fileInput = document.querySelector('input[type="file"]');
1339
+ * const file = fileInput.files[0];
1340
+ * const result = await client.storage.upload(file, file.name);
1341
+ * console.log(result.cid);
1342
+ *
1343
+ * // Node.js
1344
+ * const fs = require('fs');
1345
+ * const fileBuffer = fs.readFileSync('image.jpg');
1346
+ * const result = await client.storage.upload(fileBuffer, 'image.jpg', { pin: true });
1347
+ * ```
1348
+ */
1349
+ async upload(file, name, options) {
1350
+ const formData = new FormData();
1351
+ if (file instanceof File) {
1352
+ formData.append("file", file);
1353
+ } else if (file instanceof Blob) {
1354
+ formData.append("file", file, name);
1355
+ } else if (file instanceof ArrayBuffer) {
1356
+ const blob = new Blob([file]);
1357
+ formData.append("file", blob, name);
1358
+ } else if (file instanceof Uint8Array) {
1359
+ const buffer = file.buffer.slice(
1360
+ file.byteOffset,
1361
+ file.byteOffset + file.byteLength
1362
+ );
1363
+ const blob = new Blob([buffer], { type: "application/octet-stream" });
1364
+ formData.append("file", blob, name);
1365
+ } else if (file instanceof ReadableStream) {
1366
+ const chunks = [];
1367
+ const reader = file.getReader();
1368
+ while (true) {
1369
+ const { done, value } = await reader.read();
1370
+ if (done) break;
1371
+ const buffer = value.buffer.slice(
1372
+ value.byteOffset,
1373
+ value.byteOffset + value.byteLength
1374
+ );
1375
+ chunks.push(buffer);
1376
+ }
1377
+ const blob = new Blob(chunks);
1378
+ formData.append("file", blob, name);
1379
+ } else {
1380
+ throw new Error(
1381
+ "Unsupported file type. Use File, Blob, ArrayBuffer, Uint8Array, or ReadableStream."
1382
+ );
1383
+ }
1384
+ const shouldPin = options?.pin !== false;
1385
+ formData.append("pin", shouldPin ? "true" : "false");
1386
+ return this.httpClient.uploadFile(
1387
+ "/v1/storage/upload",
1388
+ formData,
1389
+ { timeout: 3e5 }
1390
+ // 5 minute timeout for large files
1391
+ );
1392
+ }
1393
+ /**
1394
+ * Pin an existing CID
1395
+ *
1396
+ * @param cid - Content ID to pin
1397
+ * @param name - Optional name for the pin
1398
+ * @returns Pin result
1399
+ */
1400
+ async pin(cid, name) {
1401
+ return this.httpClient.post("/v1/storage/pin", {
1402
+ cid,
1403
+ name
1404
+ });
1405
+ }
1406
+ /**
1407
+ * Get the pin status for a CID
1408
+ *
1409
+ * @param cid - Content ID to check
1410
+ * @returns Pin status information
1411
+ */
1412
+ async status(cid) {
1413
+ return this.httpClient.get(`/v1/storage/status/${cid}`);
1414
+ }
1415
+ /**
1416
+ * Retrieve content from IPFS by CID
1417
+ *
1418
+ * @param cid - Content ID to retrieve
1419
+ * @returns ReadableStream of the content
1420
+ *
1421
+ * @example
1422
+ * ```ts
1423
+ * const stream = await client.storage.get(cid);
1424
+ * const reader = stream.getReader();
1425
+ * while (true) {
1426
+ * const { done, value } = await reader.read();
1427
+ * if (done) break;
1428
+ * // Process chunk
1429
+ * }
1430
+ * ```
1431
+ */
1432
+ async get(cid) {
1433
+ const maxAttempts = 8;
1434
+ let lastError = null;
1435
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1436
+ try {
1437
+ const response = await this.httpClient.getBinary(
1438
+ `/v1/storage/get/${cid}`
1439
+ );
1440
+ if (!response.body) {
1441
+ throw new Error("Response body is null");
1442
+ }
1443
+ return response.body;
1444
+ } catch (error) {
1445
+ lastError = error;
1446
+ const isNotFound = error?.httpStatus === 404 || error?.message?.includes("not found") || error?.message?.includes("404");
1447
+ if (!isNotFound || attempt === maxAttempts) {
1448
+ throw error;
1449
+ }
1450
+ const backoffMs = attempt * 2500;
1451
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
1452
+ }
1453
+ }
1454
+ throw lastError || new Error("Failed to retrieve content");
1455
+ }
1456
+ /**
1457
+ * Retrieve content from IPFS by CID and return the full Response object
1458
+ * Useful when you need access to response headers (e.g., content-length)
1459
+ *
1460
+ * @param cid - Content ID to retrieve
1461
+ * @returns Response object with body stream and headers
1462
+ *
1463
+ * @example
1464
+ * ```ts
1465
+ * const response = await client.storage.getBinary(cid);
1466
+ * const contentLength = response.headers.get('content-length');
1467
+ * const reader = response.body.getReader();
1468
+ * // ... read stream
1469
+ * ```
1470
+ */
1471
+ async getBinary(cid) {
1472
+ const maxAttempts = 8;
1473
+ let lastError = null;
1474
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1475
+ try {
1476
+ const response = await this.httpClient.getBinary(
1477
+ `/v1/storage/get/${cid}`
1478
+ );
1479
+ if (!response) {
1480
+ throw new Error("Response is null");
1481
+ }
1482
+ return response;
1483
+ } catch (error) {
1484
+ lastError = error;
1485
+ const isNotFound = error?.httpStatus === 404 || error?.message?.includes("not found") || error?.message?.includes("404");
1486
+ if (!isNotFound || attempt === maxAttempts) {
1487
+ throw error;
1488
+ }
1489
+ const backoffMs = attempt * 2500;
1490
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
1491
+ }
1492
+ }
1493
+ throw lastError || new Error("Failed to retrieve content");
1494
+ }
1495
+ /**
1496
+ * Unpin a CID
1497
+ *
1498
+ * @param cid - Content ID to unpin
1499
+ */
1500
+ async unpin(cid) {
1501
+ await this.httpClient.delete(`/v1/storage/unpin/${cid}`);
1502
+ }
1503
+ };
1504
+
1065
1505
  // src/index.ts
1066
1506
  function createClient(config) {
1067
1507
  const httpClient = new HttpClient({
@@ -1084,15 +1524,20 @@ function createClient(config) {
1084
1524
  wsURL
1085
1525
  });
1086
1526
  const network = new NetworkClient(httpClient);
1527
+ const cache = new CacheClient(httpClient);
1528
+ const storage = new StorageClient(httpClient);
1087
1529
  return {
1088
1530
  auth,
1089
1531
  db,
1090
1532
  pubsub,
1091
- network
1533
+ network,
1534
+ cache,
1535
+ storage
1092
1536
  };
1093
1537
  }
1094
1538
  export {
1095
1539
  AuthClient,
1540
+ CacheClient,
1096
1541
  DBClient,
1097
1542
  HttpClient,
1098
1543
  LocalStorageAdapter,
@@ -1102,6 +1547,7 @@ export {
1102
1547
  QueryBuilder,
1103
1548
  Repository,
1104
1549
  SDKError,
1550
+ StorageClient,
1105
1551
  Subscription,
1106
1552
  WSClient,
1107
1553
  createClient