@xcitedbs/client 0.2.8 → 0.2.10
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/client.d.ts +65 -1
- package/dist/client.js +502 -62
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +168 -0
- package/dist/user-isolation.test.d.ts +1 -0
- package/dist/user-isolation.test.js +175 -0
- package/llms-full.txt +5 -2
- package/llms.txt +51 -4
- package/package.json +4 -2
package/dist/client.js
CHANGED
|
@@ -33,6 +33,7 @@ class XCiteDBClient {
|
|
|
33
33
|
this.onSessionInvalid = options.onSessionInvalid;
|
|
34
34
|
this.testSessionToken = options.testSessionToken;
|
|
35
35
|
this.testRequireAuth = options.testRequireAuth === true;
|
|
36
|
+
this.userIsolation = options.userIsolation;
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
39
|
* Create an ephemeral isolated database: calls `POST /api/v1/test/sessions` with your API key or Bearer,
|
|
@@ -51,6 +52,7 @@ class XCiteDBClient {
|
|
|
51
52
|
onSessionTokensUpdated: opts.onSessionTokensUpdated,
|
|
52
53
|
onAppUserTokensUpdated: opts.onAppUserTokensUpdated,
|
|
53
54
|
onSessionInvalid: opts.onSessionInvalid,
|
|
55
|
+
userIsolation: opts.userIsolation,
|
|
54
56
|
});
|
|
55
57
|
const data = await temp.request('POST', '/api/v1/test/sessions', undefined, undefined, { no401Retry: true });
|
|
56
58
|
return new XCiteDBClient({
|
|
@@ -67,6 +69,230 @@ class XCiteDBClient {
|
|
|
67
69
|
onSessionInvalid: opts.onSessionInvalid,
|
|
68
70
|
testSessionToken: data.session_token,
|
|
69
71
|
testRequireAuth: opts.testRequireAuth,
|
|
72
|
+
userIsolation: opts.userIsolation,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Canonical `project:<tenant_id>:<role>` group string. The middle segment must match the app JWT `tenant_id`
|
|
77
|
+
* (internal project id), not the human-readable project name.
|
|
78
|
+
*/
|
|
79
|
+
static buildProjectGroup(projectId, role) {
|
|
80
|
+
return `project:${projectId}:${role}`;
|
|
81
|
+
}
|
|
82
|
+
static decodeJwtPayloadJson(token) {
|
|
83
|
+
const parts = token.split('.');
|
|
84
|
+
if (parts.length < 2)
|
|
85
|
+
return null;
|
|
86
|
+
try {
|
|
87
|
+
let b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
88
|
+
const pad = (4 - (b64.length % 4)) % 4;
|
|
89
|
+
b64 += '='.repeat(pad);
|
|
90
|
+
const g = globalThis;
|
|
91
|
+
let json = null;
|
|
92
|
+
if (g.Buffer) {
|
|
93
|
+
json = g.Buffer.from(b64, 'base64').toString('utf8');
|
|
94
|
+
}
|
|
95
|
+
else if (typeof atob === 'function') {
|
|
96
|
+
json = atob(b64);
|
|
97
|
+
}
|
|
98
|
+
if (json === null)
|
|
99
|
+
return null;
|
|
100
|
+
return JSON.parse(json);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Decode `appUserAccessToken`, or `accessToken` if no app token (platform JWT), without verifying the signature.
|
|
108
|
+
* Use for debugging ABAC (compare `tenant_id` and `groups` to `project:<…>:role` in policies).
|
|
109
|
+
*/
|
|
110
|
+
getTokenClaims() {
|
|
111
|
+
const raw = this.appUserAccessToken ?? this.accessToken;
|
|
112
|
+
if (!raw)
|
|
113
|
+
return null;
|
|
114
|
+
const p = XCiteDBClient.decodeJwtPayloadJson(raw);
|
|
115
|
+
if (!p)
|
|
116
|
+
return null;
|
|
117
|
+
let groups = [];
|
|
118
|
+
const g = p['groups'];
|
|
119
|
+
if (Array.isArray(g)) {
|
|
120
|
+
groups = g.filter((x) => typeof x === 'string');
|
|
121
|
+
}
|
|
122
|
+
else if (g && typeof g === 'object' && !Array.isArray(g)) {
|
|
123
|
+
groups = Object.keys(g);
|
|
124
|
+
}
|
|
125
|
+
else if (typeof g === 'string') {
|
|
126
|
+
groups = g.split(',').map((s) => s.trim()).filter(Boolean);
|
|
127
|
+
}
|
|
128
|
+
const sub = p['sub'];
|
|
129
|
+
const tenant_id = p['tenant_id'];
|
|
130
|
+
if (typeof sub !== 'string' || typeof tenant_id !== 'string')
|
|
131
|
+
return null;
|
|
132
|
+
const email = p['email'];
|
|
133
|
+
const type = p['type'];
|
|
134
|
+
const exp = p['exp'];
|
|
135
|
+
const iat = p['iat'];
|
|
136
|
+
const iss = p['iss'];
|
|
137
|
+
const jti = p['jti'];
|
|
138
|
+
return {
|
|
139
|
+
sub,
|
|
140
|
+
tenant_id,
|
|
141
|
+
groups,
|
|
142
|
+
email: typeof email === 'string' ? email : undefined,
|
|
143
|
+
type: typeof type === 'string' ? type : undefined,
|
|
144
|
+
exp: typeof exp === 'number' ? exp : undefined,
|
|
145
|
+
iat: typeof iat === 'number' ? iat : undefined,
|
|
146
|
+
iss: typeof iss === 'string' ? iss : undefined,
|
|
147
|
+
jti: typeof jti === 'string' ? jti : undefined,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
cacheAppUserIdFromPair(pair) {
|
|
151
|
+
if (pair?.user?.user_id) {
|
|
152
|
+
this.cachedAppUserId = pair.user.user_id;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
clearAppUserIdCache() {
|
|
156
|
+
this.cachedAppUserId = undefined;
|
|
157
|
+
}
|
|
158
|
+
getAppUserId() {
|
|
159
|
+
if (this.cachedAppUserId) {
|
|
160
|
+
return this.cachedAppUserId;
|
|
161
|
+
}
|
|
162
|
+
const raw = this.appUserAccessToken;
|
|
163
|
+
if (!raw) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const p = XCiteDBClient.decodeJwtPayloadJson(raw);
|
|
167
|
+
if (!p) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const sub = p['sub'];
|
|
171
|
+
if (typeof sub === 'string' && sub.length > 0) {
|
|
172
|
+
return sub;
|
|
173
|
+
}
|
|
174
|
+
const uid = p['user_id'];
|
|
175
|
+
if (typeof uid === 'string' && uid.length > 0) {
|
|
176
|
+
return uid;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
normalizeIsolationNamespaceTemplate(tpl) {
|
|
181
|
+
return tpl.replace(/\$\{user\.id\}/g, '{userId}');
|
|
182
|
+
}
|
|
183
|
+
canonicalId(s) {
|
|
184
|
+
let t = s.trim().replace(/\/+/g, '/');
|
|
185
|
+
if (!t.startsWith('/')) {
|
|
186
|
+
t = `/${t}`;
|
|
187
|
+
}
|
|
188
|
+
return t;
|
|
189
|
+
}
|
|
190
|
+
/** Resolved namespace root, e.g. `/users/abc`, or `null` if isolation is off or no app user id. */
|
|
191
|
+
userIsolationNamespace() {
|
|
192
|
+
if (!this.userIsolation?.enabled) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const uid = this.getAppUserId();
|
|
196
|
+
if (!uid) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const rawTpl = this.normalizeIsolationNamespaceTemplate(this.userIsolation.namespace ?? '/users/{userId}');
|
|
200
|
+
const trimmed = rawTpl.replace(/\/+$/, '') || '';
|
|
201
|
+
if (!trimmed) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
return trimmed.replace(/{userId}/g, uid).replace(/{user_id}/g, uid);
|
|
205
|
+
}
|
|
206
|
+
allSharedPassthroughPrefixes() {
|
|
207
|
+
const o = this.userIsolation;
|
|
208
|
+
if (!o) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
return [...(o.shared_read_paths ?? []), ...(o.shared_write_paths ?? [])];
|
|
212
|
+
}
|
|
213
|
+
pathMatchesSharedPassthrough(path) {
|
|
214
|
+
const canon = this.canonicalId(path);
|
|
215
|
+
for (const raw of this.allSharedPassthroughPrefixes()) {
|
|
216
|
+
if (!raw) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const p = this.canonicalId(raw);
|
|
220
|
+
if (canon === p) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
if (p.endsWith('/') && canon.startsWith(p)) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
if (!p.endsWith('/') && (canon === p || canon.startsWith(`${p}/`))) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
isoPrefixId(id) {
|
|
233
|
+
const ns = this.userIsolationNamespace();
|
|
234
|
+
if (!ns) {
|
|
235
|
+
return id;
|
|
236
|
+
}
|
|
237
|
+
if (id.includes('..')) {
|
|
238
|
+
throw new types_1.XCiteDBError('Invalid identifier: path traversal not allowed', 400, null);
|
|
239
|
+
}
|
|
240
|
+
const canonical = this.canonicalId(id);
|
|
241
|
+
if (canonical.startsWith(`${ns}/`) || canonical === ns) {
|
|
242
|
+
return canonical;
|
|
243
|
+
}
|
|
244
|
+
if (this.pathMatchesSharedPassthrough(canonical)) {
|
|
245
|
+
return canonical;
|
|
246
|
+
}
|
|
247
|
+
if (canonical.startsWith('/users/') && !canonical.startsWith(`${ns}/`) && canonical !== ns) {
|
|
248
|
+
return canonical;
|
|
249
|
+
}
|
|
250
|
+
const combined = `${ns}${canonical === '/' ? '/' : canonical}`;
|
|
251
|
+
const finalId = combined.replace(/\/+/g, '/');
|
|
252
|
+
if (!finalId.startsWith(ns) || (finalId.length > ns.length && finalId.charAt(ns.length) !== '/')) {
|
|
253
|
+
throw new types_1.XCiteDBError('Identifier escapes user namespace', 400, null);
|
|
254
|
+
}
|
|
255
|
+
return finalId;
|
|
256
|
+
}
|
|
257
|
+
isoUnprefixId(id) {
|
|
258
|
+
const ns = this.userIsolationNamespace();
|
|
259
|
+
if (!ns) {
|
|
260
|
+
return id;
|
|
261
|
+
}
|
|
262
|
+
const canonical = this.canonicalId(id);
|
|
263
|
+
if (this.pathMatchesSharedPassthrough(canonical)) {
|
|
264
|
+
return canonical;
|
|
265
|
+
}
|
|
266
|
+
if (canonical.startsWith(`${ns}/`)) {
|
|
267
|
+
return canonical.slice(ns.length);
|
|
268
|
+
}
|
|
269
|
+
if (canonical === ns) {
|
|
270
|
+
return '/';
|
|
271
|
+
}
|
|
272
|
+
return canonical;
|
|
273
|
+
}
|
|
274
|
+
isoPrefixQuery(q) {
|
|
275
|
+
if (!this.userIsolationNamespace()) {
|
|
276
|
+
return q;
|
|
277
|
+
}
|
|
278
|
+
const out = { ...q };
|
|
279
|
+
if (out.match) {
|
|
280
|
+
out.match = this.isoPrefixId(out.match);
|
|
281
|
+
}
|
|
282
|
+
if (out.match_start) {
|
|
283
|
+
out.match_start = this.isoPrefixId(out.match_start);
|
|
284
|
+
}
|
|
285
|
+
if (out.match_end) {
|
|
286
|
+
out.match_end = this.isoPrefixId(out.match_end);
|
|
287
|
+
}
|
|
288
|
+
return out;
|
|
289
|
+
}
|
|
290
|
+
isoApplyXmlDbIdentifier(xml) {
|
|
291
|
+
if (!this.userIsolationNamespace()) {
|
|
292
|
+
return xml;
|
|
293
|
+
}
|
|
294
|
+
return xml.replace(/(db:identifier\s*=\s*")([^"]*)(")/, (_m, p1, mid, p3) => {
|
|
295
|
+
return `${p1}${this.isoPrefixId(String(mid))}${p3}`;
|
|
70
296
|
});
|
|
71
297
|
}
|
|
72
298
|
/** Destroy this test session on the server (`DELETE /api/v1/test/sessions/current`). */
|
|
@@ -131,10 +357,12 @@ class XCiteDBClient {
|
|
|
131
357
|
this.appUserAccessToken = access;
|
|
132
358
|
if (refresh !== undefined)
|
|
133
359
|
this.appUserRefreshToken = refresh;
|
|
360
|
+
this.clearAppUserIdCache();
|
|
134
361
|
}
|
|
135
362
|
clearAppUserTokens() {
|
|
136
363
|
this.appUserAccessToken = undefined;
|
|
137
364
|
this.appUserRefreshToken = undefined;
|
|
365
|
+
this.clearAppUserIdCache();
|
|
138
366
|
}
|
|
139
367
|
contextHeaders() {
|
|
140
368
|
const h = {};
|
|
@@ -186,14 +414,19 @@ class XCiteDBClient {
|
|
|
186
414
|
}
|
|
187
415
|
return h;
|
|
188
416
|
}
|
|
417
|
+
requestHeaders() {
|
|
418
|
+
return {
|
|
419
|
+
...this.authHeaders(),
|
|
420
|
+
...this.contextHeaders(),
|
|
421
|
+
...this.testHeaders(),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
189
424
|
async request(method, path, body, extraHeaders, opts) {
|
|
190
425
|
const no401Retry = opts?.no401Retry === true;
|
|
191
426
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
192
427
|
const url = joinUrl(this.baseUrl, path);
|
|
193
428
|
const headers = {
|
|
194
|
-
...this.
|
|
195
|
-
...this.contextHeaders(),
|
|
196
|
-
...this.testHeaders(),
|
|
429
|
+
...this.requestHeaders(),
|
|
197
430
|
...extraHeaders,
|
|
198
431
|
};
|
|
199
432
|
let init = { method, headers };
|
|
@@ -261,6 +494,7 @@ class XCiteDBClient {
|
|
|
261
494
|
const pair = await this.request('POST', '/api/v1/app/auth/refresh', this.mergeAppTenant({ refresh_token: this.appUserRefreshToken }), undefined, { no401Retry: true });
|
|
262
495
|
this.appUserAccessToken = pair.access_token;
|
|
263
496
|
this.appUserRefreshToken = pair.refresh_token;
|
|
497
|
+
this.cacheAppUserIdFromPair(pair);
|
|
264
498
|
this.onAppUserTokensUpdated?.(pair);
|
|
265
499
|
return pair;
|
|
266
500
|
}
|
|
@@ -399,12 +633,14 @@ class XCiteDBClient {
|
|
|
399
633
|
const pair = await this.request('POST', '/api/v1/app/auth/oauth/exchange', this.mergeAppTenant({ code }));
|
|
400
634
|
this.appUserAccessToken = pair.access_token;
|
|
401
635
|
this.appUserRefreshToken = pair.refresh_token;
|
|
636
|
+
this.cacheAppUserIdFromPair(pair);
|
|
402
637
|
return pair;
|
|
403
638
|
}
|
|
404
639
|
async loginAppUser(email, password) {
|
|
405
640
|
const pair = await this.request('POST', '/api/v1/app/auth/login', this.mergeAppTenant({ email, password }));
|
|
406
641
|
this.appUserAccessToken = pair.access_token;
|
|
407
642
|
this.appUserRefreshToken = pair.refresh_token;
|
|
643
|
+
this.cacheAppUserIdFromPair(pair);
|
|
408
644
|
return pair;
|
|
409
645
|
}
|
|
410
646
|
async refreshAppUser() {
|
|
@@ -414,6 +650,7 @@ class XCiteDBClient {
|
|
|
414
650
|
await this.request('POST', '/api/v1/app/auth/logout', this.mergeAppTenant({ refresh_token: this.appUserRefreshToken }));
|
|
415
651
|
this.appUserAccessToken = undefined;
|
|
416
652
|
this.appUserRefreshToken = undefined;
|
|
653
|
+
this.clearAppUserIdCache();
|
|
417
654
|
}
|
|
418
655
|
async appUserMe() {
|
|
419
656
|
return this.request('GET', '/api/v1/app/auth/me');
|
|
@@ -430,6 +667,7 @@ class XCiteDBClient {
|
|
|
430
667
|
const pair = await this.request('POST', '/api/v1/app/auth/custom-token', { token });
|
|
431
668
|
this.appUserAccessToken = pair.access_token;
|
|
432
669
|
this.appUserRefreshToken = pair.refresh_token;
|
|
670
|
+
this.cacheAppUserIdFromPair(pair);
|
|
433
671
|
return pair;
|
|
434
672
|
}
|
|
435
673
|
/** Change app-user password (requires valid app-user access token). */
|
|
@@ -646,7 +884,7 @@ class XCiteDBClient {
|
|
|
646
884
|
* ```
|
|
647
885
|
*/
|
|
648
886
|
async checkAccess(subject, identifier, action, metaPath, branch) {
|
|
649
|
-
const body = { subject, identifier, action };
|
|
887
|
+
const body = { subject, identifier: this.isoPrefixId(identifier), action };
|
|
650
888
|
if (metaPath !== undefined)
|
|
651
889
|
body.meta_path = metaPath;
|
|
652
890
|
if (branch !== undefined)
|
|
@@ -672,6 +910,37 @@ class XCiteDBClient {
|
|
|
672
910
|
async updateSecurityConfig(config) {
|
|
673
911
|
await this.request('PUT', '/api/v1/security/config', config);
|
|
674
912
|
}
|
|
913
|
+
/** Per-tenant user data spaces (`GET /api/v1/security/user-isolation`). Requires security admin. */
|
|
914
|
+
async getUserIsolationConfig() {
|
|
915
|
+
return this.request('GET', '/api/v1/security/user-isolation');
|
|
916
|
+
}
|
|
917
|
+
/** Enable or reconfigure user isolation (`PUT /api/v1/security/user-isolation`). */
|
|
918
|
+
async setUserIsolationConfig(config) {
|
|
919
|
+
return this.request('PUT', '/api/v1/security/user-isolation', config);
|
|
920
|
+
}
|
|
921
|
+
/** Disable user isolation and remove generated policies (`DELETE /api/v1/security/user-isolation`). */
|
|
922
|
+
async disableUserIsolation() {
|
|
923
|
+
await this.request('DELETE', '/api/v1/security/user-isolation');
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Loads server isolation config; when enabled, configures client-side identifier prefixing to match
|
|
927
|
+
* the server (namespace + shared paths). Does not send `X-Prefix`; identifiers in requests are rewritten.
|
|
928
|
+
*/
|
|
929
|
+
async enableUserIsolation() {
|
|
930
|
+
const cfg = await this.getUserIsolationConfig();
|
|
931
|
+
if (cfg.enabled) {
|
|
932
|
+
this.userIsolation = {
|
|
933
|
+
enabled: true,
|
|
934
|
+
namespace: cfg.namespace_pattern,
|
|
935
|
+
shared_read_paths: cfg.shared_read_paths,
|
|
936
|
+
shared_write_paths: cfg.shared_write_paths,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
this.userIsolation = { enabled: false };
|
|
941
|
+
}
|
|
942
|
+
return cfg;
|
|
943
|
+
}
|
|
675
944
|
async createBranch(name, fromBranch, fromDate) {
|
|
676
945
|
const body = { name };
|
|
677
946
|
if (fromBranch)
|
|
@@ -789,7 +1058,8 @@ class XCiteDBClient {
|
|
|
789
1058
|
}
|
|
790
1059
|
/** Send raw XML body (`Content-Type: application/xml`). For JSON wrapper + options use `writeXmlDocument`. */
|
|
791
1060
|
async writeXML(xml, _options) {
|
|
792
|
-
|
|
1061
|
+
const payload = this.isoApplyXmlDbIdentifier(xml);
|
|
1062
|
+
await this.request('POST', '/api/v1/documents', payload, {
|
|
793
1063
|
'Content-Type': 'application/xml',
|
|
794
1064
|
});
|
|
795
1065
|
}
|
|
@@ -799,7 +1069,7 @@ class XCiteDBClient {
|
|
|
799
1069
|
*/
|
|
800
1070
|
async writeXmlDocument(xml, options) {
|
|
801
1071
|
await this.request('POST', '/api/v1/documents', {
|
|
802
|
-
xml,
|
|
1072
|
+
xml: this.isoApplyXmlDbIdentifier(xml),
|
|
803
1073
|
is_top: options?.is_top ?? true,
|
|
804
1074
|
compare_attributes: options?.compare_attributes ?? false,
|
|
805
1075
|
});
|
|
@@ -812,92 +1082,97 @@ class XCiteDBClient {
|
|
|
812
1082
|
}
|
|
813
1083
|
async queryByIdentifier(identifier, flags, filter, pathFilter) {
|
|
814
1084
|
const q = buildQuery({
|
|
815
|
-
identifier,
|
|
1085
|
+
identifier: this.isoPrefixId(identifier),
|
|
816
1086
|
flags: flags,
|
|
817
1087
|
filter,
|
|
818
1088
|
path_filter: pathFilter,
|
|
819
1089
|
});
|
|
820
|
-
|
|
1090
|
+
const rows = await this.request('GET', `/api/v1/documents/by-id${q}`);
|
|
1091
|
+
return Array.isArray(rows) ? rows.map((x) => this.isoUnprefixId(String(x))) : rows;
|
|
821
1092
|
}
|
|
822
1093
|
async queryDocuments(query, flags, filter, pathFilter) {
|
|
1094
|
+
const pq = this.isoPrefixQuery(query);
|
|
823
1095
|
const params = {
|
|
824
|
-
match:
|
|
825
|
-
match_start:
|
|
826
|
-
match_end:
|
|
827
|
-
regex:
|
|
1096
|
+
match: pq.match,
|
|
1097
|
+
match_start: pq.match_start,
|
|
1098
|
+
match_end: pq.match_end,
|
|
1099
|
+
regex: pq.regex,
|
|
828
1100
|
flags: flags,
|
|
829
1101
|
filter,
|
|
830
1102
|
path_filter: pathFilter,
|
|
831
1103
|
};
|
|
832
|
-
if (
|
|
833
|
-
const c = Array.isArray(
|
|
1104
|
+
if (pq.contains !== undefined) {
|
|
1105
|
+
const c = Array.isArray(pq.contains) ? pq.contains.join(',') : pq.contains;
|
|
834
1106
|
params.contains = c;
|
|
835
1107
|
}
|
|
836
|
-
if (
|
|
837
|
-
params.required_meta_paths =
|
|
1108
|
+
if (pq.required_meta_paths !== undefined && pq.required_meta_paths.length > 0) {
|
|
1109
|
+
params.required_meta_paths = pq.required_meta_paths.join(',');
|
|
838
1110
|
}
|
|
839
|
-
if (
|
|
1111
|
+
if (pq.filter_any_meta === true) {
|
|
840
1112
|
params.any_meta = '1';
|
|
841
1113
|
}
|
|
842
|
-
|
|
1114
|
+
const rows = await this.request('GET', `/api/v1/documents${buildQuery(params)}`);
|
|
1115
|
+
return Array.isArray(rows) ? rows.map((x) => this.isoUnprefixId(String(x))) : rows;
|
|
843
1116
|
}
|
|
844
1117
|
async deleteDocument(identifier) {
|
|
845
|
-
await this.request('DELETE', `/api/v1/documents/by-id${buildQuery({ identifier })}`);
|
|
1118
|
+
await this.request('DELETE', `/api/v1/documents/by-id${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
|
|
846
1119
|
}
|
|
847
1120
|
async addIdentifier(identifier) {
|
|
848
1121
|
const r = await this.request('POST', '/api/v1/documents/identifiers', {
|
|
849
|
-
identifier,
|
|
1122
|
+
identifier: this.isoPrefixId(identifier),
|
|
850
1123
|
});
|
|
851
1124
|
return r?.ok !== false;
|
|
852
1125
|
}
|
|
853
1126
|
async addAlias(original, alias) {
|
|
854
|
-
const r = await this.request('POST', '/api/v1/documents/identifiers/alias', { original, alias });
|
|
1127
|
+
const r = await this.request('POST', '/api/v1/documents/identifiers/alias', { original: this.isoPrefixId(original), alias: this.isoPrefixId(alias) });
|
|
855
1128
|
return r?.ok !== false;
|
|
856
1129
|
}
|
|
857
1130
|
async queryChangeDate(identifier) {
|
|
858
|
-
const r = await this.request('GET', `/api/v1/documents/change-date${buildQuery({ identifier })}`);
|
|
1131
|
+
const r = await this.request('GET', `/api/v1/documents/change-date${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
|
|
859
1132
|
return r?.change_date ?? r?.date ?? '';
|
|
860
1133
|
}
|
|
861
1134
|
async getXcitepath(identifier) {
|
|
862
|
-
const r = await this.request('GET', `/api/v1/documents/xcitepath${buildQuery({ identifier })}`);
|
|
1135
|
+
const r = await this.request('GET', `/api/v1/documents/xcitepath${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
|
|
863
1136
|
return r?.xcitepath ?? '';
|
|
864
1137
|
}
|
|
865
1138
|
async changedIdentifiers(branch, fromDate, toDate) {
|
|
866
|
-
|
|
1139
|
+
const ids = await this.request('GET', `/api/v1/documents/changed${buildQuery({ branch, from_date: fromDate, to_date: toDate })}`);
|
|
1140
|
+
return Array.isArray(ids) ? ids.map((x) => this.isoUnprefixId(String(x))) : ids;
|
|
867
1141
|
}
|
|
868
1142
|
async listIdentifiers(query) {
|
|
1143
|
+
const pq = this.isoPrefixQuery(query);
|
|
869
1144
|
const params = {
|
|
870
|
-
match:
|
|
871
|
-
match_start:
|
|
872
|
-
match_end:
|
|
873
|
-
regex:
|
|
874
|
-
limit:
|
|
875
|
-
offset:
|
|
1145
|
+
match: pq.match,
|
|
1146
|
+
match_start: pq.match_start,
|
|
1147
|
+
match_end: pq.match_end,
|
|
1148
|
+
regex: pq.regex,
|
|
1149
|
+
limit: pq.limit,
|
|
1150
|
+
offset: pq.offset,
|
|
876
1151
|
};
|
|
877
|
-
if (
|
|
878
|
-
params.contains = Array.isArray(
|
|
879
|
-
? query.contains.join(',')
|
|
880
|
-
: query.contains;
|
|
1152
|
+
if (pq.contains !== undefined) {
|
|
1153
|
+
params.contains = Array.isArray(pq.contains) ? pq.contains.join(',') : pq.contains;
|
|
881
1154
|
}
|
|
882
|
-
if (
|
|
883
|
-
params.required_meta_paths =
|
|
1155
|
+
if (pq.required_meta_paths !== undefined && pq.required_meta_paths.length > 0) {
|
|
1156
|
+
params.required_meta_paths = pq.required_meta_paths.join(',');
|
|
884
1157
|
}
|
|
885
|
-
if (
|
|
1158
|
+
if (pq.filter_any_meta === true) {
|
|
886
1159
|
params.any_meta = '1';
|
|
887
1160
|
}
|
|
888
1161
|
const data = await this.request('GET', `/api/v1/documents/identifiers${buildQuery(params)}`);
|
|
1162
|
+
const un = (ids) => ids.map((id) => this.isoUnprefixId(id));
|
|
889
1163
|
// Older servers returned a bare string[]; paginated API returns { identifiers, total, offset, limit }.
|
|
890
1164
|
if (Array.isArray(data)) {
|
|
1165
|
+
const ids = un(data);
|
|
891
1166
|
return {
|
|
892
|
-
identifiers:
|
|
893
|
-
total:
|
|
1167
|
+
identifiers: ids,
|
|
1168
|
+
total: ids.length,
|
|
894
1169
|
offset: 0,
|
|
895
|
-
limit:
|
|
1170
|
+
limit: ids.length,
|
|
896
1171
|
};
|
|
897
1172
|
}
|
|
898
1173
|
if (data !== null && typeof data === 'object') {
|
|
899
1174
|
const o = data;
|
|
900
|
-
const ids = Array.isArray(o.identifiers) ? o.identifiers : [];
|
|
1175
|
+
const ids = Array.isArray(o.identifiers) ? un(o.identifiers) : [];
|
|
901
1176
|
return {
|
|
902
1177
|
identifiers: ids,
|
|
903
1178
|
total: typeof o.total === 'number' ? o.total : ids.length,
|
|
@@ -910,7 +1185,7 @@ class XCiteDBClient {
|
|
|
910
1185
|
async listIdentifierChildren(parentPath) {
|
|
911
1186
|
const params = {};
|
|
912
1187
|
if (parentPath !== undefined && parentPath !== '') {
|
|
913
|
-
params.parent_path = parentPath;
|
|
1188
|
+
params.parent_path = this.isoPrefixId(parentPath);
|
|
914
1189
|
}
|
|
915
1190
|
const data = await this.request('GET', `/api/v1/documents/identifier-children${buildQuery(params)}`);
|
|
916
1191
|
if (data !== null && typeof data === 'object') {
|
|
@@ -923,7 +1198,7 @@ class XCiteDBClient {
|
|
|
923
1198
|
const r = row;
|
|
924
1199
|
children.push({
|
|
925
1200
|
segment: typeof r.segment === 'string' ? r.segment : '',
|
|
926
|
-
full_path: typeof r.full_path === 'string' ? r.full_path : '',
|
|
1201
|
+
full_path: typeof r.full_path === 'string' ? this.isoUnprefixId(r.full_path) : '',
|
|
927
1202
|
is_identifier: r.is_identifier === true,
|
|
928
1203
|
has_children: r.has_children === true,
|
|
929
1204
|
});
|
|
@@ -931,7 +1206,7 @@ class XCiteDBClient {
|
|
|
931
1206
|
}
|
|
932
1207
|
}
|
|
933
1208
|
return {
|
|
934
|
-
parent_path: typeof o.parent_path === 'string' ? o.parent_path : '',
|
|
1209
|
+
parent_path: typeof o.parent_path === 'string' ? this.isoUnprefixId(o.parent_path) : '',
|
|
935
1210
|
parent_is_identifier: o.parent_is_identifier === true,
|
|
936
1211
|
hierarchy_index_available: o.hierarchy_index_available === true,
|
|
937
1212
|
children,
|
|
@@ -945,16 +1220,17 @@ class XCiteDBClient {
|
|
|
945
1220
|
};
|
|
946
1221
|
}
|
|
947
1222
|
async queryLog(query, fromDate, toDate) {
|
|
1223
|
+
const pq = this.isoPrefixQuery(query);
|
|
948
1224
|
const params = {
|
|
949
|
-
match_start:
|
|
950
|
-
match:
|
|
1225
|
+
match_start: pq.match_start,
|
|
1226
|
+
match: pq.match,
|
|
951
1227
|
from_date: fromDate,
|
|
952
1228
|
to_date: toDate,
|
|
953
1229
|
};
|
|
954
1230
|
return this.request('GET', `/api/v1/documents/log${buildQuery(params)}`);
|
|
955
1231
|
}
|
|
956
1232
|
async addMeta(identifier, value, path = '', opts) {
|
|
957
|
-
const body = { identifier, value, path };
|
|
1233
|
+
const body = { identifier: this.isoPrefixId(identifier), value, path };
|
|
958
1234
|
if (opts?.mode === 'append')
|
|
959
1235
|
body.mode = 'append';
|
|
960
1236
|
const r = await this.request('POST', '/api/v1/meta', body);
|
|
@@ -962,7 +1238,7 @@ class XCiteDBClient {
|
|
|
962
1238
|
}
|
|
963
1239
|
async addMetaByQuery(query, value, path = '', firstMatch = false, opts) {
|
|
964
1240
|
const body = {
|
|
965
|
-
query,
|
|
1241
|
+
query: this.isoPrefixQuery(query),
|
|
966
1242
|
value,
|
|
967
1243
|
path,
|
|
968
1244
|
first_match: firstMatch,
|
|
@@ -979,27 +1255,36 @@ class XCiteDBClient {
|
|
|
979
1255
|
return this.addMetaByQuery(query, value, path, firstMatch, { mode: 'append' });
|
|
980
1256
|
}
|
|
981
1257
|
async queryMeta(identifier, path = '') {
|
|
982
|
-
return this.request('GET', `/api/v1/meta${buildQuery({ identifier, path })}`);
|
|
1258
|
+
return this.request('GET', `/api/v1/meta${buildQuery({ identifier: this.isoPrefixId(identifier), path })}`);
|
|
983
1259
|
}
|
|
984
1260
|
async queryMetaByQuery(query, path = '') {
|
|
985
|
-
return this.request('POST', '/api/v1/meta/query', { query, path });
|
|
1261
|
+
return this.request('POST', '/api/v1/meta/query', { query: this.isoPrefixQuery(query), path });
|
|
986
1262
|
}
|
|
987
1263
|
async clearMeta(query) {
|
|
988
|
-
const r = await this.request('DELETE', '/api/v1/meta', {
|
|
1264
|
+
const r = await this.request('DELETE', '/api/v1/meta', {
|
|
1265
|
+
query: this.isoPrefixQuery(query),
|
|
1266
|
+
});
|
|
989
1267
|
return r?.ok !== false;
|
|
990
1268
|
}
|
|
991
1269
|
async acquireLock(identifier, expires = 0) {
|
|
992
|
-
|
|
1270
|
+
const lock = await this.request('POST', '/api/v1/locks', {
|
|
1271
|
+
identifier: this.isoPrefixId(identifier),
|
|
1272
|
+
expires,
|
|
1273
|
+
});
|
|
1274
|
+
return { ...lock, identifier: this.isoUnprefixId(lock.identifier) };
|
|
993
1275
|
}
|
|
994
1276
|
async releaseLock(identifier, lockId) {
|
|
995
1277
|
const r = await this.request('DELETE', '/api/v1/locks', {
|
|
996
|
-
identifier,
|
|
1278
|
+
identifier: this.isoPrefixId(identifier),
|
|
997
1279
|
lock_id: lockId,
|
|
998
1280
|
});
|
|
999
1281
|
return r?.ok !== false;
|
|
1000
1282
|
}
|
|
1001
1283
|
async findLocks(identifier) {
|
|
1002
|
-
|
|
1284
|
+
const locks = await this.request('GET', `/api/v1/locks${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
|
|
1285
|
+
return Array.isArray(locks)
|
|
1286
|
+
? locks.map((L) => ({ ...L, identifier: this.isoUnprefixId(L.identifier) }))
|
|
1287
|
+
: locks;
|
|
1003
1288
|
}
|
|
1004
1289
|
/**
|
|
1005
1290
|
* Run Unquery (`POST /api/v1/unquery`): declarative analytics over documents matching `query`.
|
|
@@ -1027,7 +1312,7 @@ class XCiteDBClient {
|
|
|
1027
1312
|
* ```
|
|
1028
1313
|
*/
|
|
1029
1314
|
async unquery(query, unquery) {
|
|
1030
|
-
return this.request('POST', '/api/v1/unquery', { query, unquery });
|
|
1315
|
+
return this.request('POST', '/api/v1/unquery', { query: this.isoPrefixQuery(query), unquery });
|
|
1031
1316
|
}
|
|
1032
1317
|
async search(q) {
|
|
1033
1318
|
const body = { query: q.query };
|
|
@@ -1039,6 +1324,12 @@ class XCiteDBClient {
|
|
|
1039
1324
|
body.offset = q.offset;
|
|
1040
1325
|
if (q.limit !== undefined)
|
|
1041
1326
|
body.limit = q.limit;
|
|
1327
|
+
if (q.mode)
|
|
1328
|
+
body.mode = q.mode;
|
|
1329
|
+
if (q.min_score !== undefined)
|
|
1330
|
+
body.min_score = q.min_score;
|
|
1331
|
+
if (q.semantic_weight !== undefined)
|
|
1332
|
+
body.semantic_weight = q.semantic_weight;
|
|
1042
1333
|
const data = await this.request('POST', '/api/v1/search', body);
|
|
1043
1334
|
const hits = [];
|
|
1044
1335
|
if (Array.isArray(data.hits)) {
|
|
@@ -1046,7 +1337,7 @@ class XCiteDBClient {
|
|
|
1046
1337
|
if (h && typeof h === 'object') {
|
|
1047
1338
|
const o = h;
|
|
1048
1339
|
const hit = {
|
|
1049
|
-
identifier: String(o.identifier ?? ''),
|
|
1340
|
+
identifier: this.isoUnprefixId(String(o.identifier ?? '')),
|
|
1050
1341
|
path: String(o.path ?? ''),
|
|
1051
1342
|
doc_type: o.doc_type === 'json' ? 'json' : 'xml',
|
|
1052
1343
|
branch: String(o.branch ?? ''),
|
|
@@ -1056,6 +1347,9 @@ class XCiteDBClient {
|
|
|
1056
1347
|
if (typeof o.xcitepath === 'string' && o.xcitepath.length > 0) {
|
|
1057
1348
|
hit.xcitepath = o.xcitepath;
|
|
1058
1349
|
}
|
|
1350
|
+
if (o.source === 'fts' || o.source === 'semantic' || o.source === 'both') {
|
|
1351
|
+
hit.source = o.source;
|
|
1352
|
+
}
|
|
1059
1353
|
hits.push(hit);
|
|
1060
1354
|
}
|
|
1061
1355
|
}
|
|
@@ -1069,24 +1363,170 @@ class XCiteDBClient {
|
|
|
1069
1363
|
async reindex() {
|
|
1070
1364
|
return this.request('POST', '/api/v1/search/reindex', {});
|
|
1071
1365
|
}
|
|
1366
|
+
async reembedVector() {
|
|
1367
|
+
return this.request('POST', '/api/v1/search/reembed-vector', {});
|
|
1368
|
+
}
|
|
1369
|
+
/** Project search / FTS / vector / LLM configuration (`GET /api/v1/project/settings/search`). */
|
|
1370
|
+
async getProjectSearchSettings() {
|
|
1371
|
+
return this.request('GET', '/api/v1/project/settings/search');
|
|
1372
|
+
}
|
|
1373
|
+
/** Update project search settings (`PUT /api/v1/project/settings/search`). Returns the same shape as GET. */
|
|
1374
|
+
async updateProjectSearchSettings(patch) {
|
|
1375
|
+
return this.request('PUT', '/api/v1/project/settings/search', patch);
|
|
1376
|
+
}
|
|
1377
|
+
/** Blocking full DB scan (admin; no calls to embedding API). Prefer {@link postVectorIndexEstimateSession} for UI. */
|
|
1378
|
+
async getVectorIndexEstimate() {
|
|
1379
|
+
return this.request('GET', '/api/v1/project/settings/search/vector-index-estimate', undefined);
|
|
1380
|
+
}
|
|
1381
|
+
/** Start background estimate (202); cancel prior session for this tenant. */
|
|
1382
|
+
async postVectorIndexEstimateSession() {
|
|
1383
|
+
return this.request('POST', '/api/v1/project/settings/search/vector-index-estimate', {});
|
|
1384
|
+
}
|
|
1385
|
+
async getVectorIndexEstimateSession(sessionId) {
|
|
1386
|
+
const q = buildQuery({ session: sessionId });
|
|
1387
|
+
return this.request('GET', `/api/v1/project/settings/search/vector-index-estimate${q}`, undefined);
|
|
1388
|
+
}
|
|
1389
|
+
async deleteVectorIndexEstimateSession(sessionId) {
|
|
1390
|
+
const q = buildQuery({ session: sessionId });
|
|
1391
|
+
return this.request('DELETE', `/api/v1/project/settings/search/vector-index-estimate${q}`, undefined);
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* RAG over indexed documents (`POST /api/v1/rag/query` with JSON body).
|
|
1395
|
+
* Requires LLM completion; embedding required when retrieval uses semantic or hybrid.
|
|
1396
|
+
*/
|
|
1397
|
+
async ragQuery(options) {
|
|
1398
|
+
const body = {
|
|
1399
|
+
question: options.question,
|
|
1400
|
+
stream: false,
|
|
1401
|
+
};
|
|
1402
|
+
if (options.branch !== undefined)
|
|
1403
|
+
body.branch = options.branch;
|
|
1404
|
+
if (options.max_context_docs !== undefined)
|
|
1405
|
+
body.max_context_docs = options.max_context_docs;
|
|
1406
|
+
if (options.doc_types?.length)
|
|
1407
|
+
body.doc_types = options.doc_types;
|
|
1408
|
+
if (options.search_mode !== undefined)
|
|
1409
|
+
body.search_mode = options.search_mode;
|
|
1410
|
+
if (options.min_score !== undefined)
|
|
1411
|
+
body.min_score = options.min_score;
|
|
1412
|
+
if (options.semantic_weight !== undefined)
|
|
1413
|
+
body.semantic_weight = options.semantic_weight;
|
|
1414
|
+
const data = await this.request('POST', '/api/v1/rag/query', body);
|
|
1415
|
+
if (!data || typeof data !== 'object') {
|
|
1416
|
+
throw new types_1.XCiteDBError('Invalid RAG response', 500, data);
|
|
1417
|
+
}
|
|
1418
|
+
const answer = typeof data.answer === 'string' ? data.answer : '';
|
|
1419
|
+
const sources = 'sources' in data ? data.sources : [];
|
|
1420
|
+
return { answer, sources };
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Streaming RAG (`POST /api/v1/rag/query` with `stream: true`). Parses SSE `data: {...}` lines.
|
|
1424
|
+
* The final event has `done: true` and may include `sources`.
|
|
1425
|
+
*/
|
|
1426
|
+
async ragQueryStream(options, onEvent) {
|
|
1427
|
+
const path = '/api/v1/rag/query';
|
|
1428
|
+
const payload = JSON.stringify({
|
|
1429
|
+
question: options.question,
|
|
1430
|
+
stream: true,
|
|
1431
|
+
...(options.branch !== undefined ? { branch: options.branch } : {}),
|
|
1432
|
+
...(options.max_context_docs !== undefined ? { max_context_docs: options.max_context_docs } : {}),
|
|
1433
|
+
...(options.doc_types?.length ? { doc_types: options.doc_types } : {}),
|
|
1434
|
+
...(options.search_mode !== undefined ? { search_mode: options.search_mode } : {}),
|
|
1435
|
+
...(options.min_score !== undefined ? { min_score: options.min_score } : {}),
|
|
1436
|
+
...(options.semantic_weight !== undefined ? { semantic_weight: options.semantic_weight } : {}),
|
|
1437
|
+
});
|
|
1438
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1439
|
+
const url = joinUrl(this.baseUrl, path);
|
|
1440
|
+
const headers = {
|
|
1441
|
+
...this.requestHeaders(),
|
|
1442
|
+
'Content-Type': 'application/json',
|
|
1443
|
+
};
|
|
1444
|
+
const res = await fetch(url, { method: 'POST', headers, body: payload });
|
|
1445
|
+
if (res.status === 401 &&
|
|
1446
|
+
attempt === 0 &&
|
|
1447
|
+
(await this.tryRefreshSessionAfter401())) {
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
if (!res.ok) {
|
|
1451
|
+
const text = await res.text();
|
|
1452
|
+
let data;
|
|
1453
|
+
try {
|
|
1454
|
+
data = text ? JSON.parse(text) : null;
|
|
1455
|
+
}
|
|
1456
|
+
catch {
|
|
1457
|
+
data = text;
|
|
1458
|
+
}
|
|
1459
|
+
const msg = typeof data === 'object' && data !== null && 'message' in data
|
|
1460
|
+
? String(data.message)
|
|
1461
|
+
: res.statusText;
|
|
1462
|
+
this.notifySessionInvalidIfNeeded(path, res.status);
|
|
1463
|
+
throw new types_1.XCiteDBError(msg || `HTTP ${res.status}`, res.status, data);
|
|
1464
|
+
}
|
|
1465
|
+
const streamBody = res.body;
|
|
1466
|
+
if (!streamBody) {
|
|
1467
|
+
const text = await res.text();
|
|
1468
|
+
throw new types_1.XCiteDBError('RAG stream: empty response body', res.status, text);
|
|
1469
|
+
}
|
|
1470
|
+
const reader = streamBody.getReader();
|
|
1471
|
+
const decoder = new TextDecoder();
|
|
1472
|
+
let buf = '';
|
|
1473
|
+
try {
|
|
1474
|
+
for (;;) {
|
|
1475
|
+
const { done, value } = await reader.read();
|
|
1476
|
+
if (value) {
|
|
1477
|
+
buf += decoder.decode(value, { stream: !done });
|
|
1478
|
+
}
|
|
1479
|
+
let sep;
|
|
1480
|
+
while ((sep = buf.indexOf('\n\n')) >= 0) {
|
|
1481
|
+
const block = buf.slice(0, sep);
|
|
1482
|
+
buf = buf.slice(sep + 2);
|
|
1483
|
+
const lines = block.split('\n');
|
|
1484
|
+
const dataLine = lines.find((l) => l.startsWith('data:'));
|
|
1485
|
+
if (!dataLine)
|
|
1486
|
+
continue;
|
|
1487
|
+
const jsonStr = dataLine.replace(/^data:\s*/, '').trim();
|
|
1488
|
+
if (!jsonStr)
|
|
1489
|
+
continue;
|
|
1490
|
+
let ev;
|
|
1491
|
+
try {
|
|
1492
|
+
ev = JSON.parse(jsonStr);
|
|
1493
|
+
}
|
|
1494
|
+
catch {
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
onEvent(ev);
|
|
1498
|
+
}
|
|
1499
|
+
if (done)
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
finally {
|
|
1504
|
+
reader.releaseLock();
|
|
1505
|
+
}
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
throw new types_1.XCiteDBError('RAG stream failed after retry', 401, null);
|
|
1509
|
+
}
|
|
1072
1510
|
async writeJsonDocument(identifier, data) {
|
|
1073
|
-
await this.request('POST', '/api/v1/json-documents', { identifier, data });
|
|
1511
|
+
await this.request('POST', '/api/v1/json-documents', { identifier: this.isoPrefixId(identifier), data });
|
|
1074
1512
|
}
|
|
1075
1513
|
async readJsonDocument(identifier) {
|
|
1076
|
-
return this.request('GET', `/api/v1/json-documents${buildQuery({ identifier })}`);
|
|
1514
|
+
return this.request('GET', `/api/v1/json-documents${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
|
|
1077
1515
|
}
|
|
1078
1516
|
async deleteJsonDocument(identifier) {
|
|
1079
|
-
await this.request('DELETE', `/api/v1/json-documents${buildQuery({ identifier })}`);
|
|
1517
|
+
await this.request('DELETE', `/api/v1/json-documents${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
|
|
1080
1518
|
}
|
|
1081
1519
|
async listJsonDocuments(match, limit, offset) {
|
|
1082
|
-
const
|
|
1520
|
+
const m = match !== undefined && match !== '' ? this.isoPrefixId(match) : match;
|
|
1521
|
+
const data = await this.request('GET', `/api/v1/json-documents/list${buildQuery({ match: m, limit, offset })}`);
|
|
1522
|
+
const un = (ids) => ids.map((id) => this.isoUnprefixId(id));
|
|
1083
1523
|
if (Array.isArray(data)) {
|
|
1084
|
-
const identifiers = data;
|
|
1524
|
+
const identifiers = un(data);
|
|
1085
1525
|
return { identifiers, total: identifiers.length, offset: 0, limit: identifiers.length };
|
|
1086
1526
|
}
|
|
1087
1527
|
if (data !== null && typeof data === 'object') {
|
|
1088
1528
|
const o = data;
|
|
1089
|
-
const ids = Array.isArray(o.identifiers) ? o.identifiers : [];
|
|
1529
|
+
const ids = Array.isArray(o.identifiers) ? un(o.identifiers) : [];
|
|
1090
1530
|
return {
|
|
1091
1531
|
identifiers: ids,
|
|
1092
1532
|
total: typeof o.total === 'number' ? o.total : ids.length,
|