@xcitedbs/client 0.2.16 → 0.2.18
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 +30 -3
- package/dist/client.js +54 -6
- package/dist/index.d.ts +1 -1
- package/dist/locks.test.d.ts +1 -0
- package/dist/locks.test.js +73 -0
- package/dist/types.d.ts +34 -0
- package/llms-full.txt +33 -5
- package/llms.txt +10 -5
- package/package.json +1 -1
- package/unquery-ai-guide.md +1 -0
- package/unquery-grammar.md +1 -1
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, 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, XCiteQuery, UserIsolationConfig, UserIsolationCreateShareParams, UserIsolationShareResult } from './types';
|
|
2
2
|
import { WebSocketSubscription } from './websocket';
|
|
3
3
|
export declare class XCiteDBClient {
|
|
4
4
|
private baseUrl;
|
|
@@ -422,10 +422,13 @@ export declare class XCiteDBClient {
|
|
|
422
422
|
/**
|
|
423
423
|
* Write an **XML** document using a JSON request body (`xml` field). The identifier is taken from `db:identifier` on the root element.
|
|
424
424
|
* For storing JSON data by key, use `writeJsonDocument`.
|
|
425
|
+
*
|
|
426
|
+
* On cooperative-lock conflict the server returns **423** with a {@link LockConflictBody} shape in {@link XCiteDBError.body}.
|
|
425
427
|
*/
|
|
426
428
|
writeXmlDocument(xml: string, options?: WriteDocumentOptions): Promise<void>;
|
|
427
429
|
/**
|
|
428
430
|
* Best-effort batch XML writes (`POST /api/v1/documents/batch`). Each item is independent; check `results[].ok`.
|
|
431
|
+
* Rows with `error === 'lock_conflict'` include `current_lock` (code **423** per row).
|
|
429
432
|
*/
|
|
430
433
|
writeXmlDocumentsBatch(items: XmlDocumentBatchItem[]): Promise<DocumentBatchResponse>;
|
|
431
434
|
/**
|
|
@@ -460,9 +463,33 @@ export declare class XCiteDBClient {
|
|
|
460
463
|
queryMeta<T = MetaValue>(identifier: string, path?: string): Promise<T>;
|
|
461
464
|
queryMetaByQuery<T = MetaValue>(query: XCiteQuery, path?: string): Promise<T>;
|
|
462
465
|
clearMeta(query: XCiteQuery): Promise<boolean>;
|
|
463
|
-
|
|
466
|
+
/**
|
|
467
|
+
* Acquire a cooperative lock. On exclusive conflict the server returns HTTP 409 with a
|
|
468
|
+
* {@link LockConflictBody} body (thrown as {@link XCiteDBError} with `.status === 409`).
|
|
469
|
+
*/
|
|
470
|
+
acquireLock(identifier: string, expiresOrOpts?: number | AcquireLockOptions): Promise<LockInfo>;
|
|
471
|
+
/**
|
|
472
|
+
* Release a lock. **404** with {@link LockUnknownBody} if the lock id is unknown; **410** with
|
|
473
|
+
* {@link LockExpiredBody} if the lock TTL expired and was garbage-collected (same server process hint).
|
|
474
|
+
*/
|
|
464
475
|
releaseLock(identifier: string, lockId: string): Promise<boolean>;
|
|
465
|
-
|
|
476
|
+
/**
|
|
477
|
+
* Force-release a lock using `force_unlock` ABAC authority. Pass the **raw** stored identifier
|
|
478
|
+
* from {@link findLocks} (no iso-prefix); often the path as returned by the server for another principal.
|
|
479
|
+
*/
|
|
480
|
+
forceReleaseLock(identifier: string, lockId: string): Promise<boolean>;
|
|
481
|
+
/**
|
|
482
|
+
* Extend lock TTL (holder must match). **404** {@link LockUnknownBody} / **410** {@link LockExpiredBody}
|
|
483
|
+
* behave like {@link releaseLock}.
|
|
484
|
+
*/
|
|
485
|
+
extendLock(identifier: string, lockId: string, ttlSeconds: number): Promise<LockInfo>;
|
|
486
|
+
/**
|
|
487
|
+
* List locks visible under the given path. Pass `metaId` style paths (e.g. `/spaces/owner/docs/docId`).
|
|
488
|
+
* With `{ asPrefix: true }`, calls `GET /api/v1/locks?prefix=...` (same truncation rules as `identifier`).
|
|
489
|
+
*/
|
|
490
|
+
findLocks(identifier: string, opts?: {
|
|
491
|
+
asPrefix?: boolean;
|
|
492
|
+
}): Promise<LockInfo[]>;
|
|
466
493
|
/**
|
|
467
494
|
* Run Unquery (`POST /api/v1/unquery`): declarative analytics over documents matching `query`.
|
|
468
495
|
* The `unquery` argument is a JSON template; keys are output fields, string values are expressions.
|
package/dist/client.js
CHANGED
|
@@ -1194,6 +1194,8 @@ class XCiteDBClient {
|
|
|
1194
1194
|
/**
|
|
1195
1195
|
* Write an **XML** document using a JSON request body (`xml` field). The identifier is taken from `db:identifier` on the root element.
|
|
1196
1196
|
* For storing JSON data by key, use `writeJsonDocument`.
|
|
1197
|
+
*
|
|
1198
|
+
* On cooperative-lock conflict the server returns **423** with a {@link LockConflictBody} shape in {@link XCiteDBError.body}.
|
|
1197
1199
|
*/
|
|
1198
1200
|
async writeXmlDocument(xml, options) {
|
|
1199
1201
|
await this.request('POST', '/api/v1/documents', {
|
|
@@ -1204,6 +1206,7 @@ class XCiteDBClient {
|
|
|
1204
1206
|
}
|
|
1205
1207
|
/**
|
|
1206
1208
|
* Best-effort batch XML writes (`POST /api/v1/documents/batch`). Each item is independent; check `results[].ok`.
|
|
1209
|
+
* Rows with `error === 'lock_conflict'` include `current_lock` (code **423** per row).
|
|
1207
1210
|
*/
|
|
1208
1211
|
async writeXmlDocumentsBatch(items) {
|
|
1209
1212
|
const body = {
|
|
@@ -1424,13 +1427,29 @@ class XCiteDBClient {
|
|
|
1424
1427
|
});
|
|
1425
1428
|
return r?.ok !== false;
|
|
1426
1429
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1430
|
+
/**
|
|
1431
|
+
* Acquire a cooperative lock. On exclusive conflict the server returns HTTP 409 with a
|
|
1432
|
+
* {@link LockConflictBody} body (thrown as {@link XCiteDBError} with `.status === 409`).
|
|
1433
|
+
*/
|
|
1434
|
+
async acquireLock(identifier, expiresOrOpts = 0) {
|
|
1435
|
+
const opts = typeof expiresOrOpts === 'number' ? { ttlSeconds: expiresOrOpts } : expiresOrOpts ?? {};
|
|
1436
|
+
const body = {
|
|
1429
1437
|
identifier: this.isoPrefixId(identifier),
|
|
1430
|
-
|
|
1431
|
-
|
|
1438
|
+
};
|
|
1439
|
+
if (opts.ttlSeconds !== undefined && opts.ttlSeconds > 0) {
|
|
1440
|
+
body.ttl_seconds = opts.ttlSeconds;
|
|
1441
|
+
}
|
|
1442
|
+
if (opts.mode)
|
|
1443
|
+
body.mode = opts.mode;
|
|
1444
|
+
if (opts.waitMs !== undefined && opts.waitMs > 0)
|
|
1445
|
+
body.wait_ms = opts.waitMs;
|
|
1446
|
+
const lock = await this.request('POST', '/api/v1/locks', body);
|
|
1432
1447
|
return { ...lock, identifier: this.isoUnprefixId(lock.identifier) };
|
|
1433
1448
|
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Release a lock. **404** with {@link LockUnknownBody} if the lock id is unknown; **410** with
|
|
1451
|
+
* {@link LockExpiredBody} if the lock TTL expired and was garbage-collected (same server process hint).
|
|
1452
|
+
*/
|
|
1434
1453
|
async releaseLock(identifier, lockId) {
|
|
1435
1454
|
const r = await this.request('DELETE', '/api/v1/locks', {
|
|
1436
1455
|
identifier: this.isoPrefixId(identifier),
|
|
@@ -1438,8 +1457,37 @@ class XCiteDBClient {
|
|
|
1438
1457
|
});
|
|
1439
1458
|
return r?.ok !== false;
|
|
1440
1459
|
}
|
|
1441
|
-
|
|
1442
|
-
|
|
1460
|
+
/**
|
|
1461
|
+
* Force-release a lock using `force_unlock` ABAC authority. Pass the **raw** stored identifier
|
|
1462
|
+
* from {@link findLocks} (no iso-prefix); often the path as returned by the server for another principal.
|
|
1463
|
+
*/
|
|
1464
|
+
async forceReleaseLock(identifier, lockId) {
|
|
1465
|
+
const r = await this.request('DELETE', '/api/v1/locks', {
|
|
1466
|
+
identifier,
|
|
1467
|
+
lock_id: lockId,
|
|
1468
|
+
force: true,
|
|
1469
|
+
});
|
|
1470
|
+
return r?.ok !== false;
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Extend lock TTL (holder must match). **404** {@link LockUnknownBody} / **410** {@link LockExpiredBody}
|
|
1474
|
+
* behave like {@link releaseLock}.
|
|
1475
|
+
*/
|
|
1476
|
+
async extendLock(identifier, lockId, ttlSeconds) {
|
|
1477
|
+
const lock = await this.request('PATCH', '/api/v1/locks', {
|
|
1478
|
+
identifier: this.isoPrefixId(identifier),
|
|
1479
|
+
lock_id: lockId,
|
|
1480
|
+
ttl_seconds: ttlSeconds,
|
|
1481
|
+
});
|
|
1482
|
+
return { ...lock, identifier: this.isoUnprefixId(lock.identifier) };
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* List locks visible under the given path. Pass `metaId` style paths (e.g. `/spaces/owner/docs/docId`).
|
|
1486
|
+
* With `{ asPrefix: true }`, calls `GET /api/v1/locks?prefix=...` (same truncation rules as `identifier`).
|
|
1487
|
+
*/
|
|
1488
|
+
async findLocks(identifier, opts) {
|
|
1489
|
+
const key = opts?.asPrefix ? 'prefix' : 'identifier';
|
|
1490
|
+
const locks = await this.request('GET', `/api/v1/locks${buildQuery({ [key]: this.isoPrefixId(identifier) })}`);
|
|
1443
1491
|
return Array.isArray(locks)
|
|
1444
1492
|
? locks.map((L) => ({ ...L, identifier: this.isoUnprefixId(L.identifier) }))
|
|
1445
1493
|
: 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, UserIsolationCreateShareParams, UserIsolationOptions, UserIsolationShareMode, UserIsolationShareResult, 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, 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, XCiteDBClientOptions, XCiteDBJwtClaims, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
|
|
4
4
|
export { XCiteDBError } from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
},
|
|
30
|
+
body: '{}',
|
|
31
|
+
});
|
|
32
|
+
const data = (await r.json());
|
|
33
|
+
if (!r.ok || !data.session_token) {
|
|
34
|
+
throw new Error(`test session failed ${r.status}`);
|
|
35
|
+
}
|
|
36
|
+
const client = new client_js_1.XCiteDBClient({
|
|
37
|
+
baseUrl: e.baseUrl,
|
|
38
|
+
accessToken: e.accessToken,
|
|
39
|
+
platformConsole: true,
|
|
40
|
+
projectId: e.tenantId,
|
|
41
|
+
context: { project_id: e.tenantId },
|
|
42
|
+
testSessionToken: data.session_token,
|
|
43
|
+
testRequireAuth: true,
|
|
44
|
+
});
|
|
45
|
+
return { client, token: data.session_token };
|
|
46
|
+
}
|
|
47
|
+
const w = wetEnv();
|
|
48
|
+
const wd = w ? node_test_1.describe : node_test_1.describe.skip;
|
|
49
|
+
wd('locks (wet)', () => {
|
|
50
|
+
(0, node_test_1.it)('acquire, extend, release', async () => {
|
|
51
|
+
const e = wetEnv();
|
|
52
|
+
if (!e)
|
|
53
|
+
throw new Error('missing env');
|
|
54
|
+
const { client: c, token } = await openTestSession(e);
|
|
55
|
+
try {
|
|
56
|
+
const path = '/sdk_locks/js_wet_roundtrip';
|
|
57
|
+
await c.writeJsonDocument(path, { k: 1 }, { overwrite: true });
|
|
58
|
+
const lock = await c.acquireLock(path, { ttlSeconds: 120, mode: 'exclusive' });
|
|
59
|
+
strict_1.default.ok(lock.lock_id);
|
|
60
|
+
strict_1.default.equal(lock.mode, 'exclusive');
|
|
61
|
+
const ext = await c.extendLock(path, lock.lock_id, 180);
|
|
62
|
+
strict_1.default.ok((ext.version ?? 0) >= (lock.version ?? 1));
|
|
63
|
+
const ok = await c.releaseLock(path, lock.lock_id);
|
|
64
|
+
strict_1.default.ok(ok);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await fetch(`${e.baseUrl.replace(/\/+$/, '')}/api/v1/test/sessions/current`, {
|
|
68
|
+
method: 'DELETE',
|
|
69
|
+
headers: { 'X-Test-Session': token },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
package/dist/types.d.ts
CHANGED
|
@@ -253,6 +253,38 @@ 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
|
+
/**
|
|
271
|
+
* Body returned with HTTP **409** on `acquireLock` conflict, or **423** on XML document write blocked by a lock.
|
|
272
|
+
* Inspect via {@link XCiteDBError} (`status` and `body`).
|
|
273
|
+
*/
|
|
274
|
+
export interface LockConflictBody {
|
|
275
|
+
error: 'lock_conflict';
|
|
276
|
+
current_lock: LockInfo;
|
|
277
|
+
}
|
|
278
|
+
/** Body returned with HTTP **410** when `extendLock` / `releaseLock` target a lock id that TTL-expired recently. */
|
|
279
|
+
export interface LockExpiredBody {
|
|
280
|
+
error: 'lock_expired';
|
|
281
|
+
message?: string;
|
|
282
|
+
expired_at?: number;
|
|
283
|
+
}
|
|
284
|
+
/** Body returned with HTTP **404** when the lock id does not exist (or never existed) for the given identifier. */
|
|
285
|
+
export interface LockUnknownBody {
|
|
286
|
+
error: 'lock_unknown';
|
|
287
|
+
message?: string;
|
|
256
288
|
}
|
|
257
289
|
export interface TokenPair {
|
|
258
290
|
access_token: string;
|
|
@@ -374,6 +406,8 @@ export interface DocumentBatchResultRow {
|
|
|
374
406
|
ok: boolean;
|
|
375
407
|
error?: string;
|
|
376
408
|
code?: number;
|
|
409
|
+
/** Present when `error === 'lock_conflict'` (HTTP-style code 423 in batch row). */
|
|
410
|
+
current_lock?: LockInfo;
|
|
377
411
|
}
|
|
378
412
|
export interface DocumentBatchResponse {
|
|
379
413
|
results: DocumentBatchResultRow[];
|
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`**
|
|
846
|
+
**`POST /api/v1/locks`**
|
|
847
847
|
|
|
848
|
-
|
|
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": "
|
|
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,
|
|
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
|
-
//
|
|
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,
|
|
270
|
-
- `releaseLock(identifier, lockId)` —
|
|
271
|
-
- `
|
|
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
package/unquery-ai-guide.md
CHANGED
|
@@ -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.
|
package/unquery-grammar.md
CHANGED
|
@@ -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
|
|