@xcitedbs/client 0.2.19 → 0.2.20

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 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. */
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.requestHeaders(),
539
+ ...this.authHeaders(),
540
+ ...this.contextHeaders(),
541
+ ...(suppressTestSessionHeader ? {} : this.testHeaders()),
512
542
  ...extraHeaders,
513
543
  };
514
544
  const outgoingRequestId = newClientRequestId();
@@ -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
@@ -223,14 +223,15 @@ For **integration and wet tests** against a shared BaaS host without touching pr
223
223
  | **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
224
  | **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
225
  | **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. |
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. **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`. |
227
+ | **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
228
  | **CORS** | Browsers may need **`X-Test-Session`** and **`X-Test-Auth`** in the deployment’s allowed CORS headers (defaults include them). |
228
229
 
229
230
  **SDK usage (summary):**
230
231
 
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()`.
232
+ - **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`).
233
+ - **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).
234
+ - **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
235
 
235
236
  **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
237
 
@@ -256,11 +257,14 @@ describe('XciteDB integration', () => {
256
257
  let client: XCiteDBClient;
257
258
 
258
259
  beforeAll(async () => {
259
- // Provisions an isolated, throwaway LMDB — production data is never touched.
260
- client = await XCiteDBClient.createTestSession({
260
+ const base = {
261
261
  baseUrl: process.env.XCITEDB_URL ?? 'http://localhost:8080',
262
262
  apiKey: process.env.XCITEDB_API_KEY!,
263
- });
263
+ };
264
+ // Optional: clear sessions leaked from killed CI runs (same API key; default cap is 5).
265
+ const janitor = new XCiteDBClient(base);
266
+ await janitor.destroyAllTestSessions();
267
+ client = await XCiteDBClient.createTestSession(base);
264
268
  });
265
269
 
266
270
  afterAll(async () => {
@@ -544,8 +548,8 @@ Attach structured **JSON metadata** to documents or nodes.
544
548
  | `identifier` **or** `query` | one required | Target document id or document query |
545
549
  | `value` | yes | JSON to write (omit only for string-specific query batch paths handled by the server) |
546
550
  | `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"` — for **arrays** at `path`, new elements are written after existing indices (extend in place). |
548
- | `overwrite` | no (default `false`) | When `true`, delete existing metadata under `path` before applying `value`. |
551
+ | `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. |
552
+ | `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
553
  | `first_match` | no | With `query`, only the first matching identifier is updated when `true`. |
550
554
 
551
555
  ```json
@@ -558,7 +562,35 @@ Attach structured **JSON metadata** to documents or nodes.
558
562
  }
559
563
  ```
560
564
 
561
- Use `"mode": "append"` to extend arrays at `path` instead of replacing them.
565
+ ### Append semantics
566
+
567
+ - **`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.
568
+ - 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`.
569
+ - **`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.
570
+
571
+ **Example — extend `tags` without overwrite** (stored `tags` is `["a","b"]`; result `["a","b","c","d"]`):
572
+
573
+ ```json
574
+ {
575
+ "identifier": "/book/ch1",
576
+ "path": "tags",
577
+ "mode": "append",
578
+ "overwrite": false,
579
+ "value": ["c", "d"]
580
+ }
581
+ ```
582
+
583
+ **Example — same payload with `overwrite: true`** (clears `tags` first; result only `["c","d"]`):
584
+
585
+ ```json
586
+ {
587
+ "identifier": "/book/ch1",
588
+ "path": "tags",
589
+ "mode": "append",
590
+ "overwrite": true,
591
+ "value": ["c", "d"]
592
+ }
593
+ ```
562
594
 
563
595
  Can also use `"query"` instead of `"identifier"` to target multiple documents by query filter.
564
596
 
package/llms.txt CHANGED
@@ -122,7 +122,8 @@ SDKs surface these fields on typed errors (e.g. `XCiteDBForbiddenError.policyId`
122
122
  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
123
  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
124
  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. **SDKs:** **JS/TS:** `XCiteDBClient.createTestSession({ baseUrl, apiKey, …, bootstrap })`, optional `overlay: true`, optional `testRequireAuth`, optional `bootstrap` (`user_isolation`, `developer_bypass`, `policies`), then read `lastTestSessionBootstrap` on the returned client; `destroyTestSession()`. **Python:** `async with XCiteDBClient.test_session(..., bootstrap={...})` or provision with **`POST /api/v1/test/sessions`** (JSON may include **`{"overlay":true}`** and/or **`bootstrap`**); use `client.last_test_session_bootstrap` after creation; pass `test_session_token` / `test_require_auth` to the constructor as before. **C++:** `XCiteDBClient::create_test_session(options)` with optional `test_session_overlay`, optional `test_session_bootstrap` on `XCiteDBClientOptions`, `last_test_session_bootstrap()` on the returned client, `destroy_test_session()`, optional `test_require_auth`.
125
+ 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.
126
+ 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
127
 
127
128
  **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
129
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcitedbs/client",
3
- "version": "0.2.19",
3
+ "version": "0.2.20",
4
4
  "description": "XCiteDB BaaS client SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",