@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/index.cjs CHANGED
@@ -652,8 +652,8 @@ __export(src_exports, {
652
652
  ComponentNotFoundError: () => ComponentNotFoundError,
653
653
  ContentClient: () => ContentClient,
654
654
  ContentError: () => ContentError,
655
- ContentNetworkError: () => NetworkError2,
656
- ContentNotFoundError: () => ContentNotFoundError,
655
+ ContentNetworkError: () => NetworkError3,
656
+ ContentNotFoundError: () => ContentNotFoundError2,
657
657
  EditClient: () => EditClient,
658
658
  EditError: () => EditError,
659
659
  EditSession: () => EditSession,
@@ -661,7 +661,7 @@ __export(src_exports, {
661
661
  GraphClient: () => GraphClient,
662
662
  GraphEntityNotFoundError: () => GraphEntityNotFoundError,
663
663
  GraphError: () => GraphError,
664
- GraphNetworkError: () => NetworkError3,
664
+ GraphNetworkError: () => NetworkError4,
665
665
  NetworkError: () => NetworkError,
666
666
  NoPathFoundError: () => NoPathFoundError,
667
667
  PermissionError: () => PermissionError,
@@ -1865,6 +1865,73 @@ var QueryClient = class {
1865
1865
  async health() {
1866
1866
  return this.request("/query/health", { method: "GET" });
1867
1867
  }
1868
+ /**
1869
+ * Direct semantic search against the vector index.
1870
+ *
1871
+ * This bypasses the path query syntax and directly queries Pinecone for
1872
+ * semantically similar entities. Useful for:
1873
+ * - Simple semantic searches without graph traversal
1874
+ * - Scoped searches filtered by source_pi (collection scope)
1875
+ * - Type-filtered semantic searches
1876
+ *
1877
+ * For graph traversal and path-based queries, use `path()` instead.
1878
+ *
1879
+ * @param text - Search query text
1880
+ * @param options - Search options (namespace, filters, top_k)
1881
+ * @returns Matching entities with similarity scores
1882
+ *
1883
+ * @example
1884
+ * ```typescript
1885
+ * // Simple semantic search
1886
+ * const results = await query.semanticSearch('photographers from New York');
1887
+ *
1888
+ * // Scoped to a specific PI (collection)
1889
+ * const scoped = await query.semanticSearch('portraits', {
1890
+ * filter: { source_pi: '01K75HQQXNTDG7BBP7PS9AWYAN' },
1891
+ * top_k: 20,
1892
+ * });
1893
+ *
1894
+ * // Filter by type
1895
+ * const people = await query.semanticSearch('artists', {
1896
+ * filter: { type: 'person' },
1897
+ * });
1898
+ *
1899
+ * // Search across merged entities from multiple source PIs
1900
+ * const merged = await query.semanticSearch('historical documents', {
1901
+ * filter: { merged_entities_source_pis: ['pi-1', 'pi-2'] },
1902
+ * });
1903
+ * ```
1904
+ */
1905
+ async semanticSearch(text, options = {}) {
1906
+ let pineconeFilter;
1907
+ if (options.filter) {
1908
+ pineconeFilter = {};
1909
+ if (options.filter.type) {
1910
+ const types = Array.isArray(options.filter.type) ? options.filter.type : [options.filter.type];
1911
+ pineconeFilter.type = types.length === 1 ? { $eq: types[0] } : { $in: types };
1912
+ }
1913
+ if (options.filter.source_pi) {
1914
+ const pis = Array.isArray(options.filter.source_pi) ? options.filter.source_pi : [options.filter.source_pi];
1915
+ pineconeFilter.source_pi = pis.length === 1 ? { $eq: pis[0] } : { $in: pis };
1916
+ }
1917
+ if (options.filter.merged_entities_source_pis) {
1918
+ const pis = Array.isArray(options.filter.merged_entities_source_pis) ? options.filter.merged_entities_source_pis : [options.filter.merged_entities_source_pis];
1919
+ pineconeFilter.merged_entities_source_pis = { $in: pis };
1920
+ }
1921
+ if (Object.keys(pineconeFilter).length === 0) {
1922
+ pineconeFilter = void 0;
1923
+ }
1924
+ }
1925
+ return this.request("/query/search/semantic", {
1926
+ method: "POST",
1927
+ body: JSON.stringify({
1928
+ text,
1929
+ namespace: options.namespace,
1930
+ filter: pineconeFilter,
1931
+ top_k: options.top_k
1932
+ })
1933
+ });
1934
+ }
1868
1935
  /**
1869
1936
  * Search for collections by semantic similarity.
1870
1937
  *
@@ -1899,6 +1966,14 @@ var QueryClient = class {
1899
1966
  }
1900
1967
  };
1901
1968
 
1969
+ // src/edit/types.ts
1970
+ var DEFAULT_RETRY_CONFIG = {
1971
+ maxRetries: 10,
1972
+ baseDelay: 100,
1973
+ maxDelay: 5e3,
1974
+ jitterFactor: 0.3
1975
+ };
1976
+
1902
1977
  // src/edit/errors.ts
1903
1978
  var EditError = class extends Error {
1904
1979
  constructor(message, code2 = "UNKNOWN_ERROR", details) {
@@ -1909,21 +1984,51 @@ var EditError = class extends Error {
1909
1984
  }
1910
1985
  };
1911
1986
  var EntityNotFoundError = class extends EditError {
1912
- constructor(pi) {
1913
- super(`Entity not found: ${pi}`, "ENTITY_NOT_FOUND", { pi });
1987
+ constructor(id) {
1988
+ super(`Entity not found: ${id}`, "ENTITY_NOT_FOUND", { id });
1914
1989
  this.name = "EntityNotFoundError";
1915
1990
  }
1916
1991
  };
1917
1992
  var CASConflictError = class extends EditError {
1918
- constructor(pi, expectedTip, actualTip) {
1993
+ constructor(id, expectedTip, actualTip) {
1919
1994
  super(
1920
- `CAS conflict: entity ${pi} was modified (expected ${expectedTip}, got ${actualTip})`,
1995
+ `CAS conflict: entity ${id} was modified (expected ${expectedTip}, got ${actualTip})`,
1921
1996
  "CAS_CONFLICT",
1922
- { pi, expectedTip, actualTip }
1997
+ { id, expectedTip, actualTip }
1923
1998
  );
1924
1999
  this.name = "CASConflictError";
1925
2000
  }
1926
2001
  };
2002
+ var EntityExistsError = class extends EditError {
2003
+ constructor(id) {
2004
+ super(`Entity already exists: ${id}`, "ENTITY_EXISTS", { id });
2005
+ this.name = "EntityExistsError";
2006
+ }
2007
+ };
2008
+ var MergeError = class extends EditError {
2009
+ constructor(message, sourceId, targetId) {
2010
+ super(message, "MERGE_ERROR", { sourceId, targetId });
2011
+ this.name = "MergeError";
2012
+ }
2013
+ };
2014
+ var UnmergeError = class extends EditError {
2015
+ constructor(message, sourceId, targetId) {
2016
+ super(message, "UNMERGE_ERROR", { sourceId, targetId });
2017
+ this.name = "UnmergeError";
2018
+ }
2019
+ };
2020
+ var DeleteError = class extends EditError {
2021
+ constructor(message, id) {
2022
+ super(message, "DELETE_ERROR", { id });
2023
+ this.name = "DeleteError";
2024
+ }
2025
+ };
2026
+ var UndeleteError = class extends EditError {
2027
+ constructor(message, id) {
2028
+ super(message, "UNDELETE_ERROR", { id });
2029
+ this.name = "UndeleteError";
2030
+ }
2031
+ };
1927
2032
  var ReprocessError = class extends EditError {
1928
2033
  constructor(message, batchId) {
1929
2034
  super(message, "REPROCESS_ERROR", { batchId });
@@ -1937,28 +2042,52 @@ var ValidationError2 = class extends EditError {
1937
2042
  }
1938
2043
  };
1939
2044
  var PermissionError = class extends EditError {
1940
- constructor(message, pi) {
1941
- super(message, "PERMISSION_DENIED", { pi });
2045
+ constructor(message, id) {
2046
+ super(message, "PERMISSION_DENIED", { id });
1942
2047
  this.name = "PermissionError";
1943
2048
  }
1944
2049
  };
2050
+ var NetworkError2 = class extends EditError {
2051
+ constructor(message, statusCode) {
2052
+ super(message, "NETWORK_ERROR", { statusCode });
2053
+ this.name = "NetworkError";
2054
+ }
2055
+ };
2056
+ var ContentNotFoundError = class extends EditError {
2057
+ constructor(cid) {
2058
+ super(`Content not found: ${cid}`, "CONTENT_NOT_FOUND", { cid });
2059
+ this.name = "ContentNotFoundError";
2060
+ }
2061
+ };
2062
+ var IPFSError = class extends EditError {
2063
+ constructor(message) {
2064
+ super(message, "IPFS_ERROR");
2065
+ this.name = "IPFSError";
2066
+ }
2067
+ };
2068
+ var BackendError = class extends EditError {
2069
+ constructor(message) {
2070
+ super(message, "BACKEND_ERROR");
2071
+ this.name = "BackendError";
2072
+ }
2073
+ };
1945
2074
 
1946
2075
  // src/edit/client.ts
1947
- var DEFAULT_RETRY_OPTIONS = {
1948
- maxRetries: 5,
1949
- initialDelayMs: 2e3,
1950
- // Start with 2s delay (orchestrator needs time to initialize)
1951
- maxDelayMs: 3e4,
1952
- // Cap at 30s
1953
- backoffMultiplier: 2
1954
- // Double each retry
1955
- };
2076
+ var RETRYABLE_STATUS_CODES = [409, 503];
2077
+ var RETRYABLE_ERRORS = ["ECONNRESET", "ETIMEDOUT", "fetch failed"];
1956
2078
  var EditClient = class {
1957
2079
  constructor(config) {
1958
2080
  this.gatewayUrl = config.gatewayUrl.replace(/\/$/, "");
1959
2081
  this.authToken = config.authToken;
2082
+ this.network = config.network || "main";
2083
+ this.userId = config.userId;
2084
+ this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retryConfig };
1960
2085
  this.statusUrlTransform = config.statusUrlTransform;
2086
+ this.apiPrefix = config.apiPrefix ?? "/api";
1961
2087
  }
2088
+ // ===========================================================================
2089
+ // Configuration Methods
2090
+ // ===========================================================================
1962
2091
  /**
1963
2092
  * Update the auth token (useful for token refresh)
1964
2093
  */
@@ -1966,135 +2095,505 @@ var EditClient = class {
1966
2095
  this.authToken = token;
1967
2096
  }
1968
2097
  /**
1969
- * Sleep for a given number of milliseconds
2098
+ * Set the network (main or test)
2099
+ */
2100
+ setNetwork(network) {
2101
+ this.network = network;
2102
+ }
2103
+ /**
2104
+ * Set the user ID for permission checks
2105
+ */
2106
+ setUserId(userId) {
2107
+ this.userId = userId;
2108
+ }
2109
+ // ===========================================================================
2110
+ // Internal Helpers
2111
+ // ===========================================================================
2112
+ /**
2113
+ * Build URL with API prefix
1970
2114
  */
2115
+ buildUrl(path2) {
2116
+ return `${this.gatewayUrl}${this.apiPrefix}${path2}`;
2117
+ }
1971
2118
  sleep(ms) {
1972
2119
  return new Promise((resolve) => setTimeout(resolve, ms));
1973
2120
  }
2121
+ getHeaders(contentType = "application/json") {
2122
+ const headers = {};
2123
+ if (contentType) {
2124
+ headers["Content-Type"] = contentType;
2125
+ }
2126
+ if (this.authToken) {
2127
+ headers["Authorization"] = `Bearer ${this.authToken}`;
2128
+ }
2129
+ headers["X-Arke-Network"] = this.network;
2130
+ if (this.userId) {
2131
+ headers["X-User-Id"] = this.userId;
2132
+ }
2133
+ return headers;
2134
+ }
2135
+ calculateDelay(attempt) {
2136
+ const { baseDelay, maxDelay, jitterFactor } = this.retryConfig;
2137
+ const exponentialDelay = baseDelay * Math.pow(2, attempt);
2138
+ const cappedDelay = Math.min(exponentialDelay, maxDelay);
2139
+ const jitter = cappedDelay * jitterFactor * (Math.random() * 2 - 1);
2140
+ return Math.max(0, cappedDelay + jitter);
2141
+ }
2142
+ isRetryableStatus(status) {
2143
+ return RETRYABLE_STATUS_CODES.includes(status);
2144
+ }
2145
+ isRetryableError(error) {
2146
+ const message = error.message.toLowerCase();
2147
+ return RETRYABLE_ERRORS.some((e) => message.includes(e.toLowerCase()));
2148
+ }
1974
2149
  /**
1975
2150
  * Execute a fetch with exponential backoff retry on transient errors
1976
2151
  */
1977
- async fetchWithRetry(url, options, retryOptions = DEFAULT_RETRY_OPTIONS) {
2152
+ async fetchWithRetry(url, options, context) {
1978
2153
  let lastError = null;
1979
- let delay = retryOptions.initialDelayMs;
1980
- for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {
2154
+ for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
1981
2155
  try {
1982
2156
  const response = await fetch(url, options);
1983
- if (response.status >= 500 && attempt < retryOptions.maxRetries) {
1984
- lastError = new Error(`Server error: ${response.status} ${response.statusText}`);
2157
+ if (this.isRetryableStatus(response.status) && attempt < this.retryConfig.maxRetries) {
2158
+ const delay = this.calculateDelay(attempt);
2159
+ lastError = new Error(`${context}: ${response.status} ${response.statusText}`);
1985
2160
  await this.sleep(delay);
1986
- delay = Math.min(delay * retryOptions.backoffMultiplier, retryOptions.maxDelayMs);
1987
2161
  continue;
1988
2162
  }
1989
2163
  return response;
1990
2164
  } catch (error) {
1991
2165
  lastError = error;
1992
- if (attempt < retryOptions.maxRetries) {
2166
+ if (this.isRetryableError(lastError) && attempt < this.retryConfig.maxRetries) {
2167
+ const delay = this.calculateDelay(attempt);
1993
2168
  await this.sleep(delay);
1994
- delay = Math.min(delay * retryOptions.backoffMultiplier, retryOptions.maxDelayMs);
2169
+ continue;
1995
2170
  }
2171
+ throw new NetworkError2(lastError.message);
1996
2172
  }
1997
2173
  }
1998
- throw lastError || new Error("Request failed after retries");
2174
+ throw lastError || new NetworkError2("Request failed after retries");
2175
+ }
2176
+ /**
2177
+ * Handle common error responses and throw appropriate error types
2178
+ */
2179
+ async handleErrorResponse(response, context) {
2180
+ let errorData = {};
2181
+ try {
2182
+ errorData = await response.json();
2183
+ } catch {
2184
+ }
2185
+ const message = errorData.message || `${context}: ${response.statusText}`;
2186
+ const errorCode = errorData.error || "";
2187
+ switch (response.status) {
2188
+ case 400:
2189
+ throw new ValidationError2(message);
2190
+ case 403:
2191
+ throw new PermissionError(message);
2192
+ case 404:
2193
+ throw new EntityNotFoundError(message);
2194
+ case 409:
2195
+ if (errorCode === "CAS_FAILURE") {
2196
+ const details = errorData.details;
2197
+ throw new CASConflictError(
2198
+ context,
2199
+ details?.expect || "unknown",
2200
+ details?.actual || "unknown"
2201
+ );
2202
+ }
2203
+ if (errorCode === "CONFLICT") {
2204
+ throw new EntityExistsError(message);
2205
+ }
2206
+ throw new EditError(message, errorCode, errorData.details);
2207
+ case 503:
2208
+ if (errorCode === "IPFS_ERROR") {
2209
+ throw new IPFSError(message);
2210
+ }
2211
+ if (errorCode === "BACKEND_ERROR") {
2212
+ throw new BackendError(message);
2213
+ }
2214
+ throw new NetworkError2(message, response.status);
2215
+ default:
2216
+ throw new EditError(message, errorCode || "API_ERROR", { status: response.status });
2217
+ }
1999
2218
  }
2000
- getHeaders() {
2001
- const headers = {
2002
- "Content-Type": "application/json"
2219
+ // ===========================================================================
2220
+ // Entity CRUD Operations
2221
+ // ===========================================================================
2222
+ /**
2223
+ * Create a new entity
2224
+ */
2225
+ async createEntity(request) {
2226
+ const url = this.buildUrl("/entities");
2227
+ const response = await this.fetchWithRetry(
2228
+ url,
2229
+ {
2230
+ method: "POST",
2231
+ headers: this.getHeaders(),
2232
+ body: JSON.stringify(request)
2233
+ },
2234
+ "Create entity"
2235
+ );
2236
+ if (!response.ok) {
2237
+ await this.handleErrorResponse(response, "Create entity");
2238
+ }
2239
+ return response.json();
2240
+ }
2241
+ /**
2242
+ * Get an entity by ID
2243
+ */
2244
+ async getEntity(id) {
2245
+ const url = this.buildUrl(`/entities/${encodeURIComponent(id)}`);
2246
+ const response = await this.fetchWithRetry(
2247
+ url,
2248
+ { headers: this.getHeaders() },
2249
+ `Get entity ${id}`
2250
+ );
2251
+ if (!response.ok) {
2252
+ await this.handleErrorResponse(response, `Get entity ${id}`);
2253
+ }
2254
+ return response.json();
2255
+ }
2256
+ /**
2257
+ * List entities with pagination
2258
+ */
2259
+ async listEntities(options = {}) {
2260
+ const params = new URLSearchParams();
2261
+ if (options.limit) params.set("limit", options.limit.toString());
2262
+ if (options.cursor) params.set("cursor", options.cursor);
2263
+ if (options.include_metadata) params.set("include_metadata", "true");
2264
+ const queryString = params.toString();
2265
+ const url = this.buildUrl(`/entities${queryString ? `?${queryString}` : ""}`);
2266
+ const response = await this.fetchWithRetry(url, { headers: this.getHeaders() }, "List entities");
2267
+ if (!response.ok) {
2268
+ await this.handleErrorResponse(response, "List entities");
2269
+ }
2270
+ return response.json();
2271
+ }
2272
+ /**
2273
+ * Update an entity (append new version)
2274
+ */
2275
+ async updateEntity(id, update) {
2276
+ const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions`);
2277
+ const response = await this.fetchWithRetry(
2278
+ url,
2279
+ {
2280
+ method: "POST",
2281
+ headers: this.getHeaders(),
2282
+ body: JSON.stringify(update)
2283
+ },
2284
+ `Update entity ${id}`
2285
+ );
2286
+ if (!response.ok) {
2287
+ await this.handleErrorResponse(response, `Update entity ${id}`);
2288
+ }
2289
+ return response.json();
2290
+ }
2291
+ // ===========================================================================
2292
+ // Version Operations
2293
+ // ===========================================================================
2294
+ /**
2295
+ * List version history for an entity
2296
+ */
2297
+ async listVersions(id, options = {}) {
2298
+ const params = new URLSearchParams();
2299
+ if (options.limit) params.set("limit", options.limit.toString());
2300
+ if (options.cursor) params.set("cursor", options.cursor);
2301
+ const queryString = params.toString();
2302
+ const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions${queryString ? `?${queryString}` : ""}`);
2303
+ const response = await this.fetchWithRetry(url, { headers: this.getHeaders() }, `List versions for ${id}`);
2304
+ if (!response.ok) {
2305
+ await this.handleErrorResponse(response, `List versions for ${id}`);
2306
+ }
2307
+ return response.json();
2308
+ }
2309
+ /**
2310
+ * Get a specific version of an entity
2311
+ */
2312
+ async getVersion(id, selector) {
2313
+ const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions/${encodeURIComponent(selector)}`);
2314
+ const response = await this.fetchWithRetry(
2315
+ url,
2316
+ { headers: this.getHeaders() },
2317
+ `Get version ${selector} for ${id}`
2318
+ );
2319
+ if (!response.ok) {
2320
+ await this.handleErrorResponse(response, `Get version ${selector} for ${id}`);
2321
+ }
2322
+ return response.json();
2323
+ }
2324
+ /**
2325
+ * Resolve an entity ID to its current tip CID (fast lookup)
2326
+ */
2327
+ async resolve(id) {
2328
+ const url = this.buildUrl(`/resolve/${encodeURIComponent(id)}`);
2329
+ const response = await this.fetchWithRetry(
2330
+ url,
2331
+ { headers: this.getHeaders() },
2332
+ `Resolve ${id}`
2333
+ );
2334
+ if (!response.ok) {
2335
+ await this.handleErrorResponse(response, `Resolve ${id}`);
2336
+ }
2337
+ return response.json();
2338
+ }
2339
+ // ===========================================================================
2340
+ // Hierarchy Operations
2341
+ // ===========================================================================
2342
+ /**
2343
+ * Update parent-child hierarchy relationships
2344
+ */
2345
+ async updateHierarchy(request) {
2346
+ const apiRequest = {
2347
+ parent_pi: request.parent_id,
2348
+ expect_tip: request.expect_tip,
2349
+ add_children: request.add_children,
2350
+ remove_children: request.remove_children,
2351
+ note: request.note
2003
2352
  };
2004
- if (this.authToken) {
2005
- headers["Authorization"] = `Bearer ${this.authToken}`;
2353
+ const url = this.buildUrl("/hierarchy");
2354
+ const response = await this.fetchWithRetry(
2355
+ url,
2356
+ {
2357
+ method: "POST",
2358
+ headers: this.getHeaders(),
2359
+ body: JSON.stringify(apiRequest)
2360
+ },
2361
+ `Update hierarchy for ${request.parent_id}`
2362
+ );
2363
+ if (!response.ok) {
2364
+ await this.handleErrorResponse(response, `Update hierarchy for ${request.parent_id}`);
2006
2365
  }
2007
- return headers;
2366
+ return response.json();
2008
2367
  }
2368
+ // ===========================================================================
2369
+ // Merge Operations
2370
+ // ===========================================================================
2009
2371
  /**
2010
- * Handle common error responses
2372
+ * Merge source entity into target entity
2011
2373
  */
2012
- handleErrorResponse(response, context) {
2013
- if (response.status === 403) {
2014
- throw new PermissionError(`Permission denied: ${context}`);
2374
+ async mergeEntity(sourceId, request) {
2375
+ const url = this.buildUrl(`/entities/${encodeURIComponent(sourceId)}/merge`);
2376
+ const response = await this.fetchWithRetry(
2377
+ url,
2378
+ {
2379
+ method: "POST",
2380
+ headers: this.getHeaders(),
2381
+ body: JSON.stringify(request)
2382
+ },
2383
+ `Merge ${sourceId} into ${request.target_id}`
2384
+ );
2385
+ if (!response.ok) {
2386
+ try {
2387
+ const error = await response.json();
2388
+ throw new MergeError(
2389
+ error.message || `Merge failed: ${response.statusText}`,
2390
+ sourceId,
2391
+ request.target_id
2392
+ );
2393
+ } catch (e) {
2394
+ if (e instanceof MergeError) throw e;
2395
+ await this.handleErrorResponse(response, `Merge ${sourceId}`);
2396
+ }
2015
2397
  }
2016
- throw new EditError(
2017
- `${context}: ${response.statusText}`,
2018
- "API_ERROR",
2019
- { status: response.status }
2398
+ return response.json();
2399
+ }
2400
+ /**
2401
+ * Unmerge (restore) a previously merged entity
2402
+ */
2403
+ async unmergeEntity(sourceId, request) {
2404
+ const url = this.buildUrl(`/entities/${encodeURIComponent(sourceId)}/unmerge`);
2405
+ const response = await this.fetchWithRetry(
2406
+ url,
2407
+ {
2408
+ method: "POST",
2409
+ headers: this.getHeaders(),
2410
+ body: JSON.stringify(request)
2411
+ },
2412
+ `Unmerge ${sourceId}`
2020
2413
  );
2414
+ if (!response.ok) {
2415
+ try {
2416
+ const error = await response.json();
2417
+ throw new UnmergeError(
2418
+ error.message || `Unmerge failed: ${response.statusText}`,
2419
+ sourceId,
2420
+ request.target_id
2421
+ );
2422
+ } catch (e) {
2423
+ if (e instanceof UnmergeError) throw e;
2424
+ await this.handleErrorResponse(response, `Unmerge ${sourceId}`);
2425
+ }
2426
+ }
2427
+ return response.json();
2021
2428
  }
2022
2429
  // ===========================================================================
2023
- // IPFS Wrapper Operations (via /api/*)
2430
+ // Delete Operations
2024
2431
  // ===========================================================================
2025
2432
  /**
2026
- * Fetch an entity by PI
2433
+ * Soft delete an entity (creates tombstone, preserves history)
2027
2434
  */
2028
- async getEntity(pi) {
2029
- const response = await fetch(`${this.gatewayUrl}/api/entities/${pi}`, {
2030
- headers: this.getHeaders()
2031
- });
2032
- if (response.status === 404) {
2033
- throw new EntityNotFoundError(pi);
2435
+ async deleteEntity(id, request) {
2436
+ const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/delete`);
2437
+ const response = await this.fetchWithRetry(
2438
+ url,
2439
+ {
2440
+ method: "POST",
2441
+ headers: this.getHeaders(),
2442
+ body: JSON.stringify(request)
2443
+ },
2444
+ `Delete ${id}`
2445
+ );
2446
+ if (!response.ok) {
2447
+ try {
2448
+ const error = await response.json();
2449
+ throw new DeleteError(error.message || `Delete failed: ${response.statusText}`, id);
2450
+ } catch (e) {
2451
+ if (e instanceof DeleteError) throw e;
2452
+ await this.handleErrorResponse(response, `Delete ${id}`);
2453
+ }
2034
2454
  }
2455
+ return response.json();
2456
+ }
2457
+ /**
2458
+ * Restore a deleted entity
2459
+ */
2460
+ async undeleteEntity(id, request) {
2461
+ const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/undelete`);
2462
+ const response = await this.fetchWithRetry(
2463
+ url,
2464
+ {
2465
+ method: "POST",
2466
+ headers: this.getHeaders(),
2467
+ body: JSON.stringify(request)
2468
+ },
2469
+ `Undelete ${id}`
2470
+ );
2035
2471
  if (!response.ok) {
2036
- this.handleErrorResponse(response, `Failed to fetch entity ${pi}`);
2472
+ try {
2473
+ const error = await response.json();
2474
+ throw new UndeleteError(error.message || `Undelete failed: ${response.statusText}`, id);
2475
+ } catch (e) {
2476
+ if (e instanceof UndeleteError) throw e;
2477
+ await this.handleErrorResponse(response, `Undelete ${id}`);
2478
+ }
2037
2479
  }
2038
2480
  return response.json();
2039
2481
  }
2482
+ // ===========================================================================
2483
+ // Content Operations
2484
+ // ===========================================================================
2040
2485
  /**
2041
- * Fetch content by CID
2486
+ * Upload files to IPFS
2042
2487
  */
2043
- async getContent(cid) {
2044
- const response = await fetch(`${this.gatewayUrl}/api/cat/${cid}`, {
2045
- headers: this.getHeaders()
2046
- });
2488
+ async upload(files) {
2489
+ let formData;
2490
+ if (files instanceof FormData) {
2491
+ formData = files;
2492
+ } else {
2493
+ formData = new FormData();
2494
+ const fileArray = Array.isArray(files) ? files : [files];
2495
+ for (const file of fileArray) {
2496
+ if (file instanceof File) {
2497
+ formData.append("file", file, file.name);
2498
+ } else {
2499
+ formData.append("file", file, "file");
2500
+ }
2501
+ }
2502
+ }
2503
+ const url = this.buildUrl("/upload");
2504
+ const response = await this.fetchWithRetry(
2505
+ url,
2506
+ {
2507
+ method: "POST",
2508
+ headers: this.getHeaders(null),
2509
+ // No Content-Type for multipart
2510
+ body: formData
2511
+ },
2512
+ "Upload files"
2513
+ );
2047
2514
  if (!response.ok) {
2048
- this.handleErrorResponse(response, `Failed to fetch content ${cid}`);
2515
+ await this.handleErrorResponse(response, "Upload files");
2049
2516
  }
2050
- return response.text();
2517
+ return response.json();
2051
2518
  }
2052
2519
  /**
2053
- * Upload content and get CID
2520
+ * Upload text content and return CID
2054
2521
  */
2055
2522
  async uploadContent(content, filename) {
2056
- const formData = new FormData();
2057
2523
  const blob = new Blob([content], { type: "text/plain" });
2058
- formData.append("file", blob, filename);
2059
- const headers = {};
2060
- if (this.authToken) {
2061
- headers["Authorization"] = `Bearer ${this.authToken}`;
2524
+ const file = new File([blob], filename, { type: "text/plain" });
2525
+ const [result] = await this.upload(file);
2526
+ return result.cid;
2527
+ }
2528
+ /**
2529
+ * Download file content by CID
2530
+ */
2531
+ async getContent(cid) {
2532
+ const url = this.buildUrl(`/cat/${encodeURIComponent(cid)}`);
2533
+ const response = await this.fetchWithRetry(
2534
+ url,
2535
+ { headers: this.getHeaders() },
2536
+ `Get content ${cid}`
2537
+ );
2538
+ if (response.status === 404) {
2539
+ throw new ContentNotFoundError(cid);
2062
2540
  }
2063
- const response = await fetch(`${this.gatewayUrl}/api/upload`, {
2064
- method: "POST",
2065
- headers,
2066
- body: formData
2067
- });
2068
2541
  if (!response.ok) {
2069
- this.handleErrorResponse(response, "Failed to upload content");
2542
+ await this.handleErrorResponse(response, `Get content ${cid}`);
2070
2543
  }
2071
- const result = await response.json();
2072
- return result[0].cid;
2544
+ return response.text();
2073
2545
  }
2074
2546
  /**
2075
- * Update an entity with new components
2547
+ * Download a DAG node (JSON) by CID
2076
2548
  */
2077
- async updateEntity(pi, update) {
2078
- const response = await fetch(`${this.gatewayUrl}/api/entities/${pi}/versions`, {
2079
- method: "POST",
2080
- headers: this.getHeaders(),
2081
- body: JSON.stringify({
2082
- expect_tip: update.expect_tip,
2083
- components: update.components,
2084
- components_remove: update.components_remove,
2085
- note: update.note
2086
- })
2087
- });
2088
- if (response.status === 409) {
2089
- const entity = await this.getEntity(pi);
2090
- throw new CASConflictError(
2091
- pi,
2092
- update.expect_tip,
2093
- entity.manifest_cid
2094
- );
2549
+ async getDag(cid) {
2550
+ const url = this.buildUrl(`/dag/${encodeURIComponent(cid)}`);
2551
+ const response = await this.fetchWithRetry(
2552
+ url,
2553
+ { headers: this.getHeaders() },
2554
+ `Get DAG ${cid}`
2555
+ );
2556
+ if (response.status === 404) {
2557
+ throw new ContentNotFoundError(cid);
2558
+ }
2559
+ if (!response.ok) {
2560
+ await this.handleErrorResponse(response, `Get DAG ${cid}`);
2561
+ }
2562
+ return response.json();
2563
+ }
2564
+ // ===========================================================================
2565
+ // Arke Origin Operations
2566
+ // ===========================================================================
2567
+ /**
2568
+ * Get the Arke origin block (genesis entity)
2569
+ */
2570
+ async getArke() {
2571
+ const url = this.buildUrl("/arke");
2572
+ const response = await this.fetchWithRetry(
2573
+ url,
2574
+ { headers: this.getHeaders() },
2575
+ "Get Arke"
2576
+ );
2577
+ if (!response.ok) {
2578
+ await this.handleErrorResponse(response, "Get Arke");
2095
2579
  }
2580
+ return response.json();
2581
+ }
2582
+ /**
2583
+ * Initialize the Arke origin block (creates if doesn't exist)
2584
+ */
2585
+ async initArke() {
2586
+ const url = this.buildUrl("/arke/init");
2587
+ const response = await this.fetchWithRetry(
2588
+ url,
2589
+ {
2590
+ method: "POST",
2591
+ headers: this.getHeaders()
2592
+ },
2593
+ "Init Arke"
2594
+ );
2096
2595
  if (!response.ok) {
2097
- this.handleErrorResponse(response, `Failed to update entity ${pi}`);
2596
+ await this.handleErrorResponse(response, "Init Arke");
2098
2597
  }
2099
2598
  return response.json();
2100
2599
  }
@@ -2105,16 +2604,20 @@ var EditClient = class {
2105
2604
  * Trigger reprocessing for an entity
2106
2605
  */
2107
2606
  async reprocess(request) {
2108
- const response = await fetch(`${this.gatewayUrl}/reprocess/reprocess`, {
2109
- method: "POST",
2110
- headers: this.getHeaders(),
2111
- body: JSON.stringify({
2112
- pi: request.pi,
2113
- phases: request.phases,
2114
- cascade: request.cascade,
2115
- options: request.options
2116
- })
2117
- });
2607
+ const response = await this.fetchWithRetry(
2608
+ `${this.gatewayUrl}/reprocess/reprocess`,
2609
+ {
2610
+ method: "POST",
2611
+ headers: this.getHeaders(),
2612
+ body: JSON.stringify({
2613
+ pi: request.pi,
2614
+ phases: request.phases,
2615
+ cascade: request.cascade,
2616
+ options: request.options
2617
+ })
2618
+ },
2619
+ `Reprocess ${request.pi}`
2620
+ );
2118
2621
  if (response.status === 403) {
2119
2622
  const error = await response.json().catch(() => ({}));
2120
2623
  throw new PermissionError(
@@ -2124,29 +2627,23 @@ var EditClient = class {
2124
2627
  }
2125
2628
  if (!response.ok) {
2126
2629
  const error = await response.json().catch(() => ({}));
2127
- throw new ReprocessError(
2128
- error.message || `Reprocess failed: ${response.statusText}`,
2129
- void 0
2130
- );
2630
+ throw new ReprocessError(error.message || `Reprocess failed: ${response.statusText}`);
2131
2631
  }
2132
2632
  return response.json();
2133
2633
  }
2134
2634
  /**
2135
2635
  * Get reprocessing status by batch ID
2136
- *
2137
- * Uses exponential backoff retry to handle transient 500 errors
2138
- * that occur when the orchestrator is initializing.
2139
- *
2140
- * @param statusUrl - The status URL returned from reprocess()
2141
- * @param isFirstPoll - If true, uses a longer initial delay (orchestrator warmup)
2142
2636
  */
2143
2637
  async getReprocessStatus(statusUrl, isFirstPoll = false) {
2144
- const retryOptions = isFirstPoll ? { ...DEFAULT_RETRY_OPTIONS, initialDelayMs: 3e3 } : DEFAULT_RETRY_OPTIONS;
2145
2638
  const fetchUrl = this.statusUrlTransform ? this.statusUrlTransform(statusUrl) : statusUrl;
2639
+ const delay = isFirstPoll ? 3e3 : this.retryConfig.baseDelay;
2640
+ if (isFirstPoll) {
2641
+ await this.sleep(delay);
2642
+ }
2146
2643
  const response = await this.fetchWithRetry(
2147
2644
  fetchUrl,
2148
2645
  { headers: this.getHeaders() },
2149
- retryOptions
2646
+ "Get reprocess status"
2150
2647
  );
2151
2648
  if (!response.ok) {
2152
2649
  throw new EditError(
@@ -2157,6 +2654,30 @@ var EditClient = class {
2157
2654
  }
2158
2655
  return response.json();
2159
2656
  }
2657
+ // ===========================================================================
2658
+ // Utility Methods
2659
+ // ===========================================================================
2660
+ /**
2661
+ * Execute an operation with automatic CAS retry
2662
+ */
2663
+ async withCAS(id, operation, maxRetries = 3) {
2664
+ let lastError = null;
2665
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
2666
+ try {
2667
+ const entity = await this.getEntity(id);
2668
+ return await operation(entity);
2669
+ } catch (error) {
2670
+ if (error instanceof CASConflictError && attempt < maxRetries - 1) {
2671
+ lastError = error;
2672
+ const delay = this.calculateDelay(attempt);
2673
+ await this.sleep(delay);
2674
+ continue;
2675
+ }
2676
+ throw error;
2677
+ }
2678
+ }
2679
+ throw lastError || new EditError("withCAS failed after retries");
2680
+ }
2160
2681
  };
2161
2682
 
2162
2683
  // src/edit/diff.ts
@@ -2674,10 +3195,10 @@ var EditSession = class {
2674
3195
  const result = {};
2675
3196
  if (!this.entity) return result;
2676
3197
  const entityContext = {
2677
- pi: this.entity.pi,
3198
+ pi: this.entity.id,
2678
3199
  ver: this.entity.ver,
2679
3200
  parentPi: this.entity.parent_pi,
2680
- childrenCount: this.entity.children_pi.length,
3201
+ childrenCount: this.entity.children_pi?.length ?? 0,
2681
3202
  currentContent: this.loadedComponents
2682
3203
  };
2683
3204
  for (const component of this.scope.components) {
@@ -2699,7 +3220,7 @@ var EditSession = class {
2699
3220
  }
2700
3221
  if (this.scope.cascade) {
2701
3222
  prompt = PromptBuilder.buildCascadePrompt(prompt, {
2702
- path: [this.entity.pi, this.entity.parent_pi || "root"].filter(Boolean),
3223
+ path: [this.entity.id, this.entity.parent_pi || "root"].filter(Boolean),
2703
3224
  depth: 0,
2704
3225
  stopAtPi: this.scope.stopAtPi
2705
3226
  });
@@ -2760,7 +3281,7 @@ var EditSession = class {
2760
3281
  note
2761
3282
  });
2762
3283
  this.result.saved = {
2763
- pi: version.pi,
3284
+ pi: version.id,
2764
3285
  newVersion: version.ver,
2765
3286
  newTip: version.tip
2766
3287
  };
@@ -2880,38 +3401,38 @@ var ContentError = class extends Error {
2880
3401
  }
2881
3402
  };
2882
3403
  var EntityNotFoundError2 = class extends ContentError {
2883
- constructor(pi) {
2884
- super(`Entity not found: ${pi}`, "ENTITY_NOT_FOUND", { pi });
3404
+ constructor(id) {
3405
+ super(`Entity not found: ${id}`, "ENTITY_NOT_FOUND", { id });
2885
3406
  this.name = "EntityNotFoundError";
2886
3407
  }
2887
3408
  };
2888
- var ContentNotFoundError = class extends ContentError {
3409
+ var ContentNotFoundError2 = class extends ContentError {
2889
3410
  constructor(cid) {
2890
3411
  super(`Content not found: ${cid}`, "CONTENT_NOT_FOUND", { cid });
2891
3412
  this.name = "ContentNotFoundError";
2892
3413
  }
2893
3414
  };
2894
3415
  var ComponentNotFoundError = class extends ContentError {
2895
- constructor(pi, componentName) {
3416
+ constructor(id, componentName) {
2896
3417
  super(
2897
- `Component '${componentName}' not found on entity ${pi}`,
3418
+ `Component '${componentName}' not found on entity ${id}`,
2898
3419
  "COMPONENT_NOT_FOUND",
2899
- { pi, componentName }
3420
+ { id, componentName }
2900
3421
  );
2901
3422
  this.name = "ComponentNotFoundError";
2902
3423
  }
2903
3424
  };
2904
3425
  var VersionNotFoundError = class extends ContentError {
2905
- constructor(pi, selector) {
3426
+ constructor(id, selector) {
2906
3427
  super(
2907
- `Version not found: ${selector} for entity ${pi}`,
3428
+ `Version not found: ${selector} for entity ${id}`,
2908
3429
  "VERSION_NOT_FOUND",
2909
- { pi, selector }
3430
+ { id, selector }
2910
3431
  );
2911
3432
  this.name = "VersionNotFoundError";
2912
3433
  }
2913
3434
  };
2914
- var NetworkError2 = class extends ContentError {
3435
+ var NetworkError3 = class extends ContentError {
2915
3436
  constructor(message, statusCode) {
2916
3437
  super(message, "NETWORK_ERROR", { statusCode });
2917
3438
  this.statusCode = statusCode;
@@ -2951,7 +3472,7 @@ var ContentClient = class {
2951
3472
  try {
2952
3473
  response = await this.fetchImpl(url, { ...options, headers });
2953
3474
  } catch (err) {
2954
- throw new NetworkError2(
3475
+ throw new NetworkError3(
2955
3476
  err instanceof Error ? err.message : "Network request failed"
2956
3477
  );
2957
3478
  }
@@ -3205,13 +3726,13 @@ var ContentClient = class {
3205
3726
  try {
3206
3727
  response = await this.fetchImpl(url);
3207
3728
  } catch (err) {
3208
- throw new NetworkError2(
3729
+ throw new NetworkError3(
3209
3730
  err instanceof Error ? err.message : "Network request failed"
3210
3731
  );
3211
3732
  }
3212
3733
  if (!response.ok) {
3213
3734
  if (response.status === 404) {
3214
- throw new ContentNotFoundError(cid);
3735
+ throw new ContentNotFoundError2(cid);
3215
3736
  }
3216
3737
  throw new ContentError(
3217
3738
  `Failed to download content: ${response.status}`,
@@ -3267,13 +3788,13 @@ var ContentClient = class {
3267
3788
  try {
3268
3789
  response = await this.fetchImpl(url);
3269
3790
  } catch (err) {
3270
- throw new NetworkError2(
3791
+ throw new NetworkError3(
3271
3792
  err instanceof Error ? err.message : "Network request failed"
3272
3793
  );
3273
3794
  }
3274
3795
  if (!response.ok) {
3275
3796
  if (response.status === 404) {
3276
- throw new ContentNotFoundError(cid);
3797
+ throw new ContentNotFoundError2(cid);
3277
3798
  }
3278
3799
  throw new ContentError(
3279
3800
  `Failed to stream content: ${response.status}`,
@@ -3286,6 +3807,45 @@ var ContentClient = class {
3286
3807
  }
3287
3808
  return response.body;
3288
3809
  }
3810
+ /**
3811
+ * Download a DAG node (JSON) by CID.
3812
+ *
3813
+ * Use this to fetch JSON components like properties and relationships.
3814
+ *
3815
+ * @param cid - Content Identifier of the DAG node
3816
+ * @returns Parsed JSON object
3817
+ * @throws ContentNotFoundError if the content doesn't exist
3818
+ *
3819
+ * @example
3820
+ * ```typescript
3821
+ * const relationships = await content.getDag<RelationshipsComponent>(
3822
+ * entity.components.relationships
3823
+ * );
3824
+ * console.log('Relationships:', relationships.relationships);
3825
+ * ```
3826
+ */
3827
+ async getDag(cid) {
3828
+ const url = this.buildUrl(`/api/dag/${encodeURIComponent(cid)}`);
3829
+ let response;
3830
+ try {
3831
+ response = await this.fetchImpl(url);
3832
+ } catch (err) {
3833
+ throw new NetworkError3(
3834
+ err instanceof Error ? err.message : "Network request failed"
3835
+ );
3836
+ }
3837
+ if (!response.ok) {
3838
+ if (response.status === 404) {
3839
+ throw new ContentNotFoundError2(cid);
3840
+ }
3841
+ throw new ContentError(
3842
+ `Failed to fetch DAG node: ${response.status}`,
3843
+ "DAG_ERROR",
3844
+ { status: response.status }
3845
+ );
3846
+ }
3847
+ return await response.json();
3848
+ }
3289
3849
  // ---------------------------------------------------------------------------
3290
3850
  // Component Helpers
3291
3851
  // ---------------------------------------------------------------------------
@@ -3306,7 +3866,7 @@ var ContentClient = class {
3306
3866
  async getComponent(entity, componentName) {
3307
3867
  const cid = entity.components[componentName];
3308
3868
  if (!cid) {
3309
- throw new ComponentNotFoundError(entity.pi, componentName);
3869
+ throw new ComponentNotFoundError(entity.id, componentName);
3310
3870
  }
3311
3871
  return this.download(cid);
3312
3872
  }
@@ -3328,10 +3888,56 @@ var ContentClient = class {
3328
3888
  getComponentUrl(entity, componentName) {
3329
3889
  const cid = entity.components[componentName];
3330
3890
  if (!cid) {
3331
- throw new ComponentNotFoundError(entity.pi, componentName);
3891
+ throw new ComponentNotFoundError(entity.id, componentName);
3332
3892
  }
3333
3893
  return this.getUrl(cid);
3334
3894
  }
3895
+ /**
3896
+ * Get the properties component for an entity.
3897
+ *
3898
+ * @param entity - Entity containing the properties component
3899
+ * @returns Properties object, or null if no properties component exists
3900
+ *
3901
+ * @example
3902
+ * ```typescript
3903
+ * const entity = await content.get('01K75HQQXNTDG7BBP7PS9AWYAN');
3904
+ * const props = await content.getProperties(entity);
3905
+ * if (props) {
3906
+ * console.log('Title:', props.title);
3907
+ * }
3908
+ * ```
3909
+ */
3910
+ async getProperties(entity) {
3911
+ const cid = entity.components.properties;
3912
+ if (!cid) {
3913
+ return null;
3914
+ }
3915
+ return this.getDag(cid);
3916
+ }
3917
+ /**
3918
+ * Get the relationships component for an entity.
3919
+ *
3920
+ * @param entity - Entity containing the relationships component
3921
+ * @returns Relationships component, or null if no relationships exist
3922
+ *
3923
+ * @example
3924
+ * ```typescript
3925
+ * const entity = await content.get('01K75HQQXNTDG7BBP7PS9AWYAN');
3926
+ * const rels = await content.getRelationships(entity);
3927
+ * if (rels) {
3928
+ * rels.relationships.forEach(r => {
3929
+ * console.log(`${r.predicate} -> ${r.target_label}`);
3930
+ * });
3931
+ * }
3932
+ * ```
3933
+ */
3934
+ async getRelationships(entity) {
3935
+ const cid = entity.components.relationships;
3936
+ if (!cid) {
3937
+ return null;
3938
+ }
3939
+ return this.getDag(cid);
3940
+ }
3335
3941
  };
3336
3942
 
3337
3943
  // src/graph/errors.ts
@@ -3359,7 +3965,7 @@ var NoPathFoundError = class extends GraphError {
3359
3965
  this.name = "NoPathFoundError";
3360
3966
  }
3361
3967
  };
3362
- var NetworkError3 = class extends GraphError {
3968
+ var NetworkError4 = class extends GraphError {
3363
3969
  constructor(message, statusCode) {
3364
3970
  super(message, "NETWORK_ERROR", { statusCode });
3365
3971
  this.statusCode = statusCode;
@@ -3399,7 +4005,7 @@ var GraphClient = class {
3399
4005
  try {
3400
4006
  response = await this.fetchImpl(url, { ...options, headers });
3401
4007
  } catch (err) {
3402
- throw new NetworkError3(
4008
+ throw new NetworkError4(
3403
4009
  err instanceof Error ? err.message : "Network request failed"
3404
4010
  );
3405
4011
  }
@@ -3431,49 +4037,8 @@ var GraphClient = class {
3431
4037
  });
3432
4038
  }
3433
4039
  // ---------------------------------------------------------------------------
3434
- // Entity Operations
4040
+ // Code-based Lookups (indexed in GraphDB)
3435
4041
  // ---------------------------------------------------------------------------
3436
- /**
3437
- * Get an entity by its canonical ID.
3438
- *
3439
- * @param canonicalId - Entity UUID
3440
- * @returns Entity data
3441
- * @throws GraphEntityNotFoundError if the entity doesn't exist
3442
- *
3443
- * @example
3444
- * ```typescript
3445
- * const entity = await graph.getEntity('uuid-123');
3446
- * console.log('Entity:', entity.label, entity.type);
3447
- * ```
3448
- */
3449
- async getEntity(canonicalId) {
3450
- const response = await this.request(
3451
- `/graphdb/entity/${encodeURIComponent(canonicalId)}`
3452
- );
3453
- if (!response.found || !response.entity) {
3454
- throw new GraphEntityNotFoundError(canonicalId);
3455
- }
3456
- return response.entity;
3457
- }
3458
- /**
3459
- * Check if an entity exists by its canonical ID.
3460
- *
3461
- * @param canonicalId - Entity UUID
3462
- * @returns True if entity exists
3463
- *
3464
- * @example
3465
- * ```typescript
3466
- * if (await graph.entityExists('uuid-123')) {
3467
- * console.log('Entity exists');
3468
- * }
3469
- * ```
3470
- */
3471
- async entityExists(canonicalId) {
3472
- const response = await this.request(
3473
- `/graphdb/entity/exists/${encodeURIComponent(canonicalId)}`
3474
- );
3475
- return response.exists;
3476
- }
3477
4042
  /**
3478
4043
  * Query entities by code with optional type filter.
3479
4044
  *
@@ -3529,11 +4094,14 @@ var GraphClient = class {
3529
4094
  // PI-based Operations
3530
4095
  // ---------------------------------------------------------------------------
3531
4096
  /**
3532
- * List entities from a specific PI or multiple PIs.
4097
+ * List entities extracted from a specific PI or multiple PIs.
4098
+ *
4099
+ * This returns knowledge graph entities (persons, places, events, etc.)
4100
+ * that were extracted from the given PI(s), not the PI entity itself.
3533
4101
  *
3534
4102
  * @param pi - Single PI or array of PIs
3535
4103
  * @param options - Filter options
3536
- * @returns Entities from the PI(s)
4104
+ * @returns Extracted entities from the PI(s)
3537
4105
  *
3538
4106
  * @example
3539
4107
  * ```typescript
@@ -3592,13 +4160,17 @@ var GraphClient = class {
3592
4160
  /**
3593
4161
  * Get the lineage (ancestors and/or descendants) of a PI.
3594
4162
  *
4163
+ * This traverses the PI hierarchy (parent_pi/children_pi relationships)
4164
+ * which is indexed in GraphDB for fast lookups.
4165
+ *
3595
4166
  * @param pi - Source PI
3596
4167
  * @param direction - 'ancestors', 'descendants', or 'both'
3597
- * @returns Lineage data
4168
+ * @param maxHops - Maximum depth to traverse (default: 10)
4169
+ * @returns Lineage data with PIs at each hop level
3598
4170
  *
3599
4171
  * @example
3600
4172
  * ```typescript
3601
- * // Get ancestors
4173
+ * // Get ancestors (parent chain)
3602
4174
  * const lineage = await graph.getLineage('01K75HQQXNTDG7BBP7PS9AWYAN', 'ancestors');
3603
4175
  *
3604
4176
  * // Get both directions
@@ -3615,25 +4187,53 @@ var GraphClient = class {
3615
4187
  // Relationship Operations
3616
4188
  // ---------------------------------------------------------------------------
3617
4189
  /**
3618
- * Get all relationships for an entity.
4190
+ * Get relationships for an entity from the GraphDB index.
4191
+ *
4192
+ * **Important distinction from ContentClient.getRelationships():**
4193
+ * - **ContentClient.getRelationships()**: Returns OUTBOUND relationships only
4194
+ * (from the entity's relationships.json in IPFS - source of truth)
4195
+ * - **GraphClient.getRelationships()**: Returns BOTH inbound AND outbound
4196
+ * relationships (from the indexed GraphDB mirror)
3619
4197
  *
3620
- * @param canonicalId - Entity UUID
3621
- * @returns Array of relationships
4198
+ * Use this method when you need to find "what references this entity" (inbound)
4199
+ * or want a complete bidirectional view.
4200
+ *
4201
+ * @param id - Entity identifier (works for both PIs and KG entities)
4202
+ * @param direction - Filter by direction: 'outgoing', 'incoming', or 'both' (default)
4203
+ * @returns Array of relationships with direction indicator
3622
4204
  *
3623
4205
  * @example
3624
4206
  * ```typescript
3625
- * const relationships = await graph.getRelationships('uuid-123');
3626
- * relationships.forEach(r => {
3627
- * console.log(`${r.direction}: ${r.predicate} -> ${r.target_label}`);
4207
+ * // Get all relationships (both directions)
4208
+ * const all = await graph.getRelationships('01K75HQQXNTDG7BBP7PS9AWYAN');
4209
+ *
4210
+ * // Get only inbound relationships ("who references this entity?")
4211
+ * const incoming = await graph.getRelationships('01K75HQQXNTDG7BBP7PS9AWYAN', 'incoming');
4212
+ *
4213
+ * // Get only outbound relationships (similar to IPFS, but from index)
4214
+ * const outgoing = await graph.getRelationships('01K75HQQXNTDG7BBP7PS9AWYAN', 'outgoing');
4215
+ *
4216
+ * // Process by direction
4217
+ * const rels = await graph.getRelationships('entity-id');
4218
+ * rels.forEach(r => {
4219
+ * if (r.direction === 'incoming') {
4220
+ * console.log(`${r.target_label} references this entity via ${r.predicate}`);
4221
+ * } else {
4222
+ * console.log(`This entity ${r.predicate} -> ${r.target_label}`);
4223
+ * }
3628
4224
  * });
3629
4225
  * ```
3630
4226
  */
3631
- async getRelationships(canonicalId) {
3632
- const response = await this.request(`/graphdb/relationships/${encodeURIComponent(canonicalId)}`);
4227
+ async getRelationships(id, direction = "both") {
4228
+ const response = await this.request(`/graphdb/relationships/${encodeURIComponent(id)}`);
3633
4229
  if (!response.found || !response.relationships) {
3634
4230
  return [];
3635
4231
  }
3636
- return response.relationships.map((rel) => ({
4232
+ let relationships = response.relationships;
4233
+ if (direction !== "both") {
4234
+ relationships = relationships.filter((rel) => rel.direction === direction);
4235
+ }
4236
+ return relationships.map((rel) => ({
3637
4237
  direction: rel.direction,
3638
4238
  predicate: rel.predicate,
3639
4239
  target_id: rel.target_id,
@@ -3659,8 +4259,8 @@ var GraphClient = class {
3659
4259
  * @example
3660
4260
  * ```typescript
3661
4261
  * const paths = await graph.findPaths(
3662
- * ['uuid-alice'],
3663
- * ['uuid-bob'],
4262
+ * ['entity-alice'],
4263
+ * ['entity-bob'],
3664
4264
  * { max_depth: 4, direction: 'both' }
3665
4265
  * );
3666
4266
  *
@@ -3697,7 +4297,7 @@ var GraphClient = class {
3697
4297
  * ```typescript
3698
4298
  * // Find all people reachable from an event
3699
4299
  * const people = await graph.findReachable(
3700
- * ['uuid-event'],
4300
+ * ['event-id'],
3701
4301
  * 'person',
3702
4302
  * { max_depth: 3 }
3703
4303
  * );