@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.js
CHANGED
|
@@ -1809,6 +1809,73 @@ var QueryClient = class {
|
|
|
1809
1809
|
async health() {
|
|
1810
1810
|
return this.request("/query/health", { method: "GET" });
|
|
1811
1811
|
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Direct semantic search against the vector index.
|
|
1814
|
+
*
|
|
1815
|
+
* This bypasses the path query syntax and directly queries Pinecone for
|
|
1816
|
+
* semantically similar entities. Useful for:
|
|
1817
|
+
* - Simple semantic searches without graph traversal
|
|
1818
|
+
* - Scoped searches filtered by source_pi (collection scope)
|
|
1819
|
+
* - Type-filtered semantic searches
|
|
1820
|
+
*
|
|
1821
|
+
* For graph traversal and path-based queries, use `path()` instead.
|
|
1822
|
+
*
|
|
1823
|
+
* @param text - Search query text
|
|
1824
|
+
* @param options - Search options (namespace, filters, top_k)
|
|
1825
|
+
* @returns Matching entities with similarity scores
|
|
1826
|
+
*
|
|
1827
|
+
* @example
|
|
1828
|
+
* ```typescript
|
|
1829
|
+
* // Simple semantic search
|
|
1830
|
+
* const results = await query.semanticSearch('photographers from New York');
|
|
1831
|
+
*
|
|
1832
|
+
* // Scoped to a specific PI (collection)
|
|
1833
|
+
* const scoped = await query.semanticSearch('portraits', {
|
|
1834
|
+
* filter: { source_pi: '01K75HQQXNTDG7BBP7PS9AWYAN' },
|
|
1835
|
+
* top_k: 20,
|
|
1836
|
+
* });
|
|
1837
|
+
*
|
|
1838
|
+
* // Filter by type
|
|
1839
|
+
* const people = await query.semanticSearch('artists', {
|
|
1840
|
+
* filter: { type: 'person' },
|
|
1841
|
+
* });
|
|
1842
|
+
*
|
|
1843
|
+
* // Search across merged entities from multiple source PIs
|
|
1844
|
+
* const merged = await query.semanticSearch('historical documents', {
|
|
1845
|
+
* filter: { merged_entities_source_pis: ['pi-1', 'pi-2'] },
|
|
1846
|
+
* });
|
|
1847
|
+
* ```
|
|
1848
|
+
*/
|
|
1849
|
+
async semanticSearch(text, options = {}) {
|
|
1850
|
+
let pineconeFilter;
|
|
1851
|
+
if (options.filter) {
|
|
1852
|
+
pineconeFilter = {};
|
|
1853
|
+
if (options.filter.type) {
|
|
1854
|
+
const types = Array.isArray(options.filter.type) ? options.filter.type : [options.filter.type];
|
|
1855
|
+
pineconeFilter.type = types.length === 1 ? { $eq: types[0] } : { $in: types };
|
|
1856
|
+
}
|
|
1857
|
+
if (options.filter.source_pi) {
|
|
1858
|
+
const pis = Array.isArray(options.filter.source_pi) ? options.filter.source_pi : [options.filter.source_pi];
|
|
1859
|
+
pineconeFilter.source_pi = pis.length === 1 ? { $eq: pis[0] } : { $in: pis };
|
|
1860
|
+
}
|
|
1861
|
+
if (options.filter.merged_entities_source_pis) {
|
|
1862
|
+
const pis = Array.isArray(options.filter.merged_entities_source_pis) ? options.filter.merged_entities_source_pis : [options.filter.merged_entities_source_pis];
|
|
1863
|
+
pineconeFilter.merged_entities_source_pis = { $in: pis };
|
|
1864
|
+
}
|
|
1865
|
+
if (Object.keys(pineconeFilter).length === 0) {
|
|
1866
|
+
pineconeFilter = void 0;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
return this.request("/query/search/semantic", {
|
|
1870
|
+
method: "POST",
|
|
1871
|
+
body: JSON.stringify({
|
|
1872
|
+
text,
|
|
1873
|
+
namespace: options.namespace,
|
|
1874
|
+
filter: pineconeFilter,
|
|
1875
|
+
top_k: options.top_k
|
|
1876
|
+
})
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1812
1879
|
/**
|
|
1813
1880
|
* Search for collections by semantic similarity.
|
|
1814
1881
|
*
|
|
@@ -1843,6 +1910,14 @@ var QueryClient = class {
|
|
|
1843
1910
|
}
|
|
1844
1911
|
};
|
|
1845
1912
|
|
|
1913
|
+
// src/edit/types.ts
|
|
1914
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
1915
|
+
maxRetries: 10,
|
|
1916
|
+
baseDelay: 100,
|
|
1917
|
+
maxDelay: 5e3,
|
|
1918
|
+
jitterFactor: 0.3
|
|
1919
|
+
};
|
|
1920
|
+
|
|
1846
1921
|
// src/edit/errors.ts
|
|
1847
1922
|
var EditError = class extends Error {
|
|
1848
1923
|
constructor(message, code2 = "UNKNOWN_ERROR", details) {
|
|
@@ -1853,21 +1928,51 @@ var EditError = class extends Error {
|
|
|
1853
1928
|
}
|
|
1854
1929
|
};
|
|
1855
1930
|
var EntityNotFoundError = class extends EditError {
|
|
1856
|
-
constructor(
|
|
1857
|
-
super(`Entity not found: ${
|
|
1931
|
+
constructor(id) {
|
|
1932
|
+
super(`Entity not found: ${id}`, "ENTITY_NOT_FOUND", { id });
|
|
1858
1933
|
this.name = "EntityNotFoundError";
|
|
1859
1934
|
}
|
|
1860
1935
|
};
|
|
1861
1936
|
var CASConflictError = class extends EditError {
|
|
1862
|
-
constructor(
|
|
1937
|
+
constructor(id, expectedTip, actualTip) {
|
|
1863
1938
|
super(
|
|
1864
|
-
`CAS conflict: entity ${
|
|
1939
|
+
`CAS conflict: entity ${id} was modified (expected ${expectedTip}, got ${actualTip})`,
|
|
1865
1940
|
"CAS_CONFLICT",
|
|
1866
|
-
{
|
|
1941
|
+
{ id, expectedTip, actualTip }
|
|
1867
1942
|
);
|
|
1868
1943
|
this.name = "CASConflictError";
|
|
1869
1944
|
}
|
|
1870
1945
|
};
|
|
1946
|
+
var EntityExistsError = class extends EditError {
|
|
1947
|
+
constructor(id) {
|
|
1948
|
+
super(`Entity already exists: ${id}`, "ENTITY_EXISTS", { id });
|
|
1949
|
+
this.name = "EntityExistsError";
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
var MergeError = class extends EditError {
|
|
1953
|
+
constructor(message, sourceId, targetId) {
|
|
1954
|
+
super(message, "MERGE_ERROR", { sourceId, targetId });
|
|
1955
|
+
this.name = "MergeError";
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
var UnmergeError = class extends EditError {
|
|
1959
|
+
constructor(message, sourceId, targetId) {
|
|
1960
|
+
super(message, "UNMERGE_ERROR", { sourceId, targetId });
|
|
1961
|
+
this.name = "UnmergeError";
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
var DeleteError = class extends EditError {
|
|
1965
|
+
constructor(message, id) {
|
|
1966
|
+
super(message, "DELETE_ERROR", { id });
|
|
1967
|
+
this.name = "DeleteError";
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
var UndeleteError = class extends EditError {
|
|
1971
|
+
constructor(message, id) {
|
|
1972
|
+
super(message, "UNDELETE_ERROR", { id });
|
|
1973
|
+
this.name = "UndeleteError";
|
|
1974
|
+
}
|
|
1975
|
+
};
|
|
1871
1976
|
var ReprocessError = class extends EditError {
|
|
1872
1977
|
constructor(message, batchId) {
|
|
1873
1978
|
super(message, "REPROCESS_ERROR", { batchId });
|
|
@@ -1881,28 +1986,52 @@ var ValidationError2 = class extends EditError {
|
|
|
1881
1986
|
}
|
|
1882
1987
|
};
|
|
1883
1988
|
var PermissionError = class extends EditError {
|
|
1884
|
-
constructor(message,
|
|
1885
|
-
super(message, "PERMISSION_DENIED", {
|
|
1989
|
+
constructor(message, id) {
|
|
1990
|
+
super(message, "PERMISSION_DENIED", { id });
|
|
1886
1991
|
this.name = "PermissionError";
|
|
1887
1992
|
}
|
|
1888
1993
|
};
|
|
1994
|
+
var NetworkError2 = class extends EditError {
|
|
1995
|
+
constructor(message, statusCode) {
|
|
1996
|
+
super(message, "NETWORK_ERROR", { statusCode });
|
|
1997
|
+
this.name = "NetworkError";
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
var ContentNotFoundError = class extends EditError {
|
|
2001
|
+
constructor(cid) {
|
|
2002
|
+
super(`Content not found: ${cid}`, "CONTENT_NOT_FOUND", { cid });
|
|
2003
|
+
this.name = "ContentNotFoundError";
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
var IPFSError = class extends EditError {
|
|
2007
|
+
constructor(message) {
|
|
2008
|
+
super(message, "IPFS_ERROR");
|
|
2009
|
+
this.name = "IPFSError";
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
var BackendError = class extends EditError {
|
|
2013
|
+
constructor(message) {
|
|
2014
|
+
super(message, "BACKEND_ERROR");
|
|
2015
|
+
this.name = "BackendError";
|
|
2016
|
+
}
|
|
2017
|
+
};
|
|
1889
2018
|
|
|
1890
2019
|
// src/edit/client.ts
|
|
1891
|
-
var
|
|
1892
|
-
|
|
1893
|
-
initialDelayMs: 2e3,
|
|
1894
|
-
// Start with 2s delay (orchestrator needs time to initialize)
|
|
1895
|
-
maxDelayMs: 3e4,
|
|
1896
|
-
// Cap at 30s
|
|
1897
|
-
backoffMultiplier: 2
|
|
1898
|
-
// Double each retry
|
|
1899
|
-
};
|
|
2020
|
+
var RETRYABLE_STATUS_CODES = [409, 503];
|
|
2021
|
+
var RETRYABLE_ERRORS = ["ECONNRESET", "ETIMEDOUT", "fetch failed"];
|
|
1900
2022
|
var EditClient = class {
|
|
1901
2023
|
constructor(config) {
|
|
1902
2024
|
this.gatewayUrl = config.gatewayUrl.replace(/\/$/, "");
|
|
1903
2025
|
this.authToken = config.authToken;
|
|
2026
|
+
this.network = config.network || "main";
|
|
2027
|
+
this.userId = config.userId;
|
|
2028
|
+
this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retryConfig };
|
|
1904
2029
|
this.statusUrlTransform = config.statusUrlTransform;
|
|
2030
|
+
this.apiPrefix = config.apiPrefix ?? "/api";
|
|
1905
2031
|
}
|
|
2032
|
+
// ===========================================================================
|
|
2033
|
+
// Configuration Methods
|
|
2034
|
+
// ===========================================================================
|
|
1906
2035
|
/**
|
|
1907
2036
|
* Update the auth token (useful for token refresh)
|
|
1908
2037
|
*/
|
|
@@ -1910,135 +2039,505 @@ var EditClient = class {
|
|
|
1910
2039
|
this.authToken = token;
|
|
1911
2040
|
}
|
|
1912
2041
|
/**
|
|
1913
|
-
*
|
|
2042
|
+
* Set the network (main or test)
|
|
2043
|
+
*/
|
|
2044
|
+
setNetwork(network) {
|
|
2045
|
+
this.network = network;
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Set the user ID for permission checks
|
|
2049
|
+
*/
|
|
2050
|
+
setUserId(userId) {
|
|
2051
|
+
this.userId = userId;
|
|
2052
|
+
}
|
|
2053
|
+
// ===========================================================================
|
|
2054
|
+
// Internal Helpers
|
|
2055
|
+
// ===========================================================================
|
|
2056
|
+
/**
|
|
2057
|
+
* Build URL with API prefix
|
|
1914
2058
|
*/
|
|
2059
|
+
buildUrl(path2) {
|
|
2060
|
+
return `${this.gatewayUrl}${this.apiPrefix}${path2}`;
|
|
2061
|
+
}
|
|
1915
2062
|
sleep(ms) {
|
|
1916
2063
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1917
2064
|
}
|
|
2065
|
+
getHeaders(contentType = "application/json") {
|
|
2066
|
+
const headers = {};
|
|
2067
|
+
if (contentType) {
|
|
2068
|
+
headers["Content-Type"] = contentType;
|
|
2069
|
+
}
|
|
2070
|
+
if (this.authToken) {
|
|
2071
|
+
headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
2072
|
+
}
|
|
2073
|
+
headers["X-Arke-Network"] = this.network;
|
|
2074
|
+
if (this.userId) {
|
|
2075
|
+
headers["X-User-Id"] = this.userId;
|
|
2076
|
+
}
|
|
2077
|
+
return headers;
|
|
2078
|
+
}
|
|
2079
|
+
calculateDelay(attempt) {
|
|
2080
|
+
const { baseDelay, maxDelay, jitterFactor } = this.retryConfig;
|
|
2081
|
+
const exponentialDelay = baseDelay * Math.pow(2, attempt);
|
|
2082
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
2083
|
+
const jitter = cappedDelay * jitterFactor * (Math.random() * 2 - 1);
|
|
2084
|
+
return Math.max(0, cappedDelay + jitter);
|
|
2085
|
+
}
|
|
2086
|
+
isRetryableStatus(status) {
|
|
2087
|
+
return RETRYABLE_STATUS_CODES.includes(status);
|
|
2088
|
+
}
|
|
2089
|
+
isRetryableError(error) {
|
|
2090
|
+
const message = error.message.toLowerCase();
|
|
2091
|
+
return RETRYABLE_ERRORS.some((e) => message.includes(e.toLowerCase()));
|
|
2092
|
+
}
|
|
1918
2093
|
/**
|
|
1919
2094
|
* Execute a fetch with exponential backoff retry on transient errors
|
|
1920
2095
|
*/
|
|
1921
|
-
async fetchWithRetry(url, options,
|
|
2096
|
+
async fetchWithRetry(url, options, context) {
|
|
1922
2097
|
let lastError = null;
|
|
1923
|
-
let
|
|
1924
|
-
for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {
|
|
2098
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
1925
2099
|
try {
|
|
1926
2100
|
const response = await fetch(url, options);
|
|
1927
|
-
if (response.status
|
|
1928
|
-
|
|
2101
|
+
if (this.isRetryableStatus(response.status) && attempt < this.retryConfig.maxRetries) {
|
|
2102
|
+
const delay = this.calculateDelay(attempt);
|
|
2103
|
+
lastError = new Error(`${context}: ${response.status} ${response.statusText}`);
|
|
1929
2104
|
await this.sleep(delay);
|
|
1930
|
-
delay = Math.min(delay * retryOptions.backoffMultiplier, retryOptions.maxDelayMs);
|
|
1931
2105
|
continue;
|
|
1932
2106
|
}
|
|
1933
2107
|
return response;
|
|
1934
2108
|
} catch (error) {
|
|
1935
2109
|
lastError = error;
|
|
1936
|
-
if (attempt <
|
|
2110
|
+
if (this.isRetryableError(lastError) && attempt < this.retryConfig.maxRetries) {
|
|
2111
|
+
const delay = this.calculateDelay(attempt);
|
|
1937
2112
|
await this.sleep(delay);
|
|
1938
|
-
|
|
2113
|
+
continue;
|
|
1939
2114
|
}
|
|
2115
|
+
throw new NetworkError2(lastError.message);
|
|
1940
2116
|
}
|
|
1941
2117
|
}
|
|
1942
|
-
throw lastError || new
|
|
2118
|
+
throw lastError || new NetworkError2("Request failed after retries");
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Handle common error responses and throw appropriate error types
|
|
2122
|
+
*/
|
|
2123
|
+
async handleErrorResponse(response, context) {
|
|
2124
|
+
let errorData = {};
|
|
2125
|
+
try {
|
|
2126
|
+
errorData = await response.json();
|
|
2127
|
+
} catch {
|
|
2128
|
+
}
|
|
2129
|
+
const message = errorData.message || `${context}: ${response.statusText}`;
|
|
2130
|
+
const errorCode = errorData.error || "";
|
|
2131
|
+
switch (response.status) {
|
|
2132
|
+
case 400:
|
|
2133
|
+
throw new ValidationError2(message);
|
|
2134
|
+
case 403:
|
|
2135
|
+
throw new PermissionError(message);
|
|
2136
|
+
case 404:
|
|
2137
|
+
throw new EntityNotFoundError(message);
|
|
2138
|
+
case 409:
|
|
2139
|
+
if (errorCode === "CAS_FAILURE") {
|
|
2140
|
+
const details = errorData.details;
|
|
2141
|
+
throw new CASConflictError(
|
|
2142
|
+
context,
|
|
2143
|
+
details?.expect || "unknown",
|
|
2144
|
+
details?.actual || "unknown"
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
if (errorCode === "CONFLICT") {
|
|
2148
|
+
throw new EntityExistsError(message);
|
|
2149
|
+
}
|
|
2150
|
+
throw new EditError(message, errorCode, errorData.details);
|
|
2151
|
+
case 503:
|
|
2152
|
+
if (errorCode === "IPFS_ERROR") {
|
|
2153
|
+
throw new IPFSError(message);
|
|
2154
|
+
}
|
|
2155
|
+
if (errorCode === "BACKEND_ERROR") {
|
|
2156
|
+
throw new BackendError(message);
|
|
2157
|
+
}
|
|
2158
|
+
throw new NetworkError2(message, response.status);
|
|
2159
|
+
default:
|
|
2160
|
+
throw new EditError(message, errorCode || "API_ERROR", { status: response.status });
|
|
2161
|
+
}
|
|
1943
2162
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
2163
|
+
// ===========================================================================
|
|
2164
|
+
// Entity CRUD Operations
|
|
2165
|
+
// ===========================================================================
|
|
2166
|
+
/**
|
|
2167
|
+
* Create a new entity
|
|
2168
|
+
*/
|
|
2169
|
+
async createEntity(request) {
|
|
2170
|
+
const url = this.buildUrl("/entities");
|
|
2171
|
+
const response = await this.fetchWithRetry(
|
|
2172
|
+
url,
|
|
2173
|
+
{
|
|
2174
|
+
method: "POST",
|
|
2175
|
+
headers: this.getHeaders(),
|
|
2176
|
+
body: JSON.stringify(request)
|
|
2177
|
+
},
|
|
2178
|
+
"Create entity"
|
|
2179
|
+
);
|
|
2180
|
+
if (!response.ok) {
|
|
2181
|
+
await this.handleErrorResponse(response, "Create entity");
|
|
2182
|
+
}
|
|
2183
|
+
return response.json();
|
|
2184
|
+
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Get an entity by ID
|
|
2187
|
+
*/
|
|
2188
|
+
async getEntity(id) {
|
|
2189
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}`);
|
|
2190
|
+
const response = await this.fetchWithRetry(
|
|
2191
|
+
url,
|
|
2192
|
+
{ headers: this.getHeaders() },
|
|
2193
|
+
`Get entity ${id}`
|
|
2194
|
+
);
|
|
2195
|
+
if (!response.ok) {
|
|
2196
|
+
await this.handleErrorResponse(response, `Get entity ${id}`);
|
|
2197
|
+
}
|
|
2198
|
+
return response.json();
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* List entities with pagination
|
|
2202
|
+
*/
|
|
2203
|
+
async listEntities(options = {}) {
|
|
2204
|
+
const params = new URLSearchParams();
|
|
2205
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
2206
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
2207
|
+
if (options.include_metadata) params.set("include_metadata", "true");
|
|
2208
|
+
const queryString = params.toString();
|
|
2209
|
+
const url = this.buildUrl(`/entities${queryString ? `?${queryString}` : ""}`);
|
|
2210
|
+
const response = await this.fetchWithRetry(url, { headers: this.getHeaders() }, "List entities");
|
|
2211
|
+
if (!response.ok) {
|
|
2212
|
+
await this.handleErrorResponse(response, "List entities");
|
|
2213
|
+
}
|
|
2214
|
+
return response.json();
|
|
2215
|
+
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Update an entity (append new version)
|
|
2218
|
+
*/
|
|
2219
|
+
async updateEntity(id, update) {
|
|
2220
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions`);
|
|
2221
|
+
const response = await this.fetchWithRetry(
|
|
2222
|
+
url,
|
|
2223
|
+
{
|
|
2224
|
+
method: "POST",
|
|
2225
|
+
headers: this.getHeaders(),
|
|
2226
|
+
body: JSON.stringify(update)
|
|
2227
|
+
},
|
|
2228
|
+
`Update entity ${id}`
|
|
2229
|
+
);
|
|
2230
|
+
if (!response.ok) {
|
|
2231
|
+
await this.handleErrorResponse(response, `Update entity ${id}`);
|
|
2232
|
+
}
|
|
2233
|
+
return response.json();
|
|
2234
|
+
}
|
|
2235
|
+
// ===========================================================================
|
|
2236
|
+
// Version Operations
|
|
2237
|
+
// ===========================================================================
|
|
2238
|
+
/**
|
|
2239
|
+
* List version history for an entity
|
|
2240
|
+
*/
|
|
2241
|
+
async listVersions(id, options = {}) {
|
|
2242
|
+
const params = new URLSearchParams();
|
|
2243
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
2244
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
2245
|
+
const queryString = params.toString();
|
|
2246
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions${queryString ? `?${queryString}` : ""}`);
|
|
2247
|
+
const response = await this.fetchWithRetry(url, { headers: this.getHeaders() }, `List versions for ${id}`);
|
|
2248
|
+
if (!response.ok) {
|
|
2249
|
+
await this.handleErrorResponse(response, `List versions for ${id}`);
|
|
2250
|
+
}
|
|
2251
|
+
return response.json();
|
|
2252
|
+
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Get a specific version of an entity
|
|
2255
|
+
*/
|
|
2256
|
+
async getVersion(id, selector) {
|
|
2257
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions/${encodeURIComponent(selector)}`);
|
|
2258
|
+
const response = await this.fetchWithRetry(
|
|
2259
|
+
url,
|
|
2260
|
+
{ headers: this.getHeaders() },
|
|
2261
|
+
`Get version ${selector} for ${id}`
|
|
2262
|
+
);
|
|
2263
|
+
if (!response.ok) {
|
|
2264
|
+
await this.handleErrorResponse(response, `Get version ${selector} for ${id}`);
|
|
2265
|
+
}
|
|
2266
|
+
return response.json();
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Resolve an entity ID to its current tip CID (fast lookup)
|
|
2270
|
+
*/
|
|
2271
|
+
async resolve(id) {
|
|
2272
|
+
const url = this.buildUrl(`/resolve/${encodeURIComponent(id)}`);
|
|
2273
|
+
const response = await this.fetchWithRetry(
|
|
2274
|
+
url,
|
|
2275
|
+
{ headers: this.getHeaders() },
|
|
2276
|
+
`Resolve ${id}`
|
|
2277
|
+
);
|
|
2278
|
+
if (!response.ok) {
|
|
2279
|
+
await this.handleErrorResponse(response, `Resolve ${id}`);
|
|
2280
|
+
}
|
|
2281
|
+
return response.json();
|
|
2282
|
+
}
|
|
2283
|
+
// ===========================================================================
|
|
2284
|
+
// Hierarchy Operations
|
|
2285
|
+
// ===========================================================================
|
|
2286
|
+
/**
|
|
2287
|
+
* Update parent-child hierarchy relationships
|
|
2288
|
+
*/
|
|
2289
|
+
async updateHierarchy(request) {
|
|
2290
|
+
const apiRequest = {
|
|
2291
|
+
parent_pi: request.parent_id,
|
|
2292
|
+
expect_tip: request.expect_tip,
|
|
2293
|
+
add_children: request.add_children,
|
|
2294
|
+
remove_children: request.remove_children,
|
|
2295
|
+
note: request.note
|
|
1947
2296
|
};
|
|
1948
|
-
|
|
1949
|
-
|
|
2297
|
+
const url = this.buildUrl("/hierarchy");
|
|
2298
|
+
const response = await this.fetchWithRetry(
|
|
2299
|
+
url,
|
|
2300
|
+
{
|
|
2301
|
+
method: "POST",
|
|
2302
|
+
headers: this.getHeaders(),
|
|
2303
|
+
body: JSON.stringify(apiRequest)
|
|
2304
|
+
},
|
|
2305
|
+
`Update hierarchy for ${request.parent_id}`
|
|
2306
|
+
);
|
|
2307
|
+
if (!response.ok) {
|
|
2308
|
+
await this.handleErrorResponse(response, `Update hierarchy for ${request.parent_id}`);
|
|
1950
2309
|
}
|
|
1951
|
-
return
|
|
2310
|
+
return response.json();
|
|
1952
2311
|
}
|
|
2312
|
+
// ===========================================================================
|
|
2313
|
+
// Merge Operations
|
|
2314
|
+
// ===========================================================================
|
|
1953
2315
|
/**
|
|
1954
|
-
*
|
|
2316
|
+
* Merge source entity into target entity
|
|
1955
2317
|
*/
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2318
|
+
async mergeEntity(sourceId, request) {
|
|
2319
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(sourceId)}/merge`);
|
|
2320
|
+
const response = await this.fetchWithRetry(
|
|
2321
|
+
url,
|
|
2322
|
+
{
|
|
2323
|
+
method: "POST",
|
|
2324
|
+
headers: this.getHeaders(),
|
|
2325
|
+
body: JSON.stringify(request)
|
|
2326
|
+
},
|
|
2327
|
+
`Merge ${sourceId} into ${request.target_id}`
|
|
2328
|
+
);
|
|
2329
|
+
if (!response.ok) {
|
|
2330
|
+
try {
|
|
2331
|
+
const error = await response.json();
|
|
2332
|
+
throw new MergeError(
|
|
2333
|
+
error.message || `Merge failed: ${response.statusText}`,
|
|
2334
|
+
sourceId,
|
|
2335
|
+
request.target_id
|
|
2336
|
+
);
|
|
2337
|
+
} catch (e) {
|
|
2338
|
+
if (e instanceof MergeError) throw e;
|
|
2339
|
+
await this.handleErrorResponse(response, `Merge ${sourceId}`);
|
|
2340
|
+
}
|
|
1959
2341
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2342
|
+
return response.json();
|
|
2343
|
+
}
|
|
2344
|
+
/**
|
|
2345
|
+
* Unmerge (restore) a previously merged entity
|
|
2346
|
+
*/
|
|
2347
|
+
async unmergeEntity(sourceId, request) {
|
|
2348
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(sourceId)}/unmerge`);
|
|
2349
|
+
const response = await this.fetchWithRetry(
|
|
2350
|
+
url,
|
|
2351
|
+
{
|
|
2352
|
+
method: "POST",
|
|
2353
|
+
headers: this.getHeaders(),
|
|
2354
|
+
body: JSON.stringify(request)
|
|
2355
|
+
},
|
|
2356
|
+
`Unmerge ${sourceId}`
|
|
1964
2357
|
);
|
|
2358
|
+
if (!response.ok) {
|
|
2359
|
+
try {
|
|
2360
|
+
const error = await response.json();
|
|
2361
|
+
throw new UnmergeError(
|
|
2362
|
+
error.message || `Unmerge failed: ${response.statusText}`,
|
|
2363
|
+
sourceId,
|
|
2364
|
+
request.target_id
|
|
2365
|
+
);
|
|
2366
|
+
} catch (e) {
|
|
2367
|
+
if (e instanceof UnmergeError) throw e;
|
|
2368
|
+
await this.handleErrorResponse(response, `Unmerge ${sourceId}`);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
return response.json();
|
|
1965
2372
|
}
|
|
1966
2373
|
// ===========================================================================
|
|
1967
|
-
//
|
|
2374
|
+
// Delete Operations
|
|
1968
2375
|
// ===========================================================================
|
|
1969
2376
|
/**
|
|
1970
|
-
*
|
|
2377
|
+
* Soft delete an entity (creates tombstone, preserves history)
|
|
1971
2378
|
*/
|
|
1972
|
-
async
|
|
1973
|
-
const
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2379
|
+
async deleteEntity(id, request) {
|
|
2380
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/delete`);
|
|
2381
|
+
const response = await this.fetchWithRetry(
|
|
2382
|
+
url,
|
|
2383
|
+
{
|
|
2384
|
+
method: "POST",
|
|
2385
|
+
headers: this.getHeaders(),
|
|
2386
|
+
body: JSON.stringify(request)
|
|
2387
|
+
},
|
|
2388
|
+
`Delete ${id}`
|
|
2389
|
+
);
|
|
2390
|
+
if (!response.ok) {
|
|
2391
|
+
try {
|
|
2392
|
+
const error = await response.json();
|
|
2393
|
+
throw new DeleteError(error.message || `Delete failed: ${response.statusText}`, id);
|
|
2394
|
+
} catch (e) {
|
|
2395
|
+
if (e instanceof DeleteError) throw e;
|
|
2396
|
+
await this.handleErrorResponse(response, `Delete ${id}`);
|
|
2397
|
+
}
|
|
1978
2398
|
}
|
|
2399
|
+
return response.json();
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Restore a deleted entity
|
|
2403
|
+
*/
|
|
2404
|
+
async undeleteEntity(id, request) {
|
|
2405
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/undelete`);
|
|
2406
|
+
const response = await this.fetchWithRetry(
|
|
2407
|
+
url,
|
|
2408
|
+
{
|
|
2409
|
+
method: "POST",
|
|
2410
|
+
headers: this.getHeaders(),
|
|
2411
|
+
body: JSON.stringify(request)
|
|
2412
|
+
},
|
|
2413
|
+
`Undelete ${id}`
|
|
2414
|
+
);
|
|
1979
2415
|
if (!response.ok) {
|
|
1980
|
-
|
|
2416
|
+
try {
|
|
2417
|
+
const error = await response.json();
|
|
2418
|
+
throw new UndeleteError(error.message || `Undelete failed: ${response.statusText}`, id);
|
|
2419
|
+
} catch (e) {
|
|
2420
|
+
if (e instanceof UndeleteError) throw e;
|
|
2421
|
+
await this.handleErrorResponse(response, `Undelete ${id}`);
|
|
2422
|
+
}
|
|
1981
2423
|
}
|
|
1982
2424
|
return response.json();
|
|
1983
2425
|
}
|
|
2426
|
+
// ===========================================================================
|
|
2427
|
+
// Content Operations
|
|
2428
|
+
// ===========================================================================
|
|
1984
2429
|
/**
|
|
1985
|
-
*
|
|
2430
|
+
* Upload files to IPFS
|
|
1986
2431
|
*/
|
|
1987
|
-
async
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
2432
|
+
async upload(files) {
|
|
2433
|
+
let formData;
|
|
2434
|
+
if (files instanceof FormData) {
|
|
2435
|
+
formData = files;
|
|
2436
|
+
} else {
|
|
2437
|
+
formData = new FormData();
|
|
2438
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
2439
|
+
for (const file of fileArray) {
|
|
2440
|
+
if (file instanceof File) {
|
|
2441
|
+
formData.append("file", file, file.name);
|
|
2442
|
+
} else {
|
|
2443
|
+
formData.append("file", file, "file");
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
const url = this.buildUrl("/upload");
|
|
2448
|
+
const response = await this.fetchWithRetry(
|
|
2449
|
+
url,
|
|
2450
|
+
{
|
|
2451
|
+
method: "POST",
|
|
2452
|
+
headers: this.getHeaders(null),
|
|
2453
|
+
// No Content-Type for multipart
|
|
2454
|
+
body: formData
|
|
2455
|
+
},
|
|
2456
|
+
"Upload files"
|
|
2457
|
+
);
|
|
1991
2458
|
if (!response.ok) {
|
|
1992
|
-
this.handleErrorResponse(response,
|
|
2459
|
+
await this.handleErrorResponse(response, "Upload files");
|
|
1993
2460
|
}
|
|
1994
|
-
return response.
|
|
2461
|
+
return response.json();
|
|
1995
2462
|
}
|
|
1996
2463
|
/**
|
|
1997
|
-
* Upload content and
|
|
2464
|
+
* Upload text content and return CID
|
|
1998
2465
|
*/
|
|
1999
2466
|
async uploadContent(content, filename) {
|
|
2000
|
-
const formData = new FormData();
|
|
2001
2467
|
const blob = new Blob([content], { type: "text/plain" });
|
|
2002
|
-
|
|
2003
|
-
const
|
|
2004
|
-
|
|
2005
|
-
|
|
2468
|
+
const file = new File([blob], filename, { type: "text/plain" });
|
|
2469
|
+
const [result] = await this.upload(file);
|
|
2470
|
+
return result.cid;
|
|
2471
|
+
}
|
|
2472
|
+
/**
|
|
2473
|
+
* Download file content by CID
|
|
2474
|
+
*/
|
|
2475
|
+
async getContent(cid) {
|
|
2476
|
+
const url = this.buildUrl(`/cat/${encodeURIComponent(cid)}`);
|
|
2477
|
+
const response = await this.fetchWithRetry(
|
|
2478
|
+
url,
|
|
2479
|
+
{ headers: this.getHeaders() },
|
|
2480
|
+
`Get content ${cid}`
|
|
2481
|
+
);
|
|
2482
|
+
if (response.status === 404) {
|
|
2483
|
+
throw new ContentNotFoundError(cid);
|
|
2006
2484
|
}
|
|
2007
|
-
const response = await fetch(`${this.gatewayUrl}/api/upload`, {
|
|
2008
|
-
method: "POST",
|
|
2009
|
-
headers,
|
|
2010
|
-
body: formData
|
|
2011
|
-
});
|
|
2012
2485
|
if (!response.ok) {
|
|
2013
|
-
this.handleErrorResponse(response,
|
|
2486
|
+
await this.handleErrorResponse(response, `Get content ${cid}`);
|
|
2014
2487
|
}
|
|
2015
|
-
|
|
2016
|
-
return result[0].cid;
|
|
2488
|
+
return response.text();
|
|
2017
2489
|
}
|
|
2018
2490
|
/**
|
|
2019
|
-
*
|
|
2491
|
+
* Download a DAG node (JSON) by CID
|
|
2020
2492
|
*/
|
|
2021
|
-
async
|
|
2022
|
-
const
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2493
|
+
async getDag(cid) {
|
|
2494
|
+
const url = this.buildUrl(`/dag/${encodeURIComponent(cid)}`);
|
|
2495
|
+
const response = await this.fetchWithRetry(
|
|
2496
|
+
url,
|
|
2497
|
+
{ headers: this.getHeaders() },
|
|
2498
|
+
`Get DAG ${cid}`
|
|
2499
|
+
);
|
|
2500
|
+
if (response.status === 404) {
|
|
2501
|
+
throw new ContentNotFoundError(cid);
|
|
2502
|
+
}
|
|
2503
|
+
if (!response.ok) {
|
|
2504
|
+
await this.handleErrorResponse(response, `Get DAG ${cid}`);
|
|
2505
|
+
}
|
|
2506
|
+
return response.json();
|
|
2507
|
+
}
|
|
2508
|
+
// ===========================================================================
|
|
2509
|
+
// Arke Origin Operations
|
|
2510
|
+
// ===========================================================================
|
|
2511
|
+
/**
|
|
2512
|
+
* Get the Arke origin block (genesis entity)
|
|
2513
|
+
*/
|
|
2514
|
+
async getArke() {
|
|
2515
|
+
const url = this.buildUrl("/arke");
|
|
2516
|
+
const response = await this.fetchWithRetry(
|
|
2517
|
+
url,
|
|
2518
|
+
{ headers: this.getHeaders() },
|
|
2519
|
+
"Get Arke"
|
|
2520
|
+
);
|
|
2521
|
+
if (!response.ok) {
|
|
2522
|
+
await this.handleErrorResponse(response, "Get Arke");
|
|
2039
2523
|
}
|
|
2524
|
+
return response.json();
|
|
2525
|
+
}
|
|
2526
|
+
/**
|
|
2527
|
+
* Initialize the Arke origin block (creates if doesn't exist)
|
|
2528
|
+
*/
|
|
2529
|
+
async initArke() {
|
|
2530
|
+
const url = this.buildUrl("/arke/init");
|
|
2531
|
+
const response = await this.fetchWithRetry(
|
|
2532
|
+
url,
|
|
2533
|
+
{
|
|
2534
|
+
method: "POST",
|
|
2535
|
+
headers: this.getHeaders()
|
|
2536
|
+
},
|
|
2537
|
+
"Init Arke"
|
|
2538
|
+
);
|
|
2040
2539
|
if (!response.ok) {
|
|
2041
|
-
this.handleErrorResponse(response,
|
|
2540
|
+
await this.handleErrorResponse(response, "Init Arke");
|
|
2042
2541
|
}
|
|
2043
2542
|
return response.json();
|
|
2044
2543
|
}
|
|
@@ -2049,16 +2548,20 @@ var EditClient = class {
|
|
|
2049
2548
|
* Trigger reprocessing for an entity
|
|
2050
2549
|
*/
|
|
2051
2550
|
async reprocess(request) {
|
|
2052
|
-
const response = await
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2551
|
+
const response = await this.fetchWithRetry(
|
|
2552
|
+
`${this.gatewayUrl}/reprocess/reprocess`,
|
|
2553
|
+
{
|
|
2554
|
+
method: "POST",
|
|
2555
|
+
headers: this.getHeaders(),
|
|
2556
|
+
body: JSON.stringify({
|
|
2557
|
+
pi: request.pi,
|
|
2558
|
+
phases: request.phases,
|
|
2559
|
+
cascade: request.cascade,
|
|
2560
|
+
options: request.options
|
|
2561
|
+
})
|
|
2562
|
+
},
|
|
2563
|
+
`Reprocess ${request.pi}`
|
|
2564
|
+
);
|
|
2062
2565
|
if (response.status === 403) {
|
|
2063
2566
|
const error = await response.json().catch(() => ({}));
|
|
2064
2567
|
throw new PermissionError(
|
|
@@ -2068,29 +2571,23 @@ var EditClient = class {
|
|
|
2068
2571
|
}
|
|
2069
2572
|
if (!response.ok) {
|
|
2070
2573
|
const error = await response.json().catch(() => ({}));
|
|
2071
|
-
throw new ReprocessError(
|
|
2072
|
-
error.message || `Reprocess failed: ${response.statusText}`,
|
|
2073
|
-
void 0
|
|
2074
|
-
);
|
|
2574
|
+
throw new ReprocessError(error.message || `Reprocess failed: ${response.statusText}`);
|
|
2075
2575
|
}
|
|
2076
2576
|
return response.json();
|
|
2077
2577
|
}
|
|
2078
2578
|
/**
|
|
2079
2579
|
* Get reprocessing status by batch ID
|
|
2080
|
-
*
|
|
2081
|
-
* Uses exponential backoff retry to handle transient 500 errors
|
|
2082
|
-
* that occur when the orchestrator is initializing.
|
|
2083
|
-
*
|
|
2084
|
-
* @param statusUrl - The status URL returned from reprocess()
|
|
2085
|
-
* @param isFirstPoll - If true, uses a longer initial delay (orchestrator warmup)
|
|
2086
2580
|
*/
|
|
2087
2581
|
async getReprocessStatus(statusUrl, isFirstPoll = false) {
|
|
2088
|
-
const retryOptions = isFirstPoll ? { ...DEFAULT_RETRY_OPTIONS, initialDelayMs: 3e3 } : DEFAULT_RETRY_OPTIONS;
|
|
2089
2582
|
const fetchUrl = this.statusUrlTransform ? this.statusUrlTransform(statusUrl) : statusUrl;
|
|
2583
|
+
const delay = isFirstPoll ? 3e3 : this.retryConfig.baseDelay;
|
|
2584
|
+
if (isFirstPoll) {
|
|
2585
|
+
await this.sleep(delay);
|
|
2586
|
+
}
|
|
2090
2587
|
const response = await this.fetchWithRetry(
|
|
2091
2588
|
fetchUrl,
|
|
2092
2589
|
{ headers: this.getHeaders() },
|
|
2093
|
-
|
|
2590
|
+
"Get reprocess status"
|
|
2094
2591
|
);
|
|
2095
2592
|
if (!response.ok) {
|
|
2096
2593
|
throw new EditError(
|
|
@@ -2101,6 +2598,30 @@ var EditClient = class {
|
|
|
2101
2598
|
}
|
|
2102
2599
|
return response.json();
|
|
2103
2600
|
}
|
|
2601
|
+
// ===========================================================================
|
|
2602
|
+
// Utility Methods
|
|
2603
|
+
// ===========================================================================
|
|
2604
|
+
/**
|
|
2605
|
+
* Execute an operation with automatic CAS retry
|
|
2606
|
+
*/
|
|
2607
|
+
async withCAS(id, operation, maxRetries = 3) {
|
|
2608
|
+
let lastError = null;
|
|
2609
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
2610
|
+
try {
|
|
2611
|
+
const entity = await this.getEntity(id);
|
|
2612
|
+
return await operation(entity);
|
|
2613
|
+
} catch (error) {
|
|
2614
|
+
if (error instanceof CASConflictError && attempt < maxRetries - 1) {
|
|
2615
|
+
lastError = error;
|
|
2616
|
+
const delay = this.calculateDelay(attempt);
|
|
2617
|
+
await this.sleep(delay);
|
|
2618
|
+
continue;
|
|
2619
|
+
}
|
|
2620
|
+
throw error;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
throw lastError || new EditError("withCAS failed after retries");
|
|
2624
|
+
}
|
|
2104
2625
|
};
|
|
2105
2626
|
|
|
2106
2627
|
// src/edit/diff.ts
|
|
@@ -2618,10 +3139,10 @@ var EditSession = class {
|
|
|
2618
3139
|
const result = {};
|
|
2619
3140
|
if (!this.entity) return result;
|
|
2620
3141
|
const entityContext = {
|
|
2621
|
-
pi: this.entity.
|
|
3142
|
+
pi: this.entity.id,
|
|
2622
3143
|
ver: this.entity.ver,
|
|
2623
3144
|
parentPi: this.entity.parent_pi,
|
|
2624
|
-
childrenCount: this.entity.children_pi
|
|
3145
|
+
childrenCount: this.entity.children_pi?.length ?? 0,
|
|
2625
3146
|
currentContent: this.loadedComponents
|
|
2626
3147
|
};
|
|
2627
3148
|
for (const component of this.scope.components) {
|
|
@@ -2643,7 +3164,7 @@ var EditSession = class {
|
|
|
2643
3164
|
}
|
|
2644
3165
|
if (this.scope.cascade) {
|
|
2645
3166
|
prompt = PromptBuilder.buildCascadePrompt(prompt, {
|
|
2646
|
-
path: [this.entity.
|
|
3167
|
+
path: [this.entity.id, this.entity.parent_pi || "root"].filter(Boolean),
|
|
2647
3168
|
depth: 0,
|
|
2648
3169
|
stopAtPi: this.scope.stopAtPi
|
|
2649
3170
|
});
|
|
@@ -2704,7 +3225,7 @@ var EditSession = class {
|
|
|
2704
3225
|
note
|
|
2705
3226
|
});
|
|
2706
3227
|
this.result.saved = {
|
|
2707
|
-
pi: version.
|
|
3228
|
+
pi: version.id,
|
|
2708
3229
|
newVersion: version.ver,
|
|
2709
3230
|
newTip: version.tip
|
|
2710
3231
|
};
|
|
@@ -2824,38 +3345,38 @@ var ContentError = class extends Error {
|
|
|
2824
3345
|
}
|
|
2825
3346
|
};
|
|
2826
3347
|
var EntityNotFoundError2 = class extends ContentError {
|
|
2827
|
-
constructor(
|
|
2828
|
-
super(`Entity not found: ${
|
|
3348
|
+
constructor(id) {
|
|
3349
|
+
super(`Entity not found: ${id}`, "ENTITY_NOT_FOUND", { id });
|
|
2829
3350
|
this.name = "EntityNotFoundError";
|
|
2830
3351
|
}
|
|
2831
3352
|
};
|
|
2832
|
-
var
|
|
3353
|
+
var ContentNotFoundError2 = class extends ContentError {
|
|
2833
3354
|
constructor(cid) {
|
|
2834
3355
|
super(`Content not found: ${cid}`, "CONTENT_NOT_FOUND", { cid });
|
|
2835
3356
|
this.name = "ContentNotFoundError";
|
|
2836
3357
|
}
|
|
2837
3358
|
};
|
|
2838
3359
|
var ComponentNotFoundError = class extends ContentError {
|
|
2839
|
-
constructor(
|
|
3360
|
+
constructor(id, componentName) {
|
|
2840
3361
|
super(
|
|
2841
|
-
`Component '${componentName}' not found on entity ${
|
|
3362
|
+
`Component '${componentName}' not found on entity ${id}`,
|
|
2842
3363
|
"COMPONENT_NOT_FOUND",
|
|
2843
|
-
{
|
|
3364
|
+
{ id, componentName }
|
|
2844
3365
|
);
|
|
2845
3366
|
this.name = "ComponentNotFoundError";
|
|
2846
3367
|
}
|
|
2847
3368
|
};
|
|
2848
3369
|
var VersionNotFoundError = class extends ContentError {
|
|
2849
|
-
constructor(
|
|
3370
|
+
constructor(id, selector) {
|
|
2850
3371
|
super(
|
|
2851
|
-
`Version not found: ${selector} for entity ${
|
|
3372
|
+
`Version not found: ${selector} for entity ${id}`,
|
|
2852
3373
|
"VERSION_NOT_FOUND",
|
|
2853
|
-
{
|
|
3374
|
+
{ id, selector }
|
|
2854
3375
|
);
|
|
2855
3376
|
this.name = "VersionNotFoundError";
|
|
2856
3377
|
}
|
|
2857
3378
|
};
|
|
2858
|
-
var
|
|
3379
|
+
var NetworkError3 = class extends ContentError {
|
|
2859
3380
|
constructor(message, statusCode) {
|
|
2860
3381
|
super(message, "NETWORK_ERROR", { statusCode });
|
|
2861
3382
|
this.statusCode = statusCode;
|
|
@@ -2895,7 +3416,7 @@ var ContentClient = class {
|
|
|
2895
3416
|
try {
|
|
2896
3417
|
response = await this.fetchImpl(url, { ...options, headers });
|
|
2897
3418
|
} catch (err) {
|
|
2898
|
-
throw new
|
|
3419
|
+
throw new NetworkError3(
|
|
2899
3420
|
err instanceof Error ? err.message : "Network request failed"
|
|
2900
3421
|
);
|
|
2901
3422
|
}
|
|
@@ -3149,13 +3670,13 @@ var ContentClient = class {
|
|
|
3149
3670
|
try {
|
|
3150
3671
|
response = await this.fetchImpl(url);
|
|
3151
3672
|
} catch (err) {
|
|
3152
|
-
throw new
|
|
3673
|
+
throw new NetworkError3(
|
|
3153
3674
|
err instanceof Error ? err.message : "Network request failed"
|
|
3154
3675
|
);
|
|
3155
3676
|
}
|
|
3156
3677
|
if (!response.ok) {
|
|
3157
3678
|
if (response.status === 404) {
|
|
3158
|
-
throw new
|
|
3679
|
+
throw new ContentNotFoundError2(cid);
|
|
3159
3680
|
}
|
|
3160
3681
|
throw new ContentError(
|
|
3161
3682
|
`Failed to download content: ${response.status}`,
|
|
@@ -3211,13 +3732,13 @@ var ContentClient = class {
|
|
|
3211
3732
|
try {
|
|
3212
3733
|
response = await this.fetchImpl(url);
|
|
3213
3734
|
} catch (err) {
|
|
3214
|
-
throw new
|
|
3735
|
+
throw new NetworkError3(
|
|
3215
3736
|
err instanceof Error ? err.message : "Network request failed"
|
|
3216
3737
|
);
|
|
3217
3738
|
}
|
|
3218
3739
|
if (!response.ok) {
|
|
3219
3740
|
if (response.status === 404) {
|
|
3220
|
-
throw new
|
|
3741
|
+
throw new ContentNotFoundError2(cid);
|
|
3221
3742
|
}
|
|
3222
3743
|
throw new ContentError(
|
|
3223
3744
|
`Failed to stream content: ${response.status}`,
|
|
@@ -3230,6 +3751,45 @@ var ContentClient = class {
|
|
|
3230
3751
|
}
|
|
3231
3752
|
return response.body;
|
|
3232
3753
|
}
|
|
3754
|
+
/**
|
|
3755
|
+
* Download a DAG node (JSON) by CID.
|
|
3756
|
+
*
|
|
3757
|
+
* Use this to fetch JSON components like properties and relationships.
|
|
3758
|
+
*
|
|
3759
|
+
* @param cid - Content Identifier of the DAG node
|
|
3760
|
+
* @returns Parsed JSON object
|
|
3761
|
+
* @throws ContentNotFoundError if the content doesn't exist
|
|
3762
|
+
*
|
|
3763
|
+
* @example
|
|
3764
|
+
* ```typescript
|
|
3765
|
+
* const relationships = await content.getDag<RelationshipsComponent>(
|
|
3766
|
+
* entity.components.relationships
|
|
3767
|
+
* );
|
|
3768
|
+
* console.log('Relationships:', relationships.relationships);
|
|
3769
|
+
* ```
|
|
3770
|
+
*/
|
|
3771
|
+
async getDag(cid) {
|
|
3772
|
+
const url = this.buildUrl(`/api/dag/${encodeURIComponent(cid)}`);
|
|
3773
|
+
let response;
|
|
3774
|
+
try {
|
|
3775
|
+
response = await this.fetchImpl(url);
|
|
3776
|
+
} catch (err) {
|
|
3777
|
+
throw new NetworkError3(
|
|
3778
|
+
err instanceof Error ? err.message : "Network request failed"
|
|
3779
|
+
);
|
|
3780
|
+
}
|
|
3781
|
+
if (!response.ok) {
|
|
3782
|
+
if (response.status === 404) {
|
|
3783
|
+
throw new ContentNotFoundError2(cid);
|
|
3784
|
+
}
|
|
3785
|
+
throw new ContentError(
|
|
3786
|
+
`Failed to fetch DAG node: ${response.status}`,
|
|
3787
|
+
"DAG_ERROR",
|
|
3788
|
+
{ status: response.status }
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
return await response.json();
|
|
3792
|
+
}
|
|
3233
3793
|
// ---------------------------------------------------------------------------
|
|
3234
3794
|
// Component Helpers
|
|
3235
3795
|
// ---------------------------------------------------------------------------
|
|
@@ -3250,7 +3810,7 @@ var ContentClient = class {
|
|
|
3250
3810
|
async getComponent(entity, componentName) {
|
|
3251
3811
|
const cid = entity.components[componentName];
|
|
3252
3812
|
if (!cid) {
|
|
3253
|
-
throw new ComponentNotFoundError(entity.
|
|
3813
|
+
throw new ComponentNotFoundError(entity.id, componentName);
|
|
3254
3814
|
}
|
|
3255
3815
|
return this.download(cid);
|
|
3256
3816
|
}
|
|
@@ -3272,10 +3832,56 @@ var ContentClient = class {
|
|
|
3272
3832
|
getComponentUrl(entity, componentName) {
|
|
3273
3833
|
const cid = entity.components[componentName];
|
|
3274
3834
|
if (!cid) {
|
|
3275
|
-
throw new ComponentNotFoundError(entity.
|
|
3835
|
+
throw new ComponentNotFoundError(entity.id, componentName);
|
|
3276
3836
|
}
|
|
3277
3837
|
return this.getUrl(cid);
|
|
3278
3838
|
}
|
|
3839
|
+
/**
|
|
3840
|
+
* Get the properties component for an entity.
|
|
3841
|
+
*
|
|
3842
|
+
* @param entity - Entity containing the properties component
|
|
3843
|
+
* @returns Properties object, or null if no properties component exists
|
|
3844
|
+
*
|
|
3845
|
+
* @example
|
|
3846
|
+
* ```typescript
|
|
3847
|
+
* const entity = await content.get('01K75HQQXNTDG7BBP7PS9AWYAN');
|
|
3848
|
+
* const props = await content.getProperties(entity);
|
|
3849
|
+
* if (props) {
|
|
3850
|
+
* console.log('Title:', props.title);
|
|
3851
|
+
* }
|
|
3852
|
+
* ```
|
|
3853
|
+
*/
|
|
3854
|
+
async getProperties(entity) {
|
|
3855
|
+
const cid = entity.components.properties;
|
|
3856
|
+
if (!cid) {
|
|
3857
|
+
return null;
|
|
3858
|
+
}
|
|
3859
|
+
return this.getDag(cid);
|
|
3860
|
+
}
|
|
3861
|
+
/**
|
|
3862
|
+
* Get the relationships component for an entity.
|
|
3863
|
+
*
|
|
3864
|
+
* @param entity - Entity containing the relationships component
|
|
3865
|
+
* @returns Relationships component, or null if no relationships exist
|
|
3866
|
+
*
|
|
3867
|
+
* @example
|
|
3868
|
+
* ```typescript
|
|
3869
|
+
* const entity = await content.get('01K75HQQXNTDG7BBP7PS9AWYAN');
|
|
3870
|
+
* const rels = await content.getRelationships(entity);
|
|
3871
|
+
* if (rels) {
|
|
3872
|
+
* rels.relationships.forEach(r => {
|
|
3873
|
+
* console.log(`${r.predicate} -> ${r.target_label}`);
|
|
3874
|
+
* });
|
|
3875
|
+
* }
|
|
3876
|
+
* ```
|
|
3877
|
+
*/
|
|
3878
|
+
async getRelationships(entity) {
|
|
3879
|
+
const cid = entity.components.relationships;
|
|
3880
|
+
if (!cid) {
|
|
3881
|
+
return null;
|
|
3882
|
+
}
|
|
3883
|
+
return this.getDag(cid);
|
|
3884
|
+
}
|
|
3279
3885
|
};
|
|
3280
3886
|
|
|
3281
3887
|
// src/graph/errors.ts
|
|
@@ -3303,7 +3909,7 @@ var NoPathFoundError = class extends GraphError {
|
|
|
3303
3909
|
this.name = "NoPathFoundError";
|
|
3304
3910
|
}
|
|
3305
3911
|
};
|
|
3306
|
-
var
|
|
3912
|
+
var NetworkError4 = class extends GraphError {
|
|
3307
3913
|
constructor(message, statusCode) {
|
|
3308
3914
|
super(message, "NETWORK_ERROR", { statusCode });
|
|
3309
3915
|
this.statusCode = statusCode;
|
|
@@ -3343,7 +3949,7 @@ var GraphClient = class {
|
|
|
3343
3949
|
try {
|
|
3344
3950
|
response = await this.fetchImpl(url, { ...options, headers });
|
|
3345
3951
|
} catch (err) {
|
|
3346
|
-
throw new
|
|
3952
|
+
throw new NetworkError4(
|
|
3347
3953
|
err instanceof Error ? err.message : "Network request failed"
|
|
3348
3954
|
);
|
|
3349
3955
|
}
|
|
@@ -3375,49 +3981,8 @@ var GraphClient = class {
|
|
|
3375
3981
|
});
|
|
3376
3982
|
}
|
|
3377
3983
|
// ---------------------------------------------------------------------------
|
|
3378
|
-
//
|
|
3984
|
+
// Code-based Lookups (indexed in GraphDB)
|
|
3379
3985
|
// ---------------------------------------------------------------------------
|
|
3380
|
-
/**
|
|
3381
|
-
* Get an entity by its canonical ID.
|
|
3382
|
-
*
|
|
3383
|
-
* @param canonicalId - Entity UUID
|
|
3384
|
-
* @returns Entity data
|
|
3385
|
-
* @throws GraphEntityNotFoundError if the entity doesn't exist
|
|
3386
|
-
*
|
|
3387
|
-
* @example
|
|
3388
|
-
* ```typescript
|
|
3389
|
-
* const entity = await graph.getEntity('uuid-123');
|
|
3390
|
-
* console.log('Entity:', entity.label, entity.type);
|
|
3391
|
-
* ```
|
|
3392
|
-
*/
|
|
3393
|
-
async getEntity(canonicalId) {
|
|
3394
|
-
const response = await this.request(
|
|
3395
|
-
`/graphdb/entity/${encodeURIComponent(canonicalId)}`
|
|
3396
|
-
);
|
|
3397
|
-
if (!response.found || !response.entity) {
|
|
3398
|
-
throw new GraphEntityNotFoundError(canonicalId);
|
|
3399
|
-
}
|
|
3400
|
-
return response.entity;
|
|
3401
|
-
}
|
|
3402
|
-
/**
|
|
3403
|
-
* Check if an entity exists by its canonical ID.
|
|
3404
|
-
*
|
|
3405
|
-
* @param canonicalId - Entity UUID
|
|
3406
|
-
* @returns True if entity exists
|
|
3407
|
-
*
|
|
3408
|
-
* @example
|
|
3409
|
-
* ```typescript
|
|
3410
|
-
* if (await graph.entityExists('uuid-123')) {
|
|
3411
|
-
* console.log('Entity exists');
|
|
3412
|
-
* }
|
|
3413
|
-
* ```
|
|
3414
|
-
*/
|
|
3415
|
-
async entityExists(canonicalId) {
|
|
3416
|
-
const response = await this.request(
|
|
3417
|
-
`/graphdb/entity/exists/${encodeURIComponent(canonicalId)}`
|
|
3418
|
-
);
|
|
3419
|
-
return response.exists;
|
|
3420
|
-
}
|
|
3421
3986
|
/**
|
|
3422
3987
|
* Query entities by code with optional type filter.
|
|
3423
3988
|
*
|
|
@@ -3473,11 +4038,14 @@ var GraphClient = class {
|
|
|
3473
4038
|
// PI-based Operations
|
|
3474
4039
|
// ---------------------------------------------------------------------------
|
|
3475
4040
|
/**
|
|
3476
|
-
* List entities from a specific PI or multiple PIs.
|
|
4041
|
+
* List entities extracted from a specific PI or multiple PIs.
|
|
4042
|
+
*
|
|
4043
|
+
* This returns knowledge graph entities (persons, places, events, etc.)
|
|
4044
|
+
* that were extracted from the given PI(s), not the PI entity itself.
|
|
3477
4045
|
*
|
|
3478
4046
|
* @param pi - Single PI or array of PIs
|
|
3479
4047
|
* @param options - Filter options
|
|
3480
|
-
* @returns
|
|
4048
|
+
* @returns Extracted entities from the PI(s)
|
|
3481
4049
|
*
|
|
3482
4050
|
* @example
|
|
3483
4051
|
* ```typescript
|
|
@@ -3536,13 +4104,17 @@ var GraphClient = class {
|
|
|
3536
4104
|
/**
|
|
3537
4105
|
* Get the lineage (ancestors and/or descendants) of a PI.
|
|
3538
4106
|
*
|
|
4107
|
+
* This traverses the PI hierarchy (parent_pi/children_pi relationships)
|
|
4108
|
+
* which is indexed in GraphDB for fast lookups.
|
|
4109
|
+
*
|
|
3539
4110
|
* @param pi - Source PI
|
|
3540
4111
|
* @param direction - 'ancestors', 'descendants', or 'both'
|
|
3541
|
-
* @
|
|
4112
|
+
* @param maxHops - Maximum depth to traverse (default: 10)
|
|
4113
|
+
* @returns Lineage data with PIs at each hop level
|
|
3542
4114
|
*
|
|
3543
4115
|
* @example
|
|
3544
4116
|
* ```typescript
|
|
3545
|
-
* // Get ancestors
|
|
4117
|
+
* // Get ancestors (parent chain)
|
|
3546
4118
|
* const lineage = await graph.getLineage('01K75HQQXNTDG7BBP7PS9AWYAN', 'ancestors');
|
|
3547
4119
|
*
|
|
3548
4120
|
* // Get both directions
|
|
@@ -3559,25 +4131,53 @@ var GraphClient = class {
|
|
|
3559
4131
|
// Relationship Operations
|
|
3560
4132
|
// ---------------------------------------------------------------------------
|
|
3561
4133
|
/**
|
|
3562
|
-
* Get
|
|
4134
|
+
* Get relationships for an entity from the GraphDB index.
|
|
4135
|
+
*
|
|
4136
|
+
* **Important distinction from ContentClient.getRelationships():**
|
|
4137
|
+
* - **ContentClient.getRelationships()**: Returns OUTBOUND relationships only
|
|
4138
|
+
* (from the entity's relationships.json in IPFS - source of truth)
|
|
4139
|
+
* - **GraphClient.getRelationships()**: Returns BOTH inbound AND outbound
|
|
4140
|
+
* relationships (from the indexed GraphDB mirror)
|
|
3563
4141
|
*
|
|
3564
|
-
*
|
|
3565
|
-
*
|
|
4142
|
+
* Use this method when you need to find "what references this entity" (inbound)
|
|
4143
|
+
* or want a complete bidirectional view.
|
|
4144
|
+
*
|
|
4145
|
+
* @param id - Entity identifier (works for both PIs and KG entities)
|
|
4146
|
+
* @param direction - Filter by direction: 'outgoing', 'incoming', or 'both' (default)
|
|
4147
|
+
* @returns Array of relationships with direction indicator
|
|
3566
4148
|
*
|
|
3567
4149
|
* @example
|
|
3568
4150
|
* ```typescript
|
|
3569
|
-
*
|
|
3570
|
-
*
|
|
3571
|
-
*
|
|
4151
|
+
* // Get all relationships (both directions)
|
|
4152
|
+
* const all = await graph.getRelationships('01K75HQQXNTDG7BBP7PS9AWYAN');
|
|
4153
|
+
*
|
|
4154
|
+
* // Get only inbound relationships ("who references this entity?")
|
|
4155
|
+
* const incoming = await graph.getRelationships('01K75HQQXNTDG7BBP7PS9AWYAN', 'incoming');
|
|
4156
|
+
*
|
|
4157
|
+
* // Get only outbound relationships (similar to IPFS, but from index)
|
|
4158
|
+
* const outgoing = await graph.getRelationships('01K75HQQXNTDG7BBP7PS9AWYAN', 'outgoing');
|
|
4159
|
+
*
|
|
4160
|
+
* // Process by direction
|
|
4161
|
+
* const rels = await graph.getRelationships('entity-id');
|
|
4162
|
+
* rels.forEach(r => {
|
|
4163
|
+
* if (r.direction === 'incoming') {
|
|
4164
|
+
* console.log(`${r.target_label} references this entity via ${r.predicate}`);
|
|
4165
|
+
* } else {
|
|
4166
|
+
* console.log(`This entity ${r.predicate} -> ${r.target_label}`);
|
|
4167
|
+
* }
|
|
3572
4168
|
* });
|
|
3573
4169
|
* ```
|
|
3574
4170
|
*/
|
|
3575
|
-
async getRelationships(
|
|
3576
|
-
const response = await this.request(`/graphdb/relationships/${encodeURIComponent(
|
|
4171
|
+
async getRelationships(id, direction = "both") {
|
|
4172
|
+
const response = await this.request(`/graphdb/relationships/${encodeURIComponent(id)}`);
|
|
3577
4173
|
if (!response.found || !response.relationships) {
|
|
3578
4174
|
return [];
|
|
3579
4175
|
}
|
|
3580
|
-
|
|
4176
|
+
let relationships = response.relationships;
|
|
4177
|
+
if (direction !== "both") {
|
|
4178
|
+
relationships = relationships.filter((rel) => rel.direction === direction);
|
|
4179
|
+
}
|
|
4180
|
+
return relationships.map((rel) => ({
|
|
3581
4181
|
direction: rel.direction,
|
|
3582
4182
|
predicate: rel.predicate,
|
|
3583
4183
|
target_id: rel.target_id,
|
|
@@ -3603,8 +4203,8 @@ var GraphClient = class {
|
|
|
3603
4203
|
* @example
|
|
3604
4204
|
* ```typescript
|
|
3605
4205
|
* const paths = await graph.findPaths(
|
|
3606
|
-
* ['
|
|
3607
|
-
* ['
|
|
4206
|
+
* ['entity-alice'],
|
|
4207
|
+
* ['entity-bob'],
|
|
3608
4208
|
* { max_depth: 4, direction: 'both' }
|
|
3609
4209
|
* );
|
|
3610
4210
|
*
|
|
@@ -3641,7 +4241,7 @@ var GraphClient = class {
|
|
|
3641
4241
|
* ```typescript
|
|
3642
4242
|
* // Find all people reachable from an event
|
|
3643
4243
|
* const people = await graph.findReachable(
|
|
3644
|
-
* ['
|
|
4244
|
+
* ['event-id'],
|
|
3645
4245
|
* 'person',
|
|
3646
4246
|
* { max_depth: 3 }
|
|
3647
4247
|
* );
|
|
@@ -3679,8 +4279,8 @@ export {
|
|
|
3679
4279
|
ComponentNotFoundError,
|
|
3680
4280
|
ContentClient,
|
|
3681
4281
|
ContentError,
|
|
3682
|
-
|
|
3683
|
-
ContentNotFoundError,
|
|
4282
|
+
NetworkError3 as ContentNetworkError,
|
|
4283
|
+
ContentNotFoundError2 as ContentNotFoundError,
|
|
3684
4284
|
EditClient,
|
|
3685
4285
|
EditError,
|
|
3686
4286
|
EditSession,
|
|
@@ -3688,7 +4288,7 @@ export {
|
|
|
3688
4288
|
GraphClient,
|
|
3689
4289
|
GraphEntityNotFoundError,
|
|
3690
4290
|
GraphError,
|
|
3691
|
-
|
|
4291
|
+
NetworkError4 as GraphNetworkError,
|
|
3692
4292
|
NetworkError,
|
|
3693
4293
|
NoPathFoundError,
|
|
3694
4294
|
PermissionError,
|