@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.cjs
CHANGED
|
@@ -30,19 +30,37 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/edit/index.ts
|
|
31
31
|
var edit_exports = {};
|
|
32
32
|
__export(edit_exports, {
|
|
33
|
+
BackendError: () => BackendError,
|
|
33
34
|
CASConflictError: () => CASConflictError,
|
|
35
|
+
ContentNotFoundError: () => ContentNotFoundError,
|
|
36
|
+
DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
|
|
37
|
+
DeleteError: () => DeleteError,
|
|
34
38
|
DiffEngine: () => DiffEngine,
|
|
35
39
|
EditClient: () => EditClient,
|
|
36
40
|
EditError: () => EditError,
|
|
37
41
|
EditSession: () => EditSession,
|
|
42
|
+
EntityExistsError: () => EntityExistsError,
|
|
38
43
|
EntityNotFoundError: () => EntityNotFoundError,
|
|
44
|
+
IPFSError: () => IPFSError,
|
|
45
|
+
MergeError: () => MergeError,
|
|
46
|
+
NetworkError: () => NetworkError,
|
|
39
47
|
PermissionError: () => PermissionError,
|
|
40
48
|
PromptBuilder: () => PromptBuilder,
|
|
41
49
|
ReprocessError: () => ReprocessError,
|
|
50
|
+
UndeleteError: () => UndeleteError,
|
|
51
|
+
UnmergeError: () => UnmergeError,
|
|
42
52
|
ValidationError: () => ValidationError
|
|
43
53
|
});
|
|
44
54
|
module.exports = __toCommonJS(edit_exports);
|
|
45
55
|
|
|
56
|
+
// src/edit/types.ts
|
|
57
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
58
|
+
maxRetries: 10,
|
|
59
|
+
baseDelay: 100,
|
|
60
|
+
maxDelay: 5e3,
|
|
61
|
+
jitterFactor: 0.3
|
|
62
|
+
};
|
|
63
|
+
|
|
46
64
|
// src/edit/errors.ts
|
|
47
65
|
var EditError = class extends Error {
|
|
48
66
|
constructor(message, code = "UNKNOWN_ERROR", details) {
|
|
@@ -53,21 +71,51 @@ var EditError = class extends Error {
|
|
|
53
71
|
}
|
|
54
72
|
};
|
|
55
73
|
var EntityNotFoundError = class extends EditError {
|
|
56
|
-
constructor(
|
|
57
|
-
super(`Entity not found: ${
|
|
74
|
+
constructor(id) {
|
|
75
|
+
super(`Entity not found: ${id}`, "ENTITY_NOT_FOUND", { id });
|
|
58
76
|
this.name = "EntityNotFoundError";
|
|
59
77
|
}
|
|
60
78
|
};
|
|
61
79
|
var CASConflictError = class extends EditError {
|
|
62
|
-
constructor(
|
|
80
|
+
constructor(id, expectedTip, actualTip) {
|
|
63
81
|
super(
|
|
64
|
-
`CAS conflict: entity ${
|
|
82
|
+
`CAS conflict: entity ${id} was modified (expected ${expectedTip}, got ${actualTip})`,
|
|
65
83
|
"CAS_CONFLICT",
|
|
66
|
-
{
|
|
84
|
+
{ id, expectedTip, actualTip }
|
|
67
85
|
);
|
|
68
86
|
this.name = "CASConflictError";
|
|
69
87
|
}
|
|
70
88
|
};
|
|
89
|
+
var EntityExistsError = class extends EditError {
|
|
90
|
+
constructor(id) {
|
|
91
|
+
super(`Entity already exists: ${id}`, "ENTITY_EXISTS", { id });
|
|
92
|
+
this.name = "EntityExistsError";
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var MergeError = class extends EditError {
|
|
96
|
+
constructor(message, sourceId, targetId) {
|
|
97
|
+
super(message, "MERGE_ERROR", { sourceId, targetId });
|
|
98
|
+
this.name = "MergeError";
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var UnmergeError = class extends EditError {
|
|
102
|
+
constructor(message, sourceId, targetId) {
|
|
103
|
+
super(message, "UNMERGE_ERROR", { sourceId, targetId });
|
|
104
|
+
this.name = "UnmergeError";
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var DeleteError = class extends EditError {
|
|
108
|
+
constructor(message, id) {
|
|
109
|
+
super(message, "DELETE_ERROR", { id });
|
|
110
|
+
this.name = "DeleteError";
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var UndeleteError = class extends EditError {
|
|
114
|
+
constructor(message, id) {
|
|
115
|
+
super(message, "UNDELETE_ERROR", { id });
|
|
116
|
+
this.name = "UndeleteError";
|
|
117
|
+
}
|
|
118
|
+
};
|
|
71
119
|
var ReprocessError = class extends EditError {
|
|
72
120
|
constructor(message, batchId) {
|
|
73
121
|
super(message, "REPROCESS_ERROR", { batchId });
|
|
@@ -81,28 +129,52 @@ var ValidationError = class extends EditError {
|
|
|
81
129
|
}
|
|
82
130
|
};
|
|
83
131
|
var PermissionError = class extends EditError {
|
|
84
|
-
constructor(message,
|
|
85
|
-
super(message, "PERMISSION_DENIED", {
|
|
132
|
+
constructor(message, id) {
|
|
133
|
+
super(message, "PERMISSION_DENIED", { id });
|
|
86
134
|
this.name = "PermissionError";
|
|
87
135
|
}
|
|
88
136
|
};
|
|
137
|
+
var NetworkError = class extends EditError {
|
|
138
|
+
constructor(message, statusCode) {
|
|
139
|
+
super(message, "NETWORK_ERROR", { statusCode });
|
|
140
|
+
this.name = "NetworkError";
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var ContentNotFoundError = class extends EditError {
|
|
144
|
+
constructor(cid) {
|
|
145
|
+
super(`Content not found: ${cid}`, "CONTENT_NOT_FOUND", { cid });
|
|
146
|
+
this.name = "ContentNotFoundError";
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var IPFSError = class extends EditError {
|
|
150
|
+
constructor(message) {
|
|
151
|
+
super(message, "IPFS_ERROR");
|
|
152
|
+
this.name = "IPFSError";
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var BackendError = class extends EditError {
|
|
156
|
+
constructor(message) {
|
|
157
|
+
super(message, "BACKEND_ERROR");
|
|
158
|
+
this.name = "BackendError";
|
|
159
|
+
}
|
|
160
|
+
};
|
|
89
161
|
|
|
90
162
|
// src/edit/client.ts
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
initialDelayMs: 2e3,
|
|
94
|
-
// Start with 2s delay (orchestrator needs time to initialize)
|
|
95
|
-
maxDelayMs: 3e4,
|
|
96
|
-
// Cap at 30s
|
|
97
|
-
backoffMultiplier: 2
|
|
98
|
-
// Double each retry
|
|
99
|
-
};
|
|
163
|
+
var RETRYABLE_STATUS_CODES = [409, 503];
|
|
164
|
+
var RETRYABLE_ERRORS = ["ECONNRESET", "ETIMEDOUT", "fetch failed"];
|
|
100
165
|
var EditClient = class {
|
|
101
166
|
constructor(config) {
|
|
102
167
|
this.gatewayUrl = config.gatewayUrl.replace(/\/$/, "");
|
|
103
168
|
this.authToken = config.authToken;
|
|
169
|
+
this.network = config.network || "main";
|
|
170
|
+
this.userId = config.userId;
|
|
171
|
+
this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retryConfig };
|
|
104
172
|
this.statusUrlTransform = config.statusUrlTransform;
|
|
173
|
+
this.apiPrefix = config.apiPrefix ?? "/api";
|
|
105
174
|
}
|
|
175
|
+
// ===========================================================================
|
|
176
|
+
// Configuration Methods
|
|
177
|
+
// ===========================================================================
|
|
106
178
|
/**
|
|
107
179
|
* Update the auth token (useful for token refresh)
|
|
108
180
|
*/
|
|
@@ -110,135 +182,505 @@ var EditClient = class {
|
|
|
110
182
|
this.authToken = token;
|
|
111
183
|
}
|
|
112
184
|
/**
|
|
113
|
-
*
|
|
185
|
+
* Set the network (main or test)
|
|
186
|
+
*/
|
|
187
|
+
setNetwork(network) {
|
|
188
|
+
this.network = network;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Set the user ID for permission checks
|
|
192
|
+
*/
|
|
193
|
+
setUserId(userId) {
|
|
194
|
+
this.userId = userId;
|
|
195
|
+
}
|
|
196
|
+
// ===========================================================================
|
|
197
|
+
// Internal Helpers
|
|
198
|
+
// ===========================================================================
|
|
199
|
+
/**
|
|
200
|
+
* Build URL with API prefix
|
|
114
201
|
*/
|
|
202
|
+
buildUrl(path) {
|
|
203
|
+
return `${this.gatewayUrl}${this.apiPrefix}${path}`;
|
|
204
|
+
}
|
|
115
205
|
sleep(ms) {
|
|
116
206
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
117
207
|
}
|
|
208
|
+
getHeaders(contentType = "application/json") {
|
|
209
|
+
const headers = {};
|
|
210
|
+
if (contentType) {
|
|
211
|
+
headers["Content-Type"] = contentType;
|
|
212
|
+
}
|
|
213
|
+
if (this.authToken) {
|
|
214
|
+
headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
215
|
+
}
|
|
216
|
+
headers["X-Arke-Network"] = this.network;
|
|
217
|
+
if (this.userId) {
|
|
218
|
+
headers["X-User-Id"] = this.userId;
|
|
219
|
+
}
|
|
220
|
+
return headers;
|
|
221
|
+
}
|
|
222
|
+
calculateDelay(attempt) {
|
|
223
|
+
const { baseDelay, maxDelay, jitterFactor } = this.retryConfig;
|
|
224
|
+
const exponentialDelay = baseDelay * Math.pow(2, attempt);
|
|
225
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
226
|
+
const jitter = cappedDelay * jitterFactor * (Math.random() * 2 - 1);
|
|
227
|
+
return Math.max(0, cappedDelay + jitter);
|
|
228
|
+
}
|
|
229
|
+
isRetryableStatus(status) {
|
|
230
|
+
return RETRYABLE_STATUS_CODES.includes(status);
|
|
231
|
+
}
|
|
232
|
+
isRetryableError(error) {
|
|
233
|
+
const message = error.message.toLowerCase();
|
|
234
|
+
return RETRYABLE_ERRORS.some((e) => message.includes(e.toLowerCase()));
|
|
235
|
+
}
|
|
118
236
|
/**
|
|
119
237
|
* Execute a fetch with exponential backoff retry on transient errors
|
|
120
238
|
*/
|
|
121
|
-
async fetchWithRetry(url, options,
|
|
239
|
+
async fetchWithRetry(url, options, context) {
|
|
122
240
|
let lastError = null;
|
|
123
|
-
let
|
|
124
|
-
for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {
|
|
241
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
125
242
|
try {
|
|
126
243
|
const response = await fetch(url, options);
|
|
127
|
-
if (response.status
|
|
128
|
-
|
|
244
|
+
if (this.isRetryableStatus(response.status) && attempt < this.retryConfig.maxRetries) {
|
|
245
|
+
const delay = this.calculateDelay(attempt);
|
|
246
|
+
lastError = new Error(`${context}: ${response.status} ${response.statusText}`);
|
|
129
247
|
await this.sleep(delay);
|
|
130
|
-
delay = Math.min(delay * retryOptions.backoffMultiplier, retryOptions.maxDelayMs);
|
|
131
248
|
continue;
|
|
132
249
|
}
|
|
133
250
|
return response;
|
|
134
251
|
} catch (error) {
|
|
135
252
|
lastError = error;
|
|
136
|
-
if (attempt <
|
|
253
|
+
if (this.isRetryableError(lastError) && attempt < this.retryConfig.maxRetries) {
|
|
254
|
+
const delay = this.calculateDelay(attempt);
|
|
137
255
|
await this.sleep(delay);
|
|
138
|
-
|
|
256
|
+
continue;
|
|
139
257
|
}
|
|
258
|
+
throw new NetworkError(lastError.message);
|
|
140
259
|
}
|
|
141
260
|
}
|
|
142
|
-
throw lastError || new
|
|
261
|
+
throw lastError || new NetworkError("Request failed after retries");
|
|
143
262
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Handle common error responses and throw appropriate error types
|
|
265
|
+
*/
|
|
266
|
+
async handleErrorResponse(response, context) {
|
|
267
|
+
let errorData = {};
|
|
268
|
+
try {
|
|
269
|
+
errorData = await response.json();
|
|
270
|
+
} catch {
|
|
271
|
+
}
|
|
272
|
+
const message = errorData.message || `${context}: ${response.statusText}`;
|
|
273
|
+
const errorCode = errorData.error || "";
|
|
274
|
+
switch (response.status) {
|
|
275
|
+
case 400:
|
|
276
|
+
throw new ValidationError(message);
|
|
277
|
+
case 403:
|
|
278
|
+
throw new PermissionError(message);
|
|
279
|
+
case 404:
|
|
280
|
+
throw new EntityNotFoundError(message);
|
|
281
|
+
case 409:
|
|
282
|
+
if (errorCode === "CAS_FAILURE") {
|
|
283
|
+
const details = errorData.details;
|
|
284
|
+
throw new CASConflictError(
|
|
285
|
+
context,
|
|
286
|
+
details?.expect || "unknown",
|
|
287
|
+
details?.actual || "unknown"
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (errorCode === "CONFLICT") {
|
|
291
|
+
throw new EntityExistsError(message);
|
|
292
|
+
}
|
|
293
|
+
throw new EditError(message, errorCode, errorData.details);
|
|
294
|
+
case 503:
|
|
295
|
+
if (errorCode === "IPFS_ERROR") {
|
|
296
|
+
throw new IPFSError(message);
|
|
297
|
+
}
|
|
298
|
+
if (errorCode === "BACKEND_ERROR") {
|
|
299
|
+
throw new BackendError(message);
|
|
300
|
+
}
|
|
301
|
+
throw new NetworkError(message, response.status);
|
|
302
|
+
default:
|
|
303
|
+
throw new EditError(message, errorCode || "API_ERROR", { status: response.status });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// ===========================================================================
|
|
307
|
+
// Entity CRUD Operations
|
|
308
|
+
// ===========================================================================
|
|
309
|
+
/**
|
|
310
|
+
* Create a new entity
|
|
311
|
+
*/
|
|
312
|
+
async createEntity(request) {
|
|
313
|
+
const url = this.buildUrl("/entities");
|
|
314
|
+
const response = await this.fetchWithRetry(
|
|
315
|
+
url,
|
|
316
|
+
{
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: this.getHeaders(),
|
|
319
|
+
body: JSON.stringify(request)
|
|
320
|
+
},
|
|
321
|
+
"Create entity"
|
|
322
|
+
);
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
await this.handleErrorResponse(response, "Create entity");
|
|
325
|
+
}
|
|
326
|
+
return response.json();
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get an entity by ID
|
|
330
|
+
*/
|
|
331
|
+
async getEntity(id) {
|
|
332
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}`);
|
|
333
|
+
const response = await this.fetchWithRetry(
|
|
334
|
+
url,
|
|
335
|
+
{ headers: this.getHeaders() },
|
|
336
|
+
`Get entity ${id}`
|
|
337
|
+
);
|
|
338
|
+
if (!response.ok) {
|
|
339
|
+
await this.handleErrorResponse(response, `Get entity ${id}`);
|
|
340
|
+
}
|
|
341
|
+
return response.json();
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* List entities with pagination
|
|
345
|
+
*/
|
|
346
|
+
async listEntities(options = {}) {
|
|
347
|
+
const params = new URLSearchParams();
|
|
348
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
349
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
350
|
+
if (options.include_metadata) params.set("include_metadata", "true");
|
|
351
|
+
const queryString = params.toString();
|
|
352
|
+
const url = this.buildUrl(`/entities${queryString ? `?${queryString}` : ""}`);
|
|
353
|
+
const response = await this.fetchWithRetry(url, { headers: this.getHeaders() }, "List entities");
|
|
354
|
+
if (!response.ok) {
|
|
355
|
+
await this.handleErrorResponse(response, "List entities");
|
|
356
|
+
}
|
|
357
|
+
return response.json();
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Update an entity (append new version)
|
|
361
|
+
*/
|
|
362
|
+
async updateEntity(id, update) {
|
|
363
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions`);
|
|
364
|
+
const response = await this.fetchWithRetry(
|
|
365
|
+
url,
|
|
366
|
+
{
|
|
367
|
+
method: "POST",
|
|
368
|
+
headers: this.getHeaders(),
|
|
369
|
+
body: JSON.stringify(update)
|
|
370
|
+
},
|
|
371
|
+
`Update entity ${id}`
|
|
372
|
+
);
|
|
373
|
+
if (!response.ok) {
|
|
374
|
+
await this.handleErrorResponse(response, `Update entity ${id}`);
|
|
375
|
+
}
|
|
376
|
+
return response.json();
|
|
377
|
+
}
|
|
378
|
+
// ===========================================================================
|
|
379
|
+
// Version Operations
|
|
380
|
+
// ===========================================================================
|
|
381
|
+
/**
|
|
382
|
+
* List version history for an entity
|
|
383
|
+
*/
|
|
384
|
+
async listVersions(id, options = {}) {
|
|
385
|
+
const params = new URLSearchParams();
|
|
386
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
387
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
388
|
+
const queryString = params.toString();
|
|
389
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions${queryString ? `?${queryString}` : ""}`);
|
|
390
|
+
const response = await this.fetchWithRetry(url, { headers: this.getHeaders() }, `List versions for ${id}`);
|
|
391
|
+
if (!response.ok) {
|
|
392
|
+
await this.handleErrorResponse(response, `List versions for ${id}`);
|
|
393
|
+
}
|
|
394
|
+
return response.json();
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Get a specific version of an entity
|
|
398
|
+
*/
|
|
399
|
+
async getVersion(id, selector) {
|
|
400
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions/${encodeURIComponent(selector)}`);
|
|
401
|
+
const response = await this.fetchWithRetry(
|
|
402
|
+
url,
|
|
403
|
+
{ headers: this.getHeaders() },
|
|
404
|
+
`Get version ${selector} for ${id}`
|
|
405
|
+
);
|
|
406
|
+
if (!response.ok) {
|
|
407
|
+
await this.handleErrorResponse(response, `Get version ${selector} for ${id}`);
|
|
408
|
+
}
|
|
409
|
+
return response.json();
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Resolve an entity ID to its current tip CID (fast lookup)
|
|
413
|
+
*/
|
|
414
|
+
async resolve(id) {
|
|
415
|
+
const url = this.buildUrl(`/resolve/${encodeURIComponent(id)}`);
|
|
416
|
+
const response = await this.fetchWithRetry(
|
|
417
|
+
url,
|
|
418
|
+
{ headers: this.getHeaders() },
|
|
419
|
+
`Resolve ${id}`
|
|
420
|
+
);
|
|
421
|
+
if (!response.ok) {
|
|
422
|
+
await this.handleErrorResponse(response, `Resolve ${id}`);
|
|
423
|
+
}
|
|
424
|
+
return response.json();
|
|
425
|
+
}
|
|
426
|
+
// ===========================================================================
|
|
427
|
+
// Hierarchy Operations
|
|
428
|
+
// ===========================================================================
|
|
429
|
+
/**
|
|
430
|
+
* Update parent-child hierarchy relationships
|
|
431
|
+
*/
|
|
432
|
+
async updateHierarchy(request) {
|
|
433
|
+
const apiRequest = {
|
|
434
|
+
parent_pi: request.parent_id,
|
|
435
|
+
expect_tip: request.expect_tip,
|
|
436
|
+
add_children: request.add_children,
|
|
437
|
+
remove_children: request.remove_children,
|
|
438
|
+
note: request.note
|
|
147
439
|
};
|
|
148
|
-
|
|
149
|
-
|
|
440
|
+
const url = this.buildUrl("/hierarchy");
|
|
441
|
+
const response = await this.fetchWithRetry(
|
|
442
|
+
url,
|
|
443
|
+
{
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers: this.getHeaders(),
|
|
446
|
+
body: JSON.stringify(apiRequest)
|
|
447
|
+
},
|
|
448
|
+
`Update hierarchy for ${request.parent_id}`
|
|
449
|
+
);
|
|
450
|
+
if (!response.ok) {
|
|
451
|
+
await this.handleErrorResponse(response, `Update hierarchy for ${request.parent_id}`);
|
|
150
452
|
}
|
|
151
|
-
return
|
|
453
|
+
return response.json();
|
|
152
454
|
}
|
|
455
|
+
// ===========================================================================
|
|
456
|
+
// Merge Operations
|
|
457
|
+
// ===========================================================================
|
|
153
458
|
/**
|
|
154
|
-
*
|
|
459
|
+
* Merge source entity into target entity
|
|
155
460
|
*/
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
461
|
+
async mergeEntity(sourceId, request) {
|
|
462
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(sourceId)}/merge`);
|
|
463
|
+
const response = await this.fetchWithRetry(
|
|
464
|
+
url,
|
|
465
|
+
{
|
|
466
|
+
method: "POST",
|
|
467
|
+
headers: this.getHeaders(),
|
|
468
|
+
body: JSON.stringify(request)
|
|
469
|
+
},
|
|
470
|
+
`Merge ${sourceId} into ${request.target_id}`
|
|
471
|
+
);
|
|
472
|
+
if (!response.ok) {
|
|
473
|
+
try {
|
|
474
|
+
const error = await response.json();
|
|
475
|
+
throw new MergeError(
|
|
476
|
+
error.message || `Merge failed: ${response.statusText}`,
|
|
477
|
+
sourceId,
|
|
478
|
+
request.target_id
|
|
479
|
+
);
|
|
480
|
+
} catch (e) {
|
|
481
|
+
if (e instanceof MergeError) throw e;
|
|
482
|
+
await this.handleErrorResponse(response, `Merge ${sourceId}`);
|
|
483
|
+
}
|
|
159
484
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
485
|
+
return response.json();
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Unmerge (restore) a previously merged entity
|
|
489
|
+
*/
|
|
490
|
+
async unmergeEntity(sourceId, request) {
|
|
491
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(sourceId)}/unmerge`);
|
|
492
|
+
const response = await this.fetchWithRetry(
|
|
493
|
+
url,
|
|
494
|
+
{
|
|
495
|
+
method: "POST",
|
|
496
|
+
headers: this.getHeaders(),
|
|
497
|
+
body: JSON.stringify(request)
|
|
498
|
+
},
|
|
499
|
+
`Unmerge ${sourceId}`
|
|
164
500
|
);
|
|
501
|
+
if (!response.ok) {
|
|
502
|
+
try {
|
|
503
|
+
const error = await response.json();
|
|
504
|
+
throw new UnmergeError(
|
|
505
|
+
error.message || `Unmerge failed: ${response.statusText}`,
|
|
506
|
+
sourceId,
|
|
507
|
+
request.target_id
|
|
508
|
+
);
|
|
509
|
+
} catch (e) {
|
|
510
|
+
if (e instanceof UnmergeError) throw e;
|
|
511
|
+
await this.handleErrorResponse(response, `Unmerge ${sourceId}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return response.json();
|
|
165
515
|
}
|
|
166
516
|
// ===========================================================================
|
|
167
|
-
//
|
|
517
|
+
// Delete Operations
|
|
168
518
|
// ===========================================================================
|
|
169
519
|
/**
|
|
170
|
-
*
|
|
520
|
+
* Soft delete an entity (creates tombstone, preserves history)
|
|
171
521
|
*/
|
|
172
|
-
async
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
522
|
+
async deleteEntity(id, request) {
|
|
523
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/delete`);
|
|
524
|
+
const response = await this.fetchWithRetry(
|
|
525
|
+
url,
|
|
526
|
+
{
|
|
527
|
+
method: "POST",
|
|
528
|
+
headers: this.getHeaders(),
|
|
529
|
+
body: JSON.stringify(request)
|
|
530
|
+
},
|
|
531
|
+
`Delete ${id}`
|
|
532
|
+
);
|
|
533
|
+
if (!response.ok) {
|
|
534
|
+
try {
|
|
535
|
+
const error = await response.json();
|
|
536
|
+
throw new DeleteError(error.message || `Delete failed: ${response.statusText}`, id);
|
|
537
|
+
} catch (e) {
|
|
538
|
+
if (e instanceof DeleteError) throw e;
|
|
539
|
+
await this.handleErrorResponse(response, `Delete ${id}`);
|
|
540
|
+
}
|
|
178
541
|
}
|
|
542
|
+
return response.json();
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Restore a deleted entity
|
|
546
|
+
*/
|
|
547
|
+
async undeleteEntity(id, request) {
|
|
548
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/undelete`);
|
|
549
|
+
const response = await this.fetchWithRetry(
|
|
550
|
+
url,
|
|
551
|
+
{
|
|
552
|
+
method: "POST",
|
|
553
|
+
headers: this.getHeaders(),
|
|
554
|
+
body: JSON.stringify(request)
|
|
555
|
+
},
|
|
556
|
+
`Undelete ${id}`
|
|
557
|
+
);
|
|
179
558
|
if (!response.ok) {
|
|
180
|
-
|
|
559
|
+
try {
|
|
560
|
+
const error = await response.json();
|
|
561
|
+
throw new UndeleteError(error.message || `Undelete failed: ${response.statusText}`, id);
|
|
562
|
+
} catch (e) {
|
|
563
|
+
if (e instanceof UndeleteError) throw e;
|
|
564
|
+
await this.handleErrorResponse(response, `Undelete ${id}`);
|
|
565
|
+
}
|
|
181
566
|
}
|
|
182
567
|
return response.json();
|
|
183
568
|
}
|
|
569
|
+
// ===========================================================================
|
|
570
|
+
// Content Operations
|
|
571
|
+
// ===========================================================================
|
|
184
572
|
/**
|
|
185
|
-
*
|
|
573
|
+
* Upload files to IPFS
|
|
186
574
|
*/
|
|
187
|
-
async
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
575
|
+
async upload(files) {
|
|
576
|
+
let formData;
|
|
577
|
+
if (files instanceof FormData) {
|
|
578
|
+
formData = files;
|
|
579
|
+
} else {
|
|
580
|
+
formData = new FormData();
|
|
581
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
582
|
+
for (const file of fileArray) {
|
|
583
|
+
if (file instanceof File) {
|
|
584
|
+
formData.append("file", file, file.name);
|
|
585
|
+
} else {
|
|
586
|
+
formData.append("file", file, "file");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const url = this.buildUrl("/upload");
|
|
591
|
+
const response = await this.fetchWithRetry(
|
|
592
|
+
url,
|
|
593
|
+
{
|
|
594
|
+
method: "POST",
|
|
595
|
+
headers: this.getHeaders(null),
|
|
596
|
+
// No Content-Type for multipart
|
|
597
|
+
body: formData
|
|
598
|
+
},
|
|
599
|
+
"Upload files"
|
|
600
|
+
);
|
|
191
601
|
if (!response.ok) {
|
|
192
|
-
this.handleErrorResponse(response,
|
|
602
|
+
await this.handleErrorResponse(response, "Upload files");
|
|
193
603
|
}
|
|
194
|
-
return response.
|
|
604
|
+
return response.json();
|
|
195
605
|
}
|
|
196
606
|
/**
|
|
197
|
-
* Upload content and
|
|
607
|
+
* Upload text content and return CID
|
|
198
608
|
*/
|
|
199
609
|
async uploadContent(content, filename) {
|
|
200
|
-
const formData = new FormData();
|
|
201
610
|
const blob = new Blob([content], { type: "text/plain" });
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
611
|
+
const file = new File([blob], filename, { type: "text/plain" });
|
|
612
|
+
const [result] = await this.upload(file);
|
|
613
|
+
return result.cid;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Download file content by CID
|
|
617
|
+
*/
|
|
618
|
+
async getContent(cid) {
|
|
619
|
+
const url = this.buildUrl(`/cat/${encodeURIComponent(cid)}`);
|
|
620
|
+
const response = await this.fetchWithRetry(
|
|
621
|
+
url,
|
|
622
|
+
{ headers: this.getHeaders() },
|
|
623
|
+
`Get content ${cid}`
|
|
624
|
+
);
|
|
625
|
+
if (response.status === 404) {
|
|
626
|
+
throw new ContentNotFoundError(cid);
|
|
206
627
|
}
|
|
207
|
-
const response = await fetch(`${this.gatewayUrl}/api/upload`, {
|
|
208
|
-
method: "POST",
|
|
209
|
-
headers,
|
|
210
|
-
body: formData
|
|
211
|
-
});
|
|
212
628
|
if (!response.ok) {
|
|
213
|
-
this.handleErrorResponse(response,
|
|
629
|
+
await this.handleErrorResponse(response, `Get content ${cid}`);
|
|
214
630
|
}
|
|
215
|
-
|
|
216
|
-
return result[0].cid;
|
|
631
|
+
return response.text();
|
|
217
632
|
}
|
|
218
633
|
/**
|
|
219
|
-
*
|
|
634
|
+
* Download a DAG node (JSON) by CID
|
|
220
635
|
*/
|
|
221
|
-
async
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
})
|
|
231
|
-
});
|
|
232
|
-
if (response.status === 409) {
|
|
233
|
-
const entity = await this.getEntity(pi);
|
|
234
|
-
throw new CASConflictError(
|
|
235
|
-
pi,
|
|
236
|
-
update.expect_tip,
|
|
237
|
-
entity.manifest_cid
|
|
238
|
-
);
|
|
636
|
+
async getDag(cid) {
|
|
637
|
+
const url = this.buildUrl(`/dag/${encodeURIComponent(cid)}`);
|
|
638
|
+
const response = await this.fetchWithRetry(
|
|
639
|
+
url,
|
|
640
|
+
{ headers: this.getHeaders() },
|
|
641
|
+
`Get DAG ${cid}`
|
|
642
|
+
);
|
|
643
|
+
if (response.status === 404) {
|
|
644
|
+
throw new ContentNotFoundError(cid);
|
|
239
645
|
}
|
|
240
646
|
if (!response.ok) {
|
|
241
|
-
this.handleErrorResponse(response, `
|
|
647
|
+
await this.handleErrorResponse(response, `Get DAG ${cid}`);
|
|
648
|
+
}
|
|
649
|
+
return response.json();
|
|
650
|
+
}
|
|
651
|
+
// ===========================================================================
|
|
652
|
+
// Arke Origin Operations
|
|
653
|
+
// ===========================================================================
|
|
654
|
+
/**
|
|
655
|
+
* Get the Arke origin block (genesis entity)
|
|
656
|
+
*/
|
|
657
|
+
async getArke() {
|
|
658
|
+
const url = this.buildUrl("/arke");
|
|
659
|
+
const response = await this.fetchWithRetry(
|
|
660
|
+
url,
|
|
661
|
+
{ headers: this.getHeaders() },
|
|
662
|
+
"Get Arke"
|
|
663
|
+
);
|
|
664
|
+
if (!response.ok) {
|
|
665
|
+
await this.handleErrorResponse(response, "Get Arke");
|
|
666
|
+
}
|
|
667
|
+
return response.json();
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Initialize the Arke origin block (creates if doesn't exist)
|
|
671
|
+
*/
|
|
672
|
+
async initArke() {
|
|
673
|
+
const url = this.buildUrl("/arke/init");
|
|
674
|
+
const response = await this.fetchWithRetry(
|
|
675
|
+
url,
|
|
676
|
+
{
|
|
677
|
+
method: "POST",
|
|
678
|
+
headers: this.getHeaders()
|
|
679
|
+
},
|
|
680
|
+
"Init Arke"
|
|
681
|
+
);
|
|
682
|
+
if (!response.ok) {
|
|
683
|
+
await this.handleErrorResponse(response, "Init Arke");
|
|
242
684
|
}
|
|
243
685
|
return response.json();
|
|
244
686
|
}
|
|
@@ -249,16 +691,20 @@ var EditClient = class {
|
|
|
249
691
|
* Trigger reprocessing for an entity
|
|
250
692
|
*/
|
|
251
693
|
async reprocess(request) {
|
|
252
|
-
const response = await
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
694
|
+
const response = await this.fetchWithRetry(
|
|
695
|
+
`${this.gatewayUrl}/reprocess/reprocess`,
|
|
696
|
+
{
|
|
697
|
+
method: "POST",
|
|
698
|
+
headers: this.getHeaders(),
|
|
699
|
+
body: JSON.stringify({
|
|
700
|
+
pi: request.pi,
|
|
701
|
+
phases: request.phases,
|
|
702
|
+
cascade: request.cascade,
|
|
703
|
+
options: request.options
|
|
704
|
+
})
|
|
705
|
+
},
|
|
706
|
+
`Reprocess ${request.pi}`
|
|
707
|
+
);
|
|
262
708
|
if (response.status === 403) {
|
|
263
709
|
const error = await response.json().catch(() => ({}));
|
|
264
710
|
throw new PermissionError(
|
|
@@ -268,29 +714,23 @@ var EditClient = class {
|
|
|
268
714
|
}
|
|
269
715
|
if (!response.ok) {
|
|
270
716
|
const error = await response.json().catch(() => ({}));
|
|
271
|
-
throw new ReprocessError(
|
|
272
|
-
error.message || `Reprocess failed: ${response.statusText}`,
|
|
273
|
-
void 0
|
|
274
|
-
);
|
|
717
|
+
throw new ReprocessError(error.message || `Reprocess failed: ${response.statusText}`);
|
|
275
718
|
}
|
|
276
719
|
return response.json();
|
|
277
720
|
}
|
|
278
721
|
/**
|
|
279
722
|
* Get reprocessing status by batch ID
|
|
280
|
-
*
|
|
281
|
-
* Uses exponential backoff retry to handle transient 500 errors
|
|
282
|
-
* that occur when the orchestrator is initializing.
|
|
283
|
-
*
|
|
284
|
-
* @param statusUrl - The status URL returned from reprocess()
|
|
285
|
-
* @param isFirstPoll - If true, uses a longer initial delay (orchestrator warmup)
|
|
286
723
|
*/
|
|
287
724
|
async getReprocessStatus(statusUrl, isFirstPoll = false) {
|
|
288
|
-
const retryOptions = isFirstPoll ? { ...DEFAULT_RETRY_OPTIONS, initialDelayMs: 3e3 } : DEFAULT_RETRY_OPTIONS;
|
|
289
725
|
const fetchUrl = this.statusUrlTransform ? this.statusUrlTransform(statusUrl) : statusUrl;
|
|
726
|
+
const delay = isFirstPoll ? 3e3 : this.retryConfig.baseDelay;
|
|
727
|
+
if (isFirstPoll) {
|
|
728
|
+
await this.sleep(delay);
|
|
729
|
+
}
|
|
290
730
|
const response = await this.fetchWithRetry(
|
|
291
731
|
fetchUrl,
|
|
292
732
|
{ headers: this.getHeaders() },
|
|
293
|
-
|
|
733
|
+
"Get reprocess status"
|
|
294
734
|
);
|
|
295
735
|
if (!response.ok) {
|
|
296
736
|
throw new EditError(
|
|
@@ -301,6 +741,30 @@ var EditClient = class {
|
|
|
301
741
|
}
|
|
302
742
|
return response.json();
|
|
303
743
|
}
|
|
744
|
+
// ===========================================================================
|
|
745
|
+
// Utility Methods
|
|
746
|
+
// ===========================================================================
|
|
747
|
+
/**
|
|
748
|
+
* Execute an operation with automatic CAS retry
|
|
749
|
+
*/
|
|
750
|
+
async withCAS(id, operation, maxRetries = 3) {
|
|
751
|
+
let lastError = null;
|
|
752
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
753
|
+
try {
|
|
754
|
+
const entity = await this.getEntity(id);
|
|
755
|
+
return await operation(entity);
|
|
756
|
+
} catch (error) {
|
|
757
|
+
if (error instanceof CASConflictError && attempt < maxRetries - 1) {
|
|
758
|
+
lastError = error;
|
|
759
|
+
const delay = this.calculateDelay(attempt);
|
|
760
|
+
await this.sleep(delay);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
throw error;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
throw lastError || new EditError("withCAS failed after retries");
|
|
767
|
+
}
|
|
304
768
|
};
|
|
305
769
|
|
|
306
770
|
// src/edit/diff.ts
|
|
@@ -818,10 +1282,10 @@ var EditSession = class {
|
|
|
818
1282
|
const result = {};
|
|
819
1283
|
if (!this.entity) return result;
|
|
820
1284
|
const entityContext = {
|
|
821
|
-
pi: this.entity.
|
|
1285
|
+
pi: this.entity.id,
|
|
822
1286
|
ver: this.entity.ver,
|
|
823
1287
|
parentPi: this.entity.parent_pi,
|
|
824
|
-
childrenCount: this.entity.children_pi
|
|
1288
|
+
childrenCount: this.entity.children_pi?.length ?? 0,
|
|
825
1289
|
currentContent: this.loadedComponents
|
|
826
1290
|
};
|
|
827
1291
|
for (const component of this.scope.components) {
|
|
@@ -843,7 +1307,7 @@ var EditSession = class {
|
|
|
843
1307
|
}
|
|
844
1308
|
if (this.scope.cascade) {
|
|
845
1309
|
prompt = PromptBuilder.buildCascadePrompt(prompt, {
|
|
846
|
-
path: [this.entity.
|
|
1310
|
+
path: [this.entity.id, this.entity.parent_pi || "root"].filter(Boolean),
|
|
847
1311
|
depth: 0,
|
|
848
1312
|
stopAtPi: this.scope.stopAtPi
|
|
849
1313
|
});
|
|
@@ -904,7 +1368,7 @@ var EditSession = class {
|
|
|
904
1368
|
note
|
|
905
1369
|
});
|
|
906
1370
|
this.result.saved = {
|
|
907
|
-
pi: version.
|
|
1371
|
+
pi: version.id,
|
|
908
1372
|
newVersion: version.ver,
|
|
909
1373
|
newTip: version.tip
|
|
910
1374
|
};
|
|
@@ -1015,15 +1479,25 @@ var EditSession = class {
|
|
|
1015
1479
|
};
|
|
1016
1480
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1017
1481
|
0 && (module.exports = {
|
|
1482
|
+
BackendError,
|
|
1018
1483
|
CASConflictError,
|
|
1484
|
+
ContentNotFoundError,
|
|
1485
|
+
DEFAULT_RETRY_CONFIG,
|
|
1486
|
+
DeleteError,
|
|
1019
1487
|
DiffEngine,
|
|
1020
1488
|
EditClient,
|
|
1021
1489
|
EditError,
|
|
1022
1490
|
EditSession,
|
|
1491
|
+
EntityExistsError,
|
|
1023
1492
|
EntityNotFoundError,
|
|
1493
|
+
IPFSError,
|
|
1494
|
+
MergeError,
|
|
1495
|
+
NetworkError,
|
|
1024
1496
|
PermissionError,
|
|
1025
1497
|
PromptBuilder,
|
|
1026
1498
|
ReprocessError,
|
|
1499
|
+
UndeleteError,
|
|
1500
|
+
UnmergeError,
|
|
1027
1501
|
ValidationError
|
|
1028
1502
|
});
|
|
1029
1503
|
//# sourceMappingURL=index.cjs.map
|