@xcitedbs/client 0.2.11 → 0.2.12

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
@@ -34,6 +34,19 @@ const docs = await client.queryByIdentifier('/test1', 'FirstMatch');
34
34
  await client.put('app.settings', { theme: 'dark' });
35
35
  ```
36
36
 
37
+ ### Full-text search (temporal)
38
+
39
+ `client.search()` accepts `TextSearchQuery` with optional **`at_date`** (point-in-time), **`date_from`** / **`date_to`** (range overlap), and **`mode`: `'fts'`** for pure keyword search. Omit temporal fields for the default “current” posting view. Hits may include **`valid_from`** / **`valid_to`** (7-character internal date keys returned by the server).
40
+
41
+ ```typescript
42
+ await client.search({
43
+ query: 'installation guide',
44
+ mode: 'fts',
45
+ at_date: '2024-06-01',
46
+ limit: 20,
47
+ });
48
+ ```
49
+
37
50
  ### WebSocket
38
51
 
39
52
  ```typescript
@@ -45,6 +58,15 @@ client.subscribe(
45
58
 
46
59
  With JWT in browsers, tokens are passed as `access_token` query parameter on the WebSocket URL. API keys can use `api_key` query parameter.
47
60
 
61
+ ## Test sessions (ephemeral and overlay)
62
+
63
+ Call **`POST /api/v1/test/sessions`** with your normal API key or Bearer (same project context as usual). Use **`XCiteDBClient.createTestSession({ baseUrl, apiKey, … })`** to get a client that sends **`X-Test-Session`** on requests.
64
+
65
+ - **Default:** isolated empty LMDB under the server’s `_test/<uuid>/` (writes never touch production).
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
+
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.
69
+
48
70
  ## Build
49
71
 
50
72
  ```bash
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, Flags, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, LogEntry, MergeResult, PublishResult, WorkspaceInfo, MetaValue, PlatformRegisterResult, PolicySubjectInput, UnqueryResult, UnqueryTemplate, PolicyUpdateResponse, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredTriggerResponse, TriggerDefinition, StoredPolicyResponse, SubscriptionOptions, TagRecord, TextSearchQuery, TextSearchResult, ProjectSearchSettings, ProjectSearchSettingsUpdate, VectorIndexEstimate, RagQueryOptions, RagQueryResult, RagStreamEvent, OAuthProvidersResponse, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspacesResponse, TokenPair, UserInfo, ApiKeyInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, XCiteDBJwtClaims, XCiteQuery, UserIsolationConfig } from './types';
1
+ import { AccessCheckResult, AppAuthConfig, AppEmailConfig, AppEmailTemplates, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BranchInfo, BookmarkRecord, CheckpointRecord, CommitRecord, CompareRef, CompareResult, DatabaseContext, DiffRef, DiffResult, Flags, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, LogEntry, MergeResult, PublishResult, WorkspaceInfo, MetaValue, PlatformRegisterResult, PolicySubjectInput, UnqueryResult, UnqueryTemplate, PolicyUpdateResponse, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredTriggerResponse, TriggerDefinition, StoredPolicyResponse, SubscriptionOptions, TagRecord, TextSearchQuery, TextSearchResult, ProjectSearchSettings, ProjectSearchSettingsUpdate, ProjectDocConfResponse, PlatformDefaultDocConfResponse, VectorIndexEstimate, RagQueryOptions, RagQueryResult, RagStreamEvent, OAuthProvidersResponse, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspacesResponse, TokenPair, UserInfo, ApiKeyInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, XCiteDBJwtClaims, XCiteQuery, UserIsolationConfig } from './types';
2
2
  import { WebSocketSubscription } from './websocket';
3
3
  export declare class XCiteDBClient {
4
4
  private baseUrl;
@@ -18,10 +18,13 @@ export declare class XCiteDBClient {
18
18
  private testRequireAuth?;
19
19
  private userIsolation?;
20
20
  private cachedAppUserId?;
21
+ private readonly requestTimeoutMs?;
21
22
  constructor(options: XCiteDBClientOptions);
22
23
  /**
23
- * Create an ephemeral isolated database: calls `POST /api/v1/test/sessions` with your API key or Bearer,
24
+ * Create an ephemeral test database: calls `POST /api/v1/test/sessions` with your API key or Bearer,
24
25
  * then returns a client that sends `X-Test-Session` (auth-free by default).
26
+ * With `opts.overlay === true`, the server stores overlay mode: reads merge the empty `_test/...` LMDB
27
+ * over the current project's production data (read-only base); writes stay under `_test/...` only.
25
28
  */
26
29
  static createTestSession(opts: CreateTestSessionOptions): Promise<XCiteDBClient>;
27
30
  /**
@@ -484,6 +487,18 @@ export declare class XCiteDBClient {
484
487
  getProjectSearchSettings(): Promise<ProjectSearchSettings>;
485
488
  /** Update project search settings (`PUT /api/v1/project/settings/search`). Returns the same shape as GET. */
486
489
  updateProjectSearchSettings(patch: ProjectSearchSettingsUpdate): Promise<ProjectSearchSettings>;
490
+ /** Per-project `document.conf` override (`GET /api/v1/project/settings/doc-conf`). */
491
+ getProjectDocConf(): Promise<ProjectDocConfResponse>;
492
+ /** Save or clear project `document.conf` (`PUT /api/v1/project/settings/doc-conf`). */
493
+ updateProjectDocConf(body: {
494
+ doc_conf_text: string;
495
+ } | {
496
+ clear: true;
497
+ }): Promise<ProjectDocConfResponse>;
498
+ /** Remove project override; server uses platform default (`DELETE /api/v1/project/settings/doc-conf`). */
499
+ deleteProjectDocConf(): Promise<ProjectDocConfResponse>;
500
+ /** Embedded platform default `document.conf` text (`GET /api/v1/platform/default-doc-conf`). */
501
+ getPlatformDefaultDocConf(): Promise<PlatformDefaultDocConfResponse>;
487
502
  /** Blocking full DB scan (admin; no calls to embedding API). Prefer {@link postVectorIndexEstimateSession} for UI. */
488
503
  getVectorIndexEstimate(): Promise<VectorIndexEstimate>;
489
504
  /** Start background estimate (202); cancel prior session for this tenant. */
package/dist/client.js CHANGED
@@ -18,6 +18,30 @@ function buildQuery(params) {
18
18
  const s = sp.toString();
19
19
  return s ? `?${s}` : '';
20
20
  }
21
+ function warnIfHttpOnTlsPort(baseUrl) {
22
+ try {
23
+ const u = new URL(baseUrl);
24
+ if (u.protocol !== 'http:')
25
+ return;
26
+ if (u.port === '443') {
27
+ if (typeof console !== 'undefined' && typeof console.warn === 'function') {
28
+ console.warn('[@xcitedbs/client] baseUrl uses http: on port 443; use https:// to avoid hangs or TLS errors.');
29
+ }
30
+ }
31
+ }
32
+ catch {
33
+ /* ignore invalid baseUrl */
34
+ }
35
+ }
36
+ /** Uses `AbortSignal.timeout` when the runtime supports it. */
37
+ function requestTimeoutSignal(ms) {
38
+ if (ms === undefined || ms <= 0)
39
+ return undefined;
40
+ const ctor = AbortSignal;
41
+ if (typeof ctor.timeout === 'function')
42
+ return ctor.timeout(ms);
43
+ return undefined;
44
+ }
21
45
  class XCiteDBClient {
22
46
  constructor(options) {
23
47
  this.baseUrl = options.baseUrl.replace(/\/+$/, '');
@@ -34,10 +58,14 @@ class XCiteDBClient {
34
58
  this.testSessionToken = options.testSessionToken;
35
59
  this.testRequireAuth = options.testRequireAuth === true;
36
60
  this.userIsolation = options.userIsolation;
61
+ this.requestTimeoutMs = options.requestTimeoutMs;
62
+ warnIfHttpOnTlsPort(this.baseUrl);
37
63
  }
38
64
  /**
39
- * Create an ephemeral isolated database: calls `POST /api/v1/test/sessions` with your API key or Bearer,
65
+ * Create an ephemeral test database: calls `POST /api/v1/test/sessions` with your API key or Bearer,
40
66
  * then returns a client that sends `X-Test-Session` (auth-free by default).
67
+ * With `opts.overlay === true`, the server stores overlay mode: reads merge the empty `_test/...` LMDB
68
+ * over the current project's production data (read-only base); writes stay under `_test/...` only.
41
69
  */
42
70
  static async createTestSession(opts) {
43
71
  const temp = new XCiteDBClient({
@@ -53,8 +81,9 @@ class XCiteDBClient {
53
81
  onAppUserTokensUpdated: opts.onAppUserTokensUpdated,
54
82
  onSessionInvalid: opts.onSessionInvalid,
55
83
  userIsolation: opts.userIsolation,
84
+ requestTimeoutMs: opts.requestTimeoutMs,
56
85
  });
57
- const data = await temp.request('POST', '/api/v1/test/sessions', undefined, undefined, { no401Retry: true });
86
+ const data = await temp.request('POST', '/api/v1/test/sessions', opts.overlay === true ? { overlay: true } : undefined, undefined, { no401Retry: true });
58
87
  return new XCiteDBClient({
59
88
  baseUrl: opts.baseUrl,
60
89
  apiKey: opts.testRequireAuth ? opts.apiKey : undefined,
@@ -70,6 +99,7 @@ class XCiteDBClient {
70
99
  testSessionToken: data.session_token,
71
100
  testRequireAuth: opts.testRequireAuth,
72
101
  userIsolation: opts.userIsolation,
102
+ requestTimeoutMs: opts.requestTimeoutMs,
73
103
  });
74
104
  }
75
105
  /**
@@ -444,6 +474,9 @@ class XCiteDBClient {
444
474
  init.body = JSON.stringify(body);
445
475
  }
446
476
  }
477
+ const sig = requestTimeoutSignal(this.requestTimeoutMs);
478
+ if (sig)
479
+ init.signal = sig;
447
480
  const res = await fetch(url, init);
448
481
  const text = await res.text();
449
482
  let data;
@@ -1410,12 +1443,18 @@ class XCiteDBClient {
1410
1443
  body.offset = q.offset;
1411
1444
  if (q.limit !== undefined)
1412
1445
  body.limit = q.limit;
1413
- if (q.mode)
1446
+ if (q.mode !== undefined)
1414
1447
  body.mode = q.mode;
1415
1448
  if (q.min_score !== undefined)
1416
1449
  body.min_score = q.min_score;
1417
1450
  if (q.semantic_weight !== undefined)
1418
1451
  body.semantic_weight = q.semantic_weight;
1452
+ if (q.at_date !== undefined && q.at_date !== '')
1453
+ body.at_date = q.at_date;
1454
+ if (q.date_from !== undefined && q.date_from !== '')
1455
+ body.date_from = q.date_from;
1456
+ if (q.date_to !== undefined && q.date_to !== '')
1457
+ body.date_to = q.date_to;
1419
1458
  const data = await this.request('POST', '/api/v1/search', body);
1420
1459
  const hits = [];
1421
1460
  if (Array.isArray(data.hits)) {
@@ -1436,6 +1475,12 @@ class XCiteDBClient {
1436
1475
  if (o.source === 'fts' || o.source === 'semantic' || o.source === 'both') {
1437
1476
  hit.source = o.source;
1438
1477
  }
1478
+ if (typeof o.valid_from === 'string' && o.valid_from.length > 0) {
1479
+ hit.valid_from = o.valid_from;
1480
+ }
1481
+ if (typeof o.valid_to === 'string' && o.valid_to.length > 0) {
1482
+ hit.valid_to = o.valid_to;
1483
+ }
1439
1484
  hits.push(hit);
1440
1485
  }
1441
1486
  }
@@ -1460,6 +1505,22 @@ class XCiteDBClient {
1460
1505
  async updateProjectSearchSettings(patch) {
1461
1506
  return this.request('PUT', '/api/v1/project/settings/search', patch);
1462
1507
  }
1508
+ /** Per-project `document.conf` override (`GET /api/v1/project/settings/doc-conf`). */
1509
+ async getProjectDocConf() {
1510
+ return this.request('GET', '/api/v1/project/settings/doc-conf');
1511
+ }
1512
+ /** Save or clear project `document.conf` (`PUT /api/v1/project/settings/doc-conf`). */
1513
+ async updateProjectDocConf(body) {
1514
+ return this.request('PUT', '/api/v1/project/settings/doc-conf', body);
1515
+ }
1516
+ /** Remove project override; server uses platform default (`DELETE /api/v1/project/settings/doc-conf`). */
1517
+ async deleteProjectDocConf() {
1518
+ return this.request('DELETE', '/api/v1/project/settings/doc-conf');
1519
+ }
1520
+ /** Embedded platform default `document.conf` text (`GET /api/v1/platform/default-doc-conf`). */
1521
+ async getPlatformDefaultDocConf() {
1522
+ return this.request('GET', '/api/v1/platform/default-doc-conf');
1523
+ }
1463
1524
  /** Blocking full DB scan (admin; no calls to embedding API). Prefer {@link postVectorIndexEstimateSession} for UI. */
1464
1525
  async getVectorIndexEstimate() {
1465
1526
  return this.request('GET', '/api/v1/project/settings/search/vector-index-estimate', undefined);
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, Flags, JsonDocumentData, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, MergeConflict, MergeResult, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, ProjectSearchSettings, ProjectSearchSettingsUpdate, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PublishConflict, PublishResult, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RagQueryOptions, RagQueryResult, RagStreamEvent, RealtimeEvent, SearchIndexingProgress, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TagRecord, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TokenPair, UserInfo, UserIsolationConfig, UserIsolationOptions, WorkspaceInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, XCiteDBJwtClaims, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
3
+ export type { AccessCheckResult, ApiKeyInfo, AppAuthConfig, AppEmailConfig, AppEmailSmtpConfig, AppEmailTemplateEntry, AppEmailTemplates, AppEmailWebhookConfig, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BookmarkRecord, BranchInfo, BranchListItem, CheckpointRecord, CommitRecord, CompareEntry, CompareRef, CompareResult, DatabaseContext, DiffEntry, DiffRef, DiffResult, Flags, JsonDocumentData, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, MergeConflict, MergeResult, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, ProjectSearchSettings, ProjectSearchSettingsUpdate, 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, CreateTestSessionOptions, XCiteDBClientOptions, XCiteDBJwtClaims, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
4
4
  export { XCiteDBError } from './types';
package/dist/types.d.ts CHANGED
@@ -41,12 +41,20 @@ export interface TextSearchQuery {
41
41
  branch?: string;
42
42
  offset?: number;
43
43
  limit?: number;
44
- /** Default `fts`. `semantic` / `hybrid` require vector search (and hybrid requires FTS). */
45
- mode?: 'fts' | 'semantic' | 'hybrid';
44
+ /**
45
+ * Default server behavior when omitted: `auto` (hybrid if FTS+vector enabled, else semantic or FTS).
46
+ * Explicit `fts` / `semantic` / `hybrid` require the matching capabilities to be enabled.
47
+ */
48
+ mode?: 'auto' | 'fts' | 'semantic' | 'hybrid';
46
49
  /** Minimum cosine similarity for semantic / hybrid vector leg (default 0.3). */
47
50
  min_score?: number;
48
51
  /** Hybrid only: weight of semantic vs FTS in weighted RRF (0–1, default 0.5). */
49
52
  semantic_weight?: number;
53
+ /** Point-in-time FTS filter (ISO date string; server maps to internal date key). */
54
+ at_date?: string;
55
+ /** Range FTS filter: interval overlap with [date_from, date_to) (ISO dates). */
56
+ date_from?: string;
57
+ date_to?: string;
50
58
  }
51
59
  export interface TextSearchHit {
52
60
  identifier: string;
@@ -59,6 +67,9 @@ export interface TextSearchHit {
59
67
  score: number;
60
68
  /** Present for semantic / hybrid search. */
61
69
  source?: 'fts' | 'semantic' | 'both';
70
+ /** FTS temporal window for this hit (internal 7-char keys), when returned by the server. */
71
+ valid_from?: string;
72
+ valid_to?: string;
62
73
  }
63
74
  export interface TextSearchResult {
64
75
  hits: TextSearchHit[];
@@ -153,6 +164,15 @@ export interface ProjectSearchSettingsUpdate {
153
164
  llm_base_url?: string | null;
154
165
  llm_api_key?: string;
155
166
  }
167
+ /** `GET /api/v1/project/settings/doc-conf` (and returned after `PUT` / `DELETE`). */
168
+ export interface ProjectDocConfResponse {
169
+ has_project_override: boolean;
170
+ doc_conf_text: string | null;
171
+ }
172
+ /** `GET /api/v1/platform/default-doc-conf` */
173
+ export interface PlatformDefaultDocConfResponse {
174
+ doc_conf_text: string;
175
+ }
156
176
  /** `POST /api/v1/rag/query` (non-streaming: set `stream: false` or omit). */
157
177
  export interface RagQueryOptions {
158
178
  question: string;
@@ -314,6 +334,11 @@ export interface DatabaseContext {
314
334
  workspace?: string;
315
335
  /** @deprecated Prefer {@link DatabaseContext.workspace}. Sent as `X-Branch` when `workspace` is unset. */
316
336
  branch?: string;
337
+ /**
338
+ * As-of revision time, sent as `X-Date`. Accepted forms (whole string must match; trailing spaces ignored):
339
+ * `mm/dd/yyyy`, `mm/dd/yyyy:HH:MM:SS`, ISO `YYYY-MM-DDTHH:MM:SS` with optional numeric timezone,
340
+ * `YYYY-MM-DD HH:MM:SS`, and ISO date-only `YYYY-MM-DD`.
341
+ */
317
342
  date?: string;
318
343
  prefix?: string;
319
344
  /**
@@ -392,6 +417,11 @@ export interface XCiteDBClientOptions {
392
417
  testRequireAuth?: boolean;
393
418
  /** Auto-prefix identifiers for app-user sessions (see {@link UserIsolationOptions}). */
394
419
  userIsolation?: UserIsolationOptions;
420
+ /**
421
+ * Per-request timeout in milliseconds for normal REST calls (uses `AbortSignal.timeout` when available).
422
+ * Omit for no timeout. Streaming RAG (`ragQueryStream`) does not apply this.
423
+ */
424
+ requestTimeoutMs?: number;
395
425
  }
396
426
  /** Options for {@link XCiteDBClient.createTestSession} (provisions via API key or Bearer). */
397
427
  export interface CreateTestSessionOptions {
@@ -403,12 +433,17 @@ export interface CreateTestSessionOptions {
403
433
  context?: DatabaseContext;
404
434
  platformConsole?: boolean;
405
435
  projectId?: string;
436
+ /**
437
+ * When true, creates an overlay test session: writable ephemeral LMDB with production project data as read-only base.
438
+ */
439
+ overlay?: boolean;
406
440
  /** Keep `apiKey` / `accessToken` on the client and send `X-Test-Auth: required` on each request. */
407
441
  testRequireAuth?: boolean;
408
442
  onSessionTokensUpdated?: (pair: TokenPair) => void;
409
443
  onAppUserTokensUpdated?: (pair: AppUserTokenPair) => void;
410
444
  onSessionInvalid?: () => void;
411
445
  userIsolation?: UserIsolationOptions;
446
+ requestTimeoutMs?: number;
412
447
  }
413
448
  /** Application user (tenant-scoped), distinct from developer users. */
414
449
  export interface AppUser {
package/llms-full.txt CHANGED
@@ -26,7 +26,9 @@ Before reading the full reference, note these critical differences from typical
26
26
 
27
27
  9. **OpenAPI:** See repository `docs/openapi.yaml` for a machine-readable route map.
28
28
 
29
- 10. **Ephemeral test sessions.** `POST /api/v1/test/sessions` (authenticated) returns a UUID **`session_token`**. Clients send **`X-Test-Session: <token>`** on API calls to use an isolated, TTL- and quota-limited LMDB instead of production project data. Unless **`X-Test-Auth: required`** is set, **developer** JWT/API-key checks are bypassed (synthetic admin for wet tests), but **app-user** identity via **`X-App-User-Token`** or Bearer app-user JWT is still recognized. Management routes under **`/api/v1/test/*`** must not include `X-Test-Session`. The test store starts empty (no cloned production project config).
29
+ 10. **Ephemeral test sessions.** `POST /api/v1/test/sessions` (authenticated) returns a UUID **`session_token`**. Clients send **`X-Test-Session: <token>`** on API calls to use an isolated, TTL- and quota-limited LMDB instead of production project data. Unless **`X-Test-Auth: required`** is set, **developer** JWT/API-key checks are bypassed (synthetic admin for wet tests), but **app-user** identity via **`X-App-User-Token`** or Bearer app-user JWT is still recognized. Management routes under **`/api/v1/test/*`** must not include `X-Test-Session`. With a default body (omit or `{}`), the test LMDB starts **empty** (no cloned production project config).
30
+
31
+ 11. **Overlay test sessions.** Same **`POST`**, with JSON **`{"overlay":true}`**, while authenticated for the **project to debug** (project-scoped API key, or platform Bearer + **`X-Project-Id`**). The session metadata records overlay mode; subsequent requests need only **`X-Test-Session`**. The server opens **`XCiteDB(_test/<uuid>/data, <production data path>)`**: production is used as a **read-only base**; reads merge overlay + base; **writes never modify production**. If the production data directory is missing, opening the session database fails. JS **`createTestSession({ …, overlay: true })`**, C++ **`test_session_overlay`** + **`create_test_session`**, MCP **`create_test_session`** tool **`overlay: true`**.
30
32
 
31
33
  ## Choosing the Right Versioning Approach
32
34
 
@@ -58,7 +60,7 @@ Legacy REST paths under `/api/v1/branches`, `/commits`, `/tags`, `/diff` remain
58
60
 
59
61
  6. **Self-registration uses server-configured default groups.** `registerAppUser()` assigns groups from the server's `auth.app_users.default_groups` config, not from the client request. To assign specific groups, use the admin endpoint `createAppUser()` instead, or update groups after registration via `updateAppUserGroups()`.
60
62
 
61
- 7. **Do not mock XciteDB in tests — use ephemeral test sessions instead.** Unlike most BaaS platforms, XciteDB has built-in support for isolated, throwaway database sessions specifically designed for wet integration tests. Mocking the client skips the actual storage, versioning, querying, and ABAC behavior, producing tests that don't catch real integration issues. Use `createTestSession()` / `test_session()` / `create_test_session()` (SDK helpers) or `POST /api/v1/test/sessions` directly to get a real, empty, isolated LMDB that is automatically scoped away from production and destroyed after the test. See "Ephemeral test sessions" below.
63
+ 7. **Do not mock XciteDB in tests — use ephemeral test sessions instead.** Unlike most BaaS platforms, XciteDB has built-in support for isolated, throwaway database sessions specifically designed for wet integration tests. Mocking the client skips the actual storage, versioning, querying, and ABAC behavior, producing tests that don't catch real integration issues. Use `createTestSession()` / `test_session()` / `create_test_session()` (SDK helpers) or `POST /api/v1/test/sessions` directly to get a real LMDB under `_test/<uuid>/` (empty by default, or **overlay** on read-only production with **`{"overlay":true}`** / **`overlay: true`** / **`test_session_overlay`**). See "Ephemeral test sessions" below.
62
64
 
63
65
  ---
64
66
 
@@ -205,7 +207,7 @@ For **integration and wet tests** against a shared BaaS host without touching pr
205
207
 
206
208
  | Step | What to do |
207
209
  |------|------------|
208
- | **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). |
210
+ | **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). |
209
211
  | **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. |
210
212
  | **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. |
211
213
  | **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. |
@@ -213,9 +215,9 @@ For **integration and wet tests** against a shared BaaS host without touching pr
213
215
 
214
216
  **SDK usage (summary):**
215
217
 
216
- - **JavaScript/TypeScript:** `XCiteDBClient.createTestSession({ baseUrl, apiKey, … })` returns a client configured with `testSessionToken`; optional `testRequireAuth: true` maps to `X-Test-Auth: required`. `destroyTestSession()` calls `DELETE …/test/sessions/current`.
217
- - **Python:** `async with XCiteDBClient.test_session(base_url, api_key=…, …)` provisions and tears down; or pass `test_session_token` / `test_require_auth` to the constructor.
218
- - **C++:** `XCiteDBClient::create_test_session(options)` after setting `api_key` (and optional `test_require_auth`); `destroy_test_session()`.
218
+ - **JavaScript/TypeScript:** `XCiteDBClient.createTestSession({ baseUrl, apiKey, … })` returns a client configured with `testSessionToken`; optional **`overlay: true`** for overlay mode; optional `testRequireAuth: true` maps to `X-Test-Auth: required`. `destroyTestSession()` calls `DELETE …/test/sessions/current`.
219
+ - **Python:** `async with XCiteDBClient.test_session(base_url, api_key=…, …)` provisions and tears down; or pass `test_session_token` / `test_require_auth` to the constructor. For overlay until the helper accepts a flag, call **`POST /api/v1/test/sessions`** with JSON **`{"overlay":true}`** then construct the client with the returned token.
220
+ - **C++:** `XCiteDBClient::create_test_session(options)` after setting `api_key`, optional **`test_session_overlay = true`**, and optional `test_require_auth`; `destroy_test_session()`.
219
221
 
220
222
  **JavaScript/TypeScript — complete test scaffold (Vitest / Jest):**
221
223
 
@@ -529,7 +531,7 @@ Can also use `"query"` instead of `"identifier"` to target multiple documents by
529
531
 
530
532
  # Search
531
533
 
532
- Full-text search (backed by Meilisearch or Elasticsearch).
534
+ Full-text search uses **embedded XciteFTS** (LMDB index per project). **Semantic** and **hybrid** (FTS + vector) search are available when vector search is enabled in project settings. The JSON field **`mode`** selects behavior (`auto` picks the best option for the project, or set `fts`, `semantic`, or `hybrid` explicitly).
533
535
 
534
536
  **Base path:** `/api/v1/search`
535
537
 
@@ -543,15 +545,26 @@ Full-text search (backed by Meilisearch or Elasticsearch).
543
545
  "doc_types": ["xml", "json"],
544
546
  "branch": "",
545
547
  "limit": 20,
546
- "offset": 0
548
+ "offset": 0,
549
+ "mode": "fts"
547
550
  }
548
551
  ```
549
552
 
550
- Response: `{ hits: [{ identifier, path, doc_type, branch, snippet, score, xcitepath? }], total, query }`.
553
+ Optional fields (also on SDK `TextSearchQuery` and the MCP `search` tool): **`min_score`**, **`semantic_weight`** for semantic/hybrid.
554
+
555
+ **Temporal FTS** (posting intervals; applies to the FTS keyword index—use **`mode`: `"fts"`** for pure temporal keyword search, or hybrid where the FTS leg participates):
556
+
557
+ | Field | Meaning |
558
+ |-------|---------|
559
+ | **`at_date`** | Date string (e.g. ISO `YYYY-MM-DD` or `mm/dd/yyyy` as accepted by the server). A posting matches if it is valid at that instant: `start <= at_date < end` (internal 7-char keys after `date2key`). |
560
+ | **`date_from`**, **`date_to`** | Date strings defining a half-open range `[date_from, date_to)`. A posting matches if its `[start, end)` **overlaps** that range. Either bound may be omitted (open-ended). |
561
+ | *(none of the three)* | **Current-time** view: only postings whose interval is still “open” (end at the internal max sentinel) are included. |
562
+
563
+ **Response:** `{ hits: [...], total, query }`. Each hit has **`identifier`**, **`path`**, **`doc_type`**, **`branch`**, **`snippet`**, **`score`**, and optionally **`xcitepath`** (XML). Semantic/hybrid hits may include **`source`**: `fts` \| `semantic` \| `both`. When the server infers a temporal window for the matched term(s), hits may include **`valid_from`** and **`valid_to`** as **7-character internal date keys** (same alphabet as revision keys—not ISO). Omitted for full-range/unversioned postings or when not computed.
551
564
 
552
565
  ## Reindex
553
566
 
554
- **`POST /api/v1/search/reindex`** — Rebuilds the search index.
567
+ **`POST /api/v1/search/reindex`** — Rebuilds the full-text search index.
555
568
 
556
569
  ---
557
570
 
@@ -1411,7 +1424,7 @@ interface DatabaseContext {
1411
1424
  - `findLocks(identifier)` → `LockInfo[]`
1412
1425
 
1413
1426
  ### Search
1414
- - `search(query: TextSearchQuery)` → `TextSearchResult`
1427
+ - `search(query: TextSearchQuery)` → `TextSearchResult`. Query may include **`at_date`**, **`date_from`**, **`date_to`** (ISO-style or `mm/dd/yyyy` strings) for temporal FTS; set **`mode`: `"fts"`** for keyword-only temporal search. Hits may include **`valid_from`** / **`valid_to`** (7-char internal keys) when the server returns an inferred validity window.
1415
1428
  - `reindex()` → `{ status, message }`
1416
1429
 
1417
1430
  ### Unquery
@@ -1544,6 +1557,7 @@ async def main():
1544
1557
  print(await client.read_json_document("app.settings"))
1545
1558
  print(await client.list_identifiers(XCiteQuery(match_start="/manual/")))
1546
1559
  print(await client.search(TextSearchQuery(query="guide", limit=10)))
1560
+ # Temporal FTS: await client.search(TextSearchQuery(query="guide", mode="fts", at_date="2024-06-01"))
1547
1561
  await client.platform_login("admin@localhost", "password")
1548
1562
  # await client.login_app_user("user@example.com", "pw") # set context.project_id / tenant_id if needed
1549
1563
  async with client.with_workspace("feature-x", message="WIP", auto_merge=True):
package/llms.txt CHANGED
@@ -22,7 +22,7 @@ These are the most common sources of confusion for developers and AI assistants:
22
22
 
23
23
  8. **Project vs tenant id.** In the SDK, prefer `context.project_id` (and `listMyProjects` / `switchProject`). Many JSON bodies and JWT claims still use the field name `tenant_id` for the same value — the client sends that wire name automatically.
24
24
 
25
- 9. **Ephemeral test sessions (wet tests).** Call **`POST /api/v1/test/sessions`** with a normal API key or Bearer token to get a `session_token` (UUID). Send **`X-Test-Session: <token>`** on subsequent document/API calls to use an isolated, short-lived LMDB instead of production data. By default **developer** auth (API key / platform JWT) is bypassed, but **app-user** identity (`X-App-User-Token` or Bearer app-user JWT) is still recognized. Send **`X-Test-Auth: required`** to exercise full developer JWT/API-key auth and ABAC against the same test database. Do not send `X-Test-Session` on `/api/v1/test/*` management routes. Server limits apply (`test.session_ttl_seconds`, `test.max_sessions_per_key`, `test.max_test_db_size_bytes` in config). The test DB starts **empty** (no copy of production project config or keys).
25
+ 9. **Ephemeral test sessions (wet tests).** Call **`POST /api/v1/test/sessions`** with a normal API key or Bearer token to get a `session_token` (UUID). Send **`X-Test-Session: <token>`** on subsequent document/API calls to use an isolated, short-lived LMDB instead of production data. By default **developer** auth (API key / platform JWT) is bypassed, but **app-user** identity (`X-App-User-Token` or Bearer app-user JWT) is still recognized. Send **`X-Test-Auth: required`** to exercise full developer JWT/API-key auth and ABAC against the same test database. Do not send `X-Test-Session` on `/api/v1/test/*` management routes. Server limits apply (`test.session_ttl_seconds`, `test.max_sessions_per_key`, `test.max_test_db_size_bytes` in config). By default the test LMDB starts **empty** (no production data). **Overlay mode:** same **`POST`** with JSON body **`{"overlay":true}`** (while authenticated for the project you want to inspect—project-scoped API key, or platform Bearer + **`X-Project-Id`**). The server opens a **writable** LMDB under `_test/<uuid>/` with that project’s on-disk store as a **read-only base** (dual LMDB): reads see production + overlay deltas; **writes never touch production**. **`XCiteDBClient.createTestSession({ …, overlay: true })`** sends that body. After creation, only **`X-Test-Session`** is required on requests (overlay is stored in session metadata).
26
26
 
27
27
  ## Choosing the Right Versioning Approach
28
28
 
@@ -58,7 +58,7 @@ Legacy REST paths (`/api/v1/branches`, `/commits`, `/tags`, `/diff`) remain as *
58
58
 
59
59
  6. **Self-registration uses server-configured default groups.** `registerAppUser()` assigns groups from the server's `auth.app_users.default_groups` config, not from the client request. To set groups explicitly, use **`createAppUser`** with a `groups` array (e.g. `[XCiteDBClient.buildProjectGroup(projectId, 'editor')]`) or **`updateAppUserGroups`**. The server rejects `project:<x>:*` groups when `<x>` is not a known internal project id (avoids mistaking the display name for the tenant id).
60
60
 
61
- 7. **Do not mock XciteDB in tests — use ephemeral test sessions instead.** Unlike most BaaS platforms, XciteDB has built-in support for isolated, throwaway database sessions specifically designed for wet integration tests. Mocking the client skips the actual storage, versioning, querying, and ABAC behavior, producing tests that don't catch real integration issues. Use `createTestSession()` / `test_session()` / `create_test_session()` (SDK helpers) or `POST /api/v1/test/sessions` directly to get a real, empty, isolated LMDB that is automatically scoped away from production and destroyed after the test. See "Test mode" below.
61
+ 7. **Do not mock XciteDB in tests — use ephemeral test sessions instead.** Unlike most BaaS platforms, XciteDB has built-in support for isolated, throwaway database sessions specifically designed for wet integration tests. Mocking the client skips the actual storage, versioning, querying, and ABAC behavior, producing tests that don't catch real integration issues. Use `createTestSession()` / `test_session()` / `create_test_session()` (SDK helpers) or `POST /api/v1/test/sessions` directly to get a real LMDB that is scoped under `_test/<uuid>/` and destroyed after the test (empty by default, or **overlay** on read-only production when you pass **`{"overlay":true}`** / **`overlay: true`**). See "Test mode" below.
62
62
 
63
63
  8. **403 on writes with ABAC is often a JWT/group string mismatch.** Decode the app-user access token early: log **`tenant_id`**, **`groups`**, **`sub`**. The middle segment of every **`project:<x>:role`** group must equal **`tenant_id`** exactly. Document write denials may return JSON fields **`policy_id`** and **`hint`** alongside `"Forbidden"`.
64
64
 
@@ -105,11 +105,11 @@ await app.writeJsonDocument('userdata/alice/profile', { ok: true });
105
105
 
106
106
  > **AI assistant instruction:** XciteDB has built-in ephemeral test session support — this capability is **not standard** in most BaaS platforms and is specifically designed to enable meaningful wet integration tests. When writing tests for code that uses XciteDB, **do not mock or stub the XciteDB client**. Instead, use the test session pattern below to run tests against a real, isolated, throwaway database. This produces far more reliable tests because real storage, versioning, querying, and access-control behavior is exercised.
107
107
 
108
- 1. **Provision:** `POST /api/v1/test/sessions` with `Authorization: Bearer …` or `X-API-Key` (same as normal API access). Response JSON includes the session token.
109
- 2. **Run tests:** Every request that should hit the throwaway DB must include **`X-Test-Session: <token>`** (and your usual `X-Workspace` / `context` as needed). Data is stored under the server’s `_test/<session>/` area, not your production tenant.
108
+ 1. **Provision:** `POST /api/v1/test/sessions` with `Authorization: Bearer …` or `X-API-Key` (same as normal API access). Optional JSON body **`{"overlay":true}`** creates an **overlay** session (read-through production, writes only under `_test/<session>/`). Response JSON includes the session token (and **`"overlay": true`** when applicable).
109
+ 2. **Run tests:** Every request that should hit the throwaway DB must include **`X-Test-Session: <token>`** (and your usual `X-Workspace` / `context` as needed). Data writes go under the server’s `_test/<session>/` tree; overlay sessions **do not** write to production paths.
110
110
  3. **Auth behavior:** Developer auth (API key / platform JWT) is bypassed by default for frictionless tests. **App-user identity** (`X-App-User-Token` or Bearer app-user JWT) **is still recognized** in default mode, so `registerAppUser` → `loginAppUser` → `appUserMe` works inside a test session. To also exercise developer auth and ABAC policies, set **`X-Test-Auth: required`** and send normal credentials; the DB is still the test session’s.
111
111
  4. **Cleanup:** `DELETE /api/v1/test/sessions/current` with `X-Test-Session` (no other auth), or `DELETE /api/v1/test/sessions/all` / `DELETE /api/v1/test/sessions/{token}` with normal auth for the owning key or JWT.
112
- 5. **SDKs:** **JS/TS:** `XCiteDBClient.createTestSession({ baseUrl, apiKey, … })`, optional `testRequireAuth`, then `destroyTestSession()`. **Python:** `async with XCiteDBClient.test_session(...)` or manual token + `test_session_token` / `test_require_auth` constructor args. **C++:** `XCiteDBClient::create_test_session(options)`, `destroy_test_session()`, optional `test_require_auth` in options.
112
+ 5. **SDKs:** **JS/TS:** `XCiteDBClient.createTestSession({ baseUrl, apiKey, … })`, optional `overlay: true`, optional `testRequireAuth`, then `destroyTestSession()`. **Python:** `async with XCiteDBClient.test_session(...)` or provision with **`POST /api/v1/test/sessions`** and JSON **`{"overlay":true}`** when you need overlay, then pass `test_session_token` / `test_require_auth` to the constructor. **C++:** `XCiteDBClient::create_test_session(options)` with optional `test_session_overlay = true`, `destroy_test_session()`, optional `test_require_auth` in options.
113
113
 
114
114
  ## JavaScript/TypeScript SDK (`@xcitedbs/client`)
115
115
 
@@ -177,8 +177,10 @@ const lock = await client.acquireLock('/manual/v1/intro');
177
177
  // ... edit ...
178
178
  await client.releaseLock('/manual/v1/intro', lock.lock_id);
179
179
 
180
- // Full-text search (requires Meilisearch or Elasticsearch backend)
181
- const results = await client.search({ query: 'installation guide', limit: 20 });
180
+ // Full-text search (embedded XciteFTS; optional temporal filters on the query body)
181
+ const results = await client.search({ query: 'installation guide', limit: 20, mode: 'fts' });
182
+ // Point-in-time keyword search: { query: '...', mode: 'fts', at_date: '2024-06-01' }
183
+ // Range overlap: { query: '...', mode: 'fts', date_from: '2024-01-01', date_to: '2025-01-01' }
182
184
 
183
185
  // Subscribe to real-time changes via WebSocket
184
186
  const sub = client.subscribe(
@@ -263,7 +265,7 @@ interface XCiteDBClientOptions {
263
265
  - `findLocks(identifier)` — Query active locks
264
266
 
265
267
  **Search & Analytics:**
266
- - `search(query)` — Full-text search
268
+ - `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).
267
269
  - `reindex()` — Rebuild search index
268
270
  - `unquery(query, unqueryDoc)` — Execute Unquery DSL
269
271
 
@@ -338,6 +340,7 @@ async def main():
338
340
  await client.write_json_document("app.settings", {"theme": "dark"})
339
341
  print(await client.read_json_document("app.settings"))
340
342
  print(await client.search(TextSearchQuery(query="guide", limit=10)))
343
+ # Temporal FTS: TextSearchQuery(query="guide", mode="fts", at_date="2024-06-01")
341
344
  pair = await client.platform_login("admin@localhost", "password")
342
345
  # await client.login_app_user("user@example.com", "secret", tenant_id="...")
343
346
 
@@ -378,7 +381,7 @@ auto ids = client.query_documents(q);
378
381
  - Documents — XML document CRUD, identifiers, hierarchy
379
382
  - JSON documents — JSON document CRUD
380
383
  - Metadata — JSON metadata on documents
381
- - Search — Full-text search
384
+ - Search — Full-text (embedded FTS; optional temporal `at_date` / `date_from` / `date_to`; hit `valid_from` / `valid_to`)
382
385
  - Unquery — Declarative query DSL
383
386
  - Workspaces — Isolated editing environments
384
387
  - Checkpoints & bookmarks — Named snapshots and references
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcitedbs/client",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "description": "XCiteDB BaaS client SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",