@xcitedbs/client 0.2.5 → 0.2.6

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, CommitRecord, DatabaseContext, DiffRef, DiffResult, Flags, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, LogEntry, MergeResult, MetaValue, PlatformRegisterResult, PolicySubjectInput, UnqueryResult, PolicyUpdateResponse, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredTriggerResponse, TriggerDefinition, StoredPolicyResponse, SubscriptionOptions, TagRecord, TextSearchQuery, TextSearchResult, OAuthProvidersResponse, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspacesResponse, TokenPair, UserInfo, ApiKeyInfo, WriteDocumentOptions, XCiteDBClientOptions, XCiteQuery } from './types';
1
+ import { AccessCheckResult, AppAuthConfig, AppEmailConfig, AppEmailTemplates, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BranchInfo, CommitRecord, DatabaseContext, DiffRef, DiffResult, Flags, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, LogEntry, MergeResult, MetaValue, PlatformRegisterResult, PolicySubjectInput, UnqueryResult, PolicyUpdateResponse, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredTriggerResponse, TriggerDefinition, StoredPolicyResponse, SubscriptionOptions, TagRecord, TextSearchQuery, TextSearchResult, OAuthProvidersResponse, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspacesResponse, TokenPair, UserInfo, ApiKeyInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, XCiteQuery } from './types';
2
2
  import { WebSocketSubscription } from './websocket';
3
3
  export declare class XCiteDBClient {
4
4
  private baseUrl;
@@ -14,7 +14,18 @@ export declare class XCiteDBClient {
14
14
  private onSessionTokensUpdated?;
15
15
  private onAppUserTokensUpdated?;
16
16
  private onSessionInvalid?;
17
+ private testSessionToken?;
18
+ private testRequireAuth?;
17
19
  constructor(options: XCiteDBClientOptions);
20
+ /**
21
+ * Create an ephemeral isolated database: calls `POST /api/v1/test/sessions` with your API key or Bearer,
22
+ * then returns a client that sends `X-Test-Session` (auth-free by default).
23
+ */
24
+ static createTestSession(opts: CreateTestSessionOptions): Promise<XCiteDBClient>;
25
+ /** Destroy this test session on the server (`DELETE /api/v1/test/sessions/current`). */
26
+ destroyTestSession(): Promise<{
27
+ message: string;
28
+ }>;
18
29
  /** True if this client would send API key or Bearer credentials on a normal request. */
19
30
  private sentAuthCredentials;
20
31
  /** 401 on these paths is an expected auth flow outcome, not a dead session. */
@@ -34,6 +45,7 @@ export declare class XCiteDBClient {
34
45
  /** Include `tenant_id` for public app-auth routes when using only app-user tokens (no developer key/JWT). */
35
46
  private mergeAppTenant;
36
47
  private authHeaders;
48
+ private testHeaders;
37
49
  private request;
38
50
  /** Developer Bearer refresh first, then app-user refresh (no API key refresh). */
39
51
  private tryRefreshSessionAfter401;
@@ -83,7 +95,7 @@ export declare class XCiteDBClient {
83
95
  createApiKey(name: string, expiresAt?: number, keyType?: 'secret' | 'public'): Promise<unknown>;
84
96
  changePassword(currentPassword: string, newPassword: string): Promise<void>;
85
97
  revokeApiKey(keyId: string): Promise<void>;
86
- registerAppUser(email: string, password: string, displayName?: string, groups?: string[], attributes?: Record<string, unknown>): Promise<AppUser>;
98
+ registerAppUser(email: string, password: string, displayName?: string, attributes?: Record<string, unknown>): Promise<AppUser>;
87
99
  getOAuthProviders(): Promise<OAuthProvidersResponse>;
88
100
  /** Relative path + query for browser navigation to start OAuth (append to API base URL). */
89
101
  oauthAuthorizePath(provider: string): string;
package/dist/client.js CHANGED
@@ -31,6 +31,50 @@ class XCiteDBClient {
31
31
  this.onSessionTokensUpdated = options.onSessionTokensUpdated;
32
32
  this.onAppUserTokensUpdated = options.onAppUserTokensUpdated;
33
33
  this.onSessionInvalid = options.onSessionInvalid;
34
+ this.testSessionToken = options.testSessionToken;
35
+ this.testRequireAuth = options.testRequireAuth === true;
36
+ }
37
+ /**
38
+ * Create an ephemeral isolated database: calls `POST /api/v1/test/sessions` with your API key or Bearer,
39
+ * then returns a client that sends `X-Test-Session` (auth-free by default).
40
+ */
41
+ static async createTestSession(opts) {
42
+ const temp = new XCiteDBClient({
43
+ baseUrl: opts.baseUrl,
44
+ apiKey: opts.apiKey,
45
+ accessToken: opts.accessToken,
46
+ appUserAccessToken: opts.appUserAccessToken,
47
+ appUserRefreshToken: opts.appUserRefreshToken,
48
+ context: opts.context,
49
+ platformConsole: opts.platformConsole,
50
+ projectId: opts.projectId,
51
+ onSessionTokensUpdated: opts.onSessionTokensUpdated,
52
+ onAppUserTokensUpdated: opts.onAppUserTokensUpdated,
53
+ onSessionInvalid: opts.onSessionInvalid,
54
+ });
55
+ const data = await temp.request('POST', '/api/v1/test/sessions', undefined, undefined, { no401Retry: true });
56
+ return new XCiteDBClient({
57
+ baseUrl: opts.baseUrl,
58
+ apiKey: opts.testRequireAuth ? opts.apiKey : undefined,
59
+ accessToken: opts.testRequireAuth ? opts.accessToken : undefined,
60
+ appUserAccessToken: opts.testRequireAuth ? opts.appUserAccessToken : undefined,
61
+ appUserRefreshToken: opts.testRequireAuth ? opts.appUserRefreshToken : undefined,
62
+ context: opts.context,
63
+ platformConsole: opts.platformConsole,
64
+ projectId: opts.projectId,
65
+ onSessionTokensUpdated: opts.onSessionTokensUpdated,
66
+ onAppUserTokensUpdated: opts.onAppUserTokensUpdated,
67
+ onSessionInvalid: opts.onSessionInvalid,
68
+ testSessionToken: data.session_token,
69
+ testRequireAuth: opts.testRequireAuth,
70
+ });
71
+ }
72
+ /** Destroy this test session on the server (`DELETE /api/v1/test/sessions/current`). */
73
+ async destroyTestSession() {
74
+ if (!this.testSessionToken) {
75
+ throw new Error('destroyTestSession: client has no testSessionToken');
76
+ }
77
+ return this.request('DELETE', '/api/v1/test/sessions/current', undefined, undefined, { no401Retry: true });
34
78
  }
35
79
  /** True if this client would send API key or Bearer credentials on a normal request. */
36
80
  sentAuthCredentials() {
@@ -132,6 +176,16 @@ class XCiteDBClient {
132
176
  }
133
177
  return h;
134
178
  }
179
+ testHeaders() {
180
+ const h = {};
181
+ if (this.testSessionToken) {
182
+ h['X-Test-Session'] = this.testSessionToken;
183
+ if (this.testRequireAuth) {
184
+ h['X-Test-Auth'] = 'required';
185
+ }
186
+ }
187
+ return h;
188
+ }
135
189
  async request(method, path, body, extraHeaders, opts) {
136
190
  const no401Retry = opts?.no401Retry === true;
137
191
  for (let attempt = 0; attempt < 2; attempt++) {
@@ -139,6 +193,7 @@ class XCiteDBClient {
139
193
  const headers = {
140
194
  ...this.authHeaders(),
141
195
  ...this.contextHeaders(),
196
+ ...this.testHeaders(),
142
197
  ...extraHeaders,
143
198
  };
144
199
  let init = { method, headers };
@@ -320,12 +375,10 @@ class XCiteDBClient {
320
375
  await this.request('DELETE', `/api/v1/project/keys/${encodeURIComponent(keyId)}`);
321
376
  }
322
377
  // --- App user auth (requires developer API key or JWT on the same tenant) ---
323
- async registerAppUser(email, password, displayName, groups, attributes) {
378
+ async registerAppUser(email, password, displayName, attributes) {
324
379
  const body = { email, password };
325
380
  if (displayName !== undefined)
326
381
  body.display_name = displayName;
327
- if (groups !== undefined)
328
- body.groups = groups;
329
382
  if (attributes !== undefined)
330
383
  body.attributes = attributes;
331
384
  return this.request('POST', '/api/v1/app/auth/register', this.mergeAppTenant(body));
@@ -937,6 +990,9 @@ class XCiteDBClient {
937
990
  ...this.authHeaders(),
938
991
  ...this.contextHeaders(),
939
992
  };
993
+ const tid = this.defaultContext.project_id ?? this.defaultContext.tenant_id;
994
+ if (tid)
995
+ headers['tenant_id'] = tid;
940
996
  const sub = new websocket_1.WebSocketSubscription(wsBase, headers);
941
997
  sub.onMessage(callback);
942
998
  if (onError)
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, DatabaseContext, Flags, JsonDocumentData, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TokenPair, UserInfo, WriteDocumentOptions, XCiteDBClientOptions, UnqueryResult, XCiteQuery, } from './types';
3
+ export type { AccessCheckResult, ApiKeyInfo, AppAuthConfig, AppEmailConfig, AppEmailSmtpConfig, AppEmailTemplateEntry, AppEmailTemplates, AppEmailWebhookConfig, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, DatabaseContext, Flags, JsonDocumentData, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TokenPair, UserInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, UnqueryResult, XCiteQuery, } from './types';
4
4
  export { XCiteDBError } from './types';
package/dist/types.d.ts CHANGED
@@ -205,6 +205,29 @@ export interface XCiteDBClientOptions {
205
205
  * Use to clear stored credentials and redirect to sign-in. Not invoked for login/register-style paths.
206
206
  */
207
207
  onSessionInvalid?: () => void;
208
+ /**
209
+ * Ephemeral BaaS test session token from `POST /api/v1/test/sessions`.
210
+ * Sends `X-Test-Session`; use with {@link XCiteDBClient.createTestSession}.
211
+ */
212
+ testSessionToken?: string;
213
+ /** When true with `testSessionToken`, sends `X-Test-Auth: required` so real credentials are validated. */
214
+ testRequireAuth?: boolean;
215
+ }
216
+ /** Options for {@link XCiteDBClient.createTestSession} (provisions via API key or Bearer). */
217
+ export interface CreateTestSessionOptions {
218
+ baseUrl: string;
219
+ apiKey?: string;
220
+ accessToken?: string;
221
+ appUserAccessToken?: string;
222
+ appUserRefreshToken?: string;
223
+ context?: DatabaseContext;
224
+ platformConsole?: boolean;
225
+ projectId?: string;
226
+ /** Keep `apiKey` / `accessToken` on the client and send `X-Test-Auth: required` on each request. */
227
+ testRequireAuth?: boolean;
228
+ onSessionTokensUpdated?: (pair: TokenPair) => void;
229
+ onAppUserTokensUpdated?: (pair: AppUserTokenPair) => void;
230
+ onSessionInvalid?: () => void;
208
231
  }
209
232
  /** Application user (tenant-scoped), distinct from developer users. */
210
233
  export interface AppUser {
package/dist/websocket.js CHANGED
@@ -19,6 +19,11 @@ class WebSocketSubscription {
19
19
  const token = this.headerParams['Authorization']?.replace(/^Bearer\s+/i, '');
20
20
  if (token)
21
21
  u.searchParams.set('access_token', token);
22
+ if (!u.searchParams.has('access_token')) {
23
+ const appUser = this.headerParams['X-App-User-Token'];
24
+ if (appUser)
25
+ u.searchParams.set('access_token', appUser);
26
+ }
22
27
  const apiKey = this.headerParams['X-API-Key'];
23
28
  if (apiKey)
24
29
  u.searchParams.set('api_key', apiKey);
package/llms-full.txt CHANGED
@@ -26,6 +26,28 @@ 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, normal JWT/API-key checks are bypassed for those requests (synthetic admin for wet tests). Management routes under **`/api/v1/test/*`** must not include `X-Test-Session`. The test store starts empty (no cloned production project config).
30
+
31
+ ## Common Pitfalls
32
+
33
+ 1. **`baseUrl` must be origin-only (no `/api` or `/api/v1` path).** The SDK prepends `/api/v1/…` to every request. If `baseUrl` is `https://host/api/v1`, requests hit `/api/v1/api/v1/…` which typically returns **405 Not Allowed** from the reverse proxy. Use only the scheme + host + optional port: `https://host` or `http://localhost:8080`.
34
+
35
+ 2. **Browser WebSocket requires correct CORS and proxy config.** Browsers add an `Origin` header to WebSocket connections — make sure your project's CORS allowed-origins includes your app's origin. Reverse proxies (nginx) must forward WebSocket `Upgrade` requests to XCiteDB's `/api/v1/ws` endpoint (the default `docker/nginx.conf` already does this). For production deployments that need fine-grained server-side filtering or short-lived auth tickets, consider proxying the WebSocket through your own backend:
36
+
37
+ ```
38
+ Browser ──ws──▶ Your API server ──ws──▶ XCiteDB /api/v1/ws
39
+ (same origin, (server-to-server,
40
+ short ticket) secret API key + user JWT)
41
+ ```
42
+
43
+ 3. **Prefer `context.project_id` over `platformConsole` for project-scoped API keys.** `platformConsole` / `projectId` is designed for platform-operator keys that select a project dynamically via the `X-Project-Id` header. For project-scoped keys (which already identify their project), set **`context.project_id`** instead — it adds `tenant_id` to app-auth JSON bodies via `mergeAppTenant()` without sending `X-Project-Id`.
44
+
45
+ 4. **Prefer `https://` in `baseUrl` for remote/production hosts.** XciteDB's default nginx uses a method-preserving **308** redirect (POST stays POST), but third-party proxies or older configurations may use **301** which silently downgrades POST to GET. Using `https://` directly avoids the redirect entirely.
46
+
47
+ 5. **`context.project_id` (or `tenant_id`) is required for app-user self-registration.** `registerAppUser()` uses `mergeAppTenant(body)` to add `tenant_id` to the JSON body only when `context.project_id` or `context.tenant_id` is set. If both are omitted, the server cannot determine which project to register the user in. Always set `project_id` in the constructor `context` when calling `registerAppUser`, `loginAppUser`, and other public app-auth methods.
48
+
49
+ 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()`.
50
+
29
51
  ---
30
52
 
31
53
  # Part 1: Product Overview
@@ -162,6 +184,26 @@ Browser and mobile apps can use OAuth2-style flows against `/api/v1/app/auth/oau
162
184
 
163
185
  ---
164
186
 
187
+ # Ephemeral test sessions
188
+
189
+ For **integration and wet tests** against a shared BaaS host without touching production data:
190
+
191
+ | Step | What to do |
192
+ |------|------------|
193
+ | **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). |
194
+ | **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. |
195
+ | **Auth** | **Default:** test requests **skip** normal auth (convenient for automated tests). **`X-Test-Auth: required`:** require normal Bearer or API key; identity and ABAC apply, but data still comes from the test session DB. |
196
+ | **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. |
197
+ | **CORS** | Browsers may need **`X-Test-Session`** and **`X-Test-Auth`** in the deployment’s allowed CORS headers (defaults include them). |
198
+
199
+ **SDK usage (summary):**
200
+
201
+ - **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`.
202
+ - **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.
203
+ - **C++:** `XCiteDBClient::create_test_session(options)` after setting `api_key` (and optional `test_require_auth`); `destroy_test_session()`.
204
+
205
+ ---
206
+
165
207
  # Health, version & discovery
166
208
 
167
209
  ## Health
@@ -1042,7 +1084,7 @@ interface DatabaseContext {
1042
1084
  - `deleteTrigger(name)` → `void`
1043
1085
 
1044
1086
  ### App User Auth
1045
- - `registerAppUser(email, password, displayName?, groups?, attributes?)` → `AppUser`
1087
+ - `registerAppUser(email, password, displayName?, attributes?)` → `AppUser` (groups assigned from server config)
1046
1088
  - `loginAppUser(email, password)` → `AppUserTokenPair`
1047
1089
  - `refreshAppUser()` → `AppUserTokenPair`
1048
1090
  - `logoutAppUser()` → `void`
@@ -1081,6 +1123,9 @@ interface DatabaseContext {
1081
1123
 
1082
1124
  ### WebSocket
1083
1125
  - `subscribe(options: SubscriptionOptions, callback, onError?)` → `WebSocketSubscription`
1126
+ - Browser WebSocket cannot send HTTP headers. The SDK maps credentials to query params:
1127
+ `Authorization: Bearer <jwt>` → `?access_token=<jwt>`, `X-App-User-Token` → `?access_token=<jwt>` (fallback),
1128
+ `X-API-Key` → `?api_key=<key>`. The SDK also propagates `context.project_id` as `?tenant_id=`.
1084
1129
 
1085
1130
  ## Key Types
1086
1131
 
package/llms.txt CHANGED
@@ -22,6 +22,30 @@ 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 those requests **skip** normal auth; send **`X-Test-Auth: required`** to exercise real JWT/API-key auth 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).
26
+
27
+ ## Common Pitfalls
28
+
29
+ 1. **`baseUrl` must be origin-only (no `/api` or `/api/v1` path).** The SDK prepends `/api/v1/…` to every request. If `baseUrl` is `https://host/api/v1`, requests hit `/api/v1/api/v1/…` which typically returns **405 Not Allowed** from the reverse proxy. Use only the scheme + host + optional port: `https://host` or `http://localhost:8080`.
30
+
31
+ 2. **Browser WebSocket requires correct CORS and proxy config.** Browsers add an `Origin` header to WebSocket connections — make sure your project's CORS allowed-origins includes your app's origin. Reverse proxies (nginx) must forward WebSocket `Upgrade` requests to XCiteDB's `/api/v1/ws` endpoint (the default `docker/nginx.conf` already does this). For production deployments that need fine-grained server-side filtering or short-lived auth tickets, consider proxying the WebSocket through your own backend.
32
+
33
+ 3. **Prefer `context.project_id` over `platformConsole` for project-scoped API keys.** `platformConsole` / `projectId` is designed for platform-operator keys that select a project dynamically via the `X-Project-Id` header. For project-scoped keys (which already identify their project), set **`context.project_id`** instead — it adds `tenant_id` to app-auth JSON bodies via `mergeAppTenant()` without sending `X-Project-Id`.
34
+
35
+ 4. **Prefer `https://` in `baseUrl` for remote/production hosts.** XciteDB's default nginx uses a method-preserving **308** redirect (POST stays POST), but third-party proxies or older configurations may use **301** which silently downgrades POST to GET. Using `https://` directly avoids the redirect entirely.
36
+
37
+ 5. **`context.project_id` (or `tenant_id`) is required for app-user self-registration.** `registerAppUser()` uses `mergeAppTenant(body)` to add `tenant_id` to the JSON body only when `context.project_id` or `context.tenant_id` is set. If both are omitted, the server cannot determine which project to register the user in. Always set `project_id` in the constructor `context` when calling `registerAppUser`, `loginAppUser`, and other public app-auth methods.
38
+
39
+ 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()`.
40
+
41
+ ## Test mode (how to use)
42
+
43
+ 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.
44
+ 2. **Run tests:** Every request that should hit the throwaway DB must include **`X-Test-Session: <token>`** (and your usual `X-Branch` / `context` as needed). Data is stored under the server’s `_test/<session>/` area, not your production tenant.
45
+ 3. **Auth behavior:** Omit extra headers for frictionless tests. To test policies and real identities, set **`X-Test-Auth: required`** and send normal credentials; the DB is still the test session’s.
46
+ 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.
47
+ 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.
48
+
25
49
  ## JavaScript/TypeScript SDK (`@xcitedbs/client`)
26
50
 
27
51
  Install: `npm install @xcitedbs/client`
@@ -184,11 +208,14 @@ interface XCiteDBClientOptions {
184
208
 
185
209
  **App Users (admin):**
186
210
  - `listAppUsers()` / `createAppUser()` / `deleteAppUser()` — Manage end-user accounts
187
- - `registerAppUser()` — Self-registration
211
+ - `registerAppUser(email, password, displayName?, attributes?)` — Self-registration (groups assigned from server config)
188
212
  - `getAppAuthConfig()` — Auth configuration
189
213
 
190
214
  **WebSocket:**
191
215
  - `subscribe(options, callback, onError?)` — Real-time document change notifications
216
+ - Browser WebSocket cannot send HTTP headers. The SDK maps credentials to query params:
217
+ `Authorization: Bearer <jwt>` → `?access_token=<jwt>`, `X-App-User-Token` → `?access_token=<jwt>` (fallback),
218
+ `X-API-Key` → `?api_key=<key>`. The SDK also propagates `context.project_id` as `?tenant_id=`.
192
219
 
193
220
  ### Query Flags
194
221
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcitedbs/client",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "XCiteDB BaaS client SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",