@xcitedbs/client 0.2.19 → 0.2.21
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/README.md +1 -1
- package/dist/client.d.ts +45 -1
- package/dist/client.js +62 -1
- package/dist/client.test.js +21 -0
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +8 -0
- package/llms-full.txt +44 -11
- package/llms.txt +9 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ Call **`POST /api/v1/test/sessions`** with your normal API key or Bearer (same p
|
|
|
65
65
|
- **Default:** isolated empty LMDB under the server’s `_test/<uuid>/` (writes never touch production).
|
|
66
66
|
- **Overlay:** pass **`overlay: true`** in **`createTestSession`** options (or **`POST`** body **`{"overlay":true}`**). The server layers a writable LMDB on top of the **current project’s on-disk data opened read-only** so you can debug against real data; changes still live only under `_test/<uuid>/`. Use project-scoped credentials or platform Bearer + **`X-Project-Id`** as when calling production APIs.
|
|
67
67
|
|
|
68
|
-
Tear down with **`destroyTestSession()`** (or **`DELETE /api/v1/test/sessions/current`** with the session header). See **`llms.txt`** / **`llms-full.txt`** in this package for full behavior, limits, and auth notes.
|
|
68
|
+
Tear down with **`destroyTestSession()`** (or **`DELETE /api/v1/test/sessions/current`** with the session header). With the **same** API key / Bearer and **no** `X-Test-Session`, use **`listTestSessions()`**, **`destroyAllTestSessions()`**, and **`destroyTestSessionByToken(token)`** to clear leaked sessions before large suites (avoids the default per-credential concurrent cap). See **`llms.txt`** / **`llms-full.txt`** in this package for full behavior, limits, and auth notes.
|
|
69
69
|
|
|
70
70
|
## Build
|
|
71
71
|
|
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, 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, 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';
|
|
2
2
|
import { WebSocketSubscription } from './websocket';
|
|
3
3
|
export declare class XCiteDBClient {
|
|
4
4
|
private baseUrl;
|
|
@@ -57,6 +57,20 @@ export declare class XCiteDBClient {
|
|
|
57
57
|
destroyTestSession(): Promise<{
|
|
58
58
|
message: string;
|
|
59
59
|
}>;
|
|
60
|
+
/**
|
|
61
|
+
* List active test sessions for the current API key or Bearer (`GET /api/v1/test/sessions`).
|
|
62
|
+
* Does not send `X-Test-Session` (management route).
|
|
63
|
+
*/
|
|
64
|
+
listTestSessions(): Promise<TestSessionInfo[]>;
|
|
65
|
+
/** Destroy every test session owned by this credential (`DELETE /api/v1/test/sessions/all`). */
|
|
66
|
+
destroyAllTestSessions(): Promise<{
|
|
67
|
+
message: string;
|
|
68
|
+
destroyed: number;
|
|
69
|
+
}>;
|
|
70
|
+
/** Destroy a single owned session by token (`DELETE /api/v1/test/sessions/{token}`). */
|
|
71
|
+
destroyTestSessionByToken(token: string): Promise<{
|
|
72
|
+
message: string;
|
|
73
|
+
}>;
|
|
60
74
|
/** True if this client would send API key or Bearer credentials on a normal request. */
|
|
61
75
|
private sentAuthCredentials;
|
|
62
76
|
/** 401 on these paths is an expected auth flow outcome, not a dead session. */
|
|
@@ -396,6 +410,36 @@ export declare class XCiteDBClient {
|
|
|
396
410
|
message?: string;
|
|
397
411
|
autoResolve?: 'none' | 'source' | 'target';
|
|
398
412
|
}): Promise<MergeResult>;
|
|
413
|
+
/** List app-user draft workspaces visible to the caller (`GET /api/v1/user-workspaces`). */
|
|
414
|
+
listUserWorkspaces(): Promise<{
|
|
415
|
+
user_workspaces: Record<string, unknown>[];
|
|
416
|
+
}>;
|
|
417
|
+
/** Create a user workspace (`POST /api/v1/user-workspaces`). Returns the workspace record (top-level JSON). */
|
|
418
|
+
createUserWorkspace(name: string, options?: {
|
|
419
|
+
sourceBranch?: string;
|
|
420
|
+
sourceDate?: string;
|
|
421
|
+
}): Promise<Record<string, unknown>>;
|
|
422
|
+
getUserWorkspace(id: string): Promise<{
|
|
423
|
+
user_workspace: Record<string, unknown>;
|
|
424
|
+
}>;
|
|
425
|
+
deleteUserWorkspace(id: string): Promise<void>;
|
|
426
|
+
addUserWorkspaceGrant(id: string, body: {
|
|
427
|
+
user_id?: string;
|
|
428
|
+
group?: string;
|
|
429
|
+
mode: string;
|
|
430
|
+
}): Promise<{
|
|
431
|
+
user_workspace: Record<string, unknown>;
|
|
432
|
+
}>;
|
|
433
|
+
removeUserWorkspaceGrant(id: string, body: {
|
|
434
|
+
user_id?: string;
|
|
435
|
+
group?: string;
|
|
436
|
+
}): Promise<{
|
|
437
|
+
user_workspace: Record<string, unknown>;
|
|
438
|
+
}>;
|
|
439
|
+
publishUserWorkspace(id: string, options?: {
|
|
440
|
+
message?: string;
|
|
441
|
+
autoResolve?: string;
|
|
442
|
+
}): Promise<Record<string, unknown>>;
|
|
399
443
|
/**
|
|
400
444
|
* Create `workspaceName` from {@link options.fromBranch} (or current context), run `fn` scoped to that workspace,
|
|
401
445
|
* create a checkpoint, then publish back unless {@link options.autoMerge} is `false`.
|
package/dist/client.js
CHANGED
|
@@ -379,6 +379,33 @@ class XCiteDBClient {
|
|
|
379
379
|
}
|
|
380
380
|
return this.request('DELETE', '/api/v1/test/sessions/current', undefined, undefined, { no401Retry: true });
|
|
381
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* List active test sessions for the current API key or Bearer (`GET /api/v1/test/sessions`).
|
|
384
|
+
* Does not send `X-Test-Session` (management route).
|
|
385
|
+
*/
|
|
386
|
+
async listTestSessions() {
|
|
387
|
+
const r = await this.request('GET', '/api/v1/test/sessions', undefined, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
388
|
+
const rows = r.sessions ?? [];
|
|
389
|
+
return rows.map((s) => ({
|
|
390
|
+
sessionToken: String(s.session_token ?? ''),
|
|
391
|
+
createdAt: Number(s.created_at ?? 0),
|
|
392
|
+
lastUsed: Number(s.last_used ?? 0),
|
|
393
|
+
...(s.overlay === true ? { overlay: true } : {}),
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
/** Destroy every test session owned by this credential (`DELETE /api/v1/test/sessions/all`). */
|
|
397
|
+
async destroyAllTestSessions() {
|
|
398
|
+
const r = await this.request('DELETE', '/api/v1/test/sessions/all', undefined, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
399
|
+
return {
|
|
400
|
+
message: String(r.message ?? ''),
|
|
401
|
+
destroyed: Number(r.destroyed ?? 0),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
/** Destroy a single owned session by token (`DELETE /api/v1/test/sessions/{token}`). */
|
|
405
|
+
async destroyTestSessionByToken(token) {
|
|
406
|
+
const enc = encodeURIComponent(token);
|
|
407
|
+
return this.request('DELETE', `/api/v1/test/sessions/${enc}`, undefined, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
408
|
+
}
|
|
382
409
|
/** True if this client would send API key or Bearer credentials on a normal request. */
|
|
383
410
|
sentAuthCredentials() {
|
|
384
411
|
return !!(this.apiKey || this.accessToken || this.appUserAccessToken);
|
|
@@ -505,10 +532,13 @@ class XCiteDBClient {
|
|
|
505
532
|
}
|
|
506
533
|
async request(method, path, body, extraHeaders, opts) {
|
|
507
534
|
const no401Retry = opts?.no401Retry === true;
|
|
535
|
+
const suppressTestSessionHeader = opts?.suppressTestSessionHeader === true;
|
|
508
536
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
509
537
|
const url = joinUrl(this.baseUrl, path);
|
|
510
538
|
const headers = {
|
|
511
|
-
...this.
|
|
539
|
+
...this.authHeaders(),
|
|
540
|
+
...this.contextHeaders(),
|
|
541
|
+
...(suppressTestSessionHeader ? {} : this.testHeaders()),
|
|
512
542
|
...extraHeaders,
|
|
513
543
|
};
|
|
514
544
|
const outgoingRequestId = newClientRequestId();
|
|
@@ -1216,6 +1246,37 @@ class XCiteDBClient {
|
|
|
1216
1246
|
async mergeBranch(targetBranch, sourceBranch, options) {
|
|
1217
1247
|
return this.publishWorkspace(targetBranch, sourceBranch, options);
|
|
1218
1248
|
}
|
|
1249
|
+
/** List app-user draft workspaces visible to the caller (`GET /api/v1/user-workspaces`). */
|
|
1250
|
+
async listUserWorkspaces() {
|
|
1251
|
+
return this.request('GET', '/api/v1/user-workspaces');
|
|
1252
|
+
}
|
|
1253
|
+
/** Create a user workspace (`POST /api/v1/user-workspaces`). Returns the workspace record (top-level JSON). */
|
|
1254
|
+
async createUserWorkspace(name, options) {
|
|
1255
|
+
const body = { name };
|
|
1256
|
+
if (options?.sourceBranch)
|
|
1257
|
+
body.source_branch = options.sourceBranch;
|
|
1258
|
+
if (options?.sourceDate)
|
|
1259
|
+
body.source_date = options.sourceDate;
|
|
1260
|
+
return this.request('POST', '/api/v1/user-workspaces', body);
|
|
1261
|
+
}
|
|
1262
|
+
async getUserWorkspace(id) {
|
|
1263
|
+
return this.request('GET', `/api/v1/user-workspaces/${encodeURIComponent(id)}`);
|
|
1264
|
+
}
|
|
1265
|
+
async deleteUserWorkspace(id) {
|
|
1266
|
+
await this.request('DELETE', `/api/v1/user-workspaces/${encodeURIComponent(id)}`);
|
|
1267
|
+
}
|
|
1268
|
+
async addUserWorkspaceGrant(id, body) {
|
|
1269
|
+
return this.request('POST', `/api/v1/user-workspaces/${encodeURIComponent(id)}/grants`, body);
|
|
1270
|
+
}
|
|
1271
|
+
async removeUserWorkspaceGrant(id, body) {
|
|
1272
|
+
return this.request('DELETE', `/api/v1/user-workspaces/${encodeURIComponent(id)}/grants`, body);
|
|
1273
|
+
}
|
|
1274
|
+
async publishUserWorkspace(id, options) {
|
|
1275
|
+
const body = { auto_resolve: options?.autoResolve ?? 'none' };
|
|
1276
|
+
if (options?.message)
|
|
1277
|
+
body.message = options.message;
|
|
1278
|
+
return this.request('POST', `/api/v1/user-workspaces/${encodeURIComponent(id)}/publish`, body);
|
|
1279
|
+
}
|
|
1219
1280
|
/**
|
|
1220
1281
|
* Create `workspaceName` from {@link options.fromBranch} (or current context), run `fn` scoped to that workspace,
|
|
1221
1282
|
* create a checkpoint, then publish back unless {@link options.autoMerge} is `false`.
|
package/dist/client.test.js
CHANGED
|
@@ -11,6 +11,27 @@ const strict_1 = __importDefault(require("node:assert/strict"));
|
|
|
11
11
|
const client_js_1 = require("./client.js");
|
|
12
12
|
const types_js_1 = require("./types.js");
|
|
13
13
|
(0, node_test_1.describe)('XCiteDBClient error mapping', () => {
|
|
14
|
+
(0, node_test_1.it)('listTestSessions omits X-Test-Session even when client has testSessionToken', async () => {
|
|
15
|
+
const orig = globalThis.fetch;
|
|
16
|
+
globalThis.fetch = node_test_1.mock.fn(async (input, init) => {
|
|
17
|
+
const h = new Headers(init?.headers);
|
|
18
|
+
strict_1.default.equal(h.get('X-Test-Session'), null);
|
|
19
|
+
strict_1.default.equal(h.get('X-API-Key'), 'test-key');
|
|
20
|
+
return new Response(JSON.stringify({ sessions: [] }), { status: 200 });
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
const c = new client_js_1.XCiteDBClient({
|
|
24
|
+
baseUrl: 'http://127.0.0.1:9',
|
|
25
|
+
apiKey: 'test-key',
|
|
26
|
+
testSessionToken: '00000000-0000-0000-0000-000000000001',
|
|
27
|
+
});
|
|
28
|
+
const list = await c.listTestSessions();
|
|
29
|
+
strict_1.default.deepEqual(list, []);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
globalThis.fetch = orig;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
14
35
|
(0, node_test_1.it)('403 becomes XCiteDBForbiddenError with policy_id and request ids', async () => {
|
|
15
36
|
const orig = globalThis.fetch;
|
|
16
37
|
globalThis.fetch = node_test_1.mock.fn(async () => {
|
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, 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, Flags, JsonDocumentData, JsonDocumentBatchItem, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, AcquireLockOptions, LockConflictBody, LockExpiredBody, LockUnknownBody, MergeConflict, MergeResult, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, ProjectSearchSettings, ProjectSearchSettingsUpdate, ProjectDocConfResponse, PlatformDefaultDocConfResponse, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PublishConflict, PublishResult, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RagQueryOptions, RagQueryResult, RagStreamEvent, RealtimeEvent, SearchIndexingProgress, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TagRecord, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TokenPair, UserInfo, UserIsolationConfig, UserIsolationCreateShareParams, UserIsolationOptions, UserIsolationShareMode, UserIsolationShareResult, WorkspaceInfo, WriteDocumentOptions, XmlDocumentBatchItem, CreateTestSessionOptions, TestSessionBootstrap, TestSessionBootstrapSummary, TestSessionInfo, XCiteDBClientOptions, XCiteDBErrorExtras, XCiteDBJwtClaims, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
|
|
4
4
|
export { XCiteDBError, XCiteDBForbiddenError, XCiteDBNotFoundError, XCiteDBAuthError, XCiteDBLockConflictError, } from './types';
|
package/dist/types.d.ts
CHANGED
|
@@ -524,6 +524,14 @@ export interface TestSessionBootstrapSummary {
|
|
|
524
524
|
developer_bypass_applied?: boolean;
|
|
525
525
|
policies_created?: string[];
|
|
526
526
|
}
|
|
527
|
+
/** One row from `GET /api/v1/test/sessions` (management; no `X-Test-Session` on that route). */
|
|
528
|
+
export interface TestSessionInfo {
|
|
529
|
+
sessionToken: string;
|
|
530
|
+
createdAt: number;
|
|
531
|
+
lastUsed: number;
|
|
532
|
+
/** Present when the session was created with overlay mode. */
|
|
533
|
+
overlay?: true;
|
|
534
|
+
}
|
|
527
535
|
/** Options for {@link XCiteDBClient.createTestSession} (provisions via API key or Bearer). */
|
|
528
536
|
export interface CreateTestSessionOptions {
|
|
529
537
|
baseUrl: 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 — 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
|
|
|
@@ -223,14 +224,15 @@ For **integration and wet tests** against a shared BaaS host without touching pr
|
|
|
223
224
|
| **Create** | **`POST /api/v1/test/sessions`** with normal **`Authorization: Bearer …`** or **`X-API-Key`**. Response includes a **`session_token`** (UUID). Server enforces per-credential limits (`test.max_sessions_per_key`, `test.session_ttl_seconds`, `test.max_test_db_size_bytes` in server config). Optional JSON body **`{"overlay":true}`** provisions a **read-through production** session (writable delta only under `_test/<uuid>/`; production LMDB is read-only base). |
|
|
224
225
|
| **Use** | Send **`X-Test-Session: <session_token>`** on document and other data API requests. The server routes to a dedicated LMDB under its data root (`_test/<id>/`), not the caller’s production tenant. **`tenant_id` / `X-Project-Id` semantics do not select production** while the test header is present—the synthetic test tenant is implied. |
|
|
225
226
|
| **Auth** | **Default:** developer auth (API key / platform JWT) is **bypassed** with a synthetic admin identity. However, **app-user identity is still recognized**: if `X-App-User-Token` or a Bearer app-user JWT is present, the request runs as that app user (for routes like `/app/auth/me`). **`X-Test-Auth: required`:** all auth is validated normally; ABAC applies, but data still comes from the test session DB. |
|
|
226
|
-
| **Manage** | **`GET /api/v1/test/sessions`** — list sessions for the current credential. **`DELETE /api/v1/test/sessions/current`** — destroy the session named by **`X-Test-Session`** (no other auth). **`DELETE /api/v1/test/sessions/all`** — destroy all sessions for the credential. **`DELETE /api/v1/test/sessions/{token}`** — destroy one session if owned by the credential. Do **not** send **`X-Test-Session`** on these `/api/v1/test/*` routes. |
|
|
227
|
+
| **Manage** | **`GET /api/v1/test/sessions`** — list sessions for the current credential. **`DELETE /api/v1/test/sessions/current`** — destroy the session named by **`X-Test-Session`** (no other auth). **`DELETE /api/v1/test/sessions/all`** — destroy all sessions for the credential. **`DELETE /api/v1/test/sessions/{token}`** — destroy one session if owned by the credential. Do **not** send **`X-Test-Session`** on these `/api/v1/test/*` routes. **SDKs:** JS `listTestSessions` / `destroyAllTestSessions` / `destroyTestSessionByToken`; Python `list_test_sessions` / `destroy_all_test_sessions` / `destroy_test_session_by_token`; C++ same snake_case; MCP tools `list_test_sessions`, `destroy_all_test_sessions`, `destroy_test_session_by_token`. |
|
|
228
|
+
| **429 / suites** | Per-credential concurrent cap (default **5**, `test.max_sessions_per_key`). If **`createTestSession`** returns **429**, call **`destroyAllTestSessions`** (same API key / Bearer, no `X-Test-Session`) before retrying — typical **once in `beforeAll`** for large wet suites. |
|
|
227
229
|
| **CORS** | Browsers may need **`X-Test-Session`** and **`X-Test-Auth`** in the deployment’s allowed CORS headers (defaults include them). |
|
|
228
230
|
|
|
229
231
|
**SDK usage (summary):**
|
|
230
232
|
|
|
231
|
-
- **JavaScript/TypeScript:** `XCiteDBClient.createTestSession({ baseUrl, apiKey, …, bootstrap })` returns a client configured with `testSessionToken`; optional **`overlay: true`** for overlay mode; optional `testRequireAuth: true` maps to `X-Test-Auth: required`; optional **`bootstrap`** (`user_isolation`, `developer_bypass`, `policies`); read **`lastTestSessionBootstrap`** on the returned client when the server included a summary. `destroyTestSession()` calls `DELETE …/test/sessions/current`.
|
|
232
|
-
- **Python:** `async with XCiteDBClient.test_session(base_url, api_key=…, …, bootstrap={…})` provisions and tears down; or **`POST /api/v1/test/sessions`** with JSON **`{"overlay":true}`**, **`bootstrap`**, or both, then construct the client with the returned token; **`client.last_test_session_bootstrap`** mirrors the server summary.
|
|
233
|
-
- **C++:** `XCiteDBClient::create_test_session(options)` after setting `api_key`, optional **`test_session_overlay`**, optional **`test_session_bootstrap`** on `XCiteDBClientOptions`, optional `test_require_auth`; **`last_test_session_bootstrap()`** on the returned client; `destroy_test_session()`.
|
|
233
|
+
- **JavaScript/TypeScript:** `XCiteDBClient.createTestSession({ baseUrl, apiKey, …, bootstrap })` returns a client configured with `testSessionToken`; optional **`overlay: true`** for overlay mode; optional `testRequireAuth: true` maps to `X-Test-Auth: required`; optional **`bootstrap`** (`user_isolation`, `developer_bypass`, `policies`); read **`lastTestSessionBootstrap`** on the returned client when the server included a summary. `destroyTestSession()` calls `DELETE …/test/sessions/current`. On a **normal** client (same `apiKey` / Bearer, **no** `testSessionToken`), **`listTestSessions()`**, **`destroyAllTestSessions()`**, **`destroyTestSessionByToken(token)`** wrap the management routes (they never send `X-Test-Session`).
|
|
234
|
+
- **Python:** `async with XCiteDBClient.test_session(base_url, api_key=…, …, bootstrap={…})` provisions and tears down; or **`POST /api/v1/test/sessions`** with JSON **`{"overlay":true}`**, **`bootstrap`**, or both, then construct the client with the returned token; **`client.last_test_session_bootstrap`** mirrors the server summary. Management: **`await client.list_test_sessions()`**, **`await client.destroy_all_test_sessions()`**, **`await client.destroy_test_session_by_token(token)`** with `suppress_test_session` behavior (omit `X-Test-Session` on those paths).
|
|
235
|
+
- **C++:** `XCiteDBClient::create_test_session(options)` after setting `api_key`, optional **`test_session_overlay`**, optional **`test_session_bootstrap`** on `XCiteDBClientOptions`, optional `test_require_auth`; **`last_test_session_bootstrap()`** on the returned client; `destroy_test_session()`. Management: **`list_test_sessions()`**, **`destroy_all_test_sessions()`**, **`destroy_test_session_by_token(token)`** on a client carrying the provisioning credential only.
|
|
234
236
|
|
|
235
237
|
**A fresh test session is an empty tenant with zero policies.** With `X-Test-Auth: required`, ABAC default-deny applies until you bootstrap user isolation and/or policies. Typical `POST /api/v1/test/sessions` body:
|
|
236
238
|
|
|
@@ -256,11 +258,14 @@ describe('XciteDB integration', () => {
|
|
|
256
258
|
let client: XCiteDBClient;
|
|
257
259
|
|
|
258
260
|
beforeAll(async () => {
|
|
259
|
-
|
|
260
|
-
client = await XCiteDBClient.createTestSession({
|
|
261
|
+
const base = {
|
|
261
262
|
baseUrl: process.env.XCITEDB_URL ?? 'http://localhost:8080',
|
|
262
263
|
apiKey: process.env.XCITEDB_API_KEY!,
|
|
263
|
-
}
|
|
264
|
+
};
|
|
265
|
+
// Optional: clear sessions leaked from killed CI runs (same API key; default cap is 5).
|
|
266
|
+
const janitor = new XCiteDBClient(base);
|
|
267
|
+
await janitor.destroyAllTestSessions();
|
|
268
|
+
client = await XCiteDBClient.createTestSession(base);
|
|
264
269
|
});
|
|
265
270
|
|
|
266
271
|
afterAll(async () => {
|
|
@@ -544,8 +549,8 @@ Attach structured **JSON metadata** to documents or nodes.
|
|
|
544
549
|
| `identifier` **or** `query` | one required | Target document id or document query |
|
|
545
550
|
| `value` | yes | JSON to write (omit only for string-specific query batch paths handled by the server) |
|
|
546
551
|
| `path` | no (default `""`) | Meta path (dot-separated keys; `[i]` for array indices) |
|
|
547
|
-
| `mode` | no (default `"set"`) | `"set"` — write/replace at `path`. For **arrays** at `path`, indices `0..n-1` are written and any previous tail beyond the new length is cleared. `"append"` —
|
|
548
|
-
| `overwrite` | no (default `false`) | When `true`, delete existing metadata under `path` before applying `value`. |
|
|
552
|
+
| `mode` | no (default `"set"`) | `"set"` — write/replace at `path`. For **arrays** at `path`, indices `0..n-1` are written and any previous tail beyond the new length is cleared. `"append"` — array-extend behavior applies only when the **payload** at `path` is a JSON **array**; new elements are written after the stored length from the array marker `[n]` at `path`. If nothing array-shaped is stored there, the effective prior length is `0` (indices start at `0`). Scalars or a non-array object at `path` are not merged via array-append (object payloads recurse; nested array fields follow the same rules under their own paths). See **Append semantics** below. |
|
|
553
|
+
| `overwrite` | no (default `false`) | When `true`, delete existing metadata under `path` **before** applying `value`. That runs before array logic: with `mode: "append"` it clears the stored `[n]` marker, so the subsequent write always starts at index `0` (you do not keep prior elements). To extend an existing array, use **`overwrite: false`**. For “clear then replace the whole array”, use **`overwrite: true`** with **`mode: "set"`** (or `append` after clear, which is equivalent to writing from `0`). |
|
|
549
554
|
| `first_match` | no | With `query`, only the first matching identifier is updated when `true`. |
|
|
550
555
|
|
|
551
556
|
```json
|
|
@@ -558,7 +563,35 @@ Attach structured **JSON metadata** to documents or nodes.
|
|
|
558
563
|
}
|
|
559
564
|
```
|
|
560
565
|
|
|
561
|
-
|
|
566
|
+
### Append semantics
|
|
567
|
+
|
|
568
|
+
- **`mode: "append"`** only changes array behavior when the **JSON at `path` in this request’s `value`** is an **array** (or when traversing an object payload, each **nested** field whose value is an array, under the same `append` flag). A **scalar** or **non-array object** at `path` does not run “append after `[n]`” logic for that node.
|
|
569
|
+
- To **extend** an existing list in storage, the **stored** value at `path` should already be an array (the server keeps a `[length]` marker). New payload elements are written at indices `length`, `length+1`, … If there is **no** valid array marker at `path` in storage, append still succeeds but behaves like a **first write** from index `0`.
|
|
570
|
+
- **`overwrite: true` + `append`:** do not use this expecting “delete old tail then append onto what was left” — overwrite **removes everything under `path` first**, so append always indexes from **`0`**. Prefer **`overwrite: false`** + **`append`** to grow a list; use **`overwrite: true`** + **`set`** when replacing the whole subtree/array.
|
|
571
|
+
|
|
572
|
+
**Example — extend `tags` without overwrite** (stored `tags` is `["a","b"]`; result `["a","b","c","d"]`):
|
|
573
|
+
|
|
574
|
+
```json
|
|
575
|
+
{
|
|
576
|
+
"identifier": "/book/ch1",
|
|
577
|
+
"path": "tags",
|
|
578
|
+
"mode": "append",
|
|
579
|
+
"overwrite": false,
|
|
580
|
+
"value": ["c", "d"]
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**Example — same payload with `overwrite: true`** (clears `tags` first; result only `["c","d"]`):
|
|
585
|
+
|
|
586
|
+
```json
|
|
587
|
+
{
|
|
588
|
+
"identifier": "/book/ch1",
|
|
589
|
+
"path": "tags",
|
|
590
|
+
"mode": "append",
|
|
591
|
+
"overwrite": true,
|
|
592
|
+
"value": ["c", "d"]
|
|
593
|
+
}
|
|
594
|
+
```
|
|
562
595
|
|
|
563
596
|
Can also use `"query"` instead of `"identifier"` to target multiple documents by query filter.
|
|
564
597
|
|
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
|
|
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
|
|
|
@@ -122,7 +124,8 @@ SDKs surface these fields on typed errors (e.g. `XCiteDBForbiddenError.policyId`
|
|
|
122
124
|
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 writes go under the server’s `_test/<session>/` tree; overlay sessions **do not** write to production paths.
|
|
123
125
|
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.
|
|
124
126
|
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.
|
|
125
|
-
5. **
|
|
127
|
+
5. **429 / leaked sessions:** The server caps concurrent sessions per credential (`test.max_sessions_per_key`, default **5**). If **`createTestSession`** returns **429** (“Too many active test sessions for this credential”), reap with the **same** API key or Bearer (do **not** send `X-Test-Session` on management routes): **`destroyAllTestSessions()`** (JS), **`destroy_all_test_sessions()`** (Python/C++), or HTTP **`DELETE /api/v1/test/sessions/all`**. Large E2E wrappers often call that once before the suite so killed runs do not exhaust the slot budget.
|
|
128
|
+
6. **SDKs (management = provisioning credential, no `X-Test-Session`):** **JS/TS:** `createTestSession`, `destroyTestSession()` (current session), `listTestSessions()`, `destroyAllTestSessions()`, `destroyTestSessionByToken(token)`; optional `overlay`, `testRequireAuth`, `bootstrap`; read `lastTestSessionBootstrap` on the returned client. **Python:** `test_session(...)` or manual POST; `destroy_test_session()`, `list_test_sessions()`, `destroy_all_test_sessions()`, `destroy_test_session_by_token(token)`; `last_test_session_bootstrap`. **C++:** `create_test_session`, `destroy_test_session()`, `list_test_sessions()`, `destroy_all_test_sessions()`, `destroy_test_session_by_token(token)`; `last_test_session_bootstrap`. **MCP:** tools `list_test_sessions`, `destroy_all_test_sessions`, `destroy_test_session_by_token`.
|
|
126
129
|
|
|
127
130
|
**A fresh test session is an empty tenant with zero policies.** With `X-Test-Auth: required` the AuthFilter runs normally and ABAC is enforced against the test tenant — default-deny rejects every app-user read/write until you either enable user isolation or add explicit allow policies. The standard recipe is to use the bootstrap option on `POST /api/v1/test/sessions`:
|
|
128
131
|
|
|
@@ -153,7 +156,7 @@ When you build a backend that calls XCiteDB on behalf of users:
|
|
|
153
156
|
|
|
154
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).
|
|
155
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).
|
|
156
|
-
- **Prefer dictionary-style objects.** Shredded JSON metadata is
|
|
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.
|
|
157
160
|
|
|
158
161
|
## JavaScript/TypeScript SDK (`@xcitedbs/client`)
|
|
159
162
|
|
|
@@ -296,6 +299,7 @@ interface XCiteDBClientOptions {
|
|
|
296
299
|
- `listWorkspaces()` — List workspaces
|
|
297
300
|
- `publishWorkspace(target, source, options?)` — Publish workspace changes to a target timeline
|
|
298
301
|
- `deleteWorkspace(name)` — Delete workspace
|
|
302
|
+
- `listUserWorkspaces()` / `createUserWorkspace(name, options?)` / `getUserWorkspace(id)` / `deleteUserWorkspace(id)` / `addUserWorkspaceGrant(id, body)` / `removeUserWorkspaceGrant(id, body)` / `publishUserWorkspace(id, options?)` — App-user **`_uw/…`** workspaces
|
|
299
303
|
- `createCheckpoint(message, author?)` — Named snapshot of current state
|
|
300
304
|
- `listCheckpoints(options?)` — List checkpoints
|
|
301
305
|
- `revertToCheckpoint(checkpointId)` — Revert workspace to a prior checkpoint
|
|
@@ -416,6 +420,7 @@ auto ids = client.query_documents(q);
|
|
|
416
420
|
## Architecture Notes
|
|
417
421
|
|
|
418
422
|
- **Multi-tenant**: Each project is an isolated tenant with its own data, users, keys, and policies.
|
|
423
|
+
- **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.
|
|
419
424
|
- **LMDB engine**: Data is memory-mapped; reads are microsecond-class on warm data.
|
|
420
425
|
- **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.
|
|
421
426
|
- **Two user tiers**: Platform operators manage infrastructure; App users are end-users of applications built on XciteDB.
|