@xcitedbs/client 0.2.15 → 0.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AccessCheckResult, AppAuthConfig, AppEmailConfig, AppEmailTemplates, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BranchInfo, BookmarkRecord, CheckpointRecord, CommitRecord, CompareRef, CompareResult, DatabaseContext, DiffRef, DiffResult, DocumentBatchResponse, Flags, JsonDocumentBatchItem, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, 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, XCiteQuery, UserIsolationConfig } 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, XCiteQuery, UserIsolationConfig, UserIsolationCreateShareParams, UserIsolationShareResult } from './types';
2
2
  import { WebSocketSubscription } from './websocket';
3
3
  export declare class XCiteDBClient {
4
4
  private baseUrl;
@@ -295,6 +295,12 @@ export declare class XCiteDBClient {
295
295
  * the server (namespace + shared paths). Does not send `X-Prefix`; identifiers in requests are rewritten.
296
296
  */
297
297
  enableUserIsolation(): Promise<UserIsolationConfig>;
298
+ /**
299
+ * Share a document from the caller’s user namespace with another app user (`POST …/user-isolation/shares`).
300
+ * Requires an **app user** session (`appUserAccessToken` / `loginAppUser`) and tenant isolation enabled.
301
+ * When {@link XCiteDBClient.enableUserIsolation} / `userIsolation` is active, `identifier` is prefixed like other document APIs.
302
+ */
303
+ createUserIsolationShare(params: UserIsolationCreateShareParams): Promise<UserIsolationShareResult>;
298
304
  createWorkspace(name: string, fromBranch?: string, fromDate?: string): Promise<void>;
299
305
  /** @deprecated Use {@link createWorkspace}. */
300
306
  createBranch(name: string, fromBranch?: string, fromDate?: string): Promise<void>;
@@ -454,8 +460,18 @@ export declare class XCiteDBClient {
454
460
  queryMeta<T = MetaValue>(identifier: string, path?: string): Promise<T>;
455
461
  queryMetaByQuery<T = MetaValue>(query: XCiteQuery, path?: string): Promise<T>;
456
462
  clearMeta(query: XCiteQuery): Promise<boolean>;
457
- acquireLock(identifier: string, expires?: number): Promise<LockInfo>;
463
+ /**
464
+ * Acquire a cooperative lock. On exclusive conflict the server returns HTTP 409 with a
465
+ * {@link LockConflictBody} body (thrown as {@link XCiteDBError} with `.status === 409`).
466
+ */
467
+ acquireLock(identifier: string, expiresOrOpts?: number | AcquireLockOptions): Promise<LockInfo>;
458
468
  releaseLock(identifier: string, lockId: string): Promise<boolean>;
469
+ /**
470
+ * Force-release a lock using `force_unlock` ABAC authority. Pass the **raw** stored identifier
471
+ * from {@link findLocks} (no iso-prefix); often the path as returned by the server for another principal.
472
+ */
473
+ forceReleaseLock(identifier: string, lockId: string): Promise<boolean>;
474
+ extendLock(identifier: string, lockId: string, ttlSeconds: number): Promise<LockInfo>;
459
475
  findLocks(identifier: string): Promise<LockInfo[]>;
460
476
  /**
461
477
  * Run Unquery (`POST /api/v1/unquery`): declarative analytics over documents matching `query`.
package/dist/client.js CHANGED
@@ -976,6 +976,18 @@ class XCiteDBClient {
976
976
  }
977
977
  return cfg;
978
978
  }
979
+ /**
980
+ * Share a document from the caller’s user namespace with another app user (`POST …/user-isolation/shares`).
981
+ * Requires an **app user** session (`appUserAccessToken` / `loginAppUser`) and tenant isolation enabled.
982
+ * When {@link XCiteDBClient.enableUserIsolation} / `userIsolation` is active, `identifier` is prefixed like other document APIs.
983
+ */
984
+ async createUserIsolationShare(params) {
985
+ return this.request('POST', '/api/v1/security/user-isolation/shares', {
986
+ identifier: this.isoPrefixId(params.identifier),
987
+ target_user_id: params.target_user_id,
988
+ mode: params.mode,
989
+ });
990
+ }
979
991
  async createWorkspace(name, fromBranch, fromDate) {
980
992
  const body = { name };
981
993
  if (fromBranch)
@@ -1412,11 +1424,23 @@ class XCiteDBClient {
1412
1424
  });
1413
1425
  return r?.ok !== false;
1414
1426
  }
1415
- async acquireLock(identifier, expires = 0) {
1416
- const lock = await this.request('POST', '/api/v1/locks', {
1427
+ /**
1428
+ * Acquire a cooperative lock. On exclusive conflict the server returns HTTP 409 with a
1429
+ * {@link LockConflictBody} body (thrown as {@link XCiteDBError} with `.status === 409`).
1430
+ */
1431
+ async acquireLock(identifier, expiresOrOpts = 0) {
1432
+ const opts = typeof expiresOrOpts === 'number' ? { ttlSeconds: expiresOrOpts } : expiresOrOpts ?? {};
1433
+ const body = {
1417
1434
  identifier: this.isoPrefixId(identifier),
1418
- expires,
1419
- });
1435
+ };
1436
+ if (opts.ttlSeconds !== undefined && opts.ttlSeconds > 0) {
1437
+ body.ttl_seconds = opts.ttlSeconds;
1438
+ }
1439
+ if (opts.mode)
1440
+ body.mode = opts.mode;
1441
+ if (opts.waitMs !== undefined && opts.waitMs > 0)
1442
+ body.wait_ms = opts.waitMs;
1443
+ const lock = await this.request('POST', '/api/v1/locks', body);
1420
1444
  return { ...lock, identifier: this.isoUnprefixId(lock.identifier) };
1421
1445
  }
1422
1446
  async releaseLock(identifier, lockId) {
@@ -1426,6 +1450,26 @@ class XCiteDBClient {
1426
1450
  });
1427
1451
  return r?.ok !== false;
1428
1452
  }
1453
+ /**
1454
+ * Force-release a lock using `force_unlock` ABAC authority. Pass the **raw** stored identifier
1455
+ * from {@link findLocks} (no iso-prefix); often the path as returned by the server for another principal.
1456
+ */
1457
+ async forceReleaseLock(identifier, lockId) {
1458
+ const r = await this.request('DELETE', '/api/v1/locks', {
1459
+ identifier,
1460
+ lock_id: lockId,
1461
+ force: true,
1462
+ });
1463
+ return r?.ok !== false;
1464
+ }
1465
+ async extendLock(identifier, lockId, ttlSeconds) {
1466
+ const lock = await this.request('PATCH', '/api/v1/locks', {
1467
+ identifier: this.isoPrefixId(identifier),
1468
+ lock_id: lockId,
1469
+ ttl_seconds: ttlSeconds,
1470
+ });
1471
+ return { ...lock, identifier: this.isoUnprefixId(lock.identifier) };
1472
+ }
1429
1473
  async findLocks(identifier) {
1430
1474
  const locks = await this.request('GET', `/api/v1/locks${buildQuery({ identifier: this.isoPrefixId(identifier) })}`);
1431
1475
  return Array.isArray(locks)
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, 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, UserIsolationOptions, WorkspaceInfo, WriteDocumentOptions, XmlDocumentBatchItem, 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, DocumentBatchResponse, DocumentBatchResultRow, Flags, JsonDocumentData, JsonDocumentBatchItem, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, AcquireLockOptions, LockConflictBody, 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, XCiteDBClientOptions, XCiteDBJwtClaims, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
4
4
  export { XCiteDBError } from './types';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ /**
7
+ * Wet tests: set XCITEDB_BASE_URL, XCITEDB_ADMIN_TOKEN, XCITEDB_TENANT_ID.
8
+ * Run: npm test
9
+ */
10
+ const node_test_1 = require("node:test");
11
+ const strict_1 = __importDefault(require("node:assert/strict"));
12
+ const client_js_1 = require("./client.js");
13
+ function wetEnv() {
14
+ const baseUrl = process.env.XCITEDB_BASE_URL?.trim();
15
+ const accessToken = process.env.XCITEDB_ADMIN_TOKEN?.trim();
16
+ const tenantId = process.env.XCITEDB_TENANT_ID?.trim();
17
+ if (!baseUrl || !accessToken || !tenantId)
18
+ return null;
19
+ return { baseUrl, accessToken, tenantId };
20
+ }
21
+ async function openTestSession(e) {
22
+ const url = `${e.baseUrl.replace(/\/+$/, '')}/api/v1/test/sessions`;
23
+ const r = await fetch(url, {
24
+ method: 'POST',
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ Authorization: `Bearer ${e.accessToken}`,
28
+ 'X-Project-Id': e.tenantId,
29
+ 'X-Branch': 'main',
30
+ },
31
+ body: '{}',
32
+ });
33
+ const data = (await r.json());
34
+ if (!r.ok || !data.session_token) {
35
+ throw new Error(`test session failed ${r.status}`);
36
+ }
37
+ const client = new client_js_1.XCiteDBClient({
38
+ baseUrl: e.baseUrl,
39
+ accessToken: e.accessToken,
40
+ platformConsole: true,
41
+ projectId: e.tenantId,
42
+ context: { branch: 'main', project_id: e.tenantId },
43
+ testSessionToken: data.session_token,
44
+ testRequireAuth: true,
45
+ });
46
+ return { client, token: data.session_token };
47
+ }
48
+ const w = wetEnv();
49
+ const wd = w ? node_test_1.describe : node_test_1.describe.skip;
50
+ wd('locks (wet)', () => {
51
+ (0, node_test_1.it)('acquire, extend, release', async () => {
52
+ const e = wetEnv();
53
+ if (!e)
54
+ throw new Error('missing env');
55
+ const { client: c, token } = await openTestSession(e);
56
+ try {
57
+ const path = '/sdk_locks/js_wet_roundtrip';
58
+ await c.writeJsonDocument(path, { k: 1 }, { overwrite: true });
59
+ const lock = await c.acquireLock(path, { ttlSeconds: 120, mode: 'exclusive' });
60
+ strict_1.default.ok(lock.lock_id);
61
+ strict_1.default.equal(lock.mode, 'exclusive');
62
+ const ext = await c.extendLock(path, lock.lock_id, 180);
63
+ strict_1.default.ok((ext.version ?? 0) >= (lock.version ?? 1));
64
+ const ok = await c.releaseLock(path, lock.lock_id);
65
+ strict_1.default.ok(ok);
66
+ }
67
+ finally {
68
+ await fetch(`${e.baseUrl.replace(/\/+$/, '')}/api/v1/test/sessions/current`, {
69
+ method: 'DELETE',
70
+ headers: { 'X-Test-Session': token },
71
+ });
72
+ }
73
+ });
74
+ });
package/dist/types.d.ts CHANGED
@@ -253,6 +253,24 @@ export interface LockInfo {
253
253
  time: number;
254
254
  expiration: number;
255
255
  branch: string;
256
+ holder?: string;
257
+ mode?: 'exclusive' | 'advisory';
258
+ version?: number;
259
+ acquired_at?: string;
260
+ expires_at?: string;
261
+ }
262
+ /** Options for {@link XCiteDBClient.acquireLock}. */
263
+ export interface AcquireLockOptions {
264
+ /** Seconds until lock expires; server clamps (default 60). */
265
+ ttlSeconds?: number;
266
+ mode?: 'exclusive' | 'advisory';
267
+ /** Max milliseconds to wait when another exclusive lock is held. */
268
+ waitMs?: number;
269
+ }
270
+ /** Body returned with HTTP 409 on exclusive lock conflict. */
271
+ export interface LockConflictBody {
272
+ error: 'lock_conflict';
273
+ current_lock: LockInfo;
256
274
  }
257
275
  export interface TokenPair {
258
276
  access_token: string;
@@ -420,6 +438,20 @@ export interface UserIsolationConfig {
420
438
  shared_write_groups: string[];
421
439
  generated_policies?: string[];
422
440
  }
441
+ /** `read` or `readwrite` for {@link XCiteDBClient.createUserIsolationShare}. */
442
+ export type UserIsolationShareMode = 'read' | 'readwrite';
443
+ /** Body for `POST /api/v1/security/user-isolation/shares` (app user). */
444
+ export interface UserIsolationCreateShareParams {
445
+ identifier: string;
446
+ /** Another app user id on the same tenant, or `"*"` for all app users (public alias tree). */
447
+ target_user_id: string;
448
+ mode: UserIsolationShareMode;
449
+ }
450
+ /** JSON body returned with HTTP 201 from create share. */
451
+ export interface UserIsolationShareResult {
452
+ alias: string;
453
+ mode: string;
454
+ }
423
455
  export interface XCiteDBClientOptions {
424
456
  baseUrl: string;
425
457
  apiKey?: string;
package/llms-full.txt CHANGED
@@ -843,17 +843,43 @@ Returns `{ changes: [{ identifier, action: "added"|"modified"|"deleted", from_co
843
843
 
844
844
  ## Acquire lock
845
845
 
846
- **`POST /api/v1/locks`** — `{ "identifier": "/book/ch1", "expires": 600 }`
846
+ **`POST /api/v1/locks`**
847
847
 
848
- Returns lock info. **`409`** if already locked.
848
+ ```json
849
+ {
850
+ "identifier": "/book/ch1",
851
+ "ttl_seconds": 120,
852
+ "mode": "exclusive",
853
+ "wait_ms": 5000
854
+ }
855
+ ```
856
+
857
+ - **`ttl_seconds`** preferred; legacy **`expires`** is an alias. Omitted or `0` → server default (**60s**), clamped to **5–600s**.
858
+ - **`mode`**: `exclusive` (blocks other exclusives; multiple `advisory` locks can coexist; an existing **exclusive** blocks new `advisory`).
859
+ - **`wait_ms`**: optional 0–30000; server retries acquire every ~100ms until granted or timeout, then **`409`** with `{ "error":"lock_conflict", "current_lock": { ... } }`.
860
+
861
+ Returns **`201`** with `LockInfo`: `lock_id`, `identifier`, `time`, `expiration`, `branch`, `holder`, `mode`, `version`, `acquired_at`, `expires_at` (ISO UTC).
862
+
863
+ ## Extend lock
864
+
865
+ **`PATCH /api/v1/locks`** — `{ "identifier": "...", "lock_id": "...", "ttl_seconds": 120 }` — holder-only; bumps `version` and sets new expiry from **now**.
849
866
 
850
867
  ## Release lock
851
868
 
852
- **`DELETE /api/v1/locks`** — `{ "identifier": "/book/ch1", "lock_id": "..." }`
869
+ **`DELETE /api/v1/locks`** — `{ "identifier": "...", "lock_id": "...", "force": false }`
870
+
871
+ - Normal release: **`403`** `lock_holder_mismatch` if the lock exists but the identifier path does not match this principal (e.g. user isolation); **`404`** if `lock_id` unknown.
872
+ - **`force: true`**: deletes the lock blob without path check; requires ABAC **`force_unlock`** (policies with **`write`** still satisfy this via synonym fallback).
853
873
 
854
874
  ## Query locks
855
875
 
856
- **`GET /api/v1/locks?identifier=...`** — Lists active locks.
876
+ **`GET /api/v1/locks?identifier=...`** or **`?prefix=...`** — Lists active locks (same resolution). Response entries include **`holder`**.
877
+
878
+ ### Caveats
879
+
880
+ - SDK **user isolation** rewrites identifiers; **`findLocks`** returns the **stored** path in `identifier` — use that for **`forceReleaseLock`** when releasing another principal’s lock.
881
+ - Locks are keyed on the **resolved path at acquire time**. Renaming/moving an ancestor invalidates listing under the new path; **release locks before structural moves**, then reacquire.
882
+ - **`lock_expired`** events are emitted when an expired lock row is garbage-collected on read (best-effort), not on a wall-clock timer.
857
883
 
858
884
  ---
859
885
 
@@ -1447,8 +1473,10 @@ interface DatabaseContext {
1447
1473
  - **Deprecated:** `diff(from: DiffRef, …)` — alias of `compare`
1448
1474
 
1449
1475
  ### Locks
1450
- - `acquireLock(identifier, expires?)` → `LockInfo`
1476
+ - `acquireLock(identifier, ttlSeconds | { ttlSeconds, mode, waitMs })` → `LockInfo` (throws **`XCiteDBError`** with **409** and `lock_conflict` body on conflict)
1451
1477
  - `releaseLock(identifier, lockId)` → `boolean`
1478
+ - `forceReleaseLock(identifier, lockId)` → `boolean` (raw identifier when using isolation)
1479
+ - `extendLock(identifier, lockId, ttlSeconds)` → `LockInfo`
1452
1480
  - `findLocks(identifier)` → `LockInfo[]`
1453
1481
 
1454
1482
  ### Search
package/llms.txt CHANGED
@@ -178,10 +178,12 @@ await client.publishWorkspace('', 'feature-x'); // publish into root (default) w
178
178
  // Attach JSON metadata to a document
179
179
  await client.addMeta('/manual/v1/intro', { status: 'draft', owner: 'alice' });
180
180
 
181
- // Acquire a cooperative lock
182
- const lock = await client.acquireLock('/manual/v1/intro');
181
+ // Cooperative locks (exclusive blocks others; advisory is informational)
182
+ const lock = await client.acquireLock('/manual/v1/intro', { ttlSeconds: 120, mode: 'exclusive' });
183
183
  // ... edit ...
184
184
  await client.releaseLock('/manual/v1/intro', lock.lock_id);
185
+ // Force-release (requires force_unlock ABAC): use raw identifier from findLocks, no iso-prefix
186
+ // await client.forceReleaseLock(storedIdentifier, lock.lock_id);
185
187
 
186
188
  // Full-text search (embedded XciteFTS; optional temporal filters on the query body)
187
189
  const results = await client.search({ query: 'installation guide', limit: 20, mode: 'fts' });
@@ -266,9 +268,12 @@ interface XCiteDBClientOptions {
266
268
  - **Deprecated aliases:** `withBranch`, `createBranch`, `listBranches`, `mergeBranch`, `deleteBranch`, `createCommit`, `listCommits`, `rollbackToCommit`, `cherryPick`, `diff`, `createTag`, `listTags`, `deleteTag` (same HTTP behavior)
267
269
 
268
270
  **Locks:**
269
- - `acquireLock(identifier, expires?)` — Acquire cooperative lock
270
- - `releaseLock(identifier, lockId)` — Release lock
271
- - `findLocks(identifier)` — Query active locks
271
+ - `acquireLock(identifier, options?)` — `options`: `ttlSeconds` (server clamps 5–600, default 60), `mode` `exclusive`|`advisory`, `waitMs` (0–30000). Legacy `acquireLock(identifier, expiresNumber)` still works (maps to `ttlSeconds`). Body fields `ttl_seconds` and legacy `expires` accepted server-side.
272
+ - `releaseLock(identifier, lockId)` — Holder release; 403 `lock_holder_mismatch` if path does not match principal; 404 if unknown `lockId`
273
+ - `forceReleaseLock(identifier, lockId)` — Admin/owner release; pass **raw** stored identifier from `findLocks` when using user isolation
274
+ - `extendLock(identifier, lockId, ttlSeconds)` — Refresh TTL (holder only)
275
+ - `findLocks(identifier)` — Returns `holder`, `mode`, `version`, `acquired_at`, `expires_at`
276
+ - ABAC actions: `lock`, `unlock`, `force_unlock`; policies with `write` only still authorize all three (compat)
272
277
 
273
278
  **Search & Analytics:**
274
279
  - `search(query)` — Full-text search (embedded FTS). Optional **`at_date`**, **`date_from`**, **`date_to`** on the query for temporal keyword search (use **`mode: 'fts'`**). Hits may include **`valid_from`** / **`valid_to`** (7-char internal keys).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcitedbs/client",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "XCiteDB BaaS client SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1128,6 +1128,7 @@ Before returning a query, scan it against this checklist. These rules come strai
1128
1128
  ## 13. Cross-reference
1129
1129
 
1130
1130
  - [`docs/unquery-grammar.md`](./unquery-grammar.md) — Parser-faithful EBNF, AST nodes, lexer tokens, every static rule. Use this for syntax-checking, error attribution, or when extending Unquery tooling. MCP resource: `xcitedb://unquery-grammar`.
1131
+ - [Unquery playground](https://unquery-playground.vercel.app/) — Browser tool to try templates and expressions interactively (no XCiteDB server required for basic experiments).
1131
1132
  - [`web/src/docs/content/unquery-language-reference.html`](../web/src/docs/content/unquery-language-reference.html) — Human-oriented reference manual.
1132
1133
  - [`web/src/docs/content/unquery-tutorial.html`](../web/src/docs/content/unquery-tutorial.html) — Hands-on tutorial with the employees dataset.
1133
1134
  - XCiteDB-specific bits (workspaces, branches, identifiers, `->$date`, `->$branch`, `->$all`, `$xml*`, `$attr`, `$xpath`, `$node_date`, `$data_date`) only apply when running against XCiteDB. With the `unq` CLI on plain JSON files, ignore them.
@@ -2,7 +2,7 @@
2
2
 
3
3
  This document is the **ground truth** for Unquery syntax as implemented in the XCiteDB engine. It is derived from `TParser` in [`XCiteDB2/XCiteDB/src/TemplateParser.cpp`](../XCiteDB2/XCiteDB/src/TemplateParser.cpp) and the AST in [`XCiteDB2/XCiteDB/include/TemplateQuery.h`](../XCiteDB2/XCiteDB/include/TemplateQuery.h). Use it for **syntax checking** and **conservative static typing** of expressions inside JSON templates.
4
4
 
5
- Human-oriented prose and examples: [Unquery reference manual](../web/src/docs/content/unquery-language-reference.html) (Asciidoc source: `web/src/docs/unquery-source/unquery-language-reference.adoc`).
5
+ Human-oriented prose and examples: [Unquery reference manual](../web/src/docs/content/unquery-language-reference.html) (Asciidoc source: `web/src/docs/unquery-source/unquery-language-reference.adoc`). Try queries in the browser: [Unquery playground](https://unquery-playground.vercel.app/).
6
6
 
7
7
  ---
8
8