@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.d.ts +209 -1
- package/dist/index.js +468 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cache/client.ts +203 -0
- package/src/core/http.ts +215 -36
- package/src/db/repository.ts +6 -2
- package/src/index.ts +29 -1
- package/src/storage/client.ts +270 -0
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|