@arke-institute/sdk 0.1.2 → 0.1.3
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/content/index.cjs +95 -10
- package/dist/content/index.cjs.map +1 -1
- package/dist/content/index.d.cts +128 -15
- package/dist/content/index.d.ts +128 -15
- package/dist/content/index.js +95 -10
- package/dist/content/index.js.map +1 -1
- package/dist/edit/index.cjs +590 -116
- package/dist/edit/index.cjs.map +1 -1
- package/dist/edit/index.d.cts +2 -2
- package/dist/edit/index.d.ts +2 -2
- package/dist/edit/index.js +580 -116
- package/dist/edit/index.js.map +1 -1
- package/dist/errors-CT7yzKkU.d.cts +874 -0
- package/dist/errors-CT7yzKkU.d.ts +874 -0
- package/dist/graph/index.cjs +52 -58
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +84 -55
- package/dist/graph/index.d.ts +84 -55
- package/dist/graph/index.js +52 -58
- package/dist/graph/index.js.map +1 -1
- package/dist/index.cjs +796 -196
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +796 -196
- package/dist/index.js.map +1 -1
- package/dist/query/index.cjs +67 -0
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +96 -1
- package/dist/query/index.d.ts +96 -1
- package/dist/query/index.js +67 -0
- package/dist/query/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/errors-B82BMmRP.d.cts +0 -343
- package/dist/errors-B82BMmRP.d.ts +0 -343
package/dist/edit/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
// src/edit/types.ts
|
|
2
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
3
|
+
maxRetries: 10,
|
|
4
|
+
baseDelay: 100,
|
|
5
|
+
maxDelay: 5e3,
|
|
6
|
+
jitterFactor: 0.3
|
|
7
|
+
};
|
|
8
|
+
|
|
1
9
|
// src/edit/errors.ts
|
|
2
10
|
var EditError = class extends Error {
|
|
3
11
|
constructor(message, code = "UNKNOWN_ERROR", details) {
|
|
@@ -8,21 +16,51 @@ var EditError = class extends Error {
|
|
|
8
16
|
}
|
|
9
17
|
};
|
|
10
18
|
var EntityNotFoundError = class extends EditError {
|
|
11
|
-
constructor(
|
|
12
|
-
super(`Entity not found: ${
|
|
19
|
+
constructor(id) {
|
|
20
|
+
super(`Entity not found: ${id}`, "ENTITY_NOT_FOUND", { id });
|
|
13
21
|
this.name = "EntityNotFoundError";
|
|
14
22
|
}
|
|
15
23
|
};
|
|
16
24
|
var CASConflictError = class extends EditError {
|
|
17
|
-
constructor(
|
|
25
|
+
constructor(id, expectedTip, actualTip) {
|
|
18
26
|
super(
|
|
19
|
-
`CAS conflict: entity ${
|
|
27
|
+
`CAS conflict: entity ${id} was modified (expected ${expectedTip}, got ${actualTip})`,
|
|
20
28
|
"CAS_CONFLICT",
|
|
21
|
-
{
|
|
29
|
+
{ id, expectedTip, actualTip }
|
|
22
30
|
);
|
|
23
31
|
this.name = "CASConflictError";
|
|
24
32
|
}
|
|
25
33
|
};
|
|
34
|
+
var EntityExistsError = class extends EditError {
|
|
35
|
+
constructor(id) {
|
|
36
|
+
super(`Entity already exists: ${id}`, "ENTITY_EXISTS", { id });
|
|
37
|
+
this.name = "EntityExistsError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var MergeError = class extends EditError {
|
|
41
|
+
constructor(message, sourceId, targetId) {
|
|
42
|
+
super(message, "MERGE_ERROR", { sourceId, targetId });
|
|
43
|
+
this.name = "MergeError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var UnmergeError = class extends EditError {
|
|
47
|
+
constructor(message, sourceId, targetId) {
|
|
48
|
+
super(message, "UNMERGE_ERROR", { sourceId, targetId });
|
|
49
|
+
this.name = "UnmergeError";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var DeleteError = class extends EditError {
|
|
53
|
+
constructor(message, id) {
|
|
54
|
+
super(message, "DELETE_ERROR", { id });
|
|
55
|
+
this.name = "DeleteError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var UndeleteError = class extends EditError {
|
|
59
|
+
constructor(message, id) {
|
|
60
|
+
super(message, "UNDELETE_ERROR", { id });
|
|
61
|
+
this.name = "UndeleteError";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
26
64
|
var ReprocessError = class extends EditError {
|
|
27
65
|
constructor(message, batchId) {
|
|
28
66
|
super(message, "REPROCESS_ERROR", { batchId });
|
|
@@ -36,28 +74,52 @@ var ValidationError = class extends EditError {
|
|
|
36
74
|
}
|
|
37
75
|
};
|
|
38
76
|
var PermissionError = class extends EditError {
|
|
39
|
-
constructor(message,
|
|
40
|
-
super(message, "PERMISSION_DENIED", {
|
|
77
|
+
constructor(message, id) {
|
|
78
|
+
super(message, "PERMISSION_DENIED", { id });
|
|
41
79
|
this.name = "PermissionError";
|
|
42
80
|
}
|
|
43
81
|
};
|
|
82
|
+
var NetworkError = class extends EditError {
|
|
83
|
+
constructor(message, statusCode) {
|
|
84
|
+
super(message, "NETWORK_ERROR", { statusCode });
|
|
85
|
+
this.name = "NetworkError";
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var ContentNotFoundError = class extends EditError {
|
|
89
|
+
constructor(cid) {
|
|
90
|
+
super(`Content not found: ${cid}`, "CONTENT_NOT_FOUND", { cid });
|
|
91
|
+
this.name = "ContentNotFoundError";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var IPFSError = class extends EditError {
|
|
95
|
+
constructor(message) {
|
|
96
|
+
super(message, "IPFS_ERROR");
|
|
97
|
+
this.name = "IPFSError";
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var BackendError = class extends EditError {
|
|
101
|
+
constructor(message) {
|
|
102
|
+
super(message, "BACKEND_ERROR");
|
|
103
|
+
this.name = "BackendError";
|
|
104
|
+
}
|
|
105
|
+
};
|
|
44
106
|
|
|
45
107
|
// src/edit/client.ts
|
|
46
|
-
var
|
|
47
|
-
|
|
48
|
-
initialDelayMs: 2e3,
|
|
49
|
-
// Start with 2s delay (orchestrator needs time to initialize)
|
|
50
|
-
maxDelayMs: 3e4,
|
|
51
|
-
// Cap at 30s
|
|
52
|
-
backoffMultiplier: 2
|
|
53
|
-
// Double each retry
|
|
54
|
-
};
|
|
108
|
+
var RETRYABLE_STATUS_CODES = [409, 503];
|
|
109
|
+
var RETRYABLE_ERRORS = ["ECONNRESET", "ETIMEDOUT", "fetch failed"];
|
|
55
110
|
var EditClient = class {
|
|
56
111
|
constructor(config) {
|
|
57
112
|
this.gatewayUrl = config.gatewayUrl.replace(/\/$/, "");
|
|
58
113
|
this.authToken = config.authToken;
|
|
114
|
+
this.network = config.network || "main";
|
|
115
|
+
this.userId = config.userId;
|
|
116
|
+
this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retryConfig };
|
|
59
117
|
this.statusUrlTransform = config.statusUrlTransform;
|
|
118
|
+
this.apiPrefix = config.apiPrefix ?? "/api";
|
|
60
119
|
}
|
|
120
|
+
// ===========================================================================
|
|
121
|
+
// Configuration Methods
|
|
122
|
+
// ===========================================================================
|
|
61
123
|
/**
|
|
62
124
|
* Update the auth token (useful for token refresh)
|
|
63
125
|
*/
|
|
@@ -65,135 +127,505 @@ var EditClient = class {
|
|
|
65
127
|
this.authToken = token;
|
|
66
128
|
}
|
|
67
129
|
/**
|
|
68
|
-
*
|
|
130
|
+
* Set the network (main or test)
|
|
131
|
+
*/
|
|
132
|
+
setNetwork(network) {
|
|
133
|
+
this.network = network;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Set the user ID for permission checks
|
|
137
|
+
*/
|
|
138
|
+
setUserId(userId) {
|
|
139
|
+
this.userId = userId;
|
|
140
|
+
}
|
|
141
|
+
// ===========================================================================
|
|
142
|
+
// Internal Helpers
|
|
143
|
+
// ===========================================================================
|
|
144
|
+
/**
|
|
145
|
+
* Build URL with API prefix
|
|
69
146
|
*/
|
|
147
|
+
buildUrl(path) {
|
|
148
|
+
return `${this.gatewayUrl}${this.apiPrefix}${path}`;
|
|
149
|
+
}
|
|
70
150
|
sleep(ms) {
|
|
71
151
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
72
152
|
}
|
|
153
|
+
getHeaders(contentType = "application/json") {
|
|
154
|
+
const headers = {};
|
|
155
|
+
if (contentType) {
|
|
156
|
+
headers["Content-Type"] = contentType;
|
|
157
|
+
}
|
|
158
|
+
if (this.authToken) {
|
|
159
|
+
headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
160
|
+
}
|
|
161
|
+
headers["X-Arke-Network"] = this.network;
|
|
162
|
+
if (this.userId) {
|
|
163
|
+
headers["X-User-Id"] = this.userId;
|
|
164
|
+
}
|
|
165
|
+
return headers;
|
|
166
|
+
}
|
|
167
|
+
calculateDelay(attempt) {
|
|
168
|
+
const { baseDelay, maxDelay, jitterFactor } = this.retryConfig;
|
|
169
|
+
const exponentialDelay = baseDelay * Math.pow(2, attempt);
|
|
170
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
171
|
+
const jitter = cappedDelay * jitterFactor * (Math.random() * 2 - 1);
|
|
172
|
+
return Math.max(0, cappedDelay + jitter);
|
|
173
|
+
}
|
|
174
|
+
isRetryableStatus(status) {
|
|
175
|
+
return RETRYABLE_STATUS_CODES.includes(status);
|
|
176
|
+
}
|
|
177
|
+
isRetryableError(error) {
|
|
178
|
+
const message = error.message.toLowerCase();
|
|
179
|
+
return RETRYABLE_ERRORS.some((e) => message.includes(e.toLowerCase()));
|
|
180
|
+
}
|
|
73
181
|
/**
|
|
74
182
|
* Execute a fetch with exponential backoff retry on transient errors
|
|
75
183
|
*/
|
|
76
|
-
async fetchWithRetry(url, options,
|
|
184
|
+
async fetchWithRetry(url, options, context) {
|
|
77
185
|
let lastError = null;
|
|
78
|
-
let
|
|
79
|
-
for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {
|
|
186
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
80
187
|
try {
|
|
81
188
|
const response = await fetch(url, options);
|
|
82
|
-
if (response.status
|
|
83
|
-
|
|
189
|
+
if (this.isRetryableStatus(response.status) && attempt < this.retryConfig.maxRetries) {
|
|
190
|
+
const delay = this.calculateDelay(attempt);
|
|
191
|
+
lastError = new Error(`${context}: ${response.status} ${response.statusText}`);
|
|
84
192
|
await this.sleep(delay);
|
|
85
|
-
delay = Math.min(delay * retryOptions.backoffMultiplier, retryOptions.maxDelayMs);
|
|
86
193
|
continue;
|
|
87
194
|
}
|
|
88
195
|
return response;
|
|
89
196
|
} catch (error) {
|
|
90
197
|
lastError = error;
|
|
91
|
-
if (attempt <
|
|
198
|
+
if (this.isRetryableError(lastError) && attempt < this.retryConfig.maxRetries) {
|
|
199
|
+
const delay = this.calculateDelay(attempt);
|
|
92
200
|
await this.sleep(delay);
|
|
93
|
-
|
|
201
|
+
continue;
|
|
94
202
|
}
|
|
203
|
+
throw new NetworkError(lastError.message);
|
|
95
204
|
}
|
|
96
205
|
}
|
|
97
|
-
throw lastError || new
|
|
206
|
+
throw lastError || new NetworkError("Request failed after retries");
|
|
98
207
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Handle common error responses and throw appropriate error types
|
|
210
|
+
*/
|
|
211
|
+
async handleErrorResponse(response, context) {
|
|
212
|
+
let errorData = {};
|
|
213
|
+
try {
|
|
214
|
+
errorData = await response.json();
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
const message = errorData.message || `${context}: ${response.statusText}`;
|
|
218
|
+
const errorCode = errorData.error || "";
|
|
219
|
+
switch (response.status) {
|
|
220
|
+
case 400:
|
|
221
|
+
throw new ValidationError(message);
|
|
222
|
+
case 403:
|
|
223
|
+
throw new PermissionError(message);
|
|
224
|
+
case 404:
|
|
225
|
+
throw new EntityNotFoundError(message);
|
|
226
|
+
case 409:
|
|
227
|
+
if (errorCode === "CAS_FAILURE") {
|
|
228
|
+
const details = errorData.details;
|
|
229
|
+
throw new CASConflictError(
|
|
230
|
+
context,
|
|
231
|
+
details?.expect || "unknown",
|
|
232
|
+
details?.actual || "unknown"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (errorCode === "CONFLICT") {
|
|
236
|
+
throw new EntityExistsError(message);
|
|
237
|
+
}
|
|
238
|
+
throw new EditError(message, errorCode, errorData.details);
|
|
239
|
+
case 503:
|
|
240
|
+
if (errorCode === "IPFS_ERROR") {
|
|
241
|
+
throw new IPFSError(message);
|
|
242
|
+
}
|
|
243
|
+
if (errorCode === "BACKEND_ERROR") {
|
|
244
|
+
throw new BackendError(message);
|
|
245
|
+
}
|
|
246
|
+
throw new NetworkError(message, response.status);
|
|
247
|
+
default:
|
|
248
|
+
throw new EditError(message, errorCode || "API_ERROR", { status: response.status });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// ===========================================================================
|
|
252
|
+
// Entity CRUD Operations
|
|
253
|
+
// ===========================================================================
|
|
254
|
+
/**
|
|
255
|
+
* Create a new entity
|
|
256
|
+
*/
|
|
257
|
+
async createEntity(request) {
|
|
258
|
+
const url = this.buildUrl("/entities");
|
|
259
|
+
const response = await this.fetchWithRetry(
|
|
260
|
+
url,
|
|
261
|
+
{
|
|
262
|
+
method: "POST",
|
|
263
|
+
headers: this.getHeaders(),
|
|
264
|
+
body: JSON.stringify(request)
|
|
265
|
+
},
|
|
266
|
+
"Create entity"
|
|
267
|
+
);
|
|
268
|
+
if (!response.ok) {
|
|
269
|
+
await this.handleErrorResponse(response, "Create entity");
|
|
270
|
+
}
|
|
271
|
+
return response.json();
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Get an entity by ID
|
|
275
|
+
*/
|
|
276
|
+
async getEntity(id) {
|
|
277
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}`);
|
|
278
|
+
const response = await this.fetchWithRetry(
|
|
279
|
+
url,
|
|
280
|
+
{ headers: this.getHeaders() },
|
|
281
|
+
`Get entity ${id}`
|
|
282
|
+
);
|
|
283
|
+
if (!response.ok) {
|
|
284
|
+
await this.handleErrorResponse(response, `Get entity ${id}`);
|
|
285
|
+
}
|
|
286
|
+
return response.json();
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* List entities with pagination
|
|
290
|
+
*/
|
|
291
|
+
async listEntities(options = {}) {
|
|
292
|
+
const params = new URLSearchParams();
|
|
293
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
294
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
295
|
+
if (options.include_metadata) params.set("include_metadata", "true");
|
|
296
|
+
const queryString = params.toString();
|
|
297
|
+
const url = this.buildUrl(`/entities${queryString ? `?${queryString}` : ""}`);
|
|
298
|
+
const response = await this.fetchWithRetry(url, { headers: this.getHeaders() }, "List entities");
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
await this.handleErrorResponse(response, "List entities");
|
|
301
|
+
}
|
|
302
|
+
return response.json();
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Update an entity (append new version)
|
|
306
|
+
*/
|
|
307
|
+
async updateEntity(id, update) {
|
|
308
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions`);
|
|
309
|
+
const response = await this.fetchWithRetry(
|
|
310
|
+
url,
|
|
311
|
+
{
|
|
312
|
+
method: "POST",
|
|
313
|
+
headers: this.getHeaders(),
|
|
314
|
+
body: JSON.stringify(update)
|
|
315
|
+
},
|
|
316
|
+
`Update entity ${id}`
|
|
317
|
+
);
|
|
318
|
+
if (!response.ok) {
|
|
319
|
+
await this.handleErrorResponse(response, `Update entity ${id}`);
|
|
320
|
+
}
|
|
321
|
+
return response.json();
|
|
322
|
+
}
|
|
323
|
+
// ===========================================================================
|
|
324
|
+
// Version Operations
|
|
325
|
+
// ===========================================================================
|
|
326
|
+
/**
|
|
327
|
+
* List version history for an entity
|
|
328
|
+
*/
|
|
329
|
+
async listVersions(id, options = {}) {
|
|
330
|
+
const params = new URLSearchParams();
|
|
331
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
332
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
333
|
+
const queryString = params.toString();
|
|
334
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions${queryString ? `?${queryString}` : ""}`);
|
|
335
|
+
const response = await this.fetchWithRetry(url, { headers: this.getHeaders() }, `List versions for ${id}`);
|
|
336
|
+
if (!response.ok) {
|
|
337
|
+
await this.handleErrorResponse(response, `List versions for ${id}`);
|
|
338
|
+
}
|
|
339
|
+
return response.json();
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Get a specific version of an entity
|
|
343
|
+
*/
|
|
344
|
+
async getVersion(id, selector) {
|
|
345
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions/${encodeURIComponent(selector)}`);
|
|
346
|
+
const response = await this.fetchWithRetry(
|
|
347
|
+
url,
|
|
348
|
+
{ headers: this.getHeaders() },
|
|
349
|
+
`Get version ${selector} for ${id}`
|
|
350
|
+
);
|
|
351
|
+
if (!response.ok) {
|
|
352
|
+
await this.handleErrorResponse(response, `Get version ${selector} for ${id}`);
|
|
353
|
+
}
|
|
354
|
+
return response.json();
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Resolve an entity ID to its current tip CID (fast lookup)
|
|
358
|
+
*/
|
|
359
|
+
async resolve(id) {
|
|
360
|
+
const url = this.buildUrl(`/resolve/${encodeURIComponent(id)}`);
|
|
361
|
+
const response = await this.fetchWithRetry(
|
|
362
|
+
url,
|
|
363
|
+
{ headers: this.getHeaders() },
|
|
364
|
+
`Resolve ${id}`
|
|
365
|
+
);
|
|
366
|
+
if (!response.ok) {
|
|
367
|
+
await this.handleErrorResponse(response, `Resolve ${id}`);
|
|
368
|
+
}
|
|
369
|
+
return response.json();
|
|
370
|
+
}
|
|
371
|
+
// ===========================================================================
|
|
372
|
+
// Hierarchy Operations
|
|
373
|
+
// ===========================================================================
|
|
374
|
+
/**
|
|
375
|
+
* Update parent-child hierarchy relationships
|
|
376
|
+
*/
|
|
377
|
+
async updateHierarchy(request) {
|
|
378
|
+
const apiRequest = {
|
|
379
|
+
parent_pi: request.parent_id,
|
|
380
|
+
expect_tip: request.expect_tip,
|
|
381
|
+
add_children: request.add_children,
|
|
382
|
+
remove_children: request.remove_children,
|
|
383
|
+
note: request.note
|
|
102
384
|
};
|
|
103
|
-
|
|
104
|
-
|
|
385
|
+
const url = this.buildUrl("/hierarchy");
|
|
386
|
+
const response = await this.fetchWithRetry(
|
|
387
|
+
url,
|
|
388
|
+
{
|
|
389
|
+
method: "POST",
|
|
390
|
+
headers: this.getHeaders(),
|
|
391
|
+
body: JSON.stringify(apiRequest)
|
|
392
|
+
},
|
|
393
|
+
`Update hierarchy for ${request.parent_id}`
|
|
394
|
+
);
|
|
395
|
+
if (!response.ok) {
|
|
396
|
+
await this.handleErrorResponse(response, `Update hierarchy for ${request.parent_id}`);
|
|
105
397
|
}
|
|
106
|
-
return
|
|
398
|
+
return response.json();
|
|
107
399
|
}
|
|
400
|
+
// ===========================================================================
|
|
401
|
+
// Merge Operations
|
|
402
|
+
// ===========================================================================
|
|
108
403
|
/**
|
|
109
|
-
*
|
|
404
|
+
* Merge source entity into target entity
|
|
110
405
|
*/
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
406
|
+
async mergeEntity(sourceId, request) {
|
|
407
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(sourceId)}/merge`);
|
|
408
|
+
const response = await this.fetchWithRetry(
|
|
409
|
+
url,
|
|
410
|
+
{
|
|
411
|
+
method: "POST",
|
|
412
|
+
headers: this.getHeaders(),
|
|
413
|
+
body: JSON.stringify(request)
|
|
414
|
+
},
|
|
415
|
+
`Merge ${sourceId} into ${request.target_id}`
|
|
416
|
+
);
|
|
417
|
+
if (!response.ok) {
|
|
418
|
+
try {
|
|
419
|
+
const error = await response.json();
|
|
420
|
+
throw new MergeError(
|
|
421
|
+
error.message || `Merge failed: ${response.statusText}`,
|
|
422
|
+
sourceId,
|
|
423
|
+
request.target_id
|
|
424
|
+
);
|
|
425
|
+
} catch (e) {
|
|
426
|
+
if (e instanceof MergeError) throw e;
|
|
427
|
+
await this.handleErrorResponse(response, `Merge ${sourceId}`);
|
|
428
|
+
}
|
|
114
429
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
430
|
+
return response.json();
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Unmerge (restore) a previously merged entity
|
|
434
|
+
*/
|
|
435
|
+
async unmergeEntity(sourceId, request) {
|
|
436
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(sourceId)}/unmerge`);
|
|
437
|
+
const response = await this.fetchWithRetry(
|
|
438
|
+
url,
|
|
439
|
+
{
|
|
440
|
+
method: "POST",
|
|
441
|
+
headers: this.getHeaders(),
|
|
442
|
+
body: JSON.stringify(request)
|
|
443
|
+
},
|
|
444
|
+
`Unmerge ${sourceId}`
|
|
119
445
|
);
|
|
446
|
+
if (!response.ok) {
|
|
447
|
+
try {
|
|
448
|
+
const error = await response.json();
|
|
449
|
+
throw new UnmergeError(
|
|
450
|
+
error.message || `Unmerge failed: ${response.statusText}`,
|
|
451
|
+
sourceId,
|
|
452
|
+
request.target_id
|
|
453
|
+
);
|
|
454
|
+
} catch (e) {
|
|
455
|
+
if (e instanceof UnmergeError) throw e;
|
|
456
|
+
await this.handleErrorResponse(response, `Unmerge ${sourceId}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return response.json();
|
|
120
460
|
}
|
|
121
461
|
// ===========================================================================
|
|
122
|
-
//
|
|
462
|
+
// Delete Operations
|
|
123
463
|
// ===========================================================================
|
|
124
464
|
/**
|
|
125
|
-
*
|
|
465
|
+
* Soft delete an entity (creates tombstone, preserves history)
|
|
126
466
|
*/
|
|
127
|
-
async
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
467
|
+
async deleteEntity(id, request) {
|
|
468
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/delete`);
|
|
469
|
+
const response = await this.fetchWithRetry(
|
|
470
|
+
url,
|
|
471
|
+
{
|
|
472
|
+
method: "POST",
|
|
473
|
+
headers: this.getHeaders(),
|
|
474
|
+
body: JSON.stringify(request)
|
|
475
|
+
},
|
|
476
|
+
`Delete ${id}`
|
|
477
|
+
);
|
|
478
|
+
if (!response.ok) {
|
|
479
|
+
try {
|
|
480
|
+
const error = await response.json();
|
|
481
|
+
throw new DeleteError(error.message || `Delete failed: ${response.statusText}`, id);
|
|
482
|
+
} catch (e) {
|
|
483
|
+
if (e instanceof DeleteError) throw e;
|
|
484
|
+
await this.handleErrorResponse(response, `Delete ${id}`);
|
|
485
|
+
}
|
|
133
486
|
}
|
|
487
|
+
return response.json();
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Restore a deleted entity
|
|
491
|
+
*/
|
|
492
|
+
async undeleteEntity(id, request) {
|
|
493
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/undelete`);
|
|
494
|
+
const response = await this.fetchWithRetry(
|
|
495
|
+
url,
|
|
496
|
+
{
|
|
497
|
+
method: "POST",
|
|
498
|
+
headers: this.getHeaders(),
|
|
499
|
+
body: JSON.stringify(request)
|
|
500
|
+
},
|
|
501
|
+
`Undelete ${id}`
|
|
502
|
+
);
|
|
134
503
|
if (!response.ok) {
|
|
135
|
-
|
|
504
|
+
try {
|
|
505
|
+
const error = await response.json();
|
|
506
|
+
throw new UndeleteError(error.message || `Undelete failed: ${response.statusText}`, id);
|
|
507
|
+
} catch (e) {
|
|
508
|
+
if (e instanceof UndeleteError) throw e;
|
|
509
|
+
await this.handleErrorResponse(response, `Undelete ${id}`);
|
|
510
|
+
}
|
|
136
511
|
}
|
|
137
512
|
return response.json();
|
|
138
513
|
}
|
|
514
|
+
// ===========================================================================
|
|
515
|
+
// Content Operations
|
|
516
|
+
// ===========================================================================
|
|
139
517
|
/**
|
|
140
|
-
*
|
|
518
|
+
* Upload files to IPFS
|
|
141
519
|
*/
|
|
142
|
-
async
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
520
|
+
async upload(files) {
|
|
521
|
+
let formData;
|
|
522
|
+
if (files instanceof FormData) {
|
|
523
|
+
formData = files;
|
|
524
|
+
} else {
|
|
525
|
+
formData = new FormData();
|
|
526
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
527
|
+
for (const file of fileArray) {
|
|
528
|
+
if (file instanceof File) {
|
|
529
|
+
formData.append("file", file, file.name);
|
|
530
|
+
} else {
|
|
531
|
+
formData.append("file", file, "file");
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const url = this.buildUrl("/upload");
|
|
536
|
+
const response = await this.fetchWithRetry(
|
|
537
|
+
url,
|
|
538
|
+
{
|
|
539
|
+
method: "POST",
|
|
540
|
+
headers: this.getHeaders(null),
|
|
541
|
+
// No Content-Type for multipart
|
|
542
|
+
body: formData
|
|
543
|
+
},
|
|
544
|
+
"Upload files"
|
|
545
|
+
);
|
|
146
546
|
if (!response.ok) {
|
|
147
|
-
this.handleErrorResponse(response,
|
|
547
|
+
await this.handleErrorResponse(response, "Upload files");
|
|
148
548
|
}
|
|
149
|
-
return response.
|
|
549
|
+
return response.json();
|
|
150
550
|
}
|
|
151
551
|
/**
|
|
152
|
-
* Upload content and
|
|
552
|
+
* Upload text content and return CID
|
|
153
553
|
*/
|
|
154
554
|
async uploadContent(content, filename) {
|
|
155
|
-
const formData = new FormData();
|
|
156
555
|
const blob = new Blob([content], { type: "text/plain" });
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
556
|
+
const file = new File([blob], filename, { type: "text/plain" });
|
|
557
|
+
const [result] = await this.upload(file);
|
|
558
|
+
return result.cid;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Download file content by CID
|
|
562
|
+
*/
|
|
563
|
+
async getContent(cid) {
|
|
564
|
+
const url = this.buildUrl(`/cat/${encodeURIComponent(cid)}`);
|
|
565
|
+
const response = await this.fetchWithRetry(
|
|
566
|
+
url,
|
|
567
|
+
{ headers: this.getHeaders() },
|
|
568
|
+
`Get content ${cid}`
|
|
569
|
+
);
|
|
570
|
+
if (response.status === 404) {
|
|
571
|
+
throw new ContentNotFoundError(cid);
|
|
161
572
|
}
|
|
162
|
-
const response = await fetch(`${this.gatewayUrl}/api/upload`, {
|
|
163
|
-
method: "POST",
|
|
164
|
-
headers,
|
|
165
|
-
body: formData
|
|
166
|
-
});
|
|
167
573
|
if (!response.ok) {
|
|
168
|
-
this.handleErrorResponse(response,
|
|
574
|
+
await this.handleErrorResponse(response, `Get content ${cid}`);
|
|
169
575
|
}
|
|
170
|
-
|
|
171
|
-
return result[0].cid;
|
|
576
|
+
return response.text();
|
|
172
577
|
}
|
|
173
578
|
/**
|
|
174
|
-
*
|
|
579
|
+
* Download a DAG node (JSON) by CID
|
|
175
580
|
*/
|
|
176
|
-
async
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
})
|
|
186
|
-
});
|
|
187
|
-
if (response.status === 409) {
|
|
188
|
-
const entity = await this.getEntity(pi);
|
|
189
|
-
throw new CASConflictError(
|
|
190
|
-
pi,
|
|
191
|
-
update.expect_tip,
|
|
192
|
-
entity.manifest_cid
|
|
193
|
-
);
|
|
581
|
+
async getDag(cid) {
|
|
582
|
+
const url = this.buildUrl(`/dag/${encodeURIComponent(cid)}`);
|
|
583
|
+
const response = await this.fetchWithRetry(
|
|
584
|
+
url,
|
|
585
|
+
{ headers: this.getHeaders() },
|
|
586
|
+
`Get DAG ${cid}`
|
|
587
|
+
);
|
|
588
|
+
if (response.status === 404) {
|
|
589
|
+
throw new ContentNotFoundError(cid);
|
|
194
590
|
}
|
|
195
591
|
if (!response.ok) {
|
|
196
|
-
this.handleErrorResponse(response, `
|
|
592
|
+
await this.handleErrorResponse(response, `Get DAG ${cid}`);
|
|
593
|
+
}
|
|
594
|
+
return response.json();
|
|
595
|
+
}
|
|
596
|
+
// ===========================================================================
|
|
597
|
+
// Arke Origin Operations
|
|
598
|
+
// ===========================================================================
|
|
599
|
+
/**
|
|
600
|
+
* Get the Arke origin block (genesis entity)
|
|
601
|
+
*/
|
|
602
|
+
async getArke() {
|
|
603
|
+
const url = this.buildUrl("/arke");
|
|
604
|
+
const response = await this.fetchWithRetry(
|
|
605
|
+
url,
|
|
606
|
+
{ headers: this.getHeaders() },
|
|
607
|
+
"Get Arke"
|
|
608
|
+
);
|
|
609
|
+
if (!response.ok) {
|
|
610
|
+
await this.handleErrorResponse(response, "Get Arke");
|
|
611
|
+
}
|
|
612
|
+
return response.json();
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Initialize the Arke origin block (creates if doesn't exist)
|
|
616
|
+
*/
|
|
617
|
+
async initArke() {
|
|
618
|
+
const url = this.buildUrl("/arke/init");
|
|
619
|
+
const response = await this.fetchWithRetry(
|
|
620
|
+
url,
|
|
621
|
+
{
|
|
622
|
+
method: "POST",
|
|
623
|
+
headers: this.getHeaders()
|
|
624
|
+
},
|
|
625
|
+
"Init Arke"
|
|
626
|
+
);
|
|
627
|
+
if (!response.ok) {
|
|
628
|
+
await this.handleErrorResponse(response, "Init Arke");
|
|
197
629
|
}
|
|
198
630
|
return response.json();
|
|
199
631
|
}
|
|
@@ -204,16 +636,20 @@ var EditClient = class {
|
|
|
204
636
|
* Trigger reprocessing for an entity
|
|
205
637
|
*/
|
|
206
638
|
async reprocess(request) {
|
|
207
|
-
const response = await
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
639
|
+
const response = await this.fetchWithRetry(
|
|
640
|
+
`${this.gatewayUrl}/reprocess/reprocess`,
|
|
641
|
+
{
|
|
642
|
+
method: "POST",
|
|
643
|
+
headers: this.getHeaders(),
|
|
644
|
+
body: JSON.stringify({
|
|
645
|
+
pi: request.pi,
|
|
646
|
+
phases: request.phases,
|
|
647
|
+
cascade: request.cascade,
|
|
648
|
+
options: request.options
|
|
649
|
+
})
|
|
650
|
+
},
|
|
651
|
+
`Reprocess ${request.pi}`
|
|
652
|
+
);
|
|
217
653
|
if (response.status === 403) {
|
|
218
654
|
const error = await response.json().catch(() => ({}));
|
|
219
655
|
throw new PermissionError(
|
|
@@ -223,29 +659,23 @@ var EditClient = class {
|
|
|
223
659
|
}
|
|
224
660
|
if (!response.ok) {
|
|
225
661
|
const error = await response.json().catch(() => ({}));
|
|
226
|
-
throw new ReprocessError(
|
|
227
|
-
error.message || `Reprocess failed: ${response.statusText}`,
|
|
228
|
-
void 0
|
|
229
|
-
);
|
|
662
|
+
throw new ReprocessError(error.message || `Reprocess failed: ${response.statusText}`);
|
|
230
663
|
}
|
|
231
664
|
return response.json();
|
|
232
665
|
}
|
|
233
666
|
/**
|
|
234
667
|
* Get reprocessing status by batch ID
|
|
235
|
-
*
|
|
236
|
-
* Uses exponential backoff retry to handle transient 500 errors
|
|
237
|
-
* that occur when the orchestrator is initializing.
|
|
238
|
-
*
|
|
239
|
-
* @param statusUrl - The status URL returned from reprocess()
|
|
240
|
-
* @param isFirstPoll - If true, uses a longer initial delay (orchestrator warmup)
|
|
241
668
|
*/
|
|
242
669
|
async getReprocessStatus(statusUrl, isFirstPoll = false) {
|
|
243
|
-
const retryOptions = isFirstPoll ? { ...DEFAULT_RETRY_OPTIONS, initialDelayMs: 3e3 } : DEFAULT_RETRY_OPTIONS;
|
|
244
670
|
const fetchUrl = this.statusUrlTransform ? this.statusUrlTransform(statusUrl) : statusUrl;
|
|
671
|
+
const delay = isFirstPoll ? 3e3 : this.retryConfig.baseDelay;
|
|
672
|
+
if (isFirstPoll) {
|
|
673
|
+
await this.sleep(delay);
|
|
674
|
+
}
|
|
245
675
|
const response = await this.fetchWithRetry(
|
|
246
676
|
fetchUrl,
|
|
247
677
|
{ headers: this.getHeaders() },
|
|
248
|
-
|
|
678
|
+
"Get reprocess status"
|
|
249
679
|
);
|
|
250
680
|
if (!response.ok) {
|
|
251
681
|
throw new EditError(
|
|
@@ -256,6 +686,30 @@ var EditClient = class {
|
|
|
256
686
|
}
|
|
257
687
|
return response.json();
|
|
258
688
|
}
|
|
689
|
+
// ===========================================================================
|
|
690
|
+
// Utility Methods
|
|
691
|
+
// ===========================================================================
|
|
692
|
+
/**
|
|
693
|
+
* Execute an operation with automatic CAS retry
|
|
694
|
+
*/
|
|
695
|
+
async withCAS(id, operation, maxRetries = 3) {
|
|
696
|
+
let lastError = null;
|
|
697
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
698
|
+
try {
|
|
699
|
+
const entity = await this.getEntity(id);
|
|
700
|
+
return await operation(entity);
|
|
701
|
+
} catch (error) {
|
|
702
|
+
if (error instanceof CASConflictError && attempt < maxRetries - 1) {
|
|
703
|
+
lastError = error;
|
|
704
|
+
const delay = this.calculateDelay(attempt);
|
|
705
|
+
await this.sleep(delay);
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
throw error;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
throw lastError || new EditError("withCAS failed after retries");
|
|
712
|
+
}
|
|
259
713
|
};
|
|
260
714
|
|
|
261
715
|
// src/edit/diff.ts
|
|
@@ -773,10 +1227,10 @@ var EditSession = class {
|
|
|
773
1227
|
const result = {};
|
|
774
1228
|
if (!this.entity) return result;
|
|
775
1229
|
const entityContext = {
|
|
776
|
-
pi: this.entity.
|
|
1230
|
+
pi: this.entity.id,
|
|
777
1231
|
ver: this.entity.ver,
|
|
778
1232
|
parentPi: this.entity.parent_pi,
|
|
779
|
-
childrenCount: this.entity.children_pi
|
|
1233
|
+
childrenCount: this.entity.children_pi?.length ?? 0,
|
|
780
1234
|
currentContent: this.loadedComponents
|
|
781
1235
|
};
|
|
782
1236
|
for (const component of this.scope.components) {
|
|
@@ -798,7 +1252,7 @@ var EditSession = class {
|
|
|
798
1252
|
}
|
|
799
1253
|
if (this.scope.cascade) {
|
|
800
1254
|
prompt = PromptBuilder.buildCascadePrompt(prompt, {
|
|
801
|
-
path: [this.entity.
|
|
1255
|
+
path: [this.entity.id, this.entity.parent_pi || "root"].filter(Boolean),
|
|
802
1256
|
depth: 0,
|
|
803
1257
|
stopAtPi: this.scope.stopAtPi
|
|
804
1258
|
});
|
|
@@ -859,7 +1313,7 @@ var EditSession = class {
|
|
|
859
1313
|
note
|
|
860
1314
|
});
|
|
861
1315
|
this.result.saved = {
|
|
862
|
-
pi: version.
|
|
1316
|
+
pi: version.id,
|
|
863
1317
|
newVersion: version.ver,
|
|
864
1318
|
newTip: version.tip
|
|
865
1319
|
};
|
|
@@ -969,15 +1423,25 @@ var EditSession = class {
|
|
|
969
1423
|
}
|
|
970
1424
|
};
|
|
971
1425
|
export {
|
|
1426
|
+
BackendError,
|
|
972
1427
|
CASConflictError,
|
|
1428
|
+
ContentNotFoundError,
|
|
1429
|
+
DEFAULT_RETRY_CONFIG,
|
|
1430
|
+
DeleteError,
|
|
973
1431
|
DiffEngine,
|
|
974
1432
|
EditClient,
|
|
975
1433
|
EditError,
|
|
976
1434
|
EditSession,
|
|
1435
|
+
EntityExistsError,
|
|
977
1436
|
EntityNotFoundError,
|
|
1437
|
+
IPFSError,
|
|
1438
|
+
MergeError,
|
|
1439
|
+
NetworkError,
|
|
978
1440
|
PermissionError,
|
|
979
1441
|
PromptBuilder,
|
|
980
1442
|
ReprocessError,
|
|
1443
|
+
UndeleteError,
|
|
1444
|
+
UnmergeError,
|
|
981
1445
|
ValidationError
|
|
982
1446
|
};
|
|
983
1447
|
//# sourceMappingURL=index.js.map
|