@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.
@@ -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(pi) {
57
- super(`Entity not found: ${pi}`, "ENTITY_NOT_FOUND", { pi });
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(pi, expectedTip, actualTip) {
80
+ constructor(id, expectedTip, actualTip) {
63
81
  super(
64
- `CAS conflict: entity ${pi} was modified (expected ${expectedTip}, got ${actualTip})`,
82
+ `CAS conflict: entity ${id} was modified (expected ${expectedTip}, got ${actualTip})`,
65
83
  "CAS_CONFLICT",
66
- { pi, expectedTip, actualTip }
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, pi) {
85
- super(message, "PERMISSION_DENIED", { pi });
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 DEFAULT_RETRY_OPTIONS = {
92
- maxRetries: 5,
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
- * Sleep for a given number of milliseconds
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, retryOptions = DEFAULT_RETRY_OPTIONS) {
239
+ async fetchWithRetry(url, options, context) {
122
240
  let lastError = null;
123
- let delay = retryOptions.initialDelayMs;
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 >= 500 && attempt < retryOptions.maxRetries) {
128
- lastError = new Error(`Server error: ${response.status} ${response.statusText}`);
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 < retryOptions.maxRetries) {
253
+ if (this.isRetryableError(lastError) && attempt < this.retryConfig.maxRetries) {
254
+ const delay = this.calculateDelay(attempt);
137
255
  await this.sleep(delay);
138
- delay = Math.min(delay * retryOptions.backoffMultiplier, retryOptions.maxDelayMs);
256
+ continue;
139
257
  }
258
+ throw new NetworkError(lastError.message);
140
259
  }
141
260
  }
142
- throw lastError || new Error("Request failed after retries");
261
+ throw lastError || new NetworkError("Request failed after retries");
143
262
  }
144
- getHeaders() {
145
- const headers = {
146
- "Content-Type": "application/json"
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
- if (this.authToken) {
149
- headers["Authorization"] = `Bearer ${this.authToken}`;
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 headers;
453
+ return response.json();
152
454
  }
455
+ // ===========================================================================
456
+ // Merge Operations
457
+ // ===========================================================================
153
458
  /**
154
- * Handle common error responses
459
+ * Merge source entity into target entity
155
460
  */
156
- handleErrorResponse(response, context) {
157
- if (response.status === 403) {
158
- throw new PermissionError(`Permission denied: ${context}`);
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
- throw new EditError(
161
- `${context}: ${response.statusText}`,
162
- "API_ERROR",
163
- { status: response.status }
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
- // IPFS Wrapper Operations (via /api/*)
517
+ // Delete Operations
168
518
  // ===========================================================================
169
519
  /**
170
- * Fetch an entity by PI
520
+ * Soft delete an entity (creates tombstone, preserves history)
171
521
  */
172
- async getEntity(pi) {
173
- const response = await fetch(`${this.gatewayUrl}/api/entities/${pi}`, {
174
- headers: this.getHeaders()
175
- });
176
- if (response.status === 404) {
177
- throw new EntityNotFoundError(pi);
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
- this.handleErrorResponse(response, `Failed to fetch entity ${pi}`);
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
- * Fetch content by CID
573
+ * Upload files to IPFS
186
574
  */
187
- async getContent(cid) {
188
- const response = await fetch(`${this.gatewayUrl}/api/cat/${cid}`, {
189
- headers: this.getHeaders()
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, `Failed to fetch content ${cid}`);
602
+ await this.handleErrorResponse(response, "Upload files");
193
603
  }
194
- return response.text();
604
+ return response.json();
195
605
  }
196
606
  /**
197
- * Upload content and get CID
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
- formData.append("file", blob, filename);
203
- const headers = {};
204
- if (this.authToken) {
205
- headers["Authorization"] = `Bearer ${this.authToken}`;
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, "Failed to upload content");
629
+ await this.handleErrorResponse(response, `Get content ${cid}`);
214
630
  }
215
- const result = await response.json();
216
- return result[0].cid;
631
+ return response.text();
217
632
  }
218
633
  /**
219
- * Update an entity with new components
634
+ * Download a DAG node (JSON) by CID
220
635
  */
221
- async updateEntity(pi, update) {
222
- const response = await fetch(`${this.gatewayUrl}/api/entities/${pi}/versions`, {
223
- method: "POST",
224
- headers: this.getHeaders(),
225
- body: JSON.stringify({
226
- expect_tip: update.expect_tip,
227
- components: update.components,
228
- components_remove: update.components_remove,
229
- note: update.note
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, `Failed to update entity ${pi}`);
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 fetch(`${this.gatewayUrl}/reprocess/reprocess`, {
253
- method: "POST",
254
- headers: this.getHeaders(),
255
- body: JSON.stringify({
256
- pi: request.pi,
257
- phases: request.phases,
258
- cascade: request.cascade,
259
- options: request.options
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
- retryOptions
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.pi,
1285
+ pi: this.entity.id,
822
1286
  ver: this.entity.ver,
823
1287
  parentPi: this.entity.parent_pi,
824
- childrenCount: this.entity.children_pi.length,
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.pi, this.entity.parent_pi || "root"].filter(Boolean),
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.pi,
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