@xcitedbs/client 0.2.20 → 0.2.23

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 CHANGED
@@ -1,4 +1,4 @@
1
- import { AccessCheckResult, AppAuthConfig, AppEmailConfig, AppEmailTemplates, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BranchInfo, BookmarkRecord, CheckpointRecord, CommitRecord, CompareRef, CompareResult, DatabaseContext, DiffRef, DiffResult, DocumentBatchResponse, Flags, JsonDocumentBatchItem, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, AcquireLockOptions, LogEntry, MergeResult, PublishResult, WorkspaceInfo, MetaValue, PlatformRegisterResult, PolicySubjectInput, UnqueryResult, UnqueryTemplate, PolicyUpdateResponse, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredTriggerResponse, TriggerDefinition, StoredPolicyResponse, SubscriptionOptions, TagRecord, TextSearchQuery, TextSearchResult, ProjectSearchSettings, ProjectSearchSettingsUpdate, ProjectDocConfResponse, PlatformDefaultDocConfResponse, VectorIndexEstimate, RagQueryOptions, RagQueryResult, RagStreamEvent, OAuthProvidersResponse, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspacesResponse, TokenPair, UserInfo, ApiKeyInfo, WriteDocumentOptions, XmlDocumentBatchItem, CreateTestSessionOptions, XCiteDBClientOptions, XCiteDBJwtClaims, TestSessionBootstrapSummary, TestSessionInfo, XCiteQuery, UserIsolationConfig, UserIsolationCreateShareParams, UserIsolationShareResult } from './types';
1
+ import { AccessCheckResult, AppAuthConfig, AppEmailConfig, AppEmailTemplates, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BranchInfo, BookmarkRecord, CheckpointRecord, CommitRecord, CompareRef, CompareResult, DatabaseContext, DiffRef, DiffResult, DocumentBatchResponse, DocumentExportFormat, ExportDocumentResult, Flags, JsonDocumentBatchItem, ImportDocumentOptions, ImportDocumentResult, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, AcquireLockOptions, LogEntry, MergeResult, PublishResult, RebaseUserWorkspaceResult, WorkspaceInfo, MetaValue, PlatformRegisterResult, PolicySubjectInput, UnqueryResult, UnqueryTemplate, PolicyUpdateResponse, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredTriggerResponse, TriggerDefinition, StoredPolicyResponse, SubscriptionOptions, TagRecord, TextSearchQuery, TextSearchResult, ProjectSearchSettings, ProjectSearchSettingsUpdate, ProjectDocConfResponse, PlatformDefaultDocConfResponse, VectorIndexEstimate, RagQueryOptions, RagQueryResult, RagStreamEvent, OAuthProvidersResponse, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspacesResponse, TokenPair, UserInfo, ApiKeyInfo, WriteDocumentOptions, XmlDocumentBatchItem, CreateTestSessionOptions, XCiteDBClientOptions, XCiteDBJwtClaims, TestSessionBootstrapSummary, TestSessionInfo, XCiteQuery, UserIsolationConfig, UserIsolationCreateShareParams, UserIsolationShareResult } from './types';
2
2
  import { WebSocketSubscription } from './websocket';
3
3
  export declare class XCiteDBClient {
4
4
  private baseUrl;
@@ -93,6 +93,12 @@ export declare class XCiteDBClient {
93
93
  private testHeaders;
94
94
  private requestHeaders;
95
95
  private request;
96
+ /**
97
+ * POST `multipart/form-data` without forcing JSON `Content-Type` (boundary is set by the runtime).
98
+ */
99
+ private requestFormPost;
100
+ /** GET binary response (e.g. document export); errors are parsed as JSON when possible. */
101
+ private requestBinaryGet;
96
102
  /** Developer Bearer refresh first, then app-user refresh (no API key refresh). */
97
103
  private tryRefreshSessionAfter401;
98
104
  private refreshAppUserNoRetry;
@@ -399,8 +405,16 @@ export declare class XCiteDBClient {
399
405
  /** @deprecated Use {@link deleteBookmark}. */
400
406
  deleteTag(name: string): Promise<void>;
401
407
  compare(from: CompareRef, to: CompareRef, includeContent?: boolean): Promise<CompareResult>;
408
+ compare(from: CompareRef, to: CompareRef, options?: {
409
+ includeContent?: boolean;
410
+ matchStart?: string;
411
+ }): Promise<CompareResult>;
402
412
  /** @deprecated Use {@link compare}. */
403
413
  diff(from: DiffRef, to: DiffRef, includeContent?: boolean): Promise<DiffResult>;
414
+ diff(from: DiffRef, to: DiffRef, options?: {
415
+ includeContent?: boolean;
416
+ matchStart?: string;
417
+ }): Promise<DiffResult>;
404
418
  publishWorkspace(targetWorkspace: string, sourceWorkspace: string, options?: {
405
419
  message?: string;
406
420
  autoResolve?: 'none' | 'source' | 'target';
@@ -410,6 +424,43 @@ export declare class XCiteDBClient {
410
424
  message?: string;
411
425
  autoResolve?: 'none' | 'source' | 'target';
412
426
  }): Promise<MergeResult>;
427
+ /** List app-user draft workspaces visible to the caller (`GET /api/v1/user-workspaces`). */
428
+ listUserWorkspaces(): Promise<{
429
+ user_workspaces: Record<string, unknown>[];
430
+ }>;
431
+ /** Create a user workspace (`POST /api/v1/user-workspaces`). Returns the workspace record (top-level JSON). */
432
+ createUserWorkspace(name: string, options?: {
433
+ sourceBranch?: string;
434
+ sourceDate?: string;
435
+ }): Promise<Record<string, unknown>>;
436
+ getUserWorkspace(id: string): Promise<{
437
+ user_workspace: Record<string, unknown>;
438
+ }>;
439
+ deleteUserWorkspace(id: string): Promise<void>;
440
+ addUserWorkspaceGrant(id: string, body: {
441
+ user_id?: string;
442
+ group?: string;
443
+ mode: string;
444
+ }): Promise<{
445
+ user_workspace: Record<string, unknown>;
446
+ }>;
447
+ removeUserWorkspaceGrant(id: string, body: {
448
+ user_id?: string;
449
+ group?: string;
450
+ }): Promise<{
451
+ user_workspace: Record<string, unknown>;
452
+ }>;
453
+ publishUserWorkspace(id: string, options?: {
454
+ message?: string;
455
+ autoResolve?: string;
456
+ }): Promise<Record<string, unknown>>;
457
+ /**
458
+ * Advance the user workspace fork to the current parent tip (`POST /api/v1/user-workspaces/{id}/rebase`).
459
+ * On overlap with local edits, returns **409** with `conflicts` (same shape as publish) unless `autoResolve` resolves it.
460
+ */
461
+ rebaseUserWorkspace(id: string, options?: {
462
+ autoResolve?: 'none' | 'source' | 'target';
463
+ }): Promise<RebaseUserWorkspaceResult>;
413
464
  /**
414
465
  * Create `workspaceName` from {@link options.fromBranch} (or current context), run `fn` scoped to that workspace,
415
466
  * create a checkpoint, then publish back unless {@link options.autoMerge} is `false`.
@@ -451,11 +502,37 @@ export declare class XCiteDBClient {
451
502
  * Rows with `error === 'lock_conflict'` include `current_lock` (code **423** per row).
452
503
  */
453
504
  writeXmlDocumentsBatch(items: XmlDocumentBatchItem[]): Promise<DocumentBatchResponse>;
505
+ /**
506
+ * Import a document (`POST /api/v1/documents/import`). Server converts DOCX, ODF, RTF, PDF, Markdown, AsciiDoc, or plain text
507
+ * to shredded XML. Field name must be `file` (multipart).
508
+ */
509
+ importDocument(file: Blob | ArrayBuffer | Uint8Array, options?: ImportDocumentOptions): Promise<ImportDocumentResult>;
510
+ /**
511
+ * Export a document (`GET /api/v1/documents/export`). Returns binary bytes and response metadata.
512
+ */
513
+ exportDocument(identifier: string, format?: DocumentExportFormat, options?: {
514
+ strict?: boolean;
515
+ }): Promise<ExportDocumentResult>;
454
516
  /**
455
517
  * @deprecated Use {@link writeXmlDocument}. This name was misleading: it writes **XML** via a JSON wrapper, not a JSON document.
456
518
  */
457
519
  writeDocumentJson(xml: string, options?: WriteDocumentOptions): Promise<void>;
458
520
  queryByIdentifier(identifier: string, flags?: Flags, filter?: string, pathFilter?: string): Promise<string[]>;
521
+ /**
522
+ * Shallow read: root element with inline leaves plus `db:N*` placeholders for shredded child slots
523
+ * (`flags=NoChildren,KeepIndexNodes,FirstMatch` on `GET /api/v1/documents/by-id`). Use with {@link listChildIdentifiers}
524
+ * or {@link queryByIdentifierWithChildren} for sidebar / AST navigation without loading the full subtree.
525
+ */
526
+ queryByIdentifierShallow(identifier: string, filter?: string, pathFilter?: string): Promise<string[]>;
527
+ /** Alias of {@link listIdentifierChildren} — immediate child segments under `parentPath` (identifier hierarchy). */
528
+ listChildIdentifiers(parentPath?: string): Promise<ListIdentifierChildrenResult>;
529
+ /**
530
+ * Parallel shallow node + one level of identifier children (`GET /by-id` + `GET /identifier-children`).
531
+ */
532
+ queryByIdentifierWithChildren(identifier: string, filter?: string, pathFilter?: string): Promise<{
533
+ node: string[];
534
+ children: IdentifierChildNode[];
535
+ }>;
459
536
  queryDocuments(query: XCiteQuery, flags?: Flags, filter?: string, pathFilter?: string): Promise<string[]>;
460
537
  deleteDocument(identifier: string): Promise<void>;
461
538
  addIdentifier(identifier: string): Promise<boolean>;
package/dist/client.js CHANGED
@@ -56,6 +56,24 @@ function buildQuery(params) {
56
56
  const s = sp.toString();
57
57
  return s ? `?${s}` : '';
58
58
  }
59
+ /** Best-effort filename from `Content-Disposition` (attachment; filename="…"). */
60
+ function parseContentDispositionFilename(cd) {
61
+ if (!cd)
62
+ return undefined;
63
+ const mStar = cd.match(/filename\*=(?:UTF-8'')?([^;]+)/i);
64
+ if (mStar?.[1]) {
65
+ try {
66
+ return decodeURIComponent(mStar[1].trim().replace(/^"|"$/g, ''));
67
+ }
68
+ catch {
69
+ return mStar[1].trim().replace(/^"|"$/g, '');
70
+ }
71
+ }
72
+ const m = cd.match(/filename\s*=\s*("?)([^";]+)\1/i);
73
+ if (m?.[2])
74
+ return m[2].trim();
75
+ return undefined;
76
+ }
59
77
  function warnIfHttpOnTlsPort(baseUrl) {
60
78
  try {
61
79
  const u = new URL(baseUrl);
@@ -590,6 +608,122 @@ class XCiteDBClient {
590
608
  this.notifySessionInvalidIfNeeded(path, 401);
591
609
  throw new types_1.XCiteDBError('Request failed after retry', 401, null);
592
610
  }
611
+ /**
612
+ * POST `multipart/form-data` without forcing JSON `Content-Type` (boundary is set by the runtime).
613
+ */
614
+ async requestFormPost(path, form, opts) {
615
+ const no401Retry = opts?.no401Retry === true;
616
+ const suppressTestSessionHeader = opts?.suppressTestSessionHeader === true;
617
+ for (let attempt = 0; attempt < 2; attempt++) {
618
+ const url = joinUrl(this.baseUrl, path);
619
+ const outgoingRequestId = newClientRequestId();
620
+ const headers = {
621
+ ...this.authHeaders(),
622
+ ...this.contextHeaders(),
623
+ ...(suppressTestSessionHeader ? {} : this.testHeaders()),
624
+ 'X-Request-Id': outgoingRequestId,
625
+ };
626
+ const init = { method: 'POST', headers, body: form };
627
+ const sig = requestTimeoutSignal(this.requestTimeoutMs);
628
+ if (sig)
629
+ init.signal = sig;
630
+ let res;
631
+ try {
632
+ res = await fetch(url, init);
633
+ }
634
+ catch (e) {
635
+ const m = e instanceof Error ? e.message : 'Network error';
636
+ throw new types_1.XCiteDBError(m, 0, null, { clientRequestId: outgoingRequestId });
637
+ }
638
+ const text = await res.text();
639
+ let data;
640
+ try {
641
+ data = text ? JSON.parse(text) : null;
642
+ }
643
+ catch {
644
+ data = text;
645
+ }
646
+ if (res.ok) {
647
+ return data;
648
+ }
649
+ if (res.status === 401 &&
650
+ attempt === 0 &&
651
+ !no401Retry &&
652
+ (await this.tryRefreshSessionAfter401())) {
653
+ continue;
654
+ }
655
+ const msg = typeof data === 'object' && data !== null && 'message' in data
656
+ ? String(data.message)
657
+ : res.statusText;
658
+ this.notifySessionInvalidIfNeeded(path, res.status);
659
+ throwForFailedHttp(res.status, path, data, msg || `HTTP ${res.status}`, res.headers.get('X-Request-Id') ?? undefined, res.headers.get('X-Client-Request-Id') ?? undefined);
660
+ }
661
+ this.notifySessionInvalidIfNeeded(path, 401);
662
+ throw new types_1.XCiteDBError('Request failed after retry', 401, null);
663
+ }
664
+ /** GET binary response (e.g. document export); errors are parsed as JSON when possible. */
665
+ async requestBinaryGet(path, opts) {
666
+ const no401Retry = opts?.no401Retry === true;
667
+ const suppressTestSessionHeader = opts?.suppressTestSessionHeader === true;
668
+ for (let attempt = 0; attempt < 2; attempt++) {
669
+ const url = joinUrl(this.baseUrl, path);
670
+ const outgoingRequestId = newClientRequestId();
671
+ const headers = {
672
+ ...this.authHeaders(),
673
+ ...this.contextHeaders(),
674
+ ...(suppressTestSessionHeader ? {} : this.testHeaders()),
675
+ 'X-Request-Id': outgoingRequestId,
676
+ };
677
+ const init = { method: 'GET', headers };
678
+ const sig = requestTimeoutSignal(this.requestTimeoutMs);
679
+ if (sig)
680
+ init.signal = sig;
681
+ let res;
682
+ try {
683
+ res = await fetch(url, init);
684
+ }
685
+ catch (e) {
686
+ const m = e instanceof Error ? e.message : 'Network error';
687
+ throw new types_1.XCiteDBError(m, 0, null, { clientRequestId: outgoingRequestId });
688
+ }
689
+ if (res.ok) {
690
+ const buf = new Uint8Array(await res.arrayBuffer());
691
+ const contentType = res.headers.get('Content-Type') ?? 'application/octet-stream';
692
+ const fn = parseContentDispositionFilename(res.headers.get('Content-Disposition')) ?? 'export.bin';
693
+ const rt = res.headers.get('X-XciteDB-Export-RoundTrip');
694
+ const roundTrip = rt === 'exact' || rt === 'lossy' ? rt : undefined;
695
+ const w = res.headers.get('X-XciteDB-Export-Warning');
696
+ return {
697
+ bytes: buf,
698
+ contentType,
699
+ filename: fn,
700
+ ...(roundTrip ? { roundTrip } : {}),
701
+ ...(w ? { warning: w } : {}),
702
+ };
703
+ }
704
+ if (res.status === 401 &&
705
+ attempt === 0 &&
706
+ !no401Retry &&
707
+ (await this.tryRefreshSessionAfter401())) {
708
+ continue;
709
+ }
710
+ const text = await res.text();
711
+ let data;
712
+ try {
713
+ data = text ? JSON.parse(text) : null;
714
+ }
715
+ catch {
716
+ data = text;
717
+ }
718
+ const msg = typeof data === 'object' && data !== null && 'message' in data
719
+ ? String(data.message)
720
+ : res.statusText;
721
+ this.notifySessionInvalidIfNeeded(path, res.status);
722
+ throwForFailedHttp(res.status, path, data, msg || `HTTP ${res.status}`, res.headers.get('X-Request-Id') ?? undefined, res.headers.get('X-Client-Request-Id') ?? undefined);
723
+ }
724
+ this.notifySessionInvalidIfNeeded(path, 401);
725
+ throw new types_1.XCiteDBError('Request failed after retry', 401, null);
726
+ }
593
727
  /** Developer Bearer refresh first, then app-user refresh (no API key refresh). */
594
728
  async tryRefreshSessionAfter401() {
595
729
  if (this.accessToken && this.refreshToken) {
@@ -1221,16 +1355,35 @@ class XCiteDBClient {
1221
1355
  async deleteTag(name) {
1222
1356
  return this.deleteBookmark(name);
1223
1357
  }
1224
- async compare(from, to, includeContent) {
1225
- return this.request('POST', '/api/v1/compare', {
1358
+ async compare(from, to, third) {
1359
+ let include_content = false;
1360
+ let match_start;
1361
+ if (typeof third === 'boolean') {
1362
+ include_content = third;
1363
+ }
1364
+ else if (third !== undefined && typeof third === 'object') {
1365
+ include_content = third.includeContent ?? false;
1366
+ match_start = third.matchStart;
1367
+ }
1368
+ const body = {
1226
1369
  from,
1227
1370
  to,
1228
- include_content: includeContent ?? false,
1229
- });
1371
+ include_content,
1372
+ };
1373
+ if (match_start !== undefined && match_start !== '') {
1374
+ body.match_start = this.isoPrefixId(match_start);
1375
+ }
1376
+ const r = await this.request('POST', '/api/v1/compare', body);
1377
+ if (r && typeof r === 'object' && typeof r.match_start === 'string' && r.match_start.length > 0) {
1378
+ r.match_start = this.isoUnprefixId(r.match_start);
1379
+ }
1380
+ return r;
1230
1381
  }
1231
- /** @deprecated Use {@link compare}. */
1232
- async diff(from, to, includeContent) {
1233
- return this.compare(from, to, includeContent);
1382
+ async diff(from, to, third) {
1383
+ if (third === undefined || typeof third === 'boolean') {
1384
+ return this.compare(from, to, third);
1385
+ }
1386
+ return this.compare(from, to, third);
1234
1387
  }
1235
1388
  async publishWorkspace(targetWorkspace, sourceWorkspace, options) {
1236
1389
  const body = {
@@ -1246,6 +1399,45 @@ class XCiteDBClient {
1246
1399
  async mergeBranch(targetBranch, sourceBranch, options) {
1247
1400
  return this.publishWorkspace(targetBranch, sourceBranch, options);
1248
1401
  }
1402
+ /** List app-user draft workspaces visible to the caller (`GET /api/v1/user-workspaces`). */
1403
+ async listUserWorkspaces() {
1404
+ return this.request('GET', '/api/v1/user-workspaces');
1405
+ }
1406
+ /** Create a user workspace (`POST /api/v1/user-workspaces`). Returns the workspace record (top-level JSON). */
1407
+ async createUserWorkspace(name, options) {
1408
+ const body = { name };
1409
+ if (options?.sourceBranch)
1410
+ body.source_branch = options.sourceBranch;
1411
+ if (options?.sourceDate)
1412
+ body.source_date = options.sourceDate;
1413
+ return this.request('POST', '/api/v1/user-workspaces', body);
1414
+ }
1415
+ async getUserWorkspace(id) {
1416
+ return this.request('GET', `/api/v1/user-workspaces/${encodeURIComponent(id)}`);
1417
+ }
1418
+ async deleteUserWorkspace(id) {
1419
+ await this.request('DELETE', `/api/v1/user-workspaces/${encodeURIComponent(id)}`);
1420
+ }
1421
+ async addUserWorkspaceGrant(id, body) {
1422
+ return this.request('POST', `/api/v1/user-workspaces/${encodeURIComponent(id)}/grants`, body);
1423
+ }
1424
+ async removeUserWorkspaceGrant(id, body) {
1425
+ return this.request('DELETE', `/api/v1/user-workspaces/${encodeURIComponent(id)}/grants`, body);
1426
+ }
1427
+ async publishUserWorkspace(id, options) {
1428
+ const body = { auto_resolve: options?.autoResolve ?? 'none' };
1429
+ if (options?.message)
1430
+ body.message = options.message;
1431
+ return this.request('POST', `/api/v1/user-workspaces/${encodeURIComponent(id)}/publish`, body);
1432
+ }
1433
+ /**
1434
+ * Advance the user workspace fork to the current parent tip (`POST /api/v1/user-workspaces/{id}/rebase`).
1435
+ * On overlap with local edits, returns **409** with `conflicts` (same shape as publish) unless `autoResolve` resolves it.
1436
+ */
1437
+ async rebaseUserWorkspace(id, options) {
1438
+ const body = { auto_resolve: options?.autoResolve ?? 'none' };
1439
+ return this.request('POST', `/api/v1/user-workspaces/${encodeURIComponent(id)}/rebase`, body);
1440
+ }
1249
1441
  /**
1250
1442
  * Create `workspaceName` from {@link options.fromBranch} (or current context), run `fn` scoped to that workspace,
1251
1443
  * create a checkpoint, then publish back unless {@link options.autoMerge} is `false`.
@@ -1323,6 +1515,49 @@ class XCiteDBClient {
1323
1515
  }
1324
1516
  return r;
1325
1517
  }
1518
+ /**
1519
+ * Import a document (`POST /api/v1/documents/import`). Server converts DOCX, ODF, RTF, PDF, Markdown, AsciiDoc, or plain text
1520
+ * to shredded XML. Field name must be `file` (multipart).
1521
+ */
1522
+ async importDocument(file, options) {
1523
+ const form = new FormData();
1524
+ const blob = file instanceof Blob
1525
+ ? file
1526
+ : file instanceof ArrayBuffer
1527
+ ? new Blob([file], { type: 'application/octet-stream' })
1528
+ : new Blob([Uint8Array.from(file)], { type: 'application/octet-stream' });
1529
+ const uploadName = options?.filename ??
1530
+ (typeof File !== 'undefined' && file instanceof File ? file.name : 'upload.bin');
1531
+ form.append('file', blob, uploadName);
1532
+ const q = buildQuery({
1533
+ ...(options?.identifier !== undefined && options.identifier !== ''
1534
+ ? { identifier: this.isoPrefixId(options.identifier) }
1535
+ : {}),
1536
+ });
1537
+ const r = await this.requestFormPost(`/api/v1/documents/import${q}`, form);
1538
+ if (r && typeof r === 'object' && 'identifier' in r && typeof r.identifier === 'string') {
1539
+ r.identifier = this.isoUnprefixId(r.identifier);
1540
+ }
1541
+ return r;
1542
+ }
1543
+ /**
1544
+ * Export a document (`GET /api/v1/documents/export`). Returns binary bytes and response metadata.
1545
+ */
1546
+ async exportDocument(identifier, format = 'docx', options) {
1547
+ const q = buildQuery({
1548
+ identifier: this.isoPrefixId(identifier),
1549
+ format,
1550
+ strict: options?.strict ? '1' : undefined,
1551
+ });
1552
+ const r = await this.requestBinaryGet(`/api/v1/documents/export${q}`);
1553
+ return {
1554
+ bytes: r.bytes,
1555
+ contentType: r.contentType,
1556
+ filename: r.filename,
1557
+ ...(r.roundTrip ? { roundTrip: r.roundTrip } : {}),
1558
+ ...(r.warning ? { warning: r.warning } : {}),
1559
+ };
1560
+ }
1326
1561
  /**
1327
1562
  * @deprecated Use {@link writeXmlDocument}. This name was misleading: it writes **XML** via a JSON wrapper, not a JSON document.
1328
1563
  */
@@ -1339,6 +1574,28 @@ class XCiteDBClient {
1339
1574
  const rows = await this.request('GET', `/api/v1/documents/by-id${q}`);
1340
1575
  return Array.isArray(rows) ? rows.map((x) => this.isoUnprefixId(String(x))) : rows;
1341
1576
  }
1577
+ /**
1578
+ * Shallow read: root element with inline leaves plus `db:N*` placeholders for shredded child slots
1579
+ * (`flags=NoChildren,KeepIndexNodes,FirstMatch` on `GET /api/v1/documents/by-id`). Use with {@link listChildIdentifiers}
1580
+ * or {@link queryByIdentifierWithChildren} for sidebar / AST navigation without loading the full subtree.
1581
+ */
1582
+ async queryByIdentifierShallow(identifier, filter, pathFilter) {
1583
+ return this.queryByIdentifier(identifier, 'NoChildren,KeepIndexNodes,FirstMatch', filter, pathFilter);
1584
+ }
1585
+ /** Alias of {@link listIdentifierChildren} — immediate child segments under `parentPath` (identifier hierarchy). */
1586
+ async listChildIdentifiers(parentPath) {
1587
+ return this.listIdentifierChildren(parentPath);
1588
+ }
1589
+ /**
1590
+ * Parallel shallow node + one level of identifier children (`GET /by-id` + `GET /identifier-children`).
1591
+ */
1592
+ async queryByIdentifierWithChildren(identifier, filter, pathFilter) {
1593
+ const [node, listed] = await Promise.all([
1594
+ this.queryByIdentifierShallow(identifier, filter, pathFilter),
1595
+ this.listIdentifierChildren(identifier),
1596
+ ]);
1597
+ return { node, children: listed.children };
1598
+ }
1342
1599
  async queryDocuments(query, flags, filter, pathFilter) {
1343
1600
  const pq = this.isoPrefixQuery(query);
1344
1601
  const params = {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { XCiteDBClient } from './client';
2
2
  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';
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, 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, 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
4
  export { XCiteDBError, XCiteDBForbiddenError, XCiteDBNotFoundError, XCiteDBAuthError, XCiteDBLockConflictError, } from './types';
package/dist/types.d.ts CHANGED
@@ -399,6 +399,31 @@ export interface WriteDocumentOptions {
399
399
  is_top?: boolean;
400
400
  compare_attributes?: boolean;
401
401
  }
402
+ /** Supported source formats for `POST /api/v1/documents/import` (server detects from bytes / filename). */
403
+ export type DocumentImportFormat = 'docx' | 'odt' | 'rtf' | 'pdf' | 'txt' | 'md' | 'adoc';
404
+ /** JSON body on successful import (`201 Created`). */
405
+ export interface ImportDocumentResult {
406
+ identifier: string;
407
+ section_count: number;
408
+ source_format: string;
409
+ }
410
+ export interface ImportDocumentOptions {
411
+ /** Override root `db:identifier`; default is `/imported/<sanitized-filename>`. */
412
+ identifier?: string;
413
+ /** Original filename (helps sniff format when `File` is not available, e.g. Node `Blob` only). */
414
+ filename?: string;
415
+ }
416
+ /** `GET /api/v1/documents/export?format=…` */
417
+ export type DocumentExportFormat = 'xml' | 'docx' | 'odt' | 'pdf' | 'txt' | 'md' | 'adoc';
418
+ export interface ExportDocumentResult {
419
+ bytes: Uint8Array;
420
+ contentType: string;
421
+ filename: string;
422
+ /** Present for txt/md/adoc when server sends `X-XciteDB-Export-RoundTrip`. */
423
+ roundTrip?: 'exact' | 'lossy';
424
+ /** From `X-XciteDB-Export-Warning` when present. */
425
+ warning?: string;
426
+ }
402
427
  /** One row in `POST /api/v1/documents/batch` or `POST /api/v1/json-documents/batch` response `results`. */
403
428
  export interface DocumentBatchResultRow {
404
429
  index: number;
@@ -835,6 +860,8 @@ export interface CompareResult {
835
860
  date_key?: string;
836
861
  };
837
862
  total_changes: number;
863
+ /** Echo of request `match_start` when path-scoped compare was used. */
864
+ match_start?: string;
838
865
  }
839
866
  /** @deprecated Use {@link CompareResult}. */
840
867
  export type DiffResult = CompareResult;
@@ -857,6 +884,16 @@ export interface PublishResult {
857
884
  }
858
885
  /** @deprecated Use {@link PublishResult}. */
859
886
  export type MergeResult = PublishResult;
887
+ /** `POST /api/v1/user-workspaces/{id}/rebase` */
888
+ export interface RebaseUserWorkspaceResult {
889
+ status: 'completed' | 'conflicts';
890
+ message?: string;
891
+ old_from_date_key?: string;
892
+ new_from_date_key?: string;
893
+ conflicts?: PublishConflict[];
894
+ auto_mergeable?: string[];
895
+ would_expose?: string[];
896
+ }
860
897
  export type XCiteDBErrorExtras = {
861
898
  reason?: string;
862
899
  policyId?: string;
package/llms-full.txt CHANGED
@@ -35,10 +35,11 @@ Before reading the full reference, note these critical differences from typical
35
35
  - **Write at a specific date:** Set `X-Date` / `context.date` and write — every dated write is a **temporal revision**; no workspace or checkpoint required.
36
36
  - **Read as-of a date:** Set `X-Date` and read; the engine returns the revision at or before that instant.
37
37
  - **Isolated editing (draft → publish):** Create a **workspace**, edit, then **publish** to the target timeline; optional **checkpoints** for named snapshots.
38
+ - **App-user user workspaces (`_uw/<owner>/<slug>`):** **`GET/POST /api/v1/user-workspaces`**, grants, publish, **rebase** — see repository **`web/src/docs/content/user-workspaces.md`**.
38
39
  - **Audit trail:** Checkpoints carry messages and affected identifiers; **bookmarks** name a checkpoint.
39
40
  - **Undo a batch:** **Revert** to a prior checkpoint on that workspace.
40
41
 
41
- Legacy REST paths under `/api/v1/branches`, `/commits`, `/tags`, `/diff` remain **deprecated** aliases; prefer **`/api/v1/workspaces`**, **`/checkpoints`**, **`/bookmarks`**, **`/compare`**.
42
+ Legacy REST paths under `/api/v1/branches`, `/commits`, `/tags`, `/diff` remain **deprecated** aliases; prefer **`/api/v1/workspaces`**, **`/api/v1/user-workspaces`**, **`/checkpoints`**, **`/bookmarks`**, **`/compare`**.
42
43
 
43
44
  ## Common Pitfalls
44
45
 
@@ -433,6 +434,16 @@ The identifier is extracted from the `db:identifier` attribute in the root XML e
433
434
 
434
435
  Query parameters: `identifier` (required), `flags` (optional: `FirstMatch`, `IncludeChildren`, `NoChildren`, `KeepIndexNodes`), `filter`, `path_filter`.
435
436
 
437
+ ### Shallow node + subtree navigation
438
+
439
+ - **`flags=NoChildren,KeepIndexNodes,FirstMatch`**: returns the matching node with inline leaf content plus **`db:N1`**, **`db:N2`**, … placeholders for shredded child slots (avoids loading the full deep subtree in one response).
440
+ - Combine with **`GET /api/v1/documents/identifier-children?parent_path=…`** for the next level of hierarchy (`segment`, `full_path`, `is_identifier`, `has_children`).
441
+ - **JavaScript SDK:** `queryByIdentifierShallow(id)`, `listChildIdentifiers(parentPath?)`, `queryByIdentifierWithChildren(id)` (shallow XML + children in parallel).
442
+
443
+ ### Standalone subtree write (shredded model)
444
+
445
+ You may `POST /api/v1/documents` with a root element whose `db:identifier` is a nested path (e.g. `/spaces/u/docs/doc1/sec-1`) without re-posting the parent document: the engine writes that subtree only and updates the identifier hierarchy index for the parent automatically. Keep `is_top: true` (default) on the write.
446
+
436
447
  ## Delete document
437
448
 
438
449
  **`DELETE /api/v1/documents/by-id?identifier=/book/ch1`**
@@ -525,7 +536,7 @@ Returns `{ identifiers: string[], total, offset, limit }`.
525
536
 
526
537
  ## Export document
527
538
 
528
- **`GET /api/v1/documents/export?id=/book/ch1&format=...`**
539
+ **`GET /api/v1/documents/export?identifier=/book/ch1&format=...`**
529
540
 
530
541
  ## Export provision
531
542
 
@@ -846,6 +857,22 @@ Returns `{ status: "completed"|"conflicts", checkpoint?, commit?, merged_identif
846
857
 
847
858
  ---
848
859
 
860
+ # App-user workspaces (`/api/v1/user-workspaces`)
861
+
862
+ Per-user draft branches (`_uw/<owner>/<slug>`). See also **`web/src/docs/content/user-workspaces.md`**.
863
+
864
+ ## Rebase user workspace
865
+
866
+ **`POST /api/v1/user-workspaces/{id}/rebase`**
867
+
868
+ ```json
869
+ { "auto_resolve": "none" }
870
+ ```
871
+
872
+ Advances the workspace branch fork metadata to the **current tip** of its parent branch. Returns **`200`** with `{ status: "completed", message?, old_from_date_key, new_from_date_key, would_expose[], auto_mergeable[], conflicts: [] }`, or **`409`** with `{ status: "conflicts", conflicts: [...], auto_mergeable?, would_expose? }` when the same identifiers changed on both parent and workspace since the fork (same conflict shape as workspace publish).
873
+
874
+ ---
875
+
849
876
  # Checkpoints, bookmarks & compare
850
877
 
851
878
  ## Create checkpoint
@@ -895,13 +922,16 @@ Returns `{ checkpoints: [...], commits: [...], total, branch }` (mirrors).
895
922
  {
896
923
  "from": { "branch": "", "date": "2024-01-15T00:00:00" },
897
924
  "to": { "branch": "feature-x" },
898
- "include_content": true
925
+ "include_content": true,
926
+ "match_start": "/spaces/u/docs/doc1"
899
927
  }
900
928
  ```
901
929
 
930
+ Optional **`match_start`**: when set, only identifiers equal to that path or under it (prefix + `/`) are included in **`changes`** (tenant-wide noise is dropped).
931
+
902
932
  (Deprecated: **`POST /api/v1/diff`**; `date_key` still accepted internally but prefer **`date`**.)
903
933
 
904
- Returns `{ changes: [{ identifier, action: "added"|"modified"|"deleted", from_content?, to_content? }], total_changes }`.
934
+ Returns `{ changes: [{ identifier, action: "added"|"modified"|"deleted", from_content?, to_content? }], total_changes, match_start? }` ( **`match_start`** echoed when the filter was used).
905
935
 
906
936
  ---
907
937
 
@@ -1505,7 +1535,12 @@ interface DatabaseContext {
1505
1535
  ### XML Documents
1506
1536
  - `writeXmlDocument(xml, options?)` → `void` — XML via JSON body (recommended). Deprecated: `writeDocumentJson`.
1507
1537
  - `writeXML(xml)` → `void` — Raw XML body
1538
+ - `importDocument(file, options?)` → `ImportDocumentResult` — multipart `POST /api/v1/documents/import`
1539
+ - `exportDocument(identifier, format?, options?)` → `ExportDocumentResult` — binary `GET /api/v1/documents/export`
1508
1540
  - `queryByIdentifier(identifier, flags?, filter?, pathFilter?)` → `string[]`
1541
+ - `queryByIdentifierShallow(identifier, filter?, pathFilter?)` → `string[]` — `NoChildren,KeepIndexNodes,FirstMatch`
1542
+ - `listChildIdentifiers(parentPath?)` → `ListIdentifierChildrenResult` — alias of `listIdentifierChildren`
1543
+ - `queryByIdentifierWithChildren(identifier, filter?, pathFilter?)` → `{ node, children }`
1509
1544
  - `queryDocuments(query: XCiteQuery, flags?, filter?, pathFilter?)` → `string[]`
1510
1545
  - `deleteDocument(identifier)` → `void`
1511
1546
  - `listIdentifiers(query: XCiteQuery)` → `ListIdentifiersResult`
@@ -1543,6 +1578,13 @@ interface DatabaseContext {
1543
1578
  - `publishWorkspace(targetWorkspace, sourceWorkspace, options?)` → `PublishResult`
1544
1579
  - **Deprecated:** `withBranch`, `createBranch`, `listBranches`, `getBranch`, `deleteBranch`, `mergeBranch` (same HTTP behavior)
1545
1580
 
1581
+ ### App-user user workspaces (`_uw/…`)
1582
+ - `listUserWorkspaces()` → `{ user_workspaces: unknown[] }`
1583
+ - `createUserWorkspace(name, options?)` → `Record<string, unknown>`
1584
+ - `getUserWorkspace(id)` / `deleteUserWorkspace(id)` / `addUserWorkspaceGrant(id, body)` / `removeUserWorkspaceGrant(id, body)`
1585
+ - `publishUserWorkspace(id, options?)` → `PublishResult`-shaped JSON
1586
+ - `rebaseUserWorkspace(id, options?)` → `RebaseUserWorkspaceResult` — advances fork onto parent tip; **409** + `conflicts` on overlap (optional `autoResolve`: `none` | `source` | `target`)
1587
+
1546
1588
  ### Checkpoints
1547
1589
  - `createCheckpoint(message, author?)` → `CheckpointRecord`
1548
1590
  - `listCheckpoints(options?)` → `{ checkpoints, total, branch }` (wire JSON may also include `commits` mirror)
@@ -1559,7 +1601,7 @@ interface DatabaseContext {
1559
1601
  - **Deprecated:** `createTag`, `listTags`, `getTag`, `deleteTag`
1560
1602
 
1561
1603
  ### Compare
1562
- - `compare(from: CompareRef, to: CompareRef, includeContent?)` → `CompareResult`
1604
+ - `compare(from: CompareRef, to: CompareRef, includeContent?)` or `compare(from, to, { includeContent?, matchStart? })` → `CompareResult`
1563
1605
  - **Deprecated:** `diff(from: DiffRef, …)` — alias of `compare`
1564
1606
 
1565
1607
  ### Locks
package/llms.txt CHANGED
@@ -10,7 +10,7 @@ These are the most common sources of confusion for developers and AI assistants:
10
10
 
11
11
  2. **Identifiers are hierarchical, path-like strings** — e.g. `/us/bills/hr1`, `/manual/v2/chapter3`. They are NOT auto-generated UUIDs. The leading `/` is part of the identifier. Parent/child relationships are derived from the path structure (like a filesystem). The server indexes this hierarchy natively.
12
12
 
13
- 3. **Documents are XML or JSON, not arbitrary blobs.** XML documents are the primary document type, stored with structural shredding. JSON documents are a parallel store keyed by identifier string. Both are fully versioned.
13
+ 3. **Shredding vs sharding (do not confuse them).** **Shredding** breaks **XML and JSON** hierarchically into small **fragments** (elements, attributes, objects, fields, array slots, and similar structural pieces) so each fragment can live under its **own LMDB key/value**. That is **not** “sharding” in the usual distributed-database sense. **Sharding** in XCiteDB means **per-tenant** isolation (each **project** is its own shard of data); **finer-grain sharding** is planned separately. Sharding and shredding are unrelated axes. **Documents are XML or JSON, not arbitrary blobs.** XML documents are the primary document type; JSON documents are a parallel store keyed by identifier string. Both are fully versioned.
14
14
 
15
15
  4. **`X-Date` and temporal revision (not “only now”).** Workspace and date context travel as HTTP headers (`X-Workspace` preferred, `X-Branch` alias, `X-Date`), or the SDK `context` option — not as URL path segments. **`X-Date` is not limited to the current server time.** Pass any instant you need as **ISO 8601** (e.g. `2024-01-15T00:00:00`) or **`mm/dd/yyyy`** (optional **`:HH:MM:SS`**) — for example an **official publication or approval date**, or any historical “as of” moment. When **`X-Date` is set**, that value applies to **both** **reads** (query as-of that instant) **and** **writes** (the new revision is stored under that revision instant). When **`X-Date` is omitted** and you do **not** send **`X-Unversioned: true`**, **writes** use **flat** keys (no date suffix on that write); **reads** still resolve **as of the current time**. **`X-Unversioned: true`** requests explicit flat keys and must **not** be combined with **`X-Date`** (the server returns 400).
16
16
 
@@ -32,11 +32,13 @@ These are the most common sources of confusion for developers and AI assistants:
32
32
 
33
33
  - **Isolated editing** (draft/review/publish): Create a **workspace**, make changes, then **publish** to the main timeline. Optionally create **checkpoints** for named snapshots within the workspace.
34
34
 
35
+ - **App-user draft workspaces (`_uw/<owner>/<slug>`):** REST **`/api/v1/user-workspaces`**; SDK: `listUserWorkspaces`, `createUserWorkspace`, `getUserWorkspace`, `deleteUserWorkspace`, `addUserWorkspaceGrant`, `removeUserWorkspaceGrant`, `publishUserWorkspace`.
36
+
35
37
  - **Audit trail with named snapshots:** Create checkpoints with messages. List/inspect checkpoints for history.
36
38
 
37
39
  - **Undo a batch of changes:** **Revert** to a prior checkpoint (removes all changes after that point on the workspace).
38
40
 
39
- Legacy REST paths (`/api/v1/branches`, `/commits`, `/tags`, `/diff`) remain as **deprecated** aliases; prefer **`/api/v1/workspaces`**, **`/checkpoints`**, **`/bookmarks`**, **`/compare`**.
41
+ Legacy REST paths (`/api/v1/branches`, `/commits`, `/tags`, `/diff`) remain as **deprecated** aliases; prefer **`/api/v1/workspaces`**, **`/api/v1/user-workspaces`**, **`/checkpoints`**, **`/bookmarks`**, **`/compare`**.
40
42
 
41
43
  ## Glossary: Project id, display name, and groups
42
44
 
@@ -154,7 +156,11 @@ When you build a backend that calls XCiteDB on behalf of users:
154
156
 
155
157
  - **JSON documents merge by default.** `POST /api/v1/json-documents` merges the posted `data` into the existing document (object fields combined per XCiteDB meta merge rules). Send **`overwrite: true`** to clear all stored JSON under that document root first, then write only the new payload. SDKs accept the same flag (e.g. `writeJsonDocument(id, data, { overwrite: true })` in JavaScript).
156
158
  - **Metadata `mode` and arrays.** **`POST /api/v1/meta`** uses **`mode`**: default **`set`** writes or replaces at `path` (arrays are replaced in range; excess old indices cleared); **`append`** appends array elements after existing ones at `path`. Optional **`overwrite: true`** clears metadata under `path` before writing. JavaScript: **`appendMeta`** or **`addMeta(..., { mode: 'append' })`**; Python/C++: **`append_meta`** or **`add_meta`** with **`mode`** / **`overwrite`** (see SDK sections below).
157
- - **Prefer dictionary-style objects.** Shredded JSON metadata is keyed by field names. **Object maps** get per-field storage; when an object accumulates enough distinct field names (server default threshold **32**), XCiteDB switches automatically to **dictionary storage** (`{*}` plus per-field keys) for efficient indexed access. For lookup-heavy or wide records, use **`{ "key": value, ... }`** shapes (or one document per logical row) rather than opaque arrays when you need keyed reads.
159
+ - **Prefer dictionary-style objects.** **Shredded** JSON metadata is stored in **fragment-level** keys (e.g. per field name). **Object maps** get per-field storage; when an object accumulates enough distinct field names (server default threshold **32**), XCiteDB switches automatically to **dictionary storage** (`{*}` plus per-field keys) for efficient indexed access. For lookup-heavy or wide records, use **`{ "key": value, ... }`** shapes (or one document per logical row) rather than opaque arrays when you need keyed reads.
160
+
161
+ ## XML subtree writes (shredded model)
162
+
163
+ - **Standalone subtree root.** Because XCiteDB shreds XML per identifier, you can `writeXmlDocument('<sec db:identifier="/spaces/u/docs/doc1/sec-1">…</sec>')` and the engine writes only that subtree — you do **not** need to re-send the parent document. The parent's children index (`id_hier`) is updated automatically. Keep `is_top: true` (default) on `POST /api/v1/documents` even when the `db:identifier` is nested under another path. Use `compare_attributes: true` only when you need attribute-level diffing for triggers.
158
164
 
159
165
  ## JavaScript/TypeScript SDK (`@xcitedbs/client`)
160
166
 
@@ -189,6 +195,11 @@ await client.writeXmlDocument(
189
195
  '<chapter db:identifier="/manual/v1/intro"><title>Introduction</title></chapter>'
190
196
  );
191
197
 
198
+ // Import DOCX/PDF (etc.) server-side — shredded into XML (`POST /api/v1/documents/import`, multipart field `file`)
199
+ // const fileBlob = …; // Blob from <input type="file"> or fetch
200
+ // await client.importDocument(fileBlob, { identifier: '/manual/imported/spec', filename: 'spec.docx' });
201
+ // const out = await client.exportDocument('/manual/imported/spec', 'docx'); // bytes in `out.bytes`
202
+
192
203
  // Write a JSON document
193
204
  await client.writeJsonDocument('app.settings', { theme: 'dark', locale: 'en' });
194
205
 
@@ -272,7 +283,12 @@ interface XCiteDBClientOptions {
272
283
  **XML Documents:**
273
284
  - `writeXmlDocument(xml, options?)` — Store XML via JSON body (identifier inside XML). Deprecated alias: `writeDocumentJson`.
274
285
  - `writeXML(xml)` — Store raw XML with `Content-Type: application/xml`
286
+ - `importDocument(file, options?)` — `POST /api/v1/documents/import` (multipart `file`). Server converts DOCX, ODF, RTF, PDF, Markdown, AsciiDoc, or plain text into shredded XML. Optional `identifier` query override; optional `filename` for sniffing when not a `File`.
287
+ - `exportDocument(id, format?, options?)` — `GET /api/v1/documents/export` binary (`xml` | `docx` | `odt` | `pdf` | `txt` | `md` | `adoc`). Optional `strict` for text exports.
275
288
  - `queryByIdentifier(id, flags?, filter?)` — Get document(s) by identifier
289
+ - `queryByIdentifierShallow(id, filter?, pathFilter?)` — Same as `flags=NoChildren,KeepIndexNodes,FirstMatch` (skeleton: placeholders `db:N*` for shredded slots; avoids full subtree)
290
+ - `listChildIdentifiers(parentPath?)` — Alias of `listIdentifierChildren`; next level of identifier hierarchy
291
+ - `queryByIdentifierWithChildren(id, filter?, pathFilter?)` — `Promise.all` of shallow node + `identifier-children` for one expansion step
276
292
  - `queryDocuments(query, flags?)` — List/filter documents
277
293
  - `deleteDocument(identifier)` — Delete by identifier
278
294
  - `listIdentifiers(query)` — List known identifiers with pagination
@@ -297,11 +313,12 @@ interface XCiteDBClientOptions {
297
313
  - `listWorkspaces()` — List workspaces
298
314
  - `publishWorkspace(target, source, options?)` — Publish workspace changes to a target timeline
299
315
  - `deleteWorkspace(name)` — Delete workspace
316
+ - `listUserWorkspaces()` / `createUserWorkspace(name, options?)` / `getUserWorkspace(id)` / `deleteUserWorkspace(id)` / `addUserWorkspaceGrant(id, body)` / `removeUserWorkspaceGrant(id, body)` / `publishUserWorkspace(id, options?)` / `rebaseUserWorkspace(id, options?)` — App-user **`_uw/…`** workspaces (`rebase` advances the fork onto the parent tip; **409** + `conflicts` on overlap, like publish)
300
317
  - `createCheckpoint(message, author?)` — Named snapshot of current state
301
318
  - `listCheckpoints(options?)` — List checkpoints
302
319
  - `revertToCheckpoint(checkpointId)` — Revert workspace to a prior checkpoint
303
320
  - `applyCheckpoint(checkpointId, message?)` — Apply another checkpoint’s changes here
304
- - `compare(from, to, includeContent?)` — Compare revisions
321
+ - `compare(from, to, includeContent?)` or `compare(from, to, { includeContent?, matchStart? })` — Compare revisions; optional **`matchStart`** restricts results to that identifier and descendants (same as document `match_start` query semantics)
305
322
  - `createBookmark(name, checkpointId, message?)` — Bookmark a checkpoint
306
323
  - `listBookmarks()` / `deleteBookmark(name)` — Manage bookmarks
307
324
  - **Deprecated aliases:** `withBranch`, `createBranch`, `listBranches`, `mergeBranch`, `deleteBranch`, `createCommit`, `listCommits`, `rollbackToCommit`, `cherryPick`, `diff`, `createTag`, `listTags`, `deleteTag` (same HTTP behavior)
@@ -366,6 +383,8 @@ When calling `queryByIdentifier` or `queryDocuments`, the `flags` parameter cont
366
383
  - `'NoChildren'` — Exclude children
367
384
  - `'KeepIndexNodes'` — Include index/structural nodes
368
385
 
386
+ **Sidebar / AST navigation:** combine **`'NoChildren,KeepIndexNodes,FirstMatch'`** (or `queryByIdentifierShallow`) with **`listChildIdentifiers`** / **`listIdentifierChildren`** so each expansion is one shallow XML read plus one child list, instead of `IncludeChildren` loading the entire subtree.
387
+
369
388
  ### Errors
370
389
 
371
390
  All API errors throw `XCiteDBError` with `.status` (HTTP code) and `.body` (parsed response). Common codes: `401` unauthenticated, `403` forbidden by policy or RBAC, `404` not found, `409` conflict (lock), `422` validation, `423` project encrypted and locked, `429` rate limited. Many **ABAC** denials return `403` with `"message":"Forbidden"` plus optional **`policy_id`** and **`hint`** (check JWT `tenant_id` vs `project:` group middle segment).
@@ -417,6 +436,7 @@ auto ids = client.query_documents(q);
417
436
  ## Architecture Notes
418
437
 
419
438
  - **Multi-tenant**: Each project is an isolated tenant with its own data, users, keys, and policies.
439
+ - **Sharding vs shredding**: **Sharding** partitions the **tenant plane** (today, one isolated dataset per project; finer-grain sharding is on the roadmap). **Shredding** splits **individual documents** into **fragments** with their own keys—see convention 3.
420
440
  - **LMDB engine**: Data is memory-mapped; reads are microsecond-class on warm data.
421
441
  - **Versioning**: **Temporal revisions** are always on when using dates. **Workspaces** isolate draft work; **checkpoints** are optional named snapshots; **publish** merges a workspace into a target timeline. **`X-Date`** selects an instant for **as-of reads** and, when set, for **writes** under that revision (any calendar time you choose — e.g. publication date — not only “now”). Omit **`X-Date`** for **flat** writes on that request, or use **`X-Unversioned: true`** to state that explicitly.
422
442
  - **Two user tiers**: Platform operators manage infrastructure; App users are end-users of applications built on XciteDB.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcitedbs/client",
3
- "version": "0.2.20",
3
+ "version": "0.2.23",
4
4
  "description": "XCiteDB BaaS client SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",