@xcitedbs/client 0.2.10 → 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.d.ts +69 -6
- package/dist/client.js +135 -49
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +64 -26
- package/llms-full.txt +116 -93
- package/llms.txt +58 -40
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AccessCheckResult, AppAuthConfig, AppEmailConfig, AppEmailTemplates, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BranchInfo, CommitRecord, DatabaseContext, DiffRef, DiffResult, Flags, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, LogEntry, MergeResult, MetaValue, PlatformRegisterResult, PolicySubjectInput, UnqueryResult, UnqueryTemplate, PolicyUpdateResponse, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredTriggerResponse, TriggerDefinition, StoredPolicyResponse, SubscriptionOptions, TagRecord, TextSearchQuery, TextSearchResult, ProjectSearchSettings, ProjectSearchSettingsUpdate, VectorIndexEstimate, RagQueryOptions, RagQueryResult, RagStreamEvent, OAuthProvidersResponse, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspacesResponse, TokenPair, UserInfo, ApiKeyInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, XCiteDBJwtClaims, XCiteQuery, UserIsolationConfig } from './types';
|
|
1
|
+
import { AccessCheckResult, AppAuthConfig, AppEmailConfig, AppEmailTemplates, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BranchInfo, BookmarkRecord, CheckpointRecord, CommitRecord, CompareRef, CompareResult, DatabaseContext, DiffRef, DiffResult, Flags, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, LogEntry, MergeResult, PublishResult, WorkspaceInfo, MetaValue, PlatformRegisterResult, PolicySubjectInput, UnqueryResult, UnqueryTemplate, PolicyUpdateResponse, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredTriggerResponse, TriggerDefinition, StoredPolicyResponse, SubscriptionOptions, TagRecord, TextSearchQuery, TextSearchResult, ProjectSearchSettings, ProjectSearchSettingsUpdate, VectorIndexEstimate, RagQueryOptions, RagQueryResult, RagStreamEvent, OAuthProvidersResponse, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspacesResponse, TokenPair, UserInfo, ApiKeyInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, XCiteDBJwtClaims, XCiteQuery, UserIsolationConfig } from './types';
|
|
2
2
|
import { WebSocketSubscription } from './websocket';
|
|
3
3
|
export declare class XCiteDBClient {
|
|
4
4
|
private baseUrl;
|
|
@@ -293,12 +293,34 @@ export declare class XCiteDBClient {
|
|
|
293
293
|
* the server (namespace + shared paths). Does not send `X-Prefix`; identifiers in requests are rewritten.
|
|
294
294
|
*/
|
|
295
295
|
enableUserIsolation(): Promise<UserIsolationConfig>;
|
|
296
|
+
createWorkspace(name: string, fromBranch?: string, fromDate?: string): Promise<void>;
|
|
297
|
+
/** @deprecated Use {@link createWorkspace}. */
|
|
296
298
|
createBranch(name: string, fromBranch?: string, fromDate?: string): Promise<void>;
|
|
299
|
+
deleteWorkspace(name: string): Promise<void>;
|
|
300
|
+
/** @deprecated Use {@link deleteWorkspace}. */
|
|
297
301
|
deleteBranch(name: string): Promise<void>;
|
|
302
|
+
deleteWorkspaceRevision(workspace: string, date: string): Promise<void>;
|
|
303
|
+
/** @deprecated Use {@link deleteWorkspaceRevision}. */
|
|
298
304
|
deleteRevision(branch: string, date: string): Promise<void>;
|
|
305
|
+
listWorkspaces(): Promise<WorkspaceInfo[]>;
|
|
306
|
+
/** @deprecated Use {@link listWorkspaces}. */
|
|
299
307
|
listBranches(): Promise<BranchInfo[]>;
|
|
308
|
+
getWorkspace(name: string): Promise<WorkspaceInfo>;
|
|
309
|
+
/** @deprecated Use {@link getWorkspace}. */
|
|
300
310
|
getBranch(name: string): Promise<BranchInfo>;
|
|
311
|
+
createCheckpoint(message: string, author?: string): Promise<CheckpointRecord>;
|
|
312
|
+
/** @deprecated Use {@link createCheckpoint}. */
|
|
301
313
|
createCommit(message: string, author?: string): Promise<CommitRecord>;
|
|
314
|
+
listCheckpoints(options?: {
|
|
315
|
+
branch?: string;
|
|
316
|
+
limit?: number;
|
|
317
|
+
offset?: number;
|
|
318
|
+
}): Promise<{
|
|
319
|
+
checkpoints: CheckpointRecord[];
|
|
320
|
+
total: number;
|
|
321
|
+
branch: string;
|
|
322
|
+
}>;
|
|
323
|
+
/** @deprecated Use {@link listCheckpoints}. */
|
|
302
324
|
listCommits(options?: {
|
|
303
325
|
branch?: string;
|
|
304
326
|
limit?: number;
|
|
@@ -308,13 +330,33 @@ export declare class XCiteDBClient {
|
|
|
308
330
|
total: number;
|
|
309
331
|
branch: string;
|
|
310
332
|
}>;
|
|
333
|
+
getCheckpoint(checkpointId: string): Promise<CheckpointRecord>;
|
|
334
|
+
/** @deprecated Use {@link getCheckpoint}. */
|
|
311
335
|
getCommit(commitId: string): Promise<CommitRecord>;
|
|
336
|
+
revertToCheckpoint(checkpointId: string, _confirm?: boolean): Promise<{
|
|
337
|
+
rolled_back_checkpoints: string[];
|
|
338
|
+
rolled_back_commits?: string[];
|
|
339
|
+
current_tip: string;
|
|
340
|
+
}>;
|
|
341
|
+
/** @deprecated Use {@link revertToCheckpoint}. */
|
|
312
342
|
rollbackToCommit(commitId: string, _confirm?: boolean): Promise<{
|
|
313
343
|
rolled_back_commits: string[];
|
|
314
344
|
current_tip: string;
|
|
315
345
|
}>;
|
|
346
|
+
applyCheckpoint(checkpointId: string, message?: string, author?: string): Promise<CheckpointRecord>;
|
|
347
|
+
/** @deprecated Use {@link applyCheckpoint}. */
|
|
316
348
|
cherryPick(commitId: string, message?: string, author?: string): Promise<CommitRecord>;
|
|
349
|
+
createBookmark(name: string, checkpointId: string, message?: string, author?: string): Promise<BookmarkRecord>;
|
|
350
|
+
/** @deprecated Use {@link createBookmark}. */
|
|
317
351
|
createTag(name: string, commitId: string, message?: string, author?: string): Promise<TagRecord>;
|
|
352
|
+
listBookmarks(options?: {
|
|
353
|
+
limit?: number;
|
|
354
|
+
offset?: number;
|
|
355
|
+
}): Promise<{
|
|
356
|
+
bookmarks: BookmarkRecord[];
|
|
357
|
+
total: number;
|
|
358
|
+
}>;
|
|
359
|
+
/** @deprecated Use {@link listBookmarks}. */
|
|
318
360
|
listTags(options?: {
|
|
319
361
|
limit?: number;
|
|
320
362
|
offset?: number;
|
|
@@ -322,23 +364,44 @@ export declare class XCiteDBClient {
|
|
|
322
364
|
tags: TagRecord[];
|
|
323
365
|
total: number;
|
|
324
366
|
}>;
|
|
367
|
+
getBookmark(name: string): Promise<BookmarkRecord>;
|
|
368
|
+
/** @deprecated Use {@link getBookmark}. */
|
|
325
369
|
getTag(name: string): Promise<TagRecord>;
|
|
370
|
+
deleteBookmark(name: string): Promise<void>;
|
|
371
|
+
/** @deprecated Use {@link deleteBookmark}. */
|
|
326
372
|
deleteTag(name: string): Promise<void>;
|
|
373
|
+
compare(from: CompareRef, to: CompareRef, includeContent?: boolean): Promise<CompareResult>;
|
|
374
|
+
/** @deprecated Use {@link compare}. */
|
|
327
375
|
diff(from: DiffRef, to: DiffRef, includeContent?: boolean): Promise<DiffResult>;
|
|
376
|
+
publishWorkspace(targetWorkspace: string, sourceWorkspace: string, options?: {
|
|
377
|
+
message?: string;
|
|
378
|
+
autoResolve?: 'none' | 'source' | 'target';
|
|
379
|
+
}): Promise<PublishResult>;
|
|
380
|
+
/** @deprecated Use {@link publishWorkspace}. */
|
|
328
381
|
mergeBranch(targetBranch: string, sourceBranch: string, options?: {
|
|
329
382
|
message?: string;
|
|
330
383
|
autoResolve?: 'none' | 'source' | 'target';
|
|
331
384
|
}): Promise<MergeResult>;
|
|
332
385
|
/**
|
|
333
|
-
* Create `
|
|
334
|
-
* create a
|
|
335
|
-
* Restores previous {@link DatabaseContext} afterward.
|
|
386
|
+
* Create `workspaceName` from {@link options.fromBranch} (or current context), run `fn` scoped to that workspace,
|
|
387
|
+
* create a checkpoint, then publish back unless {@link options.autoMerge} is `false`.
|
|
336
388
|
*/
|
|
389
|
+
withWorkspace<T>(workspaceName: string, fn: (client: XCiteDBClient) => Promise<T>, options?: {
|
|
390
|
+
message?: string;
|
|
391
|
+
/** When true (default), publish `workspaceName` into the parent workspace after checkpoint. */
|
|
392
|
+
autoMerge?: boolean;
|
|
393
|
+
/** Workspace to fork from (default: current `context.branch` / `context.workspace`, or `""`). */
|
|
394
|
+
fromBranch?: string;
|
|
395
|
+
author?: string;
|
|
396
|
+
}): Promise<{
|
|
397
|
+
result: T;
|
|
398
|
+
checkpoint?: CheckpointRecord;
|
|
399
|
+
publish?: PublishResult;
|
|
400
|
+
}>;
|
|
401
|
+
/** @deprecated Use {@link withWorkspace}. */
|
|
337
402
|
withBranch<T>(branchName: string, fn: (client: XCiteDBClient) => Promise<T>, options?: {
|
|
338
403
|
message?: string;
|
|
339
|
-
/** When true (default), merge `branchName` into the parent branch after commit. */
|
|
340
404
|
autoMerge?: boolean;
|
|
341
|
-
/** Branch to fork from (default: current `context.branch`, or `""`). */
|
|
342
405
|
fromBranch?: string;
|
|
343
406
|
author?: string;
|
|
344
407
|
}): Promise<{
|
package/dist/client.js
CHANGED
|
@@ -345,7 +345,11 @@ class XCiteDBClient {
|
|
|
345
345
|
this.projectId = projectId;
|
|
346
346
|
}
|
|
347
347
|
setContext(ctx) {
|
|
348
|
-
|
|
348
|
+
const next = { ...this.defaultContext, ...ctx };
|
|
349
|
+
if (ctx.workspace !== undefined) {
|
|
350
|
+
next.branch = ctx.workspace;
|
|
351
|
+
}
|
|
352
|
+
this.defaultContext = next;
|
|
349
353
|
}
|
|
350
354
|
setTokens(access, refresh) {
|
|
351
355
|
this.accessToken = access;
|
|
@@ -367,8 +371,9 @@ class XCiteDBClient {
|
|
|
367
371
|
contextHeaders() {
|
|
368
372
|
const h = {};
|
|
369
373
|
const c = this.defaultContext;
|
|
370
|
-
|
|
371
|
-
|
|
374
|
+
const ws = c.workspace ?? c.branch;
|
|
375
|
+
if (ws)
|
|
376
|
+
h['X-Workspace'] = ws;
|
|
372
377
|
if (c.date)
|
|
373
378
|
h['X-Date'] = c.date;
|
|
374
379
|
if (c.prefix)
|
|
@@ -941,121 +946,202 @@ class XCiteDBClient {
|
|
|
941
946
|
}
|
|
942
947
|
return cfg;
|
|
943
948
|
}
|
|
944
|
-
async
|
|
949
|
+
async createWorkspace(name, fromBranch, fromDate) {
|
|
945
950
|
const body = { name };
|
|
946
951
|
if (fromBranch)
|
|
947
952
|
body.from_branch = fromBranch;
|
|
948
953
|
if (fromDate)
|
|
949
954
|
body.from_date = fromDate;
|
|
950
|
-
await this.request('POST', '/api/v1/
|
|
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);
|
|
960
|
+
}
|
|
961
|
+
async deleteWorkspace(name) {
|
|
962
|
+
await this.request('DELETE', `/api/v1/workspaces/${encodeURIComponent(name)}`);
|
|
951
963
|
}
|
|
964
|
+
/** @deprecated Use {@link deleteWorkspace}. */
|
|
952
965
|
async deleteBranch(name) {
|
|
953
|
-
|
|
966
|
+
return this.deleteWorkspace(name);
|
|
967
|
+
}
|
|
968
|
+
async deleteWorkspaceRevision(workspace, date) {
|
|
969
|
+
await this.request('DELETE', `/api/v1/workspaces/${encodeURIComponent(workspace)}/revisions/${encodeURIComponent(date)}`);
|
|
954
970
|
}
|
|
971
|
+
/** @deprecated Use {@link deleteWorkspaceRevision}. */
|
|
955
972
|
async deleteRevision(branch, date) {
|
|
956
|
-
|
|
973
|
+
return this.deleteWorkspaceRevision(branch, date);
|
|
957
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}. */
|
|
958
980
|
async listBranches() {
|
|
959
|
-
|
|
960
|
-
|
|
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);
|
|
961
986
|
}
|
|
987
|
+
/** @deprecated Use {@link getWorkspace}. */
|
|
962
988
|
async getBranch(name) {
|
|
963
|
-
|
|
964
|
-
return r.branch;
|
|
989
|
+
return this.getWorkspace(name);
|
|
965
990
|
}
|
|
966
|
-
async
|
|
991
|
+
async createCheckpoint(message, author) {
|
|
967
992
|
const body = { message };
|
|
968
993
|
if (author)
|
|
969
994
|
body.author = author;
|
|
970
|
-
const r = await this.request('POST', '/api/v1/
|
|
971
|
-
return r.commit;
|
|
995
|
+
const r = await this.request('POST', '/api/v1/checkpoints', body);
|
|
996
|
+
return (r.checkpoint ?? r.commit);
|
|
972
997
|
}
|
|
973
|
-
|
|
998
|
+
/** @deprecated Use {@link createCheckpoint}. */
|
|
999
|
+
async createCommit(message, author) {
|
|
1000
|
+
return this.createCheckpoint(message, author);
|
|
1001
|
+
}
|
|
1002
|
+
async listCheckpoints(options) {
|
|
974
1003
|
const q = buildQuery({
|
|
975
1004
|
branch: options?.branch,
|
|
976
1005
|
limit: options?.limit,
|
|
977
1006
|
offset: options?.offset,
|
|
978
1007
|
});
|
|
979
|
-
|
|
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 };
|
|
980
1016
|
}
|
|
1017
|
+
async getCheckpoint(checkpointId) {
|
|
1018
|
+
const r = await this.request('GET', `/api/v1/checkpoints/${encodeURIComponent(checkpointId)}`);
|
|
1019
|
+
return (r.checkpoint ?? r.commit);
|
|
1020
|
+
}
|
|
1021
|
+
/** @deprecated Use {@link getCheckpoint}. */
|
|
981
1022
|
async getCommit(commitId) {
|
|
982
|
-
|
|
983
|
-
return r.commit;
|
|
1023
|
+
return this.getCheckpoint(commitId);
|
|
984
1024
|
}
|
|
985
|
-
async
|
|
986
|
-
return this.request('POST', `/api/v1/
|
|
1025
|
+
async revertToCheckpoint(checkpointId, _confirm) {
|
|
1026
|
+
return this.request('POST', `/api/v1/checkpoints/${encodeURIComponent(checkpointId)}/revert`, {
|
|
987
1027
|
confirm: true,
|
|
988
1028
|
});
|
|
989
1029
|
}
|
|
990
|
-
|
|
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) {
|
|
991
1039
|
const body = {};
|
|
992
1040
|
if (message)
|
|
993
1041
|
body.message = message;
|
|
994
1042
|
if (author)
|
|
995
1043
|
body.author = author;
|
|
996
|
-
const r = await this.request('POST', `/api/v1/
|
|
997
|
-
return r.commit;
|
|
1044
|
+
const r = await this.request('POST', `/api/v1/checkpoints/${encodeURIComponent(checkpointId)}/apply`, body);
|
|
1045
|
+
return (r.checkpoint ?? r.commit);
|
|
998
1046
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
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 };
|
|
1001
1053
|
if (message)
|
|
1002
1054
|
body.message = message;
|
|
1003
1055
|
if (author)
|
|
1004
1056
|
body.author = author;
|
|
1005
|
-
const r = await this.request('POST', '/api/v1/
|
|
1006
|
-
return r.tag;
|
|
1057
|
+
const r = await this.request('POST', '/api/v1/bookmarks', body);
|
|
1058
|
+
return (r.bookmark ?? r.tag);
|
|
1007
1059
|
}
|
|
1008
|
-
|
|
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) {
|
|
1009
1065
|
const q = buildQuery({ limit: options?.limit, offset: options?.offset });
|
|
1010
|
-
|
|
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 };
|
|
1011
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}. */
|
|
1012
1080
|
async getTag(name) {
|
|
1013
|
-
|
|
1014
|
-
return r.tag;
|
|
1081
|
+
return this.getBookmark(name);
|
|
1015
1082
|
}
|
|
1083
|
+
async deleteBookmark(name) {
|
|
1084
|
+
await this.request('DELETE', `/api/v1/bookmarks/${encodeURIComponent(name)}`);
|
|
1085
|
+
}
|
|
1086
|
+
/** @deprecated Use {@link deleteBookmark}. */
|
|
1016
1087
|
async deleteTag(name) {
|
|
1017
|
-
|
|
1088
|
+
return this.deleteBookmark(name);
|
|
1018
1089
|
}
|
|
1019
|
-
async
|
|
1020
|
-
return this.request('POST', '/api/v1/
|
|
1090
|
+
async compare(from, to, includeContent) {
|
|
1091
|
+
return this.request('POST', '/api/v1/compare', {
|
|
1021
1092
|
from,
|
|
1022
1093
|
to,
|
|
1023
1094
|
include_content: includeContent ?? false,
|
|
1024
1095
|
});
|
|
1025
1096
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
+
};
|
|
1028
1106
|
if (options?.message)
|
|
1029
1107
|
body.message = options.message;
|
|
1030
1108
|
body.auto_resolve = options?.autoResolve ?? 'none';
|
|
1031
|
-
return this.request('POST', `/api/v1/
|
|
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);
|
|
1032
1114
|
}
|
|
1033
1115
|
/**
|
|
1034
|
-
* Create `
|
|
1035
|
-
* create a
|
|
1036
|
-
* 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`.
|
|
1037
1118
|
*/
|
|
1038
|
-
async
|
|
1119
|
+
async withWorkspace(workspaceName, fn, options) {
|
|
1039
1120
|
const prev = { ...this.defaultContext };
|
|
1040
|
-
const
|
|
1041
|
-
let
|
|
1042
|
-
let
|
|
1121
|
+
const fromWs = options?.fromBranch ?? prev.branch ?? prev.workspace ?? '';
|
|
1122
|
+
let checkpoint;
|
|
1123
|
+
let publish;
|
|
1043
1124
|
try {
|
|
1044
|
-
await this.
|
|
1045
|
-
this.setContext({ branch:
|
|
1125
|
+
await this.createWorkspace(workspaceName, fromWs || undefined, prev.date || undefined);
|
|
1126
|
+
this.setContext({ workspace: workspaceName, branch: workspaceName });
|
|
1046
1127
|
const result = await fn(this);
|
|
1047
|
-
|
|
1128
|
+
checkpoint = await this.createCheckpoint(options?.message ?? `Workspace ${workspaceName}`, options?.author);
|
|
1048
1129
|
if (options?.autoMerge !== false) {
|
|
1049
|
-
|
|
1130
|
+
publish = await this.publishWorkspace(fromWs, workspaceName, {
|
|
1050
1131
|
message: options?.message,
|
|
1051
1132
|
});
|
|
1052
1133
|
}
|
|
1053
|
-
return { result,
|
|
1134
|
+
return { result, checkpoint, publish };
|
|
1054
1135
|
}
|
|
1055
1136
|
finally {
|
|
1056
1137
|
this.setContext(prev);
|
|
1057
1138
|
}
|
|
1058
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
|
+
}
|
|
1059
1145
|
/** Send raw XML body (`Content-Type: application/xml`). For JSON wrapper + options use `writeXmlDocument`. */
|
|
1060
1146
|
async writeXML(xml, _options) {
|
|
1061
1147
|
const payload = this.isoApplyXmlDbIdentifier(xml);
|
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, DatabaseContext, Flags, JsonDocumentData, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, ProjectSearchSettings, ProjectSearchSettingsUpdate, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RagQueryOptions, RagQueryResult, RagStreamEvent, RealtimeEvent, SearchIndexingProgress, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TokenPair, UserInfo, UserIsolationConfig, UserIsolationOptions, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, 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, Flags, JsonDocumentData, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, MergeConflict, MergeResult, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, ProjectSearchSettings, ProjectSearchSettingsUpdate, 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, UserIsolationOptions, WorkspaceInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, XCiteDBJwtClaims, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
|
|
4
4
|
export { XCiteDBError } from './types';
|
package/dist/types.d.ts
CHANGED
|
@@ -307,6 +307,12 @@ export interface ApiKeyInfo {
|
|
|
307
307
|
}
|
|
308
308
|
export type Flags = 'None' | 'FirstMatch' | 'IncludeChildren' | 'NoChildren' | 'KeepIndexNodes' | 'PrevIfDeleted' | string;
|
|
309
309
|
export interface DatabaseContext {
|
|
310
|
+
/**
|
|
311
|
+
* Workspace name (isolated editing environment). Empty / unset = main timeline.
|
|
312
|
+
* Sent as `X-Workspace` (preferred); see also {@link DatabaseContext.branch}.
|
|
313
|
+
*/
|
|
314
|
+
workspace?: string;
|
|
315
|
+
/** @deprecated Prefer {@link DatabaseContext.workspace}. Sent as `X-Branch` when `workspace` is unset. */
|
|
310
316
|
branch?: string;
|
|
311
317
|
date?: string;
|
|
312
318
|
prefix?: string;
|
|
@@ -596,83 +602,115 @@ export interface StoredTriggerResponse {
|
|
|
596
602
|
trigger_id: string;
|
|
597
603
|
trigger: TriggerDefinition;
|
|
598
604
|
}
|
|
599
|
-
/**
|
|
600
|
-
export interface
|
|
605
|
+
/** Named snapshot on a workspace (checkpoint). */
|
|
606
|
+
export interface CheckpointRecord {
|
|
601
607
|
id: string;
|
|
608
|
+
/** Same as `id` when returned by newer APIs. */
|
|
609
|
+
checkpoint_id?: string;
|
|
602
610
|
branch: string;
|
|
603
|
-
/**
|
|
604
|
-
date_key: string;
|
|
605
|
-
date_human?: string;
|
|
606
|
-
/** Alias for human-readable date (same as date_human when present). */
|
|
611
|
+
/** Human-readable revision date/time. */
|
|
607
612
|
date?: string;
|
|
613
|
+
date_human?: string;
|
|
614
|
+
/** @deprecated Internal key; omitted from newer API responses. */
|
|
615
|
+
date_key?: string;
|
|
608
616
|
message: string;
|
|
609
617
|
author: string;
|
|
610
618
|
parent_id: string | null;
|
|
611
619
|
merge_source?: {
|
|
612
620
|
branch: string;
|
|
613
|
-
|
|
621
|
+
checkpoint_id?: string;
|
|
622
|
+
commit_id?: string;
|
|
614
623
|
};
|
|
615
624
|
cherry_picked_from?: string;
|
|
616
625
|
affected_identifiers: string[];
|
|
617
626
|
status: 'active' | 'rolled_back';
|
|
618
627
|
created_at: number;
|
|
619
628
|
}
|
|
620
|
-
/**
|
|
621
|
-
export
|
|
629
|
+
/** @deprecated Use {@link CheckpointRecord}. */
|
|
630
|
+
export type CommitRecord = CheckpointRecord;
|
|
631
|
+
/** Workspace row from `GET /api/v1/workspaces` (legacy: `/api/v1/branches`). */
|
|
632
|
+
export interface WorkspaceInfo {
|
|
622
633
|
name: string;
|
|
623
634
|
from_branch: string;
|
|
624
635
|
from_date: string;
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
tip_commit?:
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
export
|
|
636
|
+
tip_checkpoint?: CheckpointRecord;
|
|
637
|
+
/** @deprecated Alias of {@link WorkspaceInfo.tip_checkpoint}. */
|
|
638
|
+
tip_commit?: CheckpointRecord;
|
|
639
|
+
}
|
|
640
|
+
/** @deprecated Use {@link WorkspaceInfo}. */
|
|
641
|
+
export type BranchInfo = WorkspaceInfo;
|
|
642
|
+
/** @deprecated Use {@link WorkspaceInfo}. */
|
|
643
|
+
export type BranchListItem = WorkspaceInfo;
|
|
644
|
+
export interface BookmarkRecord {
|
|
631
645
|
name: string;
|
|
632
|
-
|
|
646
|
+
checkpoint_id: string;
|
|
647
|
+
/** @deprecated Same as `checkpoint_id`. */
|
|
648
|
+
commit_id?: string;
|
|
633
649
|
branch: string;
|
|
634
|
-
|
|
650
|
+
date?: string;
|
|
651
|
+
/** @deprecated */
|
|
652
|
+
date_key?: string;
|
|
635
653
|
message?: string;
|
|
636
654
|
author?: string;
|
|
637
655
|
created_at: number;
|
|
638
656
|
}
|
|
639
|
-
|
|
657
|
+
/** @deprecated Use {@link BookmarkRecord}. */
|
|
658
|
+
export type TagRecord = BookmarkRecord;
|
|
659
|
+
export interface CompareEntry {
|
|
640
660
|
identifier: string;
|
|
641
661
|
action: 'added' | 'modified' | 'deleted';
|
|
642
662
|
from_content?: string;
|
|
643
663
|
to_content?: string;
|
|
644
664
|
}
|
|
645
|
-
|
|
665
|
+
/** @deprecated Use {@link CompareEntry}. */
|
|
666
|
+
export type DiffEntry = CompareEntry;
|
|
667
|
+
export interface CompareRef {
|
|
646
668
|
branch?: string;
|
|
647
|
-
/**
|
|
669
|
+
/** Human-readable date (preferred). */
|
|
670
|
+
date?: string;
|
|
671
|
+
checkpoint_id?: string;
|
|
672
|
+
/** @deprecated Undocumented fallback. */
|
|
648
673
|
date_key?: string;
|
|
674
|
+
/** @deprecated Use {@link CompareRef.checkpoint_id}. */
|
|
649
675
|
commit_id?: string;
|
|
650
676
|
}
|
|
651
|
-
|
|
652
|
-
|
|
677
|
+
/** @deprecated Use {@link CompareRef}. */
|
|
678
|
+
export type DiffRef = CompareRef;
|
|
679
|
+
export interface CompareResult {
|
|
680
|
+
changes: CompareEntry[];
|
|
653
681
|
from: {
|
|
654
682
|
branch?: string;
|
|
683
|
+
date?: string;
|
|
655
684
|
date_key?: string;
|
|
656
685
|
};
|
|
657
686
|
to: {
|
|
658
687
|
branch?: string;
|
|
688
|
+
date?: string;
|
|
659
689
|
date_key?: string;
|
|
660
690
|
};
|
|
661
691
|
total_changes: number;
|
|
662
692
|
}
|
|
663
|
-
|
|
693
|
+
/** @deprecated Use {@link CompareResult}. */
|
|
694
|
+
export type DiffResult = CompareResult;
|
|
695
|
+
export interface PublishConflict {
|
|
664
696
|
identifier: string;
|
|
665
697
|
source_action: string;
|
|
666
698
|
target_action: string;
|
|
667
699
|
}
|
|
668
|
-
|
|
700
|
+
/** @deprecated Use {@link PublishConflict}. */
|
|
701
|
+
export type MergeConflict = PublishConflict;
|
|
702
|
+
export interface PublishResult {
|
|
669
703
|
status: 'completed' | 'conflicts';
|
|
670
|
-
|
|
704
|
+
checkpoint?: CheckpointRecord;
|
|
705
|
+
/** @deprecated Alias of {@link PublishResult.checkpoint}. */
|
|
706
|
+
commit?: CheckpointRecord;
|
|
671
707
|
merged_identifiers?: string[];
|
|
672
|
-
conflicts?:
|
|
708
|
+
conflicts?: PublishConflict[];
|
|
673
709
|
auto_mergeable?: string[];
|
|
674
710
|
message?: string;
|
|
675
711
|
}
|
|
712
|
+
/** @deprecated Use {@link PublishResult}. */
|
|
713
|
+
export type MergeResult = PublishResult;
|
|
676
714
|
export declare class XCiteDBError extends Error {
|
|
677
715
|
readonly status: number;
|
|
678
716
|
readonly body?: unknown | undefined;
|
package/llms-full.txt
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
Before reading the full reference, note these critical differences from typical databases:
|
|
10
10
|
|
|
11
|
-
1. **The default
|
|
11
|
+
1. **The default workspace is the empty string `""`, not `"main"`.** When no `X-Workspace` header is sent (or `context.workspace` / `context.branch` is omitted/empty), the server operates on the root timeline. `X-Branch` is a supported alias of `X-Workspace`. A workspace named `"main"` may exist as user-created metadata, but it is not required or special.
|
|
12
12
|
|
|
13
13
|
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.
|
|
14
14
|
|
|
15
15
|
3. **Documents are XML or JSON, not arbitrary blobs.** XML documents are the primary document type. JSON documents are a parallel store. Both are fully versioned.
|
|
16
16
|
|
|
17
|
-
4. **Context (
|
|
17
|
+
4. **Context (workspace + date) travels as HTTP headers** (`X-Workspace` preferred, `X-Branch` alias, `X-Date`, and optionally `X-Unversioned` for explicit flat writes), not URL path segments.
|
|
18
18
|
|
|
19
19
|
5. **XML documents carry their identifier inside the XML** via a `db:identifier` attribute on the root element.
|
|
20
20
|
|
|
@@ -28,6 +28,16 @@ Before reading the full reference, note these critical differences from typical
|
|
|
28
28
|
|
|
29
29
|
10. **Ephemeral test sessions.** `POST /api/v1/test/sessions` (authenticated) returns a UUID **`session_token`**. Clients send **`X-Test-Session: <token>`** on API calls to use an isolated, TTL- and quota-limited LMDB instead of production project data. Unless **`X-Test-Auth: required`** is set, **developer** JWT/API-key checks are bypassed (synthetic admin for wet tests), but **app-user** identity via **`X-App-User-Token`** or Bearer app-user JWT is still recognized. Management routes under **`/api/v1/test/*`** must not include `X-Test-Session`. The test store starts empty (no cloned production project config).
|
|
30
30
|
|
|
31
|
+
## Choosing the Right Versioning Approach
|
|
32
|
+
|
|
33
|
+
- **Write at a specific date:** Set `X-Date` / `context.date` and write — every dated write is a **temporal revision**; no workspace or checkpoint required.
|
|
34
|
+
- **Read as-of a date:** Set `X-Date` and read; the engine returns the revision at or before that instant.
|
|
35
|
+
- **Isolated editing (draft → publish):** Create a **workspace**, edit, then **publish** to the target timeline; optional **checkpoints** for named snapshots.
|
|
36
|
+
- **Audit trail:** Checkpoints carry messages and affected identifiers; **bookmarks** name a checkpoint.
|
|
37
|
+
- **Undo a batch:** **Revert** to a prior checkpoint on that workspace.
|
|
38
|
+
|
|
39
|
+
Legacy REST paths under `/api/v1/branches`, `/commits`, `/tags`, `/diff` remain **deprecated** aliases; prefer **`/api/v1/workspaces`**, **`/checkpoints`**, **`/bookmarks`**, **`/compare`**.
|
|
40
|
+
|
|
31
41
|
## Common Pitfalls
|
|
32
42
|
|
|
33
43
|
1. **`baseUrl` must be origin-only (no `/api` or `/api/v1` path).** The SDK prepends `/api/v1/…` to every request. If `baseUrl` is `https://host/api/v1`, requests hit `/api/v1/api/v1/…` which typically returns **405 Not Allowed** from the reverse proxy. Use only the scheme + host + optional port: `https://host` or `http://localhost:8080`.
|
|
@@ -65,25 +75,25 @@ Before reading the full reference, note these critical differences from typical
|
|
|
65
75
|
### 1.1 What is XciteDB?
|
|
66
76
|
XciteDB is an enterprise-grade **Backend-as-a-Service (BaaS)** built on top of a highly optimized **embedded LMDB** database engine. Powered by a high-performance C++17 HTTP/WebSocket API, XciteDB exposes a multi-tenant, secure platform for managing complex, versioned, structured documents.
|
|
67
77
|
|
|
68
|
-
**Both XML and JSON are first-class citizens.** XciteDB can operate as a pure **XML document store**, a pure **JSON document database**, or any combination of the two — including the common pattern of **XML documents enriched with structured JSON metadata**. In every mode, data is deeply shredded into its structural components (elements, attributes, objects, fields, array items), versioned,
|
|
78
|
+
**Both XML and JSON are first-class citizens.** XciteDB can operate as a pure **XML document store**, a pure **JSON document database**, or any combination of the two — including the common pattern of **XML documents enriched with structured JSON metadata**. In every mode, data is deeply shredded into its structural components (elements, attributes, objects, fields, array items), versioned, indexed by path, and fully queryable through the same Unquery analytics engine — with **temporal revisions**, **workspaces**, and **checkpoints** when you need collaborative workflow.
|
|
69
79
|
|
|
70
80
|
### 1.2 The Problem It Solves
|
|
71
81
|
Standard relational databases and generic NoSQL stores struggle with highly structured, hierarchical, and deeply versioned content. Building collaborative authoring tools, legislative drafting systems, or complex content management platforms usually requires bolting a Git-like versioning layer and document-aware parsers onto an ill-fitting database.
|
|
72
82
|
|
|
73
|
-
XciteDB bridges this gap. It provides **
|
|
83
|
+
XciteDB bridges this gap. It provides **temporal versioning plus optional workspaces and checkpoints** for structured XML and JSON data out of the box, delivered as a modern, API-first cloud platform with multi-tenancy, Attribute-Based Access Control (ABAC), and S3-compatible backups.
|
|
74
84
|
|
|
75
85
|
### 1.3 Target Workloads
|
|
76
86
|
XciteDB shines in domains requiring strict auditability, collaborative authoring, and complex document hierarchies:
|
|
77
87
|
- **Legal and Legislative Drafting:** Tracking amendments, clauses, and exact historical states of laws.
|
|
78
88
|
- **Structured Technical Documentation:** Managing manuals, specifications, and compliance documents where every change must be versioned and attributable.
|
|
79
|
-
- **Collaborative Content Management:** Systems requiring
|
|
89
|
+
- **Collaborative Content Management:** Systems requiring isolated workspaces, publish workflows, and cooperative locking to prevent editor conflicts.
|
|
80
90
|
- **Application State and Configuration:** Storing deeply structured JSON configuration, feature flags, or workflow state that benefits from versioning and path-level querying.
|
|
81
91
|
|
|
82
92
|
### 1.4 Unique Differentiators
|
|
83
|
-
- **First-Class XML & JSON:** Both formats are shredded, indexed by path, versioned,
|
|
93
|
+
- **First-Class XML & JSON:** Both formats are shredded, indexed by path, versioned, and fully queryable through the same engine.
|
|
84
94
|
- **Embedded LMDB Engine:** A memory-mapped, ACID-compliant storage core purpose-built for structured document workloads.
|
|
85
95
|
- **Bare-Metal Speed:** Reads hit memory-mapped pages with no network round-trip. Microsecond-class read latency on warm data.
|
|
86
|
-
- **
|
|
96
|
+
- **Document-centric versioning:** Temporal revisions (`X-Date`), workspaces, checkpoints, bookmarks, compare, publish, revert, and apply — plus time-travel reads.
|
|
87
97
|
- **Unquery DSL:** A purpose-built declarative query language for navigating, filtering, and aggregating data across both XML trees and JSON structures.
|
|
88
98
|
- **Native Office Ingestion:** Directly convert and ingest DOCX, ODF, RTF, and PDF formats into clean, versioned XML documents.
|
|
89
99
|
- **Transparent Tenant Routing:** Scale out horizontally using a coordinator/worker topology without breaking the client API contract.
|
|
@@ -106,10 +116,10 @@ XciteDB shines in domains requiring strict auditability, collaborative authoring
|
|
|
106
116
|
- **JSON Metadata on XML:** JSON metadata can be attached to any XML document or path.
|
|
107
117
|
- **Hierarchical Identifiers:** Both XML and JSON documents are addressed via logical, path-like strings. The engine natively indexes parent/child relationships.
|
|
108
118
|
|
|
109
|
-
### 2.3
|
|
110
|
-
- **
|
|
111
|
-
- **
|
|
112
|
-
- **Time
|
|
119
|
+
### 2.3 Document versioning model
|
|
120
|
+
- **Temporal revisions & workspaces:** Every write with `X-Date` records under that revision instant. **Workspaces** isolate draft edits from the root timeline.
|
|
121
|
+
- **Checkpoints, publish, apply, revert:** Optional named snapshots (**checkpoints**); **publish** merges a workspace into a target; **apply** brings another checkpoint’s changes; **revert** rolls the workspace back to a checkpoint.
|
|
122
|
+
- **Time travel:** Read historical state at any point in time using the `X-Date` header. For **writes**, `X-Unversioned: true` explicitly requests flat (unversioned) storage; it conflicts with `X-Date` (**400**).
|
|
113
123
|
- **Cooperative Locking:** TTL locks prevent conflicting writes (HTTP 409 on conflict).
|
|
114
124
|
|
|
115
125
|
### 2.4 Unquery: Declarative Query DSL
|
|
@@ -142,7 +152,8 @@ Welcome to the **XciteDB HTTP API**. This reference describes REST endpoints und
|
|
|
142
152
|
|--------|------|-------------|
|
|
143
153
|
| `Authorization` | Usually required | `Bearer <JWT>` or `Bearer <api_key>` |
|
|
144
154
|
| `X-Project-Id` | Platform console / multi-project JWT | Selects the **tenant project** when the token is not already bound to one tenant |
|
|
145
|
-
| `X-
|
|
155
|
+
| `X-Workspace` | Optional | Active workspace name for document and versioning operations (preferred) |
|
|
156
|
+
| `X-Branch` | Optional | Deprecated alias of `X-Workspace` |
|
|
146
157
|
| `X-Date` | Optional | Point-in-time / revision context (ISO-like string as used by your deployment) |
|
|
147
158
|
| `X-Unversioned` | Optional | When `true` or `1`, **writes** use flat LMDB keys (no date revision). Must not be combined with `X-Date` (**400**). Omitting `X-Date` remains valid (implicit unversioned). |
|
|
148
159
|
|
|
@@ -241,8 +252,8 @@ describe('XciteDB integration', () => {
|
|
|
241
252
|
expect(xml).toContain('<title>Hello</title>');
|
|
242
253
|
});
|
|
243
254
|
|
|
244
|
-
it('creates a
|
|
245
|
-
await client.
|
|
255
|
+
it('creates a workspace, checkpoints, and publishes', async () => {
|
|
256
|
+
await client.withWorkspace('feature-test', async (c) => {
|
|
246
257
|
await c.writeJsonDocument('test.feature', { active: true });
|
|
247
258
|
}, { message: 'Add feature flag', autoMerge: true });
|
|
248
259
|
const doc = await client.readJsonDocument<{ active: boolean }>('test.feature');
|
|
@@ -283,8 +294,8 @@ async def test_xml_document(db):
|
|
|
283
294
|
assert "<title>Hello</title>" in result
|
|
284
295
|
|
|
285
296
|
@pytest.mark.asyncio
|
|
286
|
-
async def
|
|
287
|
-
async with db.
|
|
297
|
+
async def test_workspace_and_publish(db):
|
|
298
|
+
async with db.with_workspace("feature-test", message="Add flag", auto_merge=True):
|
|
288
299
|
await db.put("test.feature", {"active": True})
|
|
289
300
|
doc = await db.get("test.feature")
|
|
290
301
|
assert doc["active"] is True
|
|
@@ -518,7 +529,7 @@ Can also use `"query"` instead of `"identifier"` to target multiple documents by
|
|
|
518
529
|
|
|
519
530
|
# Search
|
|
520
531
|
|
|
521
|
-
Full-text search
|
|
532
|
+
Full-text search (backed by Meilisearch or Elasticsearch).
|
|
522
533
|
|
|
523
534
|
**Base path:** `/api/v1/search`
|
|
524
535
|
|
|
@@ -680,100 +691,107 @@ Dry-run: **`POST /api/v1/security/check`** with `subject`, `identifier`, `action
|
|
|
680
691
|
|
|
681
692
|
---
|
|
682
693
|
|
|
683
|
-
#
|
|
694
|
+
# Workspaces (branches)
|
|
684
695
|
|
|
685
|
-
**
|
|
696
|
+
**Preferred base path:** `/api/v1/workspaces`
|
|
697
|
+
**Deprecated alias:** `/api/v1/branches` (same handlers)
|
|
686
698
|
|
|
687
|
-
**IMPORTANT: The default
|
|
699
|
+
**IMPORTANT: The default workspace is the empty string `""`.** When no workspace is specified, the server uses the root timeline.
|
|
688
700
|
|
|
689
|
-
## List
|
|
701
|
+
## List workspaces
|
|
690
702
|
|
|
691
|
-
**`GET /api/v1/
|
|
703
|
+
**`GET /api/v1/workspaces`** — Returns `{ workspaces: [...], branches: [...] }` where each item includes `name`, `from_branch`, `from_date`, `tip_checkpoint` (and `tip_commit` mirror).
|
|
692
704
|
|
|
693
|
-
## Create
|
|
705
|
+
## Create workspace
|
|
694
706
|
|
|
695
|
-
**`POST /api/v1/
|
|
707
|
+
**`POST /api/v1/workspaces`** — `{ "name": "feature-x", "from_branch": "", "from_date": "" }`
|
|
696
708
|
|
|
697
|
-
## Get
|
|
709
|
+
## Get workspace
|
|
698
710
|
|
|
699
|
-
**`GET /api/v1/
|
|
711
|
+
**`GET /api/v1/workspaces/{name}`** — Returns `{ workspace: {...}, branch: {...} }`.
|
|
700
712
|
|
|
701
|
-
## Delete
|
|
713
|
+
## Delete workspace
|
|
702
714
|
|
|
703
|
-
**`DELETE /api/v1/
|
|
715
|
+
**`DELETE /api/v1/workspaces/{name}`**
|
|
704
716
|
|
|
705
|
-
##
|
|
717
|
+
## Publish workspace
|
|
706
718
|
|
|
707
|
-
**`POST /api/v1/
|
|
719
|
+
**`POST /api/v1/workspaces/{target}/publish`**
|
|
708
720
|
|
|
709
721
|
```json
|
|
710
722
|
{
|
|
723
|
+
"source_workspace": "feature-x",
|
|
711
724
|
"source_branch": "feature-x",
|
|
712
|
-
"message": "
|
|
725
|
+
"message": "Publish feature",
|
|
713
726
|
"auto_resolve": "none"
|
|
714
727
|
}
|
|
715
728
|
```
|
|
716
729
|
|
|
717
730
|
`auto_resolve`: `"none"` | `"source"` | `"target"`.
|
|
718
731
|
|
|
719
|
-
Returns `{ status: "completed"|"conflicts", commit?, merged_identifiers?, conflicts? }
|
|
732
|
+
Returns `{ status: "completed"|"conflicts", checkpoint?, commit?, merged_identifiers?, conflicts? }` (`commit` mirrors `checkpoint`).
|
|
720
733
|
|
|
721
|
-
## Delete revision on
|
|
734
|
+
## Delete revision on workspace
|
|
722
735
|
|
|
723
|
-
**`DELETE /api/v1/
|
|
736
|
+
**`DELETE /api/v1/workspaces/{name}/revisions/{date}`** (human-readable date in path; legacy `/branches/...` path still works)
|
|
724
737
|
|
|
725
738
|
---
|
|
726
739
|
|
|
727
|
-
#
|
|
740
|
+
# Checkpoints, bookmarks & compare
|
|
728
741
|
|
|
729
|
-
## Create
|
|
742
|
+
## Create checkpoint
|
|
730
743
|
|
|
731
|
-
**`POST /api/v1/
|
|
744
|
+
**`POST /api/v1/checkpoints`** — `{ "message": "...", "author": "..." }`
|
|
745
|
+
(Deprecated: **`POST /api/v1/commits`**)
|
|
732
746
|
|
|
733
|
-
Returns `{
|
|
747
|
+
Returns `{ checkpoint: { id, checkpoint_id, branch, date, message, author, ... }, commit: <same> }`. Public JSON uses human-readable **`date`** (no `date_key`).
|
|
734
748
|
|
|
735
|
-
## List
|
|
749
|
+
## List checkpoints
|
|
736
750
|
|
|
737
|
-
**`GET /api/v1/
|
|
751
|
+
**`GET /api/v1/checkpoints?branch=...&limit=...&offset=...`** (query param name unchanged; means workspace)
|
|
752
|
+
Returns `{ checkpoints: [...], commits: [...], total, branch }` (mirrors).
|
|
738
753
|
|
|
739
|
-
|
|
754
|
+
## Get checkpoint
|
|
740
755
|
|
|
741
|
-
|
|
756
|
+
**`GET /api/v1/checkpoints/{id}`** — Returns `{ checkpoint, commit }`.
|
|
742
757
|
|
|
743
|
-
|
|
758
|
+
## Revert to checkpoint
|
|
744
759
|
|
|
745
|
-
|
|
760
|
+
**`POST /api/v1/checkpoints/{id}/revert`** — `{ "confirm": true }`
|
|
761
|
+
(Deprecated: **`.../rollback`**)
|
|
746
762
|
|
|
747
|
-
|
|
763
|
+
## Apply checkpoint
|
|
748
764
|
|
|
749
|
-
|
|
765
|
+
**`POST /api/v1/checkpoints/{id}/apply`** — `{ "message": "...", "author": "..." }`
|
|
766
|
+
(Deprecated: **`.../cherry-pick`**)
|
|
750
767
|
|
|
751
|
-
|
|
768
|
+
## Create bookmark
|
|
752
769
|
|
|
753
|
-
|
|
770
|
+
**`POST /api/v1/bookmarks`** — `{ "name": "v1.0", "checkpoint_id": "...", "commit_id": "...", "message": "..." }`
|
|
771
|
+
(Deprecated: **`POST /api/v1/tags`**)
|
|
754
772
|
|
|
755
|
-
|
|
773
|
+
## List bookmarks
|
|
756
774
|
|
|
757
|
-
|
|
775
|
+
**`GET /api/v1/bookmarks?limit=...&offset=...`** — Returns `{ bookmarks: [...], tags: [...], total }` (mirrors where applicable).
|
|
758
776
|
|
|
759
|
-
|
|
777
|
+
## Get / Delete bookmark
|
|
760
778
|
|
|
761
|
-
|
|
779
|
+
**`GET /api/v1/bookmarks/{name}`** / **`DELETE /api/v1/bookmarks/{name}`** (deprecated `/tags/...`)
|
|
762
780
|
|
|
763
|
-
|
|
781
|
+
## Compare
|
|
764
782
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
**`POST /api/v1/diff`**
|
|
783
|
+
**`POST /api/v1/compare`**
|
|
768
784
|
|
|
769
785
|
```json
|
|
770
786
|
{
|
|
771
|
-
"from": { "branch": "", "
|
|
787
|
+
"from": { "branch": "", "date": "2024-01-15T00:00:00" },
|
|
772
788
|
"to": { "branch": "feature-x" },
|
|
773
789
|
"include_content": true
|
|
774
790
|
}
|
|
775
791
|
```
|
|
776
792
|
|
|
793
|
+
(Deprecated: **`POST /api/v1/diff`**; `date_key` still accepted internally but prefer **`date`**.)
|
|
794
|
+
|
|
777
795
|
Returns `{ changes: [{ identifier, action: "added"|"modified"|"deleted", from_content?, to_content? }], total_changes }`.
|
|
778
796
|
|
|
779
797
|
---
|
|
@@ -1243,7 +1261,7 @@ import { XCiteDBClient } from '@xcitedbs/client';
|
|
|
1243
1261
|
const client = new XCiteDBClient({
|
|
1244
1262
|
baseUrl: 'http://localhost:8080',
|
|
1245
1263
|
apiKey: 'your-api-key',
|
|
1246
|
-
context: {
|
|
1264
|
+
context: { workspace: '', date: '' },
|
|
1247
1265
|
});
|
|
1248
1266
|
|
|
1249
1267
|
// Health check
|
|
@@ -1263,12 +1281,12 @@ await client.writeJsonDocument('app.settings', { theme: 'dark' });
|
|
|
1263
1281
|
// Read JSON document
|
|
1264
1282
|
const settings = await client.readJsonDocument('app.settings');
|
|
1265
1283
|
|
|
1266
|
-
//
|
|
1267
|
-
await client.
|
|
1268
|
-
client.setContext({
|
|
1284
|
+
// Workspace, edit, checkpoint, publish
|
|
1285
|
+
await client.createWorkspace('feature-x');
|
|
1286
|
+
client.setContext({ workspace: 'feature-x' });
|
|
1269
1287
|
await client.writeJsonDocument('app.settings', { theme: 'light' });
|
|
1270
|
-
await client.
|
|
1271
|
-
await client.
|
|
1288
|
+
await client.createCheckpoint('Switch to light theme');
|
|
1289
|
+
await client.publishWorkspace('', 'feature-x');
|
|
1272
1290
|
```
|
|
1273
1291
|
|
|
1274
1292
|
## Constructor Options
|
|
@@ -1289,7 +1307,8 @@ interface XCiteDBClientOptions {
|
|
|
1289
1307
|
}
|
|
1290
1308
|
|
|
1291
1309
|
interface DatabaseContext {
|
|
1292
|
-
|
|
1310
|
+
workspace?: string; // '' = default (root timeline); preferred
|
|
1311
|
+
branch?: string; // @deprecated alias of workspace
|
|
1293
1312
|
date?: string; // Point-in-time
|
|
1294
1313
|
prefix?: string; // Identifier prefix filter
|
|
1295
1314
|
unversioned?: boolean; // Sends X-Unversioned: true (flat writes; do not combine with date)
|
|
@@ -1357,30 +1376,34 @@ interface DatabaseContext {
|
|
|
1357
1376
|
- `queryMetaByQuery<T>(query, path?)` → `T`
|
|
1358
1377
|
- `clearMeta(query)` → `boolean`
|
|
1359
1378
|
|
|
1360
|
-
###
|
|
1361
|
-
- `
|
|
1362
|
-
- `
|
|
1363
|
-
- `
|
|
1364
|
-
- `
|
|
1365
|
-
- `
|
|
1366
|
-
- `deleteRevision(branch, date)` → `void`
|
|
1367
|
-
- `
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
- `
|
|
1372
|
-
- `
|
|
1373
|
-
- `
|
|
1374
|
-
- `
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
- `
|
|
1380
|
-
- `
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
- `
|
|
1379
|
+
### Workspaces & checkpoints
|
|
1380
|
+
- `withWorkspace(name, fn, options?)` → `{ result, checkpoint?, publish? }` — workspace, callback, checkpoint, publish back
|
|
1381
|
+
- `createWorkspace(name, fromBranch?, fromDate?)` → `void`
|
|
1382
|
+
- `listWorkspaces()` → `WorkspaceInfo[]`
|
|
1383
|
+
- `getWorkspace(name)` → `WorkspaceInfo`
|
|
1384
|
+
- `deleteWorkspace(name)` → `void`
|
|
1385
|
+
- `deleteRevision(branch, date)` → `void` (workspace name + human date)
|
|
1386
|
+
- `publishWorkspace(targetWorkspace, sourceWorkspace, options?)` → `PublishResult`
|
|
1387
|
+
- **Deprecated:** `withBranch`, `createBranch`, `listBranches`, `getBranch`, `deleteBranch`, `mergeBranch` (same HTTP behavior)
|
|
1388
|
+
|
|
1389
|
+
### Checkpoints
|
|
1390
|
+
- `createCheckpoint(message, author?)` → `CheckpointRecord`
|
|
1391
|
+
- `listCheckpoints(options?)` → `{ checkpoints, total, branch }` (wire JSON may also include `commits` mirror)
|
|
1392
|
+
- `getCheckpoint(checkpointId)` → `CheckpointRecord`
|
|
1393
|
+
- `revertToCheckpoint(checkpointId)` → `{ rolled_back_checkpoints, rolled_back_commits?, current_tip }`
|
|
1394
|
+
- `applyCheckpoint(checkpointId, message?, author?)` → `CheckpointRecord`
|
|
1395
|
+
- **Deprecated:** `createCommit`, `listCommits`, `getCommit`, `rollbackToCommit`, `cherryPick`
|
|
1396
|
+
|
|
1397
|
+
### Bookmarks
|
|
1398
|
+
- `createBookmark(name, checkpointId, message?, author?)` → `BookmarkRecord`
|
|
1399
|
+
- `listBookmarks(options?)` → `{ bookmarks, tags, total, limit?, offset? }`
|
|
1400
|
+
- `getBookmark(name)` → `BookmarkRecord`
|
|
1401
|
+
- `deleteBookmark(name)` → `void`
|
|
1402
|
+
- **Deprecated:** `createTag`, `listTags`, `getTag`, `deleteTag`
|
|
1403
|
+
|
|
1404
|
+
### Compare
|
|
1405
|
+
- `compare(from: CompareRef, to: CompareRef, includeContent?)` → `CompareResult`
|
|
1406
|
+
- **Deprecated:** `diff(from: DiffRef, …)` — alias of `compare`
|
|
1384
1407
|
|
|
1385
1408
|
### Locks
|
|
1386
1409
|
- `acquireLock(identifier, expires?)` → `LockInfo`
|
|
@@ -1513,7 +1536,7 @@ async def main():
|
|
|
1513
1536
|
async with XCiteDBClient(
|
|
1514
1537
|
"http://localhost:8080",
|
|
1515
1538
|
api_key="your-key",
|
|
1516
|
-
context=DatabaseContext(
|
|
1539
|
+
context=DatabaseContext(workspace="", date=""),
|
|
1517
1540
|
) as client:
|
|
1518
1541
|
print(await client.health())
|
|
1519
1542
|
print(await client.query_documents(XCiteQuery(match_start="/manual/")))
|
|
@@ -1523,13 +1546,13 @@ async def main():
|
|
|
1523
1546
|
print(await client.search(TextSearchQuery(query="guide", limit=10)))
|
|
1524
1547
|
await client.platform_login("admin@localhost", "password")
|
|
1525
1548
|
# await client.login_app_user("user@example.com", "pw") # set context.project_id / tenant_id if needed
|
|
1526
|
-
async with client.
|
|
1549
|
+
async with client.with_workspace("feature-x", message="WIP", auto_merge=True):
|
|
1527
1550
|
await client.put("app.settings", {"theme": "light"})
|
|
1528
1551
|
|
|
1529
1552
|
asyncio.run(main())
|
|
1530
1553
|
```
|
|
1531
1554
|
|
|
1532
|
-
Async client: `write_xml_document` / `write_document_json` (deprecated), `write_json_document`, `read_json_document`, `list_json_documents`, `list_identifiers`, `search`, `reindex`, `platform_login` / `login` (deprecated), `login_app_user`, `refresh_app_user`, `logout_app_user`, `register_app_user`, `put` / `get` / `remove` / `list_documents` (JSON aliases), `
|
|
1555
|
+
Async client: `write_xml_document` / `write_document_json` (deprecated), `write_json_document`, `read_json_document`, `list_json_documents`, `list_identifiers`, `search`, `reindex`, `platform_login` / `login` (deprecated), `login_app_user`, `refresh_app_user`, `logout_app_user`, `register_app_user`, `put` / `get` / `remove` / `list_documents` (JSON aliases), `with_workspace` (async context manager; `with_branch` deprecated alias).
|
|
1533
1556
|
|
|
1534
1557
|
---
|
|
1535
1558
|
|
|
@@ -1541,7 +1564,7 @@ Async client: `write_xml_document` / `write_document_json` (deprecated), `write_
|
|
|
1541
1564
|
xcitedb::XCiteDBClientOptions opt;
|
|
1542
1565
|
opt.base_url = "http://127.0.0.1:8080";
|
|
1543
1566
|
opt.api_key = "your-key";
|
|
1544
|
-
opt.context.
|
|
1567
|
+
opt.context.workspace = ""; // root timeline (`branch` is deprecated alias)
|
|
1545
1568
|
|
|
1546
1569
|
xcitedb::XCiteDBClient client(opt);
|
|
1547
1570
|
auto health = client.health();
|
|
@@ -1553,4 +1576,4 @@ auto ids = client.query_documents(q);
|
|
|
1553
1576
|
|
|
1554
1577
|
Synchronous (blocking) HTTP client. Methods mirror the JavaScript SDK with C++ naming (`write_xml_document`, deprecated `write_document_json`). Errors throw `xcitedb::XCiteDBError` with `.status()` and `.body()`.
|
|
1555
1578
|
|
|
1556
|
-
Includes optional `xcitevcs` CLI for command-line operations (
|
|
1579
|
+
Includes optional `xcitevcs` CLI for command-line operations (legacy branch/commit vocabulary on the wire, documents, search, import/export).
|
package/llms.txt
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# XciteDB
|
|
2
2
|
|
|
3
|
-
> XciteDB is a versioned XML and JSON document database delivered as a Backend-as-a-Service (BaaS). It provides
|
|
3
|
+
> XciteDB is a versioned XML and JSON document database delivered as a Backend-as-a-Service (BaaS). It provides **temporal revisions** (every dated write is a revision), **workspaces** for isolated editing, **checkpoints** for optional named snapshots, time-travel reads, and hierarchical document identifiers over a high-performance embedded LMDB engine, exposed through a REST + WebSocket API.
|
|
4
4
|
|
|
5
5
|
## Important: Non-Standard Conventions
|
|
6
6
|
|
|
7
7
|
These are the most common sources of confusion for developers and AI assistants:
|
|
8
8
|
|
|
9
|
-
1. **The default
|
|
9
|
+
1. **The default workspace is the empty string `""`, not `"main"`.** When no `X-Workspace` header is sent (or `context.workspace` / `context.branch` is omitted/empty in the SDK), the server operates on the root timeline. `X-Branch` is a supported alias of `X-Workspace`. A workspace named `"main"` may exist as user-created metadata, but it is not required or special.
|
|
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
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.
|
|
14
14
|
|
|
15
|
-
4.
|
|
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
|
|
|
17
17
|
5. **XML documents carry their identifier inside the XML** — specifically via a `db:identifier` attribute on the root element (e.g. `<chapter db:identifier="/book/ch1">...</chapter>`). When writing XML, the server extracts the identifier from the document body.
|
|
18
18
|
|
|
@@ -24,6 +24,20 @@ These are the most common sources of confusion for developers and AI assistants:
|
|
|
24
24
|
|
|
25
25
|
9. **Ephemeral test sessions (wet tests).** Call **`POST /api/v1/test/sessions`** with a normal API key or Bearer token to get a `session_token` (UUID). Send **`X-Test-Session: <token>`** on subsequent document/API calls to use an isolated, short-lived LMDB instead of production data. By default **developer** auth (API key / platform JWT) is bypassed, but **app-user** identity (`X-App-User-Token` or Bearer app-user JWT) is still recognized. Send **`X-Test-Auth: required`** to exercise full developer JWT/API-key auth and ABAC against the same test database. Do not send `X-Test-Session` on `/api/v1/test/*` management routes. Server limits apply (`test.session_ttl_seconds`, `test.max_sessions_per_key`, `test.max_test_db_size_bytes` in config). The test DB starts **empty** (no copy of production project config or keys).
|
|
26
26
|
|
|
27
|
+
## Choosing the Right Versioning Approach
|
|
28
|
+
|
|
29
|
+
- **Write at a specific date** (law effective date, publication date, approval date): Set `context.date` / `X-Date` and write. No workspaces or checkpoints needed. Every write with a date creates a **temporal revision** automatically.
|
|
30
|
+
|
|
31
|
+
- **Read historical data** (“what was the document on March 1, 2024?”): Set `context.date` / `X-Date` and read. The engine returns the revision at or before that instant.
|
|
32
|
+
|
|
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
|
+
|
|
35
|
+
- **Audit trail with named snapshots:** Create checkpoints with messages. List/inspect checkpoints for history.
|
|
36
|
+
|
|
37
|
+
- **Undo a batch of changes:** **Revert** to a prior checkpoint (removes all changes after that point on the workspace).
|
|
38
|
+
|
|
39
|
+
Legacy REST paths (`/api/v1/branches`, `/commits`, `/tags`, `/diff`) remain as **deprecated** aliases; prefer **`/api/v1/workspaces`**, **`/checkpoints`**, **`/bookmarks`**, **`/compare`**.
|
|
40
|
+
|
|
27
41
|
## Glossary: Project id, display name, and groups
|
|
28
42
|
|
|
29
43
|
- **Project display name** (human-readable, e.g. `invoices`): Shown in the console. The **`X-Project-Id`** header may match either this name **or** the internal project id (server convenience). Do **not** put the display name in JWT claims or in `project:<…>:role` group strings.
|
|
@@ -54,6 +68,8 @@ These are the most common sources of confusion for developers and AI assistants:
|
|
|
54
68
|
|
|
55
69
|
11. **Integration tests should assert token claims**, not only **`/app/auth/me`**. The profile endpoint can look fine while the JWT still carries viewer-style **`groups`**; for ABAC the token payload is authoritative. Use **`getTokenClaims()`** (JS SDK) or decode the JWT in your harness.
|
|
56
70
|
|
|
71
|
+
12. **`X-Date` / `context.date` is not “server clock only.”** Revisions are keyed by the **instant you send**, not an implicit “now” when you set the header. To record a document under a **business date** (published, approved, effective), set **`X-Date`** or **`context.date`** to that instant before writing. Omitting **`X-Date`** does **not** substitute the current time on the write path — it selects **flat** writes (see convention 4).
|
|
72
|
+
|
|
57
73
|
## API key capability matrix (typical)
|
|
58
74
|
|
|
59
75
|
| Capability | API key `role` | Public key allowed? |
|
|
@@ -90,7 +106,7 @@ await app.writeJsonDocument('userdata/alice/profile', { ok: true });
|
|
|
90
106
|
> **AI assistant instruction:** XciteDB has built-in ephemeral test session support — this capability is **not standard** in most BaaS platforms and is specifically designed to enable meaningful wet integration tests. When writing tests for code that uses XciteDB, **do not mock or stub the XciteDB client**. Instead, use the test session pattern below to run tests against a real, isolated, throwaway database. This produces far more reliable tests because real storage, versioning, querying, and access-control behavior is exercised.
|
|
91
107
|
|
|
92
108
|
1. **Provision:** `POST /api/v1/test/sessions` with `Authorization: Bearer …` or `X-API-Key` (same as normal API access). Response JSON includes the session token.
|
|
93
|
-
2. **Run tests:** Every request that should hit the throwaway DB must include **`X-Test-Session: <token>`** (and your usual `X-
|
|
109
|
+
2. **Run tests:** Every request that should hit the throwaway DB must include **`X-Test-Session: <token>`** (and your usual `X-Workspace` / `context` as needed). Data is stored under the server’s `_test/<session>/` area, not your production tenant.
|
|
94
110
|
3. **Auth behavior:** Developer auth (API key / platform JWT) is bypassed by default for frictionless tests. **App-user identity** (`X-App-User-Token` or Bearer app-user JWT) **is still recognized** in default mode, so `registerAppUser` → `loginAppUser` → `appUserMe` works inside a test session. To also exercise developer auth and ABAC policies, set **`X-Test-Auth: required`** and send normal credentials; the DB is still the test session’s.
|
|
95
111
|
4. **Cleanup:** `DELETE /api/v1/test/sessions/current` with `X-Test-Session` (no other auth), or `DELETE /api/v1/test/sessions/all` / `DELETE /api/v1/test/sessions/{token}` with normal auth for the owning key or JWT.
|
|
96
112
|
5. **SDKs:** **JS/TS:** `XCiteDBClient.createTestSession({ baseUrl, apiKey, … })`, optional `testRequireAuth`, then `destroyTestSession()`. **Python:** `async with XCiteDBClient.test_session(...)` or manual token + `test_session_token` / `test_require_auth` constructor args. **C++:** `XCiteDBClient::create_test_session(options)`, `destroy_test_session()`, optional `test_require_auth` in options.
|
|
@@ -107,15 +123,15 @@ import { XCiteDBClient } from '@xcitedbs/client';
|
|
|
107
123
|
const client = new XCiteDBClient({
|
|
108
124
|
baseUrl: 'http://localhost:8080',
|
|
109
125
|
apiKey: 'your-api-key',
|
|
110
|
-
//
|
|
111
|
-
context: {
|
|
126
|
+
// workspace: '' means root timeline (the default); omit or use '' for default
|
|
127
|
+
context: { workspace: '', date: '' },
|
|
112
128
|
});
|
|
113
129
|
|
|
114
130
|
// Health check (no auth required)
|
|
115
131
|
await client.health();
|
|
116
132
|
|
|
117
|
-
// List all
|
|
118
|
-
const
|
|
133
|
+
// List all workspaces
|
|
134
|
+
const workspaces = await client.listWorkspaces();
|
|
119
135
|
|
|
120
136
|
// Query documents by identifier prefix
|
|
121
137
|
const docs = await client.queryDocuments({ match_start: '/manual/' });
|
|
@@ -140,18 +156,18 @@ const prefs = await client.get<Record<string, unknown>>('app.prefs');
|
|
|
140
156
|
await client.remove('app.prefs');
|
|
141
157
|
const keys = await client.list(undefined, 50, 0);
|
|
142
158
|
|
|
143
|
-
//
|
|
144
|
-
await client.
|
|
159
|
+
// Workspace helper: create workspace, run work, checkpoint, publish back (restores context)
|
|
160
|
+
await client.withWorkspace('feature-x', async (c) => {
|
|
145
161
|
await c.writeJsonDocument('app.settings', { theme: 'light' });
|
|
146
162
|
return 'ok';
|
|
147
163
|
}, { message: 'Light theme', autoMerge: true, fromBranch: '' });
|
|
148
164
|
|
|
149
|
-
// Create a
|
|
150
|
-
await client.
|
|
151
|
-
client.setContext({
|
|
165
|
+
// Create a workspace, make changes, checkpoint, publish
|
|
166
|
+
await client.createWorkspace('feature-x');
|
|
167
|
+
client.setContext({ workspace: 'feature-x' });
|
|
152
168
|
await client.writeJsonDocument('app.settings', { theme: 'light' });
|
|
153
|
-
const
|
|
154
|
-
await client.
|
|
169
|
+
const checkpoint = await client.createCheckpoint('Switch to light theme');
|
|
170
|
+
await client.publishWorkspace('', 'feature-x'); // publish into root (default) workspace
|
|
155
171
|
|
|
156
172
|
// Attach JSON metadata to a document
|
|
157
173
|
await client.addMeta('/manual/v1/intro', { status: 'draft', owner: 'alice' });
|
|
@@ -161,7 +177,7 @@ const lock = await client.acquireLock('/manual/v1/intro');
|
|
|
161
177
|
// ... edit ...
|
|
162
178
|
await client.releaseLock('/manual/v1/intro', lock.lock_id);
|
|
163
179
|
|
|
164
|
-
// Full-text search (
|
|
180
|
+
// Full-text search (requires Meilisearch or Elasticsearch backend)
|
|
165
181
|
const results = await client.search({ query: 'installation guide', limit: 20 });
|
|
166
182
|
|
|
167
183
|
// Subscribe to real-time changes via WebSocket
|
|
@@ -180,8 +196,9 @@ interface XCiteDBClientOptions {
|
|
|
180
196
|
accessToken?: string; // Platform JWT (from platformLogin)
|
|
181
197
|
appUserAccessToken?: string; // End-user JWT (from loginAppUser)
|
|
182
198
|
context?: {
|
|
183
|
-
|
|
184
|
-
|
|
199
|
+
workspace?: string; // Workspace name; '' = default/root timeline (preferred)
|
|
200
|
+
branch?: string; // @deprecated alias of workspace; sends X-Workspace
|
|
201
|
+
date?: string; // X-Date: as-of reads + write revision; any ISO/mm-dd-yyyy instant, not only "now"
|
|
185
202
|
prefix?: string; // Optional identifier prefix filter
|
|
186
203
|
project_id?: string; // Preferred: project id for app-user public auth (`tenant_id` on wire)
|
|
187
204
|
tenant_id?: string; // Deprecated alias of project_id
|
|
@@ -200,7 +217,7 @@ interface XCiteDBClientOptions {
|
|
|
200
217
|
- `loginAppUser(email, password)` — App end-user sign-in
|
|
201
218
|
- `XCiteDBClient.buildProjectGroup(projectId, 'admin'|'editor'|'viewer')` — Static helper: canonical `project:<tenant_id>:<role>` string
|
|
202
219
|
- `getTokenClaims()` — Decode current `appUserAccessToken` or `accessToken` payload (no signature verification); use for ABAC debugging
|
|
203
|
-
- `setContext(ctx)` — Update
|
|
220
|
+
- `setContext(ctx)` — Update workspace/date context
|
|
204
221
|
- `setProjectId(id)` — Switch active project (platform console)
|
|
205
222
|
|
|
206
223
|
**XML Documents:**
|
|
@@ -225,19 +242,20 @@ interface XCiteDBClientOptions {
|
|
|
225
242
|
- `queryMeta(identifier, path?)` — Read metadata
|
|
226
243
|
- `clearMeta(query)` — Remove metadata
|
|
227
244
|
|
|
228
|
-
**Versioning:**
|
|
229
|
-
- `
|
|
230
|
-
- `
|
|
231
|
-
- `
|
|
232
|
-
- `
|
|
233
|
-
- `
|
|
234
|
-
- `
|
|
235
|
-
- `
|
|
236
|
-
- `
|
|
237
|
-
- `
|
|
238
|
-
- `
|
|
239
|
-
- `
|
|
240
|
-
- `
|
|
245
|
+
**Versioning (workspaces & checkpoints):**
|
|
246
|
+
- `withWorkspace(name, fn, options?)` — Create workspace, run callback, checkpoint, publish back (optional `autoMerge: false`; `fromBranch` = parent workspace)
|
|
247
|
+
- `createWorkspace(name, fromBranch?, fromDate?)` — Create workspace
|
|
248
|
+
- `listWorkspaces()` — List workspaces
|
|
249
|
+
- `publishWorkspace(target, source, options?)` — Publish workspace changes to a target timeline
|
|
250
|
+
- `deleteWorkspace(name)` — Delete workspace
|
|
251
|
+
- `createCheckpoint(message, author?)` — Named snapshot of current state
|
|
252
|
+
- `listCheckpoints(options?)` — List checkpoints
|
|
253
|
+
- `revertToCheckpoint(checkpointId)` — Revert workspace to a prior checkpoint
|
|
254
|
+
- `applyCheckpoint(checkpointId, message?)` — Apply another checkpoint’s changes here
|
|
255
|
+
- `compare(from, to, includeContent?)` — Compare revisions
|
|
256
|
+
- `createBookmark(name, checkpointId, message?)` — Bookmark a checkpoint
|
|
257
|
+
- `listBookmarks()` / `deleteBookmark(name)` — Manage bookmarks
|
|
258
|
+
- **Deprecated aliases:** `withBranch`, `createBranch`, `listBranches`, `mergeBranch`, `deleteBranch`, `createCommit`, `listCommits`, `rollbackToCommit`, `cherryPick`, `diff`, `createTag`, `listTags`, `deleteTag` (same HTTP behavior)
|
|
241
259
|
|
|
242
260
|
**Locks:**
|
|
243
261
|
- `acquireLock(identifier, expires?)` — Acquire cooperative lock
|
|
@@ -260,7 +278,7 @@ interface XCiteDBClientOptions {
|
|
|
260
278
|
### Advanced: policy expressions (ABAC)
|
|
261
279
|
|
|
262
280
|
- **Actions** (use in `policy.actions`): `read`, `write`, `delete`, `list`, `meta:read`, `meta:write`, `unquery`.
|
|
263
|
-
- **`conditions.expression`** uses the same predicate syntax as Unquery `?` filters. **Context:** `subject.id` and **`subject.user_id`** (same value for app users), `subject.email`, `subject.role`, `subject.groups`, `subject.attr.*` (app-user JSON attributes), `resource.identifier`, `resource.path` (array of path segments; indices follow non-empty `/` segments after API canonicalization, e.g. `/userdata/u1/x` → `[userdata,u1,x]`), `env.branch
|
|
281
|
+
- **`conditions.expression`** uses the same predicate syntax as Unquery `?` filters. **Context:** `subject.id` and **`subject.user_id`** (same value for app users), `subject.email`, `subject.role`, `subject.groups`, `subject.attr.*` (app-user JSON attributes), `resource.identifier`, `resource.path` (array of path segments; indices follow non-empty `/` segments after API canonicalization, e.g. `/userdata/u1/x` → `[userdata,u1,x]`), `env.branch` (workspace name; legacy name retained in policy engine). Policy **`match_start` / `match_end` / `exact`** strings are canonicalized like API identifiers (add leading `/` when omitted).
|
|
264
282
|
- **Operators:** `=`, `!=`, `>=`, `<=`, `>`, `<`, `in`, `contains`, `starts_with`, `ends_with`, `&`, `|`, `!`, `+` (string concat), postfix `!` (exists).
|
|
265
283
|
- **Examples:** `resource.path[0] = subject.attr.tenant_code` — tenant isolation; `subject.attr.level >= 5` — numeric attribute gate.
|
|
266
284
|
|
|
@@ -269,7 +287,7 @@ interface XCiteDBClientOptions {
|
|
|
269
287
|
- **Events:** `meta_changed`, `document_written`, `document_deleted`.
|
|
270
288
|
- **`match`:** required non-empty **`identifiers`** (same pattern objects as policy `resources.identifiers`); optional **`match_meta_path`** (exact or `prefix*`); optional **`match_operation`:** `set` | `append` | `delete`.
|
|
271
289
|
- **`action`:** **`query`** (document query), **`unquery`** (Unquery template), **`target_identifier`** (or `"$trigger_identifier"`), **`meta_path`**, **`mode`:** `set` | `append`.
|
|
272
|
-
- **Unquery vars:** `$trigger_identifier`, `$trigger_meta_path`, `$trigger_operation`, `$trigger_value`. **Trigger `conditions.expression` context:** `trigger.{event,meta_path,operation}`, `value`, `resource`, `env.branch` (no `subject`).
|
|
290
|
+
- **Unquery vars:** `$trigger_identifier`, `$trigger_meta_path`, `$trigger_operation`, `$trigger_value`. **Trigger `conditions.expression` context:** `trigger.{event,meta_path,operation}`, `value`, `resource`, `env.branch` (workspace; no `subject`).
|
|
273
291
|
|
|
274
292
|
### Advanced: Unquery DSL (`unquery(query, unqueryDoc)`)
|
|
275
293
|
|
|
@@ -312,7 +330,7 @@ async def main():
|
|
|
312
330
|
async with XCiteDBClient(
|
|
313
331
|
"http://localhost:8080",
|
|
314
332
|
api_key="your-key",
|
|
315
|
-
context=DatabaseContext(
|
|
333
|
+
context=DatabaseContext(workspace="", date=""),
|
|
316
334
|
) as client:
|
|
317
335
|
print(await client.health())
|
|
318
336
|
ids = await client.query_documents(XCiteQuery(match_start="/manual/"))
|
|
@@ -334,7 +352,7 @@ asyncio.run(main())
|
|
|
334
352
|
xcitedb::XCiteDBClientOptions opt;
|
|
335
353
|
opt.base_url = "http://127.0.0.1:8080";
|
|
336
354
|
opt.api_key = "your-key";
|
|
337
|
-
opt.context.
|
|
355
|
+
opt.context.workspace = ""; // root timeline (branch is deprecated alias)
|
|
338
356
|
|
|
339
357
|
xcitedb::XCiteDBClient client(opt);
|
|
340
358
|
auto health = client.health();
|
|
@@ -347,9 +365,9 @@ auto ids = client.query_documents(q);
|
|
|
347
365
|
|
|
348
366
|
- **Multi-tenant**: Each project is an isolated tenant with its own data, users, keys, and policies.
|
|
349
367
|
- **LMDB engine**: Data is memory-mapped; reads are microsecond-class on warm data.
|
|
350
|
-
- **Versioning**:
|
|
368
|
+
- **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.
|
|
351
369
|
- **Two user tiers**: Platform operators manage infrastructure; App users are end-users of applications built on XciteDB.
|
|
352
|
-
- **Security policies (ABAC)**: Fine-grained allow/deny rules on actions, resources, roles, and
|
|
370
|
+
- **Security policies (ABAC)**: Fine-grained allow/deny rules on actions, resources, roles, and workspaces (`env.branch` in expressions).
|
|
353
371
|
- **WebSocket**: Real-time notifications for document changes at `/api/v1/ws`.
|
|
354
372
|
|
|
355
373
|
## Documentation
|
|
@@ -362,8 +380,8 @@ auto ids = client.query_documents(q);
|
|
|
362
380
|
- Metadata — JSON metadata on documents
|
|
363
381
|
- Search — Full-text search
|
|
364
382
|
- Unquery — Declarative query DSL
|
|
365
|
-
-
|
|
366
|
-
-
|
|
383
|
+
- Workspaces — Isolated editing environments
|
|
384
|
+
- Checkpoints & bookmarks — Named snapshots and references
|
|
367
385
|
- Locks — Cooperative editing locks
|
|
368
386
|
- Authentication — JWT, API keys, platform vs app users
|
|
369
387
|
- Security policies — ABAC access control
|