@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/index.cjs
CHANGED
|
@@ -652,8 +652,8 @@ __export(src_exports, {
|
|
|
652
652
|
ComponentNotFoundError: () => ComponentNotFoundError,
|
|
653
653
|
ContentClient: () => ContentClient,
|
|
654
654
|
ContentError: () => ContentError,
|
|
655
|
-
ContentNetworkError: () =>
|
|
656
|
-
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: () =>
|
|
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(
|
|
1913
|
-
super(`Entity not found: ${
|
|
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(
|
|
1993
|
+
constructor(id, expectedTip, actualTip) {
|
|
1919
1994
|
super(
|
|
1920
|
-
`CAS conflict: entity ${
|
|
1995
|
+
`CAS conflict: entity ${id} was modified (expected ${expectedTip}, got ${actualTip})`,
|
|
1921
1996
|
"CAS_CONFLICT",
|
|
1922
|
-
{
|
|
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,
|
|
1941
|
-
super(message, "PERMISSION_DENIED", {
|
|
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
|
|
1948
|
-
|
|
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
|
-
*
|
|
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,
|
|
2152
|
+
async fetchWithRetry(url, options, context) {
|
|
1978
2153
|
let lastError = null;
|
|
1979
|
-
let
|
|
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
|
|
1984
|
-
|
|
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 <
|
|
2166
|
+
if (this.isRetryableError(lastError) && attempt < this.retryConfig.maxRetries) {
|
|
2167
|
+
const delay = this.calculateDelay(attempt);
|
|
1993
2168
|
await this.sleep(delay);
|
|
1994
|
-
|
|
2169
|
+
continue;
|
|
1995
2170
|
}
|
|
2171
|
+
throw new NetworkError2(lastError.message);
|
|
1996
2172
|
}
|
|
1997
2173
|
}
|
|
1998
|
-
throw lastError || new
|
|
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
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
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
|
-
|
|
2005
|
-
|
|
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
|
|
2366
|
+
return response.json();
|
|
2008
2367
|
}
|
|
2368
|
+
// ===========================================================================
|
|
2369
|
+
// Merge Operations
|
|
2370
|
+
// ===========================================================================
|
|
2009
2371
|
/**
|
|
2010
|
-
*
|
|
2372
|
+
* Merge source entity into target entity
|
|
2011
2373
|
*/
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
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
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
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
|
-
//
|
|
2430
|
+
// Delete Operations
|
|
2024
2431
|
// ===========================================================================
|
|
2025
2432
|
/**
|
|
2026
|
-
*
|
|
2433
|
+
* Soft delete an entity (creates tombstone, preserves history)
|
|
2027
2434
|
*/
|
|
2028
|
-
async
|
|
2029
|
-
const
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
2486
|
+
* Upload files to IPFS
|
|
2042
2487
|
*/
|
|
2043
|
-
async
|
|
2044
|
-
|
|
2045
|
-
|
|
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,
|
|
2515
|
+
await this.handleErrorResponse(response, "Upload files");
|
|
2049
2516
|
}
|
|
2050
|
-
return response.
|
|
2517
|
+
return response.json();
|
|
2051
2518
|
}
|
|
2052
2519
|
/**
|
|
2053
|
-
* Upload content and
|
|
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
|
-
|
|
2059
|
-
const
|
|
2060
|
-
|
|
2061
|
-
|
|
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,
|
|
2542
|
+
await this.handleErrorResponse(response, `Get content ${cid}`);
|
|
2070
2543
|
}
|
|
2071
|
-
|
|
2072
|
-
return result[0].cid;
|
|
2544
|
+
return response.text();
|
|
2073
2545
|
}
|
|
2074
2546
|
/**
|
|
2075
|
-
*
|
|
2547
|
+
* Download a DAG node (JSON) by CID
|
|
2076
2548
|
*/
|
|
2077
|
-
async
|
|
2078
|
-
const
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
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,
|
|
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
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
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
|
-
|
|
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.
|
|
3198
|
+
pi: this.entity.id,
|
|
2678
3199
|
ver: this.entity.ver,
|
|
2679
3200
|
parentPi: this.entity.parent_pi,
|
|
2680
|
-
childrenCount: this.entity.children_pi
|
|
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.
|
|
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.
|
|
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(
|
|
2884
|
-
super(`Entity not found: ${
|
|
3404
|
+
constructor(id) {
|
|
3405
|
+
super(`Entity not found: ${id}`, "ENTITY_NOT_FOUND", { id });
|
|
2885
3406
|
this.name = "EntityNotFoundError";
|
|
2886
3407
|
}
|
|
2887
3408
|
};
|
|
2888
|
-
var
|
|
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(
|
|
3416
|
+
constructor(id, componentName) {
|
|
2896
3417
|
super(
|
|
2897
|
-
`Component '${componentName}' not found on entity ${
|
|
3418
|
+
`Component '${componentName}' not found on entity ${id}`,
|
|
2898
3419
|
"COMPONENT_NOT_FOUND",
|
|
2899
|
-
{
|
|
3420
|
+
{ id, componentName }
|
|
2900
3421
|
);
|
|
2901
3422
|
this.name = "ComponentNotFoundError";
|
|
2902
3423
|
}
|
|
2903
3424
|
};
|
|
2904
3425
|
var VersionNotFoundError = class extends ContentError {
|
|
2905
|
-
constructor(
|
|
3426
|
+
constructor(id, selector) {
|
|
2906
3427
|
super(
|
|
2907
|
-
`Version not found: ${selector} for entity ${
|
|
3428
|
+
`Version not found: ${selector} for entity ${id}`,
|
|
2908
3429
|
"VERSION_NOT_FOUND",
|
|
2909
|
-
{
|
|
3430
|
+
{ id, selector }
|
|
2910
3431
|
);
|
|
2911
3432
|
this.name = "VersionNotFoundError";
|
|
2912
3433
|
}
|
|
2913
3434
|
};
|
|
2914
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
* @
|
|
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
|
|
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
|
-
*
|
|
3621
|
-
*
|
|
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
|
-
*
|
|
3626
|
-
*
|
|
3627
|
-
*
|
|
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(
|
|
3632
|
-
const response = await this.request(`/graphdb/relationships/${encodeURIComponent(
|
|
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
|
-
|
|
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
|
-
* ['
|
|
3663
|
-
* ['
|
|
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
|
-
* ['
|
|
4300
|
+
* ['event-id'],
|
|
3701
4301
|
* 'person',
|
|
3702
4302
|
* { max_depth: 3 }
|
|
3703
4303
|
* );
|