@xcitedbs/client 0.2.9 → 0.2.11

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.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,7 @@ class XCiteDBClient {
67
69
  onSessionInvalid: opts.onSessionInvalid,
68
70
  testSessionToken: data.session_token,
69
71
  testRequireAuth: opts.testRequireAuth,
72
+ userIsolation: opts.userIsolation,
70
73
  });
71
74
  }
72
75
  /**
@@ -144,6 +147,154 @@ class XCiteDBClient {
144
147
  jti: typeof jti === 'string' ? jti : undefined,
145
148
  };
146
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}`;
296
+ });
297
+ }
147
298
  /** Destroy this test session on the server (`DELETE /api/v1/test/sessions/current`). */
148
299
  async destroyTestSession() {
149
300
  if (!this.testSessionToken) {
@@ -194,7 +345,11 @@ class XCiteDBClient {
194
345
  this.projectId = projectId;
195
346
  }
196
347
  setContext(ctx) {
197
- this.defaultContext = { ...this.defaultContext, ...ctx };
348
+ const next = { ...this.defaultContext, ...ctx };
349
+ if (ctx.workspace !== undefined) {
350
+ next.branch = ctx.workspace;
351
+ }
352
+ this.defaultContext = next;
198
353
  }
199
354
  setTokens(access, refresh) {
200
355
  this.accessToken = access;
@@ -206,16 +361,19 @@ class XCiteDBClient {
206
361
  this.appUserAccessToken = access;
207
362
  if (refresh !== undefined)
208
363
  this.appUserRefreshToken = refresh;
364
+ this.clearAppUserIdCache();
209
365
  }
210
366
  clearAppUserTokens() {
211
367
  this.appUserAccessToken = undefined;
212
368
  this.appUserRefreshToken = undefined;
369
+ this.clearAppUserIdCache();
213
370
  }
214
371
  contextHeaders() {
215
372
  const h = {};
216
373
  const c = this.defaultContext;
217
- if (c.branch)
218
- h['X-Branch'] = c.branch;
374
+ const ws = c.workspace ?? c.branch;
375
+ if (ws)
376
+ h['X-Workspace'] = ws;
219
377
  if (c.date)
220
378
  h['X-Date'] = c.date;
221
379
  if (c.prefix)
@@ -261,14 +419,19 @@ class XCiteDBClient {
261
419
  }
262
420
  return h;
263
421
  }
422
+ requestHeaders() {
423
+ return {
424
+ ...this.authHeaders(),
425
+ ...this.contextHeaders(),
426
+ ...this.testHeaders(),
427
+ };
428
+ }
264
429
  async request(method, path, body, extraHeaders, opts) {
265
430
  const no401Retry = opts?.no401Retry === true;
266
431
  for (let attempt = 0; attempt < 2; attempt++) {
267
432
  const url = joinUrl(this.baseUrl, path);
268
433
  const headers = {
269
- ...this.authHeaders(),
270
- ...this.contextHeaders(),
271
- ...this.testHeaders(),
434
+ ...this.requestHeaders(),
272
435
  ...extraHeaders,
273
436
  };
274
437
  let init = { method, headers };
@@ -336,6 +499,7 @@ class XCiteDBClient {
336
499
  const pair = await this.request('POST', '/api/v1/app/auth/refresh', this.mergeAppTenant({ refresh_token: this.appUserRefreshToken }), undefined, { no401Retry: true });
337
500
  this.appUserAccessToken = pair.access_token;
338
501
  this.appUserRefreshToken = pair.refresh_token;
502
+ this.cacheAppUserIdFromPair(pair);
339
503
  this.onAppUserTokensUpdated?.(pair);
340
504
  return pair;
341
505
  }
@@ -474,12 +638,14 @@ class XCiteDBClient {
474
638
  const pair = await this.request('POST', '/api/v1/app/auth/oauth/exchange', this.mergeAppTenant({ code }));
475
639
  this.appUserAccessToken = pair.access_token;
476
640
  this.appUserRefreshToken = pair.refresh_token;
641
+ this.cacheAppUserIdFromPair(pair);
477
642
  return pair;
478
643
  }
479
644
  async loginAppUser(email, password) {
480
645
  const pair = await this.request('POST', '/api/v1/app/auth/login', this.mergeAppTenant({ email, password }));
481
646
  this.appUserAccessToken = pair.access_token;
482
647
  this.appUserRefreshToken = pair.refresh_token;
648
+ this.cacheAppUserIdFromPair(pair);
483
649
  return pair;
484
650
  }
485
651
  async refreshAppUser() {
@@ -489,6 +655,7 @@ class XCiteDBClient {
489
655
  await this.request('POST', '/api/v1/app/auth/logout', this.mergeAppTenant({ refresh_token: this.appUserRefreshToken }));
490
656
  this.appUserAccessToken = undefined;
491
657
  this.appUserRefreshToken = undefined;
658
+ this.clearAppUserIdCache();
492
659
  }
493
660
  async appUserMe() {
494
661
  return this.request('GET', '/api/v1/app/auth/me');
@@ -505,6 +672,7 @@ class XCiteDBClient {
505
672
  const pair = await this.request('POST', '/api/v1/app/auth/custom-token', { token });
506
673
  this.appUserAccessToken = pair.access_token;
507
674
  this.appUserRefreshToken = pair.refresh_token;
675
+ this.cacheAppUserIdFromPair(pair);
508
676
  return pair;
509
677
  }
510
678
  /** Change app-user password (requires valid app-user access token). */
@@ -721,7 +889,7 @@ class XCiteDBClient {
721
889
  * ```
722
890
  */
723
891
  async checkAccess(subject, identifier, action, metaPath, branch) {
724
- const body = { subject, identifier, action };
892
+ const body = { subject, identifier: this.isoPrefixId(identifier), action };
725
893
  if (metaPath !== undefined)
726
894
  body.meta_path = metaPath;
727
895
  if (branch !== undefined)
@@ -747,124 +915,237 @@ class XCiteDBClient {
747
915
  async updateSecurityConfig(config) {
748
916
  await this.request('PUT', '/api/v1/security/config', config);
749
917
  }
750
- async createBranch(name, fromBranch, fromDate) {
918
+ /** Per-tenant user data spaces (`GET /api/v1/security/user-isolation`). Requires security admin. */
919
+ async getUserIsolationConfig() {
920
+ return this.request('GET', '/api/v1/security/user-isolation');
921
+ }
922
+ /** Enable or reconfigure user isolation (`PUT /api/v1/security/user-isolation`). */
923
+ async setUserIsolationConfig(config) {
924
+ return this.request('PUT', '/api/v1/security/user-isolation', config);
925
+ }
926
+ /** Disable user isolation and remove generated policies (`DELETE /api/v1/security/user-isolation`). */
927
+ async disableUserIsolation() {
928
+ await this.request('DELETE', '/api/v1/security/user-isolation');
929
+ }
930
+ /**
931
+ * Loads server isolation config; when enabled, configures client-side identifier prefixing to match
932
+ * the server (namespace + shared paths). Does not send `X-Prefix`; identifiers in requests are rewritten.
933
+ */
934
+ async enableUserIsolation() {
935
+ const cfg = await this.getUserIsolationConfig();
936
+ if (cfg.enabled) {
937
+ this.userIsolation = {
938
+ enabled: true,
939
+ namespace: cfg.namespace_pattern,
940
+ shared_read_paths: cfg.shared_read_paths,
941
+ shared_write_paths: cfg.shared_write_paths,
942
+ };
943
+ }
944
+ else {
945
+ this.userIsolation = { enabled: false };
946
+ }
947
+ return cfg;
948
+ }
949
+ async createWorkspace(name, fromBranch, fromDate) {
751
950
  const body = { name };
752
951
  if (fromBranch)
753
952
  body.from_branch = fromBranch;
754
953
  if (fromDate)
755
954
  body.from_date = fromDate;
756
- await this.request('POST', '/api/v1/branches', body);
955
+ await this.request('POST', '/api/v1/workspaces', body);
956
+ }
957
+ /** @deprecated Use {@link createWorkspace}. */
958
+ async createBranch(name, fromBranch, fromDate) {
959
+ return this.createWorkspace(name, fromBranch, fromDate);
757
960
  }
961
+ async deleteWorkspace(name) {
962
+ await this.request('DELETE', `/api/v1/workspaces/${encodeURIComponent(name)}`);
963
+ }
964
+ /** @deprecated Use {@link deleteWorkspace}. */
758
965
  async deleteBranch(name) {
759
- await this.request('DELETE', `/api/v1/branches/${encodeURIComponent(name)}`);
966
+ return this.deleteWorkspace(name);
967
+ }
968
+ async deleteWorkspaceRevision(workspace, date) {
969
+ await this.request('DELETE', `/api/v1/workspaces/${encodeURIComponent(workspace)}/revisions/${encodeURIComponent(date)}`);
760
970
  }
971
+ /** @deprecated Use {@link deleteWorkspaceRevision}. */
761
972
  async deleteRevision(branch, date) {
762
- await this.request('DELETE', `/api/v1/branches/${encodeURIComponent(branch)}/revisions/${encodeURIComponent(date)}`);
973
+ return this.deleteWorkspaceRevision(branch, date);
763
974
  }
975
+ async listWorkspaces() {
976
+ const r = await this.request('GET', '/api/v1/workspaces');
977
+ return r.workspaces ?? r.branches ?? [];
978
+ }
979
+ /** @deprecated Use {@link listWorkspaces}. */
764
980
  async listBranches() {
765
- const r = await this.request('GET', '/api/v1/branches');
766
- return r.branches ?? [];
981
+ return this.listWorkspaces();
982
+ }
983
+ async getWorkspace(name) {
984
+ const r = await this.request('GET', `/api/v1/workspaces/${encodeURIComponent(name)}`);
985
+ return (r.workspace ?? r.branch);
767
986
  }
987
+ /** @deprecated Use {@link getWorkspace}. */
768
988
  async getBranch(name) {
769
- const r = await this.request('GET', `/api/v1/branches/${encodeURIComponent(name)}`);
770
- return r.branch;
989
+ return this.getWorkspace(name);
771
990
  }
772
- async createCommit(message, author) {
991
+ async createCheckpoint(message, author) {
773
992
  const body = { message };
774
993
  if (author)
775
994
  body.author = author;
776
- const r = await this.request('POST', '/api/v1/commits', body);
777
- return r.commit;
995
+ const r = await this.request('POST', '/api/v1/checkpoints', body);
996
+ return (r.checkpoint ?? r.commit);
778
997
  }
779
- async listCommits(options) {
998
+ /** @deprecated Use {@link createCheckpoint}. */
999
+ async createCommit(message, author) {
1000
+ return this.createCheckpoint(message, author);
1001
+ }
1002
+ async listCheckpoints(options) {
780
1003
  const q = buildQuery({
781
1004
  branch: options?.branch,
782
1005
  limit: options?.limit,
783
1006
  offset: options?.offset,
784
1007
  });
785
- return this.request('GET', `/api/v1/commits${q}`);
1008
+ const r = await this.request('GET', `/api/v1/checkpoints${q}`);
1009
+ const list = r.checkpoints ?? r.commits ?? [];
1010
+ return { checkpoints: list, total: r.total, branch: r.branch };
1011
+ }
1012
+ /** @deprecated Use {@link listCheckpoints}. */
1013
+ async listCommits(options) {
1014
+ const r = await this.listCheckpoints(options);
1015
+ return { commits: r.checkpoints, total: r.total, branch: r.branch };
1016
+ }
1017
+ async getCheckpoint(checkpointId) {
1018
+ const r = await this.request('GET', `/api/v1/checkpoints/${encodeURIComponent(checkpointId)}`);
1019
+ return (r.checkpoint ?? r.commit);
786
1020
  }
1021
+ /** @deprecated Use {@link getCheckpoint}. */
787
1022
  async getCommit(commitId) {
788
- const r = await this.request('GET', `/api/v1/commits/${encodeURIComponent(commitId)}`);
789
- return r.commit;
1023
+ return this.getCheckpoint(commitId);
790
1024
  }
791
- async rollbackToCommit(commitId, _confirm) {
792
- return this.request('POST', `/api/v1/commits/${encodeURIComponent(commitId)}/rollback`, {
1025
+ async revertToCheckpoint(checkpointId, _confirm) {
1026
+ return this.request('POST', `/api/v1/checkpoints/${encodeURIComponent(checkpointId)}/revert`, {
793
1027
  confirm: true,
794
1028
  });
795
1029
  }
796
- async cherryPick(commitId, message, author) {
1030
+ /** @deprecated Use {@link revertToCheckpoint}. */
1031
+ async rollbackToCommit(commitId, _confirm) {
1032
+ const r = await this.revertToCheckpoint(commitId, _confirm);
1033
+ return {
1034
+ rolled_back_commits: r.rolled_back_commits ?? r.rolled_back_checkpoints,
1035
+ current_tip: r.current_tip,
1036
+ };
1037
+ }
1038
+ async applyCheckpoint(checkpointId, message, author) {
797
1039
  const body = {};
798
1040
  if (message)
799
1041
  body.message = message;
800
1042
  if (author)
801
1043
  body.author = author;
802
- const r = await this.request('POST', `/api/v1/commits/${encodeURIComponent(commitId)}/cherry-pick`, body);
803
- return r.commit;
1044
+ const r = await this.request('POST', `/api/v1/checkpoints/${encodeURIComponent(checkpointId)}/apply`, body);
1045
+ return (r.checkpoint ?? r.commit);
804
1046
  }
805
- async createTag(name, commitId, message, author) {
806
- const body = { name, commit_id: commitId };
1047
+ /** @deprecated Use {@link applyCheckpoint}. */
1048
+ async cherryPick(commitId, message, author) {
1049
+ return this.applyCheckpoint(commitId, message, author);
1050
+ }
1051
+ async createBookmark(name, checkpointId, message, author) {
1052
+ const body = { name, checkpoint_id: checkpointId };
807
1053
  if (message)
808
1054
  body.message = message;
809
1055
  if (author)
810
1056
  body.author = author;
811
- const r = await this.request('POST', '/api/v1/tags', body);
812
- return r.tag;
1057
+ const r = await this.request('POST', '/api/v1/bookmarks', body);
1058
+ return (r.bookmark ?? r.tag);
813
1059
  }
814
- async listTags(options) {
1060
+ /** @deprecated Use {@link createBookmark}. */
1061
+ async createTag(name, commitId, message, author) {
1062
+ return this.createBookmark(name, commitId, message, author);
1063
+ }
1064
+ async listBookmarks(options) {
815
1065
  const q = buildQuery({ limit: options?.limit, offset: options?.offset });
816
- return this.request('GET', `/api/v1/tags${q}`);
1066
+ const r = await this.request('GET', `/api/v1/bookmarks${q}`);
1067
+ const list = r.bookmarks ?? r.tags ?? [];
1068
+ return { bookmarks: list, total: r.total };
817
1069
  }
1070
+ /** @deprecated Use {@link listBookmarks}. */
1071
+ async listTags(options) {
1072
+ const r = await this.listBookmarks(options);
1073
+ return { tags: r.bookmarks, total: r.total };
1074
+ }
1075
+ async getBookmark(name) {
1076
+ const r = await this.request('GET', `/api/v1/bookmarks/${encodeURIComponent(name)}`);
1077
+ return (r.bookmark ?? r.tag);
1078
+ }
1079
+ /** @deprecated Use {@link getBookmark}. */
818
1080
  async getTag(name) {
819
- const r = await this.request('GET', `/api/v1/tags/${encodeURIComponent(name)}`);
820
- return r.tag;
1081
+ return this.getBookmark(name);
821
1082
  }
1083
+ async deleteBookmark(name) {
1084
+ await this.request('DELETE', `/api/v1/bookmarks/${encodeURIComponent(name)}`);
1085
+ }
1086
+ /** @deprecated Use {@link deleteBookmark}. */
822
1087
  async deleteTag(name) {
823
- await this.request('DELETE', `/api/v1/tags/${encodeURIComponent(name)}`);
1088
+ return this.deleteBookmark(name);
824
1089
  }
825
- async diff(from, to, includeContent) {
826
- return this.request('POST', '/api/v1/diff', {
1090
+ async compare(from, to, includeContent) {
1091
+ return this.request('POST', '/api/v1/compare', {
827
1092
  from,
828
1093
  to,
829
1094
  include_content: includeContent ?? false,
830
1095
  });
831
1096
  }
832
- async mergeBranch(targetBranch, sourceBranch, options) {
833
- const body = { source_branch: sourceBranch };
1097
+ /** @deprecated Use {@link compare}. */
1098
+ async diff(from, to, includeContent) {
1099
+ return this.compare(from, to, includeContent);
1100
+ }
1101
+ async publishWorkspace(targetWorkspace, sourceWorkspace, options) {
1102
+ const body = {
1103
+ source_workspace: sourceWorkspace,
1104
+ source_branch: sourceWorkspace,
1105
+ };
834
1106
  if (options?.message)
835
1107
  body.message = options.message;
836
1108
  body.auto_resolve = options?.autoResolve ?? 'none';
837
- return this.request('POST', `/api/v1/branches/${encodeURIComponent(targetBranch)}/merge`, body);
1109
+ return this.request('POST', `/api/v1/workspaces/${encodeURIComponent(targetWorkspace)}/publish`, body);
1110
+ }
1111
+ /** @deprecated Use {@link publishWorkspace}. */
1112
+ async mergeBranch(targetBranch, sourceBranch, options) {
1113
+ return this.publishWorkspace(targetBranch, sourceBranch, options);
838
1114
  }
839
1115
  /**
840
- * Create `branchName` from {@link options.fromBranch} (or current context branch), run `fn` scoped to that branch,
841
- * create a commit, then merge back into the parent branch unless {@link options.autoMerge} is `false`.
842
- * Restores previous {@link DatabaseContext} afterward.
1116
+ * Create `workspaceName` from {@link options.fromBranch} (or current context), run `fn` scoped to that workspace,
1117
+ * create a checkpoint, then publish back unless {@link options.autoMerge} is `false`.
843
1118
  */
844
- async withBranch(branchName, fn, options) {
1119
+ async withWorkspace(workspaceName, fn, options) {
845
1120
  const prev = { ...this.defaultContext };
846
- const fromBranch = options?.fromBranch ?? prev.branch ?? '';
847
- let commit;
848
- let merge;
1121
+ const fromWs = options?.fromBranch ?? prev.branch ?? prev.workspace ?? '';
1122
+ let checkpoint;
1123
+ let publish;
849
1124
  try {
850
- await this.createBranch(branchName, fromBranch || undefined, prev.date || undefined);
851
- this.setContext({ branch: branchName });
1125
+ await this.createWorkspace(workspaceName, fromWs || undefined, prev.date || undefined);
1126
+ this.setContext({ workspace: workspaceName, branch: workspaceName });
852
1127
  const result = await fn(this);
853
- commit = await this.createCommit(options?.message ?? `Branch ${branchName}`, options?.author);
1128
+ checkpoint = await this.createCheckpoint(options?.message ?? `Workspace ${workspaceName}`, options?.author);
854
1129
  if (options?.autoMerge !== false) {
855
- merge = await this.mergeBranch(fromBranch, branchName, {
1130
+ publish = await this.publishWorkspace(fromWs, workspaceName, {
856
1131
  message: options?.message,
857
1132
  });
858
1133
  }
859
- return { result, commit, merge };
1134
+ return { result, checkpoint, publish };
860
1135
  }
861
1136
  finally {
862
1137
  this.setContext(prev);
863
1138
  }
864
1139
  }
1140
+ /** @deprecated Use {@link withWorkspace}. */
1141
+ async withBranch(branchName, fn, options) {
1142
+ const r = await this.withWorkspace(branchName, fn, options);
1143
+ return { result: r.result, commit: r.checkpoint, merge: r.publish };
1144
+ }
865
1145
  /** Send raw XML body (`Content-Type: application/xml`). For JSON wrapper + options use `writeXmlDocument`. */
866
1146
  async writeXML(xml, _options) {
867
- await this.request('POST', '/api/v1/documents', xml, {
1147
+ const payload = this.isoApplyXmlDbIdentifier(xml);
1148
+ await this.request('POST', '/api/v1/documents', payload, {
868
1149
  'Content-Type': 'application/xml',
869
1150
  });
870
1151
  }
@@ -874,7 +1155,7 @@ class XCiteDBClient {
874
1155
  */
875
1156
  async writeXmlDocument(xml, options) {
876
1157
  await this.request('POST', '/api/v1/documents', {
877
- xml,
1158
+ xml: this.isoApplyXmlDbIdentifier(xml),
878
1159
  is_top: options?.is_top ?? true,
879
1160
  compare_attributes: options?.compare_attributes ?? false,
880
1161
  });
@@ -887,92 +1168,97 @@ class XCiteDBClient {
887
1168
  }
888
1169
  async queryByIdentifier(identifier, flags, filter, pathFilter) {
889
1170
  const q = buildQuery({
890
- identifier,
1171
+ identifier: this.isoPrefixId(identifier),
891
1172
  flags: flags,
892
1173
  filter,
893
1174
  path_filter: pathFilter,
894
1175
  });
895
- return this.request('GET', `/api/v1/documents/by-id${q}`);
1176
+ const rows = await this.request('GET', `/api/v1/documents/by-id${q}`);
1177
+ return Array.isArray(rows) ? rows.map((x) => this.isoUnprefixId(String(x))) : rows;
896
1178
  }
897
1179
  async queryDocuments(query, flags, filter, pathFilter) {
1180
+ const pq = this.isoPrefixQuery(query);
898
1181
  const params = {
899
- match: query.match,
900
- match_start: query.match_start,
901
- match_end: query.match_end,
902
- regex: query.regex,
1182
+ match: pq.match,
1183
+ match_start: pq.match_start,
1184
+ match_end: pq.match_end,
1185
+ regex: pq.regex,
903
1186
  flags: flags,
904
1187
  filter,
905
1188
  path_filter: pathFilter,
906
1189
  };
907
- if (query.contains !== undefined) {
908
- const c = Array.isArray(query.contains) ? query.contains.join(',') : query.contains;
1190
+ if (pq.contains !== undefined) {
1191
+ const c = Array.isArray(pq.contains) ? pq.contains.join(',') : pq.contains;
909
1192
  params.contains = c;
910
1193
  }
911
- if (query.required_meta_paths !== undefined && query.required_meta_paths.length > 0) {
912
- params.required_meta_paths = query.required_meta_paths.join(',');
1194
+ if (pq.required_meta_paths !== undefined && pq.required_meta_paths.length > 0) {
1195
+ params.required_meta_paths = pq.required_meta_paths.join(',');
913
1196
  }
914
- if (query.filter_any_meta === true) {
1197
+ if (pq.filter_any_meta === true) {
915
1198
  params.any_meta = '1';
916
1199
  }
917
- return this.request('GET', `/api/v1/documents${buildQuery(params)}`);
1200
+ const rows = await this.request('GET', `/api/v1/documents${buildQuery(params)}`);
1201
+ return Array.isArray(rows) ? rows.map((x) => this.isoUnprefixId(String(x))) : rows;
918
1202
  }
919
1203
  async deleteDocument(identifier) {
920
- await this.request('DELETE', `/api/v1/documents/by-id${buildQuery({ identifier })}`);
1204
+ await this.request('DELETE', `/api/v1/documents/by-id${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
921
1205
  }
922
1206
  async addIdentifier(identifier) {
923
1207
  const r = await this.request('POST', '/api/v1/documents/identifiers', {
924
- identifier,
1208
+ identifier: this.isoPrefixId(identifier),
925
1209
  });
926
1210
  return r?.ok !== false;
927
1211
  }
928
1212
  async addAlias(original, alias) {
929
- const r = await this.request('POST', '/api/v1/documents/identifiers/alias', { original, alias });
1213
+ const r = await this.request('POST', '/api/v1/documents/identifiers/alias', { original: this.isoPrefixId(original), alias: this.isoPrefixId(alias) });
930
1214
  return r?.ok !== false;
931
1215
  }
932
1216
  async queryChangeDate(identifier) {
933
- const r = await this.request('GET', `/api/v1/documents/change-date${buildQuery({ identifier })}`);
1217
+ const r = await this.request('GET', `/api/v1/documents/change-date${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
934
1218
  return r?.change_date ?? r?.date ?? '';
935
1219
  }
936
1220
  async getXcitepath(identifier) {
937
- const r = await this.request('GET', `/api/v1/documents/xcitepath${buildQuery({ identifier })}`);
1221
+ const r = await this.request('GET', `/api/v1/documents/xcitepath${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
938
1222
  return r?.xcitepath ?? '';
939
1223
  }
940
1224
  async changedIdentifiers(branch, fromDate, toDate) {
941
- return this.request('GET', `/api/v1/documents/changed${buildQuery({ branch, from_date: fromDate, to_date: toDate })}`);
1225
+ const ids = await this.request('GET', `/api/v1/documents/changed${buildQuery({ branch, from_date: fromDate, to_date: toDate })}`);
1226
+ return Array.isArray(ids) ? ids.map((x) => this.isoUnprefixId(String(x))) : ids;
942
1227
  }
943
1228
  async listIdentifiers(query) {
1229
+ const pq = this.isoPrefixQuery(query);
944
1230
  const params = {
945
- match: query.match,
946
- match_start: query.match_start,
947
- match_end: query.match_end,
948
- regex: query.regex,
949
- limit: query.limit,
950
- offset: query.offset,
1231
+ match: pq.match,
1232
+ match_start: pq.match_start,
1233
+ match_end: pq.match_end,
1234
+ regex: pq.regex,
1235
+ limit: pq.limit,
1236
+ offset: pq.offset,
951
1237
  };
952
- if (query.contains !== undefined) {
953
- params.contains = Array.isArray(query.contains)
954
- ? query.contains.join(',')
955
- : query.contains;
1238
+ if (pq.contains !== undefined) {
1239
+ params.contains = Array.isArray(pq.contains) ? pq.contains.join(',') : pq.contains;
956
1240
  }
957
- if (query.required_meta_paths !== undefined && query.required_meta_paths.length > 0) {
958
- params.required_meta_paths = query.required_meta_paths.join(',');
1241
+ if (pq.required_meta_paths !== undefined && pq.required_meta_paths.length > 0) {
1242
+ params.required_meta_paths = pq.required_meta_paths.join(',');
959
1243
  }
960
- if (query.filter_any_meta === true) {
1244
+ if (pq.filter_any_meta === true) {
961
1245
  params.any_meta = '1';
962
1246
  }
963
1247
  const data = await this.request('GET', `/api/v1/documents/identifiers${buildQuery(params)}`);
1248
+ const un = (ids) => ids.map((id) => this.isoUnprefixId(id));
964
1249
  // Older servers returned a bare string[]; paginated API returns { identifiers, total, offset, limit }.
965
1250
  if (Array.isArray(data)) {
1251
+ const ids = un(data);
966
1252
  return {
967
- identifiers: data,
968
- total: data.length,
1253
+ identifiers: ids,
1254
+ total: ids.length,
969
1255
  offset: 0,
970
- limit: data.length,
1256
+ limit: ids.length,
971
1257
  };
972
1258
  }
973
1259
  if (data !== null && typeof data === 'object') {
974
1260
  const o = data;
975
- const ids = Array.isArray(o.identifiers) ? o.identifiers : [];
1261
+ const ids = Array.isArray(o.identifiers) ? un(o.identifiers) : [];
976
1262
  return {
977
1263
  identifiers: ids,
978
1264
  total: typeof o.total === 'number' ? o.total : ids.length,
@@ -985,7 +1271,7 @@ class XCiteDBClient {
985
1271
  async listIdentifierChildren(parentPath) {
986
1272
  const params = {};
987
1273
  if (parentPath !== undefined && parentPath !== '') {
988
- params.parent_path = parentPath;
1274
+ params.parent_path = this.isoPrefixId(parentPath);
989
1275
  }
990
1276
  const data = await this.request('GET', `/api/v1/documents/identifier-children${buildQuery(params)}`);
991
1277
  if (data !== null && typeof data === 'object') {
@@ -998,7 +1284,7 @@ class XCiteDBClient {
998
1284
  const r = row;
999
1285
  children.push({
1000
1286
  segment: typeof r.segment === 'string' ? r.segment : '',
1001
- full_path: typeof r.full_path === 'string' ? r.full_path : '',
1287
+ full_path: typeof r.full_path === 'string' ? this.isoUnprefixId(r.full_path) : '',
1002
1288
  is_identifier: r.is_identifier === true,
1003
1289
  has_children: r.has_children === true,
1004
1290
  });
@@ -1006,7 +1292,7 @@ class XCiteDBClient {
1006
1292
  }
1007
1293
  }
1008
1294
  return {
1009
- parent_path: typeof o.parent_path === 'string' ? o.parent_path : '',
1295
+ parent_path: typeof o.parent_path === 'string' ? this.isoUnprefixId(o.parent_path) : '',
1010
1296
  parent_is_identifier: o.parent_is_identifier === true,
1011
1297
  hierarchy_index_available: o.hierarchy_index_available === true,
1012
1298
  children,
@@ -1020,16 +1306,17 @@ class XCiteDBClient {
1020
1306
  };
1021
1307
  }
1022
1308
  async queryLog(query, fromDate, toDate) {
1309
+ const pq = this.isoPrefixQuery(query);
1023
1310
  const params = {
1024
- match_start: query.match_start,
1025
- match: query.match,
1311
+ match_start: pq.match_start,
1312
+ match: pq.match,
1026
1313
  from_date: fromDate,
1027
1314
  to_date: toDate,
1028
1315
  };
1029
1316
  return this.request('GET', `/api/v1/documents/log${buildQuery(params)}`);
1030
1317
  }
1031
1318
  async addMeta(identifier, value, path = '', opts) {
1032
- const body = { identifier, value, path };
1319
+ const body = { identifier: this.isoPrefixId(identifier), value, path };
1033
1320
  if (opts?.mode === 'append')
1034
1321
  body.mode = 'append';
1035
1322
  const r = await this.request('POST', '/api/v1/meta', body);
@@ -1037,7 +1324,7 @@ class XCiteDBClient {
1037
1324
  }
1038
1325
  async addMetaByQuery(query, value, path = '', firstMatch = false, opts) {
1039
1326
  const body = {
1040
- query,
1327
+ query: this.isoPrefixQuery(query),
1041
1328
  value,
1042
1329
  path,
1043
1330
  first_match: firstMatch,
@@ -1054,27 +1341,36 @@ class XCiteDBClient {
1054
1341
  return this.addMetaByQuery(query, value, path, firstMatch, { mode: 'append' });
1055
1342
  }
1056
1343
  async queryMeta(identifier, path = '') {
1057
- return this.request('GET', `/api/v1/meta${buildQuery({ identifier, path })}`);
1344
+ return this.request('GET', `/api/v1/meta${buildQuery({ identifier: this.isoPrefixId(identifier), path })}`);
1058
1345
  }
1059
1346
  async queryMetaByQuery(query, path = '') {
1060
- return this.request('POST', '/api/v1/meta/query', { query, path });
1347
+ return this.request('POST', '/api/v1/meta/query', { query: this.isoPrefixQuery(query), path });
1061
1348
  }
1062
1349
  async clearMeta(query) {
1063
- const r = await this.request('DELETE', '/api/v1/meta', { query });
1350
+ const r = await this.request('DELETE', '/api/v1/meta', {
1351
+ query: this.isoPrefixQuery(query),
1352
+ });
1064
1353
  return r?.ok !== false;
1065
1354
  }
1066
1355
  async acquireLock(identifier, expires = 0) {
1067
- return this.request('POST', '/api/v1/locks', { identifier, expires });
1356
+ const lock = await this.request('POST', '/api/v1/locks', {
1357
+ identifier: this.isoPrefixId(identifier),
1358
+ expires,
1359
+ });
1360
+ return { ...lock, identifier: this.isoUnprefixId(lock.identifier) };
1068
1361
  }
1069
1362
  async releaseLock(identifier, lockId) {
1070
1363
  const r = await this.request('DELETE', '/api/v1/locks', {
1071
- identifier,
1364
+ identifier: this.isoPrefixId(identifier),
1072
1365
  lock_id: lockId,
1073
1366
  });
1074
1367
  return r?.ok !== false;
1075
1368
  }
1076
1369
  async findLocks(identifier) {
1077
- return this.request('GET', `/api/v1/locks${buildQuery({ identifier })}`);
1370
+ const locks = await this.request('GET', `/api/v1/locks${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
1371
+ return Array.isArray(locks)
1372
+ ? locks.map((L) => ({ ...L, identifier: this.isoUnprefixId(L.identifier) }))
1373
+ : locks;
1078
1374
  }
1079
1375
  /**
1080
1376
  * Run Unquery (`POST /api/v1/unquery`): declarative analytics over documents matching `query`.
@@ -1102,7 +1398,7 @@ class XCiteDBClient {
1102
1398
  * ```
1103
1399
  */
1104
1400
  async unquery(query, unquery) {
1105
- return this.request('POST', '/api/v1/unquery', { query, unquery });
1401
+ return this.request('POST', '/api/v1/unquery', { query: this.isoPrefixQuery(query), unquery });
1106
1402
  }
1107
1403
  async search(q) {
1108
1404
  const body = { query: q.query };
@@ -1114,6 +1410,12 @@ class XCiteDBClient {
1114
1410
  body.offset = q.offset;
1115
1411
  if (q.limit !== undefined)
1116
1412
  body.limit = q.limit;
1413
+ if (q.mode)
1414
+ body.mode = q.mode;
1415
+ if (q.min_score !== undefined)
1416
+ body.min_score = q.min_score;
1417
+ if (q.semantic_weight !== undefined)
1418
+ body.semantic_weight = q.semantic_weight;
1117
1419
  const data = await this.request('POST', '/api/v1/search', body);
1118
1420
  const hits = [];
1119
1421
  if (Array.isArray(data.hits)) {
@@ -1121,7 +1423,7 @@ class XCiteDBClient {
1121
1423
  if (h && typeof h === 'object') {
1122
1424
  const o = h;
1123
1425
  const hit = {
1124
- identifier: String(o.identifier ?? ''),
1426
+ identifier: this.isoUnprefixId(String(o.identifier ?? '')),
1125
1427
  path: String(o.path ?? ''),
1126
1428
  doc_type: o.doc_type === 'json' ? 'json' : 'xml',
1127
1429
  branch: String(o.branch ?? ''),
@@ -1131,6 +1433,9 @@ class XCiteDBClient {
1131
1433
  if (typeof o.xcitepath === 'string' && o.xcitepath.length > 0) {
1132
1434
  hit.xcitepath = o.xcitepath;
1133
1435
  }
1436
+ if (o.source === 'fts' || o.source === 'semantic' || o.source === 'both') {
1437
+ hit.source = o.source;
1438
+ }
1134
1439
  hits.push(hit);
1135
1440
  }
1136
1441
  }
@@ -1144,24 +1449,170 @@ class XCiteDBClient {
1144
1449
  async reindex() {
1145
1450
  return this.request('POST', '/api/v1/search/reindex', {});
1146
1451
  }
1452
+ async reembedVector() {
1453
+ return this.request('POST', '/api/v1/search/reembed-vector', {});
1454
+ }
1455
+ /** Project search / FTS / vector / LLM configuration (`GET /api/v1/project/settings/search`). */
1456
+ async getProjectSearchSettings() {
1457
+ return this.request('GET', '/api/v1/project/settings/search');
1458
+ }
1459
+ /** Update project search settings (`PUT /api/v1/project/settings/search`). Returns the same shape as GET. */
1460
+ async updateProjectSearchSettings(patch) {
1461
+ return this.request('PUT', '/api/v1/project/settings/search', patch);
1462
+ }
1463
+ /** Blocking full DB scan (admin; no calls to embedding API). Prefer {@link postVectorIndexEstimateSession} for UI. */
1464
+ async getVectorIndexEstimate() {
1465
+ return this.request('GET', '/api/v1/project/settings/search/vector-index-estimate', undefined);
1466
+ }
1467
+ /** Start background estimate (202); cancel prior session for this tenant. */
1468
+ async postVectorIndexEstimateSession() {
1469
+ return this.request('POST', '/api/v1/project/settings/search/vector-index-estimate', {});
1470
+ }
1471
+ async getVectorIndexEstimateSession(sessionId) {
1472
+ const q = buildQuery({ session: sessionId });
1473
+ return this.request('GET', `/api/v1/project/settings/search/vector-index-estimate${q}`, undefined);
1474
+ }
1475
+ async deleteVectorIndexEstimateSession(sessionId) {
1476
+ const q = buildQuery({ session: sessionId });
1477
+ return this.request('DELETE', `/api/v1/project/settings/search/vector-index-estimate${q}`, undefined);
1478
+ }
1479
+ /**
1480
+ * RAG over indexed documents (`POST /api/v1/rag/query` with JSON body).
1481
+ * Requires LLM completion; embedding required when retrieval uses semantic or hybrid.
1482
+ */
1483
+ async ragQuery(options) {
1484
+ const body = {
1485
+ question: options.question,
1486
+ stream: false,
1487
+ };
1488
+ if (options.branch !== undefined)
1489
+ body.branch = options.branch;
1490
+ if (options.max_context_docs !== undefined)
1491
+ body.max_context_docs = options.max_context_docs;
1492
+ if (options.doc_types?.length)
1493
+ body.doc_types = options.doc_types;
1494
+ if (options.search_mode !== undefined)
1495
+ body.search_mode = options.search_mode;
1496
+ if (options.min_score !== undefined)
1497
+ body.min_score = options.min_score;
1498
+ if (options.semantic_weight !== undefined)
1499
+ body.semantic_weight = options.semantic_weight;
1500
+ const data = await this.request('POST', '/api/v1/rag/query', body);
1501
+ if (!data || typeof data !== 'object') {
1502
+ throw new types_1.XCiteDBError('Invalid RAG response', 500, data);
1503
+ }
1504
+ const answer = typeof data.answer === 'string' ? data.answer : '';
1505
+ const sources = 'sources' in data ? data.sources : [];
1506
+ return { answer, sources };
1507
+ }
1508
+ /**
1509
+ * Streaming RAG (`POST /api/v1/rag/query` with `stream: true`). Parses SSE `data: {...}` lines.
1510
+ * The final event has `done: true` and may include `sources`.
1511
+ */
1512
+ async ragQueryStream(options, onEvent) {
1513
+ const path = '/api/v1/rag/query';
1514
+ const payload = JSON.stringify({
1515
+ question: options.question,
1516
+ stream: true,
1517
+ ...(options.branch !== undefined ? { branch: options.branch } : {}),
1518
+ ...(options.max_context_docs !== undefined ? { max_context_docs: options.max_context_docs } : {}),
1519
+ ...(options.doc_types?.length ? { doc_types: options.doc_types } : {}),
1520
+ ...(options.search_mode !== undefined ? { search_mode: options.search_mode } : {}),
1521
+ ...(options.min_score !== undefined ? { min_score: options.min_score } : {}),
1522
+ ...(options.semantic_weight !== undefined ? { semantic_weight: options.semantic_weight } : {}),
1523
+ });
1524
+ for (let attempt = 0; attempt < 2; attempt++) {
1525
+ const url = joinUrl(this.baseUrl, path);
1526
+ const headers = {
1527
+ ...this.requestHeaders(),
1528
+ 'Content-Type': 'application/json',
1529
+ };
1530
+ const res = await fetch(url, { method: 'POST', headers, body: payload });
1531
+ if (res.status === 401 &&
1532
+ attempt === 0 &&
1533
+ (await this.tryRefreshSessionAfter401())) {
1534
+ continue;
1535
+ }
1536
+ if (!res.ok) {
1537
+ const text = await res.text();
1538
+ let data;
1539
+ try {
1540
+ data = text ? JSON.parse(text) : null;
1541
+ }
1542
+ catch {
1543
+ data = text;
1544
+ }
1545
+ const msg = typeof data === 'object' && data !== null && 'message' in data
1546
+ ? String(data.message)
1547
+ : res.statusText;
1548
+ this.notifySessionInvalidIfNeeded(path, res.status);
1549
+ throw new types_1.XCiteDBError(msg || `HTTP ${res.status}`, res.status, data);
1550
+ }
1551
+ const streamBody = res.body;
1552
+ if (!streamBody) {
1553
+ const text = await res.text();
1554
+ throw new types_1.XCiteDBError('RAG stream: empty response body', res.status, text);
1555
+ }
1556
+ const reader = streamBody.getReader();
1557
+ const decoder = new TextDecoder();
1558
+ let buf = '';
1559
+ try {
1560
+ for (;;) {
1561
+ const { done, value } = await reader.read();
1562
+ if (value) {
1563
+ buf += decoder.decode(value, { stream: !done });
1564
+ }
1565
+ let sep;
1566
+ while ((sep = buf.indexOf('\n\n')) >= 0) {
1567
+ const block = buf.slice(0, sep);
1568
+ buf = buf.slice(sep + 2);
1569
+ const lines = block.split('\n');
1570
+ const dataLine = lines.find((l) => l.startsWith('data:'));
1571
+ if (!dataLine)
1572
+ continue;
1573
+ const jsonStr = dataLine.replace(/^data:\s*/, '').trim();
1574
+ if (!jsonStr)
1575
+ continue;
1576
+ let ev;
1577
+ try {
1578
+ ev = JSON.parse(jsonStr);
1579
+ }
1580
+ catch {
1581
+ continue;
1582
+ }
1583
+ onEvent(ev);
1584
+ }
1585
+ if (done)
1586
+ break;
1587
+ }
1588
+ }
1589
+ finally {
1590
+ reader.releaseLock();
1591
+ }
1592
+ return;
1593
+ }
1594
+ throw new types_1.XCiteDBError('RAG stream failed after retry', 401, null);
1595
+ }
1147
1596
  async writeJsonDocument(identifier, data) {
1148
- await this.request('POST', '/api/v1/json-documents', { identifier, data });
1597
+ await this.request('POST', '/api/v1/json-documents', { identifier: this.isoPrefixId(identifier), data });
1149
1598
  }
1150
1599
  async readJsonDocument(identifier) {
1151
- return this.request('GET', `/api/v1/json-documents${buildQuery({ identifier })}`);
1600
+ return this.request('GET', `/api/v1/json-documents${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
1152
1601
  }
1153
1602
  async deleteJsonDocument(identifier) {
1154
- await this.request('DELETE', `/api/v1/json-documents${buildQuery({ identifier })}`);
1603
+ await this.request('DELETE', `/api/v1/json-documents${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
1155
1604
  }
1156
1605
  async listJsonDocuments(match, limit, offset) {
1157
- const data = await this.request('GET', `/api/v1/json-documents/list${buildQuery({ match, limit, offset })}`);
1606
+ const m = match !== undefined && match !== '' ? this.isoPrefixId(match) : match;
1607
+ const data = await this.request('GET', `/api/v1/json-documents/list${buildQuery({ match: m, limit, offset })}`);
1608
+ const un = (ids) => ids.map((id) => this.isoUnprefixId(id));
1158
1609
  if (Array.isArray(data)) {
1159
- const identifiers = data;
1610
+ const identifiers = un(data);
1160
1611
  return { identifiers, total: identifiers.length, offset: 0, limit: identifiers.length };
1161
1612
  }
1162
1613
  if (data !== null && typeof data === 'object') {
1163
1614
  const o = data;
1164
- const ids = Array.isArray(o.identifiers) ? o.identifiers : [];
1615
+ const ids = Array.isArray(o.identifiers) ? un(o.identifiers) : [];
1165
1616
  return {
1166
1617
  identifiers: ids,
1167
1618
  total: typeof o.total === 'number' ? o.total : ids.length,