@xcitedbs/client 0.2.21 → 0.2.24
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/assetUri.d.ts +14 -0
- package/dist/assetUri.js +119 -0
- package/dist/assetUri.test.d.ts +1 -0
- package/dist/assetUri.test.js +20 -0
- package/dist/client.d.ts +94 -1
- package/dist/client.js +544 -7
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -1
- package/dist/types.d.ts +161 -0
- package/llms-full.txt +94 -7
- package/llms.txt +64 -3
- package/package.json +1 -1
- package/unquery-ai-guide.md +2 -0
package/dist/client.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.XCiteDBClient = void 0;
|
|
4
4
|
const types_1 = require("./types");
|
|
5
5
|
const websocket_1 = require("./websocket");
|
|
6
|
+
const assetUri_1 = require("./assetUri");
|
|
6
7
|
function joinUrl(base, path) {
|
|
7
8
|
const b = base.replace(/\/+$/, '');
|
|
8
9
|
const p = path.startsWith('/') ? path : `/${path}`;
|
|
@@ -56,6 +57,24 @@ function buildQuery(params) {
|
|
|
56
57
|
const s = sp.toString();
|
|
57
58
|
return s ? `?${s}` : '';
|
|
58
59
|
}
|
|
60
|
+
/** Best-effort filename from `Content-Disposition` (attachment; filename="…"). */
|
|
61
|
+
function parseContentDispositionFilename(cd) {
|
|
62
|
+
if (!cd)
|
|
63
|
+
return undefined;
|
|
64
|
+
const mStar = cd.match(/filename\*=(?:UTF-8'')?([^;]+)/i);
|
|
65
|
+
if (mStar?.[1]) {
|
|
66
|
+
try {
|
|
67
|
+
return decodeURIComponent(mStar[1].trim().replace(/^"|"$/g, ''));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return mStar[1].trim().replace(/^"|"$/g, '');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const m = cd.match(/filename\s*=\s*("?)([^";]+)\1/i);
|
|
74
|
+
if (m?.[2])
|
|
75
|
+
return m[2].trim();
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
59
78
|
function warnIfHttpOnTlsPort(baseUrl) {
|
|
60
79
|
try {
|
|
61
80
|
const u = new URL(baseUrl);
|
|
@@ -590,6 +609,122 @@ class XCiteDBClient {
|
|
|
590
609
|
this.notifySessionInvalidIfNeeded(path, 401);
|
|
591
610
|
throw new types_1.XCiteDBError('Request failed after retry', 401, null);
|
|
592
611
|
}
|
|
612
|
+
/**
|
|
613
|
+
* POST `multipart/form-data` without forcing JSON `Content-Type` (boundary is set by the runtime).
|
|
614
|
+
*/
|
|
615
|
+
async requestFormPost(path, form, opts) {
|
|
616
|
+
const no401Retry = opts?.no401Retry === true;
|
|
617
|
+
const suppressTestSessionHeader = opts?.suppressTestSessionHeader === true;
|
|
618
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
619
|
+
const url = joinUrl(this.baseUrl, path);
|
|
620
|
+
const outgoingRequestId = newClientRequestId();
|
|
621
|
+
const headers = {
|
|
622
|
+
...this.authHeaders(),
|
|
623
|
+
...this.contextHeaders(),
|
|
624
|
+
...(suppressTestSessionHeader ? {} : this.testHeaders()),
|
|
625
|
+
'X-Request-Id': outgoingRequestId,
|
|
626
|
+
};
|
|
627
|
+
const init = { method: 'POST', headers, body: form };
|
|
628
|
+
const sig = requestTimeoutSignal(this.requestTimeoutMs);
|
|
629
|
+
if (sig)
|
|
630
|
+
init.signal = sig;
|
|
631
|
+
let res;
|
|
632
|
+
try {
|
|
633
|
+
res = await fetch(url, init);
|
|
634
|
+
}
|
|
635
|
+
catch (e) {
|
|
636
|
+
const m = e instanceof Error ? e.message : 'Network error';
|
|
637
|
+
throw new types_1.XCiteDBError(m, 0, null, { clientRequestId: outgoingRequestId });
|
|
638
|
+
}
|
|
639
|
+
const text = await res.text();
|
|
640
|
+
let data;
|
|
641
|
+
try {
|
|
642
|
+
data = text ? JSON.parse(text) : null;
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
data = text;
|
|
646
|
+
}
|
|
647
|
+
if (res.ok) {
|
|
648
|
+
return data;
|
|
649
|
+
}
|
|
650
|
+
if (res.status === 401 &&
|
|
651
|
+
attempt === 0 &&
|
|
652
|
+
!no401Retry &&
|
|
653
|
+
(await this.tryRefreshSessionAfter401())) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
const msg = typeof data === 'object' && data !== null && 'message' in data
|
|
657
|
+
? String(data.message)
|
|
658
|
+
: res.statusText;
|
|
659
|
+
this.notifySessionInvalidIfNeeded(path, res.status);
|
|
660
|
+
throwForFailedHttp(res.status, path, data, msg || `HTTP ${res.status}`, res.headers.get('X-Request-Id') ?? undefined, res.headers.get('X-Client-Request-Id') ?? undefined);
|
|
661
|
+
}
|
|
662
|
+
this.notifySessionInvalidIfNeeded(path, 401);
|
|
663
|
+
throw new types_1.XCiteDBError('Request failed after retry', 401, null);
|
|
664
|
+
}
|
|
665
|
+
/** GET binary response (e.g. document export); errors are parsed as JSON when possible. */
|
|
666
|
+
async requestBinaryGet(path, opts) {
|
|
667
|
+
const no401Retry = opts?.no401Retry === true;
|
|
668
|
+
const suppressTestSessionHeader = opts?.suppressTestSessionHeader === true;
|
|
669
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
670
|
+
const url = joinUrl(this.baseUrl, path);
|
|
671
|
+
const outgoingRequestId = newClientRequestId();
|
|
672
|
+
const headers = {
|
|
673
|
+
...this.authHeaders(),
|
|
674
|
+
...this.contextHeaders(),
|
|
675
|
+
...(suppressTestSessionHeader ? {} : this.testHeaders()),
|
|
676
|
+
'X-Request-Id': outgoingRequestId,
|
|
677
|
+
};
|
|
678
|
+
const init = { method: 'GET', headers };
|
|
679
|
+
const sig = requestTimeoutSignal(this.requestTimeoutMs);
|
|
680
|
+
if (sig)
|
|
681
|
+
init.signal = sig;
|
|
682
|
+
let res;
|
|
683
|
+
try {
|
|
684
|
+
res = await fetch(url, init);
|
|
685
|
+
}
|
|
686
|
+
catch (e) {
|
|
687
|
+
const m = e instanceof Error ? e.message : 'Network error';
|
|
688
|
+
throw new types_1.XCiteDBError(m, 0, null, { clientRequestId: outgoingRequestId });
|
|
689
|
+
}
|
|
690
|
+
if (res.ok) {
|
|
691
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
692
|
+
const contentType = res.headers.get('Content-Type') ?? 'application/octet-stream';
|
|
693
|
+
const fn = parseContentDispositionFilename(res.headers.get('Content-Disposition')) ?? 'export.bin';
|
|
694
|
+
const rt = res.headers.get('X-XciteDB-Export-RoundTrip');
|
|
695
|
+
const roundTrip = rt === 'exact' || rt === 'lossy' ? rt : undefined;
|
|
696
|
+
const w = res.headers.get('X-XciteDB-Export-Warning');
|
|
697
|
+
return {
|
|
698
|
+
bytes: buf,
|
|
699
|
+
contentType,
|
|
700
|
+
filename: fn,
|
|
701
|
+
...(roundTrip ? { roundTrip } : {}),
|
|
702
|
+
...(w ? { warning: w } : {}),
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
if (res.status === 401 &&
|
|
706
|
+
attempt === 0 &&
|
|
707
|
+
!no401Retry &&
|
|
708
|
+
(await this.tryRefreshSessionAfter401())) {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
const text = await res.text();
|
|
712
|
+
let data;
|
|
713
|
+
try {
|
|
714
|
+
data = text ? JSON.parse(text) : null;
|
|
715
|
+
}
|
|
716
|
+
catch {
|
|
717
|
+
data = text;
|
|
718
|
+
}
|
|
719
|
+
const msg = typeof data === 'object' && data !== null && 'message' in data
|
|
720
|
+
? String(data.message)
|
|
721
|
+
: res.statusText;
|
|
722
|
+
this.notifySessionInvalidIfNeeded(path, res.status);
|
|
723
|
+
throwForFailedHttp(res.status, path, data, msg || `HTTP ${res.status}`, res.headers.get('X-Request-Id') ?? undefined, res.headers.get('X-Client-Request-Id') ?? undefined);
|
|
724
|
+
}
|
|
725
|
+
this.notifySessionInvalidIfNeeded(path, 401);
|
|
726
|
+
throw new types_1.XCiteDBError('Request failed after retry', 401, null);
|
|
727
|
+
}
|
|
593
728
|
/** Developer Bearer refresh first, then app-user refresh (no API key refresh). */
|
|
594
729
|
async tryRefreshSessionAfter401() {
|
|
595
730
|
if (this.accessToken && this.refreshToken) {
|
|
@@ -1080,6 +1215,64 @@ class XCiteDBClient {
|
|
|
1080
1215
|
mode: params.mode,
|
|
1081
1216
|
});
|
|
1082
1217
|
}
|
|
1218
|
+
/** Share an asset path via prefix alias (`POST /api/v1/security/user-isolation/asset-shares`). */
|
|
1219
|
+
async createAssetShare(params) {
|
|
1220
|
+
const body = {
|
|
1221
|
+
target_user_id: params.target_user_id,
|
|
1222
|
+
mode: params.mode,
|
|
1223
|
+
};
|
|
1224
|
+
if (params.source_uri !== undefined && params.source_uri !== '') {
|
|
1225
|
+
body.source_uri = params.source_uri;
|
|
1226
|
+
}
|
|
1227
|
+
else if (params.identifier !== undefined && params.identifier !== '') {
|
|
1228
|
+
body.identifier = this.isoPrefixId(params.identifier);
|
|
1229
|
+
}
|
|
1230
|
+
else {
|
|
1231
|
+
throw new types_1.XCiteDBError('createAssetShare requires identifier or source_uri', 400, null);
|
|
1232
|
+
}
|
|
1233
|
+
return this.request('POST', '/api/v1/security/user-isolation/asset-shares', body);
|
|
1234
|
+
}
|
|
1235
|
+
/** Remove an asset share (`DELETE /api/v1/security/user-isolation/asset-shares`). */
|
|
1236
|
+
async deleteAssetShare(params) {
|
|
1237
|
+
const body = {};
|
|
1238
|
+
if (params.source_uri !== undefined && params.source_uri !== '') {
|
|
1239
|
+
body.source_uri = params.source_uri;
|
|
1240
|
+
}
|
|
1241
|
+
else if (params.identifier !== undefined && params.identifier !== '') {
|
|
1242
|
+
body.identifier = this.isoPrefixId(params.identifier);
|
|
1243
|
+
}
|
|
1244
|
+
else {
|
|
1245
|
+
throw new types_1.XCiteDBError('deleteAssetShare requires identifier or source_uri', 400, null);
|
|
1246
|
+
}
|
|
1247
|
+
if (params.target_user_id !== undefined) {
|
|
1248
|
+
body.target_user_id = params.target_user_id;
|
|
1249
|
+
}
|
|
1250
|
+
if (params.sharer_user_id !== undefined) {
|
|
1251
|
+
body.sharer_user_id = params.sharer_user_id;
|
|
1252
|
+
}
|
|
1253
|
+
await this.request('DELETE', '/api/v1/security/user-isolation/asset-shares', body);
|
|
1254
|
+
}
|
|
1255
|
+
/** List incoming/public asset share aliases (`GET /api/v1/security/user-isolation/asset-shares`). */
|
|
1256
|
+
async listAssetShares(opts) {
|
|
1257
|
+
const q = buildQuery({
|
|
1258
|
+
direction: opts?.direction ?? 'incoming',
|
|
1259
|
+
...(opts?.scope ? { scope: opts.scope } : {}),
|
|
1260
|
+
});
|
|
1261
|
+
return this.request('GET', `/api/v1/security/user-isolation/asset-shares${q}`);
|
|
1262
|
+
}
|
|
1263
|
+
/** Issue a time-limited magic link for one asset identifier (`POST /api/v1/security/asset-magic-links`). */
|
|
1264
|
+
async createAssetMagicLink(body) {
|
|
1265
|
+
return this.request('POST', '/api/v1/security/asset-magic-links', body);
|
|
1266
|
+
}
|
|
1267
|
+
/** List issued magic links (no secrets) (`GET /api/v1/security/asset-magic-links`). */
|
|
1268
|
+
async listAssetMagicLinks() {
|
|
1269
|
+
return this.request('GET', '/api/v1/security/asset-magic-links');
|
|
1270
|
+
}
|
|
1271
|
+
/** Revoke a magic link (`DELETE /api/v1/security/asset-magic-links/{token_id}`). */
|
|
1272
|
+
async revokeAssetMagicLink(tokenId) {
|
|
1273
|
+
const id = encodeURIComponent(tokenId.trim());
|
|
1274
|
+
await this.request('DELETE', `/api/v1/security/asset-magic-links/${id}`);
|
|
1275
|
+
}
|
|
1083
1276
|
async createWorkspace(name, fromBranch, fromDate) {
|
|
1084
1277
|
const body = { name };
|
|
1085
1278
|
if (fromBranch)
|
|
@@ -1221,16 +1414,35 @@ class XCiteDBClient {
|
|
|
1221
1414
|
async deleteTag(name) {
|
|
1222
1415
|
return this.deleteBookmark(name);
|
|
1223
1416
|
}
|
|
1224
|
-
async compare(from, to,
|
|
1225
|
-
|
|
1417
|
+
async compare(from, to, third) {
|
|
1418
|
+
let include_content = false;
|
|
1419
|
+
let match_start;
|
|
1420
|
+
if (typeof third === 'boolean') {
|
|
1421
|
+
include_content = third;
|
|
1422
|
+
}
|
|
1423
|
+
else if (third !== undefined && typeof third === 'object') {
|
|
1424
|
+
include_content = third.includeContent ?? false;
|
|
1425
|
+
match_start = third.matchStart;
|
|
1426
|
+
}
|
|
1427
|
+
const body = {
|
|
1226
1428
|
from,
|
|
1227
1429
|
to,
|
|
1228
|
-
include_content
|
|
1229
|
-
}
|
|
1430
|
+
include_content,
|
|
1431
|
+
};
|
|
1432
|
+
if (match_start !== undefined && match_start !== '') {
|
|
1433
|
+
body.match_start = this.isoPrefixId(match_start);
|
|
1434
|
+
}
|
|
1435
|
+
const r = await this.request('POST', '/api/v1/compare', body);
|
|
1436
|
+
if (r && typeof r === 'object' && typeof r.match_start === 'string' && r.match_start.length > 0) {
|
|
1437
|
+
r.match_start = this.isoUnprefixId(r.match_start);
|
|
1438
|
+
}
|
|
1439
|
+
return r;
|
|
1230
1440
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1441
|
+
async diff(from, to, third) {
|
|
1442
|
+
if (third === undefined || typeof third === 'boolean') {
|
|
1443
|
+
return this.compare(from, to, third);
|
|
1444
|
+
}
|
|
1445
|
+
return this.compare(from, to, third);
|
|
1234
1446
|
}
|
|
1235
1447
|
async publishWorkspace(targetWorkspace, sourceWorkspace, options) {
|
|
1236
1448
|
const body = {
|
|
@@ -1277,6 +1489,14 @@ class XCiteDBClient {
|
|
|
1277
1489
|
body.message = options.message;
|
|
1278
1490
|
return this.request('POST', `/api/v1/user-workspaces/${encodeURIComponent(id)}/publish`, body);
|
|
1279
1491
|
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Advance the user workspace fork to the current parent tip (`POST /api/v1/user-workspaces/{id}/rebase`).
|
|
1494
|
+
* On overlap with local edits, returns **409** with `conflicts` (same shape as publish) unless `autoResolve` resolves it.
|
|
1495
|
+
*/
|
|
1496
|
+
async rebaseUserWorkspace(id, options) {
|
|
1497
|
+
const body = { auto_resolve: options?.autoResolve ?? 'none' };
|
|
1498
|
+
return this.request('POST', `/api/v1/user-workspaces/${encodeURIComponent(id)}/rebase`, body);
|
|
1499
|
+
}
|
|
1280
1500
|
/**
|
|
1281
1501
|
* Create `workspaceName` from {@link options.fromBranch} (or current context), run `fn` scoped to that workspace,
|
|
1282
1502
|
* create a checkpoint, then publish back unless {@link options.autoMerge} is `false`.
|
|
@@ -1354,6 +1574,49 @@ class XCiteDBClient {
|
|
|
1354
1574
|
}
|
|
1355
1575
|
return r;
|
|
1356
1576
|
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Import a document (`POST /api/v1/documents/import`). Server converts DOCX, ODF, RTF, PDF, Markdown, AsciiDoc, or plain text
|
|
1579
|
+
* to shredded XML. Field name must be `file` (multipart).
|
|
1580
|
+
*/
|
|
1581
|
+
async importDocument(file, options) {
|
|
1582
|
+
const form = new FormData();
|
|
1583
|
+
const blob = file instanceof Blob
|
|
1584
|
+
? file
|
|
1585
|
+
: file instanceof ArrayBuffer
|
|
1586
|
+
? new Blob([file], { type: 'application/octet-stream' })
|
|
1587
|
+
: new Blob([Uint8Array.from(file)], { type: 'application/octet-stream' });
|
|
1588
|
+
const uploadName = options?.filename ??
|
|
1589
|
+
(typeof File !== 'undefined' && file instanceof File ? file.name : 'upload.bin');
|
|
1590
|
+
form.append('file', blob, uploadName);
|
|
1591
|
+
const q = buildQuery({
|
|
1592
|
+
...(options?.identifier !== undefined && options.identifier !== ''
|
|
1593
|
+
? { identifier: this.isoPrefixId(options.identifier) }
|
|
1594
|
+
: {}),
|
|
1595
|
+
});
|
|
1596
|
+
const r = await this.requestFormPost(`/api/v1/documents/import${q}`, form);
|
|
1597
|
+
if (r && typeof r === 'object' && 'identifier' in r && typeof r.identifier === 'string') {
|
|
1598
|
+
r.identifier = this.isoUnprefixId(r.identifier);
|
|
1599
|
+
}
|
|
1600
|
+
return r;
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Export a document (`GET /api/v1/documents/export`). Returns binary bytes and response metadata.
|
|
1604
|
+
*/
|
|
1605
|
+
async exportDocument(identifier, format = 'docx', options) {
|
|
1606
|
+
const q = buildQuery({
|
|
1607
|
+
identifier: this.isoPrefixId(identifier),
|
|
1608
|
+
format,
|
|
1609
|
+
strict: options?.strict ? '1' : undefined,
|
|
1610
|
+
});
|
|
1611
|
+
const r = await this.requestBinaryGet(`/api/v1/documents/export${q}`);
|
|
1612
|
+
return {
|
|
1613
|
+
bytes: r.bytes,
|
|
1614
|
+
contentType: r.contentType,
|
|
1615
|
+
filename: r.filename,
|
|
1616
|
+
...(r.roundTrip ? { roundTrip: r.roundTrip } : {}),
|
|
1617
|
+
...(r.warning ? { warning: r.warning } : {}),
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1357
1620
|
/**
|
|
1358
1621
|
* @deprecated Use {@link writeXmlDocument}. This name was misleading: it writes **XML** via a JSON wrapper, not a JSON document.
|
|
1359
1622
|
*/
|
|
@@ -1370,6 +1633,26 @@ class XCiteDBClient {
|
|
|
1370
1633
|
const rows = await this.request('GET', `/api/v1/documents/by-id${q}`);
|
|
1371
1634
|
return Array.isArray(rows) ? rows.map((x) => this.isoUnprefixId(String(x))) : rows;
|
|
1372
1635
|
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Shallow read: root element with inline leaves plus `db:N*` placeholders for shredded child slots
|
|
1638
|
+
* (`flags=NoChildren,KeepIndexNodes,FirstMatch` on `GET /api/v1/documents/by-id`). For sidebar / AST
|
|
1639
|
+
* navigation, pair with {@link listIdentifierChildren} (e.g. `Promise.all` of shallow + children).
|
|
1640
|
+
*/
|
|
1641
|
+
async queryByIdentifierShallow(identifier, filter, pathFilter) {
|
|
1642
|
+
return this.queryByIdentifier(identifier, 'NoChildren,KeepIndexNodes,FirstMatch', filter, pathFilter);
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Load a document with all shredded children inlined (`FirstMatch,IncludeChildren` on `GET /by-id`).
|
|
1646
|
+
* Use this for editor round-trips. For navigation without loading the full subtree, use
|
|
1647
|
+
* {@link queryByIdentifierShallow} plus {@link listIdentifierChildren}.
|
|
1648
|
+
*/
|
|
1649
|
+
async queryByIdentifierFull(identifier, filter, pathFilter) {
|
|
1650
|
+
return this.queryByIdentifier(identifier, 'FirstMatch,IncludeChildren', filter, pathFilter);
|
|
1651
|
+
}
|
|
1652
|
+
/** Alias of {@link listIdentifierChildren} — immediate child segments under `parentPath` (identifier hierarchy). */
|
|
1653
|
+
async listChildIdentifiers(parentPath) {
|
|
1654
|
+
return this.listIdentifierChildren(parentPath);
|
|
1655
|
+
}
|
|
1373
1656
|
async queryDocuments(query, flags, filter, pathFilter) {
|
|
1374
1657
|
const pq = this.isoPrefixQuery(query);
|
|
1375
1658
|
const params = {
|
|
@@ -1727,6 +2010,260 @@ class XCiteDBClient {
|
|
|
1727
2010
|
async deleteProjectDocConf() {
|
|
1728
2011
|
return this.request('DELETE', '/api/v1/project/settings/doc-conf');
|
|
1729
2012
|
}
|
|
2013
|
+
/** Admin: asset storage v2 (`GET /api/v1/project/settings/asset-storage`). */
|
|
2014
|
+
async getProjectAssetStorage() {
|
|
2015
|
+
return this.request('GET', '/api/v1/project/settings/asset-storage');
|
|
2016
|
+
}
|
|
2017
|
+
/** Admin: save asset storage v2 (`PUT /api/v1/project/settings/asset-storage`). */
|
|
2018
|
+
async updateProjectAssetStorage(body) {
|
|
2019
|
+
return this.request('PUT', '/api/v1/project/settings/asset-storage', body);
|
|
2020
|
+
}
|
|
2021
|
+
resolveAssetIdentifier(uriOrIdentifier) {
|
|
2022
|
+
const trimmed = uriOrIdentifier.trim();
|
|
2023
|
+
const fromUri = (0, assetUri_1.parseAssetUri)(trimmed);
|
|
2024
|
+
if (fromUri) {
|
|
2025
|
+
return this.canonicalId(fromUri);
|
|
2026
|
+
}
|
|
2027
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(trimmed)) {
|
|
2028
|
+
return `/assets/${trimmed.toLowerCase()}`;
|
|
2029
|
+
}
|
|
2030
|
+
return this.canonicalId(trimmed);
|
|
2031
|
+
}
|
|
2032
|
+
/** URL path under `/api/v1/assets/…` with per-segment encoding. */
|
|
2033
|
+
assetRequestPath(uriOrIdentifier) {
|
|
2034
|
+
const id = this.resolveAssetIdentifier(uriOrIdentifier);
|
|
2035
|
+
const segments = id.split('/').filter((s) => s.length > 0).map((s) => encodeURIComponent(s));
|
|
2036
|
+
return `/api/v1/assets/${segments.join('/')}`;
|
|
2037
|
+
}
|
|
2038
|
+
assetQuerySuffix(opts) {
|
|
2039
|
+
const ml = opts?.ml?.trim();
|
|
2040
|
+
if (!ml) {
|
|
2041
|
+
return '';
|
|
2042
|
+
}
|
|
2043
|
+
return `?ml=${encodeURIComponent(ml)}`;
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Upload raw bytes (`POST /api/v1/assets`). Returns canonical `identifier` and `xcitedb:asset:…` `uri`.
|
|
2047
|
+
*/
|
|
2048
|
+
async uploadAsset(bytes, opts) {
|
|
2049
|
+
let bodyBytes;
|
|
2050
|
+
if (bytes instanceof Blob) {
|
|
2051
|
+
bodyBytes = await bytes.arrayBuffer();
|
|
2052
|
+
}
|
|
2053
|
+
else if (bytes instanceof ArrayBuffer) {
|
|
2054
|
+
bodyBytes = bytes;
|
|
2055
|
+
}
|
|
2056
|
+
else {
|
|
2057
|
+
bodyBytes = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2058
|
+
}
|
|
2059
|
+
const ct = opts.contentType.trim() || 'application/octet-stream';
|
|
2060
|
+
const qParts = [];
|
|
2061
|
+
if (opts.scope === 'public') {
|
|
2062
|
+
qParts.push('scope=public');
|
|
2063
|
+
}
|
|
2064
|
+
if (opts.identifier !== undefined && opts.identifier !== '') {
|
|
2065
|
+
qParts.push(`identifier=${encodeURIComponent(opts.identifier)}`);
|
|
2066
|
+
}
|
|
2067
|
+
const q = qParts.length ? `?${qParts.join('&')}` : '';
|
|
2068
|
+
const no401Retry = false;
|
|
2069
|
+
const suppressTestSessionHeader = false;
|
|
2070
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2071
|
+
const url = joinUrl(this.baseUrl, `/api/v1/assets${q}`);
|
|
2072
|
+
const outgoingRequestId = newClientRequestId();
|
|
2073
|
+
const headers = {
|
|
2074
|
+
...this.authHeaders(),
|
|
2075
|
+
...this.contextHeaders(),
|
|
2076
|
+
...(suppressTestSessionHeader ? {} : this.testHeaders()),
|
|
2077
|
+
'X-Request-Id': outgoingRequestId,
|
|
2078
|
+
'Content-Type': ct,
|
|
2079
|
+
};
|
|
2080
|
+
const init = { method: 'POST', headers, body: bodyBytes };
|
|
2081
|
+
const sig = requestTimeoutSignal(this.requestTimeoutMs);
|
|
2082
|
+
if (sig)
|
|
2083
|
+
init.signal = sig;
|
|
2084
|
+
let res;
|
|
2085
|
+
try {
|
|
2086
|
+
res = await fetch(url, init);
|
|
2087
|
+
}
|
|
2088
|
+
catch (e) {
|
|
2089
|
+
const m = e instanceof Error ? e.message : 'Network error';
|
|
2090
|
+
throw new types_1.XCiteDBError(m, 0, null, { clientRequestId: outgoingRequestId });
|
|
2091
|
+
}
|
|
2092
|
+
const text = await res.text();
|
|
2093
|
+
let data;
|
|
2094
|
+
try {
|
|
2095
|
+
data = text ? JSON.parse(text) : null;
|
|
2096
|
+
}
|
|
2097
|
+
catch {
|
|
2098
|
+
data = text;
|
|
2099
|
+
}
|
|
2100
|
+
if (res.ok) {
|
|
2101
|
+
if (!data || typeof data !== 'object') {
|
|
2102
|
+
throw new types_1.XCiteDBError('Invalid asset upload response', res.status, data);
|
|
2103
|
+
}
|
|
2104
|
+
const o = data;
|
|
2105
|
+
const identifier = typeof o.identifier === 'string' ? o.identifier : '';
|
|
2106
|
+
const uri = typeof o.uri === 'string' ? o.uri : '';
|
|
2107
|
+
if (!identifier || !uri) {
|
|
2108
|
+
throw new types_1.XCiteDBError('Invalid asset upload response', res.status, data);
|
|
2109
|
+
}
|
|
2110
|
+
const out = { identifier, uri };
|
|
2111
|
+
if (typeof o.target_name === 'string')
|
|
2112
|
+
out.target_name = o.target_name;
|
|
2113
|
+
if (typeof o.content_type === 'string')
|
|
2114
|
+
out.content_type = o.content_type;
|
|
2115
|
+
if (typeof o.size === 'number')
|
|
2116
|
+
out.size = o.size;
|
|
2117
|
+
if (typeof o.sha256 === 'string')
|
|
2118
|
+
out.sha256 = o.sha256;
|
|
2119
|
+
if (typeof o.etag === 'string')
|
|
2120
|
+
out.etag = o.etag;
|
|
2121
|
+
return out;
|
|
2122
|
+
}
|
|
2123
|
+
if (res.status === 401 &&
|
|
2124
|
+
attempt === 0 &&
|
|
2125
|
+
!no401Retry &&
|
|
2126
|
+
(await this.tryRefreshSessionAfter401())) {
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
const msg = typeof data === 'object' && data !== null && 'message' in data
|
|
2130
|
+
? String(data.message)
|
|
2131
|
+
: res.statusText;
|
|
2132
|
+
this.notifySessionInvalidIfNeeded('/api/v1/assets', res.status);
|
|
2133
|
+
throwForFailedHttp(res.status, '/api/v1/assets', data, msg || `HTTP ${res.status}`, res.headers.get('X-Request-Id') ?? undefined, res.headers.get('X-Client-Request-Id') ?? undefined);
|
|
2134
|
+
}
|
|
2135
|
+
this.notifySessionInvalidIfNeeded('/api/v1/assets', 401);
|
|
2136
|
+
throw new types_1.XCiteDBError('Request failed after retry', 401, null);
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* Download asset bytes (`GET /api/v1/assets/…`). Follows `302` to presigned S3 URLs.
|
|
2140
|
+
* Pass `ml` for magic-link access (same token as `Authorization: MagicLink …` when not using cookies).
|
|
2141
|
+
*/
|
|
2142
|
+
async getAsset(uriOrIdentifier, opts) {
|
|
2143
|
+
const path = `${this.assetRequestPath(uriOrIdentifier)}${this.assetQuerySuffix({ ml: opts?.ml })}`;
|
|
2144
|
+
const no401Retry = false;
|
|
2145
|
+
const suppressTestSessionHeader = false;
|
|
2146
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2147
|
+
const url = joinUrl(this.baseUrl, path);
|
|
2148
|
+
const outgoingRequestId = newClientRequestId();
|
|
2149
|
+
const headers = {
|
|
2150
|
+
...this.authHeaders(),
|
|
2151
|
+
...this.contextHeaders(),
|
|
2152
|
+
...(suppressTestSessionHeader ? {} : this.testHeaders()),
|
|
2153
|
+
'X-Request-Id': outgoingRequestId,
|
|
2154
|
+
};
|
|
2155
|
+
if (opts?.ifNoneMatch) {
|
|
2156
|
+
headers['If-None-Match'] = opts.ifNoneMatch;
|
|
2157
|
+
}
|
|
2158
|
+
const init = { method: 'GET', headers, redirect: 'follow' };
|
|
2159
|
+
const sig = requestTimeoutSignal(this.requestTimeoutMs);
|
|
2160
|
+
if (sig)
|
|
2161
|
+
init.signal = sig;
|
|
2162
|
+
let res;
|
|
2163
|
+
try {
|
|
2164
|
+
res = await fetch(url, init);
|
|
2165
|
+
}
|
|
2166
|
+
catch (e) {
|
|
2167
|
+
const m = e instanceof Error ? e.message : 'Network error';
|
|
2168
|
+
throw new types_1.XCiteDBError(m, 0, null, { clientRequestId: outgoingRequestId });
|
|
2169
|
+
}
|
|
2170
|
+
if (res.status === 304) {
|
|
2171
|
+
return new Blob();
|
|
2172
|
+
}
|
|
2173
|
+
if (res.ok) {
|
|
2174
|
+
return await res.blob();
|
|
2175
|
+
}
|
|
2176
|
+
if (res.status === 401 &&
|
|
2177
|
+
attempt === 0 &&
|
|
2178
|
+
!no401Retry &&
|
|
2179
|
+
(await this.tryRefreshSessionAfter401())) {
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
const text = await res.text();
|
|
2183
|
+
let data;
|
|
2184
|
+
try {
|
|
2185
|
+
data = text ? JSON.parse(text) : null;
|
|
2186
|
+
}
|
|
2187
|
+
catch {
|
|
2188
|
+
data = text;
|
|
2189
|
+
}
|
|
2190
|
+
const msg = typeof data === 'object' && data !== null && 'message' in data
|
|
2191
|
+
? String(data.message)
|
|
2192
|
+
: res.statusText;
|
|
2193
|
+
this.notifySessionInvalidIfNeeded(path, res.status);
|
|
2194
|
+
throwForFailedHttp(res.status, path, data, msg || `HTTP ${res.status}`, res.headers.get('X-Request-Id') ?? undefined, res.headers.get('X-Client-Request-Id') ?? undefined);
|
|
2195
|
+
}
|
|
2196
|
+
this.notifySessionInvalidIfNeeded(path, 401);
|
|
2197
|
+
throw new types_1.XCiteDBError('Request failed after retry', 401, null);
|
|
2198
|
+
}
|
|
2199
|
+
/** Delete an asset (`DELETE /api/v1/assets/…`). */
|
|
2200
|
+
async deleteAsset(uriOrIdentifier, opts) {
|
|
2201
|
+
const path = `${this.assetRequestPath(uriOrIdentifier)}${this.assetQuerySuffix({ ml: opts?.ml })}`;
|
|
2202
|
+
await this.request('DELETE', path);
|
|
2203
|
+
}
|
|
2204
|
+
/** `HEAD /api/v1/assets/…` — metadata only. */
|
|
2205
|
+
async headAsset(uriOrIdentifier, opts) {
|
|
2206
|
+
const path = `${this.assetRequestPath(uriOrIdentifier)}${this.assetQuerySuffix({ ml: opts?.ml })}`;
|
|
2207
|
+
const no401Retry = false;
|
|
2208
|
+
const suppressTestSessionHeader = false;
|
|
2209
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2210
|
+
const url = joinUrl(this.baseUrl, path);
|
|
2211
|
+
const outgoingRequestId = newClientRequestId();
|
|
2212
|
+
const headers = {
|
|
2213
|
+
...this.authHeaders(),
|
|
2214
|
+
...this.contextHeaders(),
|
|
2215
|
+
...(suppressTestSessionHeader ? {} : this.testHeaders()),
|
|
2216
|
+
'X-Request-Id': outgoingRequestId,
|
|
2217
|
+
};
|
|
2218
|
+
const init = { method: 'HEAD', headers };
|
|
2219
|
+
const sig = requestTimeoutSignal(this.requestTimeoutMs);
|
|
2220
|
+
if (sig)
|
|
2221
|
+
init.signal = sig;
|
|
2222
|
+
let res;
|
|
2223
|
+
try {
|
|
2224
|
+
res = await fetch(url, init);
|
|
2225
|
+
}
|
|
2226
|
+
catch (e) {
|
|
2227
|
+
const m = e instanceof Error ? e.message : 'Network error';
|
|
2228
|
+
throw new types_1.XCiteDBError(m, 0, null, { clientRequestId: outgoingRequestId });
|
|
2229
|
+
}
|
|
2230
|
+
if (res.ok) {
|
|
2231
|
+
const etagRaw = res.headers.get('ETag') ?? undefined;
|
|
2232
|
+
const ct = res.headers.get('Content-Type') ?? 'application/octet-stream';
|
|
2233
|
+
const len = res.headers.get('Content-Length');
|
|
2234
|
+
const size = len ? parseInt(len, 10) : 0;
|
|
2235
|
+
return { contentType: ct, size: Number.isFinite(size) ? size : 0, etag: etagRaw };
|
|
2236
|
+
}
|
|
2237
|
+
if (res.status === 401 &&
|
|
2238
|
+
attempt === 0 &&
|
|
2239
|
+
!no401Retry &&
|
|
2240
|
+
(await this.tryRefreshSessionAfter401())) {
|
|
2241
|
+
continue;
|
|
2242
|
+
}
|
|
2243
|
+
const text = await res.text();
|
|
2244
|
+
let data;
|
|
2245
|
+
try {
|
|
2246
|
+
data = text ? JSON.parse(text) : null;
|
|
2247
|
+
}
|
|
2248
|
+
catch {
|
|
2249
|
+
data = text;
|
|
2250
|
+
}
|
|
2251
|
+
const msg = typeof data === 'object' && data !== null && 'message' in data
|
|
2252
|
+
? String(data.message)
|
|
2253
|
+
: res.statusText;
|
|
2254
|
+
this.notifySessionInvalidIfNeeded(path, res.status);
|
|
2255
|
+
throwForFailedHttp(res.status, path, data, msg || `HTTP ${res.status}`, res.headers.get('X-Request-Id') ?? undefined, res.headers.get('X-Client-Request-Id') ?? undefined);
|
|
2256
|
+
}
|
|
2257
|
+
this.notifySessionInvalidIfNeeded(path, 401);
|
|
2258
|
+
throw new types_1.XCiteDBError('Request failed after retry', 401, null);
|
|
2259
|
+
}
|
|
2260
|
+
/** Admin: GC dry-run — manifest vs live meta references (`POST /api/v1/admin/assets/gc/dry-run`). */
|
|
2261
|
+
async adminAssetsGcDryRun(opts) {
|
|
2262
|
+
const q = opts?.ownerUserId && opts.ownerUserId.trim()
|
|
2263
|
+
? `?owner=${encodeURIComponent(opts.ownerUserId.trim())}`
|
|
2264
|
+
: '';
|
|
2265
|
+
return this.request('POST', `/api/v1/admin/assets/gc/dry-run${q}`, {});
|
|
2266
|
+
}
|
|
1730
2267
|
/** Embedded platform default `document.conf` text (`GET /api/v1/platform/default-doc-conf`). */
|
|
1731
2268
|
async getPlatformDefaultDocConf() {
|
|
1732
2269
|
return this.request('GET', '/api/v1/platform/default-doc-conf');
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { XCiteDBClient } from './client';
|
|
2
|
+
export { parseAssetUri, formatAssetUri, collectIdentifiersFromText, ASSET_URI_PREFIX } from './assetUri';
|
|
2
3
|
export { WebSocketSubscription } from './websocket';
|
|
3
|
-
export type { AccessCheckResult, ApiKeyInfo, AppAuthConfig, AppEmailConfig, AppEmailSmtpConfig, AppEmailTemplateEntry, AppEmailTemplates, AppEmailWebhookConfig, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BookmarkRecord, BranchInfo, BranchListItem, CheckpointRecord, CommitRecord, CompareEntry, CompareRef, CompareResult, DatabaseContext, DiffEntry, DiffRef, DiffResult, DocumentBatchResponse, DocumentBatchResultRow, Flags, JsonDocumentData, JsonDocumentBatchItem, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, AcquireLockOptions, LockConflictBody, LockExpiredBody, LockUnknownBody, MergeConflict, MergeResult, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, ProjectSearchSettings, ProjectSearchSettingsUpdate, ProjectDocConfResponse, PlatformDefaultDocConfResponse, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PublishConflict, PublishResult, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RagQueryOptions, RagQueryResult, RagStreamEvent, RealtimeEvent, SearchIndexingProgress, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TagRecord, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TokenPair, UserInfo, UserIsolationConfig, UserIsolationCreateShareParams, UserIsolationOptions, UserIsolationShareMode, UserIsolationShareResult, WorkspaceInfo, WriteDocumentOptions, XmlDocumentBatchItem, CreateTestSessionOptions, TestSessionBootstrap, TestSessionBootstrapSummary, TestSessionInfo, XCiteDBClientOptions, XCiteDBErrorExtras, XCiteDBJwtClaims, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
|
|
4
|
+
export type { AccessCheckResult, ApiKeyInfo, AppAuthConfig, AppEmailConfig, AppEmailSmtpConfig, AppEmailTemplateEntry, AppEmailTemplates, AppEmailWebhookConfig, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BookmarkRecord, BranchInfo, BranchListItem, CheckpointRecord, CommitRecord, CompareEntry, CompareRef, CompareResult, DatabaseContext, DiffEntry, DiffRef, DiffResult, DocumentBatchResponse, DocumentBatchResultRow, DocumentExportFormat, DocumentImportFormat, ExportDocumentResult, Flags, ImportDocumentOptions, ImportDocumentResult, JsonDocumentData, JsonDocumentBatchItem, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, AcquireLockOptions, LockConflictBody, LockExpiredBody, LockUnknownBody, MergeConflict, MergeResult, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, ProjectSearchSettings, ProjectSearchSettingsUpdate, ProjectDocConfResponse, AssetGcDryRunResult, AssetHeadResult, AssetMagicLinkListResponse, AssetMagicLinkRecord, AssetMagicLinkResult, AssetShareListEntry, AssetShareListResponse, AssetShareRequest, AssetStorageImport, AssetStorageMount, AssetStorageTarget, AssetStorageTargetType, AssetUnshareRequest, AssetUploadResult, CreateAssetMagicLinkRequest, ProjectAssetStorageConfig, UploadAssetOptions, PlatformDefaultDocConfResponse, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PublishConflict, PublishResult, RebaseUserWorkspaceResult, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RagQueryOptions, RagQueryResult, RagStreamEvent, RealtimeEvent, SearchIndexingProgress, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TagRecord, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TokenPair, UserInfo, UserIsolationConfig, UserIsolationCreateShareParams, UserIsolationOptions, UserIsolationShareMode, UserIsolationShareResult, WorkspaceInfo, WriteDocumentOptions, XmlDocumentBatchItem, CreateTestSessionOptions, TestSessionBootstrap, TestSessionBootstrapSummary, TestSessionInfo, XCiteDBClientOptions, XCiteDBErrorExtras, XCiteDBJwtClaims, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
|
|
4
5
|
export { XCiteDBError, XCiteDBForbiddenError, XCiteDBNotFoundError, XCiteDBAuthError, XCiteDBLockConflictError, } from './types';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.XCiteDBLockConflictError = exports.XCiteDBAuthError = exports.XCiteDBNotFoundError = exports.XCiteDBForbiddenError = exports.XCiteDBError = exports.WebSocketSubscription = exports.XCiteDBClient = void 0;
|
|
3
|
+
exports.XCiteDBLockConflictError = exports.XCiteDBAuthError = exports.XCiteDBNotFoundError = exports.XCiteDBForbiddenError = exports.XCiteDBError = exports.WebSocketSubscription = exports.ASSET_URI_PREFIX = exports.collectIdentifiersFromText = exports.formatAssetUri = exports.parseAssetUri = exports.XCiteDBClient = void 0;
|
|
4
4
|
var client_1 = require("./client");
|
|
5
5
|
Object.defineProperty(exports, "XCiteDBClient", { enumerable: true, get: function () { return client_1.XCiteDBClient; } });
|
|
6
|
+
var assetUri_1 = require("./assetUri");
|
|
7
|
+
Object.defineProperty(exports, "parseAssetUri", { enumerable: true, get: function () { return assetUri_1.parseAssetUri; } });
|
|
8
|
+
Object.defineProperty(exports, "formatAssetUri", { enumerable: true, get: function () { return assetUri_1.formatAssetUri; } });
|
|
9
|
+
Object.defineProperty(exports, "collectIdentifiersFromText", { enumerable: true, get: function () { return assetUri_1.collectIdentifiersFromText; } });
|
|
10
|
+
Object.defineProperty(exports, "ASSET_URI_PREFIX", { enumerable: true, get: function () { return assetUri_1.ASSET_URI_PREFIX; } });
|
|
6
11
|
var websocket_1 = require("./websocket");
|
|
7
12
|
Object.defineProperty(exports, "WebSocketSubscription", { enumerable: true, get: function () { return websocket_1.WebSocketSubscription; } });
|
|
8
13
|
var types_1 = require("./types");
|