@xcitedbs/client 0.2.9 → 0.2.11

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/llms.txt CHANGED
@@ -1,18 +1,18 @@
1
1
  # XciteDB
2
2
 
3
- > XciteDB is a versioned XML and JSON document database delivered as a Backend-as-a-Service (BaaS). It provides Git-like branching, commits, time-travel, and hierarchical document identifiers over a high-performance embedded LMDB engine, exposed through a REST + WebSocket API.
3
+ > XciteDB is a versioned XML and JSON document database delivered as a Backend-as-a-Service (BaaS). It provides **temporal revisions** (every dated write is a revision), **workspaces** for isolated editing, **checkpoints** for optional named snapshots, time-travel reads, and hierarchical document identifiers over a high-performance embedded LMDB engine, exposed through a REST + WebSocket API.
4
4
 
5
5
  ## Important: Non-Standard Conventions
6
6
 
7
7
  These are the most common sources of confusion for developers and AI assistants:
8
8
 
9
- 1. **The default branch is the empty string `""`, not `"main"`.** When no `X-Branch` header is sent (or `context.branch` is omitted/empty in the SDK), the server operates on the root timeline. A branch named `"main"` may exist as a user-created branch, but it is not required or special.
9
+ 1. **The default workspace is the empty string `""`, not `"main"`.** When no `X-Workspace` header is sent (or `context.workspace` / `context.branch` is omitted/empty in the SDK), the server operates on the root timeline. `X-Branch` is a supported alias of `X-Workspace`. A workspace named `"main"` may exist as user-created metadata, but it is not required or special.
10
10
 
11
11
  2. **Identifiers are hierarchical, path-like strings** — e.g. `/us/bills/hr1`, `/manual/v2/chapter3`. They are NOT auto-generated UUIDs. The leading `/` is part of the identifier. Parent/child relationships are derived from the path structure (like a filesystem). The server indexes this hierarchy natively.
12
12
 
13
13
  3. **Documents are XML or JSON, not arbitrary blobs.** XML documents are the primary document type, stored with structural shredding. JSON documents are a parallel store keyed by identifier string. Both are fully versioned.
14
14
 
15
- 4. **Context (branch + date) travels as HTTP headers**, not URL path segments. Use `X-Branch` and `X-Date` headers (or the SDK `context` option) to select which branch and point-in-time you are reading/writing.
15
+ 4. **`X-Date` and temporal revision (not “only now”).** Workspace and date context travel as HTTP headers (`X-Workspace` preferred, `X-Branch` alias, `X-Date`), or the SDK `context` option — not as URL path segments. **`X-Date` is not limited to the current server time.** Pass any instant you need as **ISO 8601** (e.g. `2024-01-15T00:00:00`) or **`mm/dd/yyyy`** (optional **`:HH:MM:SS`**) — for example an **official publication or approval date**, or any historical “as of” moment. When **`X-Date` is set**, that value applies to **both** **reads** (query as-of that instant) **and** **writes** (the new revision is stored under that revision instant). When **`X-Date` is omitted** and you do **not** send **`X-Unversioned: true`**, **writes** use **flat** keys (no date suffix on that write); **reads** still resolve **as of the current time**. **`X-Unversioned: true`** requests explicit flat keys and must **not** be combined with **`X-Date`** (the server returns 400).
16
16
 
17
17
  5. **XML documents carry their identifier inside the XML** — specifically via a `db:identifier` attribute on the root element (e.g. `<chapter db:identifier="/book/ch1">...</chapter>`). When writing XML, the server extracts the identifier from the document body.
18
18
 
@@ -24,6 +24,20 @@ These are the most common sources of confusion for developers and AI assistants:
24
24
 
25
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).
26
26
 
27
+ ## Choosing the Right Versioning Approach
28
+
29
+ - **Write at a specific date** (law effective date, publication date, approval date): Set `context.date` / `X-Date` and write. No workspaces or checkpoints needed. Every write with a date creates a **temporal revision** automatically.
30
+
31
+ - **Read historical data** (“what was the document on March 1, 2024?”): Set `context.date` / `X-Date` and read. The engine returns the revision at or before that instant.
32
+
33
+ - **Isolated editing** (draft/review/publish): Create a **workspace**, make changes, then **publish** to the main timeline. Optionally create **checkpoints** for named snapshots within the workspace.
34
+
35
+ - **Audit trail with named snapshots:** Create checkpoints with messages. List/inspect checkpoints for history.
36
+
37
+ - **Undo a batch of changes:** **Revert** to a prior checkpoint (removes all changes after that point on the workspace).
38
+
39
+ Legacy REST paths (`/api/v1/branches`, `/commits`, `/tags`, `/diff`) remain as **deprecated** aliases; prefer **`/api/v1/workspaces`**, **`/checkpoints`**, **`/bookmarks`**, **`/compare`**.
40
+
27
41
  ## Glossary: Project id, display name, and groups
28
42
 
29
43
  - **Project display name** (human-readable, e.g. `invoices`): Shown in the console. The **`X-Project-Id`** header may match either this name **or** the internal project id (server convenience). Do **not** put the display name in JWT claims or in `project:<…>:role` group strings.
@@ -54,6 +68,8 @@ These are the most common sources of confusion for developers and AI assistants:
54
68
 
55
69
  11. **Integration tests should assert token claims**, not only **`/app/auth/me`**. The profile endpoint can look fine while the JWT still carries viewer-style **`groups`**; for ABAC the token payload is authoritative. Use **`getTokenClaims()`** (JS SDK) or decode the JWT in your harness.
56
70
 
71
+ 12. **`X-Date` / `context.date` is not “server clock only.”** Revisions are keyed by the **instant you send**, not an implicit “now” when you set the header. To record a document under a **business date** (published, approved, effective), set **`X-Date`** or **`context.date`** to that instant before writing. Omitting **`X-Date`** does **not** substitute the current time on the write path — it selects **flat** writes (see convention 4).
72
+
57
73
  ## API key capability matrix (typical)
58
74
 
59
75
  | Capability | API key `role` | Public key allowed? |
@@ -90,7 +106,7 @@ await app.writeJsonDocument('userdata/alice/profile', { ok: true });
90
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.
91
107
 
92
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.
93
- 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.
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.
94
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.
95
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.
96
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.
@@ -107,15 +123,15 @@ import { XCiteDBClient } from '@xcitedbs/client';
107
123
  const client = new XCiteDBClient({
108
124
  baseUrl: 'http://localhost:8080',
109
125
  apiKey: 'your-api-key',
110
- // branch: '' means root timeline (the default); omit or use '' for default
111
- context: { branch: '', date: '' },
126
+ // workspace: '' means root timeline (the default); omit or use '' for default
127
+ context: { workspace: '', date: '' },
112
128
  });
113
129
 
114
130
  // Health check (no auth required)
115
131
  await client.health();
116
132
 
117
- // List all branches
118
- const branches = await client.listBranches();
133
+ // List all workspaces
134
+ const workspaces = await client.listWorkspaces();
119
135
 
120
136
  // Query documents by identifier prefix
121
137
  const docs = await client.queryDocuments({ match_start: '/manual/' });
@@ -140,18 +156,18 @@ const prefs = await client.get<Record<string, unknown>>('app.prefs');
140
156
  await client.remove('app.prefs');
141
157
  const keys = await client.list(undefined, 50, 0);
142
158
 
143
- // Branch helper: create branch, run work, commit, merge back (restores context)
144
- await client.withBranch('feature-x', async (c) => {
159
+ // Workspace helper: create workspace, run work, checkpoint, publish back (restores context)
160
+ await client.withWorkspace('feature-x', async (c) => {
145
161
  await c.writeJsonDocument('app.settings', { theme: 'light' });
146
162
  return 'ok';
147
163
  }, { message: 'Light theme', autoMerge: true, fromBranch: '' });
148
164
 
149
- // Create a branch, make changes, commit, merge
150
- await client.createBranch('feature-x');
151
- client.setContext({ branch: 'feature-x' });
165
+ // Create a workspace, make changes, checkpoint, publish
166
+ await client.createWorkspace('feature-x');
167
+ client.setContext({ workspace: 'feature-x' });
152
168
  await client.writeJsonDocument('app.settings', { theme: 'light' });
153
- const commit = await client.createCommit('Switch to light theme');
154
- await client.mergeBranch('', 'feature-x'); // merge into root (default) branch
169
+ const checkpoint = await client.createCheckpoint('Switch to light theme');
170
+ await client.publishWorkspace('', 'feature-x'); // publish into root (default) workspace
155
171
 
156
172
  // Attach JSON metadata to a document
157
173
  await client.addMeta('/manual/v1/intro', { status: 'draft', owner: 'alice' });
@@ -180,8 +196,9 @@ interface XCiteDBClientOptions {
180
196
  accessToken?: string; // Platform JWT (from platformLogin)
181
197
  appUserAccessToken?: string; // End-user JWT (from loginAppUser)
182
198
  context?: {
183
- branch?: string; // Branch name; '' = default/root timeline
184
- date?: string; // Point-in-time (ISO-like or internal date key)
199
+ workspace?: string; // Workspace name; '' = default/root timeline (preferred)
200
+ branch?: string; // @deprecated alias of workspace; sends X-Workspace
201
+ date?: string; // X-Date: as-of reads + write revision; any ISO/mm-dd-yyyy instant, not only "now"
185
202
  prefix?: string; // Optional identifier prefix filter
186
203
  project_id?: string; // Preferred: project id for app-user public auth (`tenant_id` on wire)
187
204
  tenant_id?: string; // Deprecated alias of project_id
@@ -200,7 +217,7 @@ interface XCiteDBClientOptions {
200
217
  - `loginAppUser(email, password)` — App end-user sign-in
201
218
  - `XCiteDBClient.buildProjectGroup(projectId, 'admin'|'editor'|'viewer')` — Static helper: canonical `project:<tenant_id>:<role>` string
202
219
  - `getTokenClaims()` — Decode current `appUserAccessToken` or `accessToken` payload (no signature verification); use for ABAC debugging
203
- - `setContext(ctx)` — Update branch/date context
220
+ - `setContext(ctx)` — Update workspace/date context
204
221
  - `setProjectId(id)` — Switch active project (platform console)
205
222
 
206
223
  **XML Documents:**
@@ -225,19 +242,20 @@ interface XCiteDBClientOptions {
225
242
  - `queryMeta(identifier, path?)` — Read metadata
226
243
  - `clearMeta(query)` — Remove metadata
227
244
 
228
- **Versioning:**
229
- - `withBranch(name, fn, options?)` — Create branch, run callback on client, commit, merge back (optional `autoMerge: false`)
230
- - `createBranch(name, fromBranch?, fromDate?)` — Create branch
231
- - `listBranches()` — List all branches
232
- - `mergeBranch(target, source, options?)` — Merge branches
233
- - `deleteBranch(name)` — Delete branch
234
- - `createCommit(message, author?)` — Commit current state
235
- - `listCommits(options?)` — List commits
236
- - `rollbackToCommit(commitId)` — Rollback to prior commit
237
- - `cherryPick(commitId, message?)` — Cherry-pick a commit
238
- - `diff(from, to, includeContent?)` — Diff between revisions
239
- - `createTag(name, commitId, message?)` — Tag a commit
240
- - `listTags()` / `deleteTag(name)` — Manage tags
245
+ **Versioning (workspaces & checkpoints):**
246
+ - `withWorkspace(name, fn, options?)` — Create workspace, run callback, checkpoint, publish back (optional `autoMerge: false`; `fromBranch` = parent workspace)
247
+ - `createWorkspace(name, fromBranch?, fromDate?)` — Create workspace
248
+ - `listWorkspaces()` — List workspaces
249
+ - `publishWorkspace(target, source, options?)` — Publish workspace changes to a target timeline
250
+ - `deleteWorkspace(name)` — Delete workspace
251
+ - `createCheckpoint(message, author?)` — Named snapshot of current state
252
+ - `listCheckpoints(options?)` — List checkpoints
253
+ - `revertToCheckpoint(checkpointId)` — Revert workspace to a prior checkpoint
254
+ - `applyCheckpoint(checkpointId, message?)` — Apply another checkpoint’s changes here
255
+ - `compare(from, to, includeContent?)` — Compare revisions
256
+ - `createBookmark(name, checkpointId, message?)` — Bookmark a checkpoint
257
+ - `listBookmarks()` / `deleteBookmark(name)` — Manage bookmarks
258
+ - **Deprecated aliases:** `withBranch`, `createBranch`, `listBranches`, `mergeBranch`, `deleteBranch`, `createCommit`, `listCommits`, `rollbackToCommit`, `cherryPick`, `diff`, `createTag`, `listTags`, `deleteTag` (same HTTP behavior)
241
259
 
242
260
  **Locks:**
243
261
  - `acquireLock(identifier, expires?)` — Acquire cooperative lock
@@ -260,7 +278,7 @@ interface XCiteDBClientOptions {
260
278
  ### Advanced: policy expressions (ABAC)
261
279
 
262
280
  - **Actions** (use in `policy.actions`): `read`, `write`, `delete`, `list`, `meta:read`, `meta:write`, `unquery`.
263
- - **`conditions.expression`** uses the same predicate syntax as Unquery `?` filters. **Context:** `subject.id` and **`subject.user_id`** (same value for app users), `subject.email`, `subject.role`, `subject.groups`, `subject.attr.*` (app-user JSON attributes), `resource.identifier`, `resource.path` (array of path segments; indices follow non-empty `/` segments after API canonicalization, e.g. `/userdata/u1/x` → `[userdata,u1,x]`), `env.branch`. Policy **`match_start` / `match_end` / `exact`** strings are canonicalized like API identifiers (add leading `/` when omitted).
281
+ - **`conditions.expression`** uses the same predicate syntax as Unquery `?` filters. **Context:** `subject.id` and **`subject.user_id`** (same value for app users), `subject.email`, `subject.role`, `subject.groups`, `subject.attr.*` (app-user JSON attributes), `resource.identifier`, `resource.path` (array of path segments; indices follow non-empty `/` segments after API canonicalization, e.g. `/userdata/u1/x` → `[userdata,u1,x]`), `env.branch` (workspace name; legacy name retained in policy engine). Policy **`match_start` / `match_end` / `exact`** strings are canonicalized like API identifiers (add leading `/` when omitted).
264
282
  - **Operators:** `=`, `!=`, `>=`, `<=`, `>`, `<`, `in`, `contains`, `starts_with`, `ends_with`, `&`, `|`, `!`, `+` (string concat), postfix `!` (exists).
265
283
  - **Examples:** `resource.path[0] = subject.attr.tenant_code` — tenant isolation; `subject.attr.level >= 5` — numeric attribute gate.
266
284
 
@@ -269,7 +287,7 @@ interface XCiteDBClientOptions {
269
287
  - **Events:** `meta_changed`, `document_written`, `document_deleted`.
270
288
  - **`match`:** required non-empty **`identifiers`** (same pattern objects as policy `resources.identifiers`); optional **`match_meta_path`** (exact or `prefix*`); optional **`match_operation`:** `set` | `append` | `delete`.
271
289
  - **`action`:** **`query`** (document query), **`unquery`** (Unquery template), **`target_identifier`** (or `"$trigger_identifier"`), **`meta_path`**, **`mode`:** `set` | `append`.
272
- - **Unquery vars:** `$trigger_identifier`, `$trigger_meta_path`, `$trigger_operation`, `$trigger_value`. **Trigger `conditions.expression` context:** `trigger.{event,meta_path,operation}`, `value`, `resource`, `env.branch` (no `subject`).
290
+ - **Unquery vars:** `$trigger_identifier`, `$trigger_meta_path`, `$trigger_operation`, `$trigger_value`. **Trigger `conditions.expression` context:** `trigger.{event,meta_path,operation}`, `value`, `resource`, `env.branch` (workspace; no `subject`).
273
291
 
274
292
  ### Advanced: Unquery DSL (`unquery(query, unqueryDoc)`)
275
293
 
@@ -312,7 +330,7 @@ async def main():
312
330
  async with XCiteDBClient(
313
331
  "http://localhost:8080",
314
332
  api_key="your-key",
315
- context=DatabaseContext(branch="", date=""),
333
+ context=DatabaseContext(workspace="", date=""),
316
334
  ) as client:
317
335
  print(await client.health())
318
336
  ids = await client.query_documents(XCiteQuery(match_start="/manual/"))
@@ -334,7 +352,7 @@ asyncio.run(main())
334
352
  xcitedb::XCiteDBClientOptions opt;
335
353
  opt.base_url = "http://127.0.0.1:8080";
336
354
  opt.api_key = "your-key";
337
- opt.context.branch = ""; // root timeline
355
+ opt.context.workspace = ""; // root timeline (branch is deprecated alias)
338
356
 
339
357
  xcitedb::XCiteDBClient client(opt);
340
358
  auto health = client.health();
@@ -347,9 +365,9 @@ auto ids = client.query_documents(q);
347
365
 
348
366
  - **Multi-tenant**: Each project is an isolated tenant with its own data, users, keys, and policies.
349
367
  - **LMDB engine**: Data is memory-mapped; reads are microsecond-class on warm data.
350
- - **Versioning**: Every write is versioned. Branches isolate work. Commits snapshot state. Time-travel reads any historical state via `X-Date`.
368
+ - **Versioning**: **Temporal revisions** are always on when using dates. **Workspaces** isolate draft work; **checkpoints** are optional named snapshots; **publish** merges a workspace into a target timeline. **`X-Date`** selects an instant for **as-of reads** and, when set, for **writes** under that revision (any calendar time you choose — e.g. publication date — not only “now”). Omit **`X-Date`** for **flat** writes on that request, or use **`X-Unversioned: true`** to state that explicitly.
351
369
  - **Two user tiers**: Platform operators manage infrastructure; App users are end-users of applications built on XciteDB.
352
- - **Security policies (ABAC)**: Fine-grained allow/deny rules on actions, resources, roles, and branches.
370
+ - **Security policies (ABAC)**: Fine-grained allow/deny rules on actions, resources, roles, and workspaces (`env.branch` in expressions).
353
371
  - **WebSocket**: Real-time notifications for document changes at `/api/v1/ws`.
354
372
 
355
373
  ## Documentation
@@ -362,8 +380,8 @@ auto ids = client.query_documents(q);
362
380
  - Metadata — JSON metadata on documents
363
381
  - Search — Full-text search
364
382
  - Unquery — Declarative query DSL
365
- - BranchesBranch management
366
- - Commits & tagsVersion snapshots
383
+ - WorkspacesIsolated editing environments
384
+ - Checkpoints & bookmarksNamed snapshots and references
367
385
  - Locks — Cooperative editing locks
368
386
  - Authentication — JWT, API keys, platform vs app users
369
387
  - Security policies — ABAC access control
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@xcitedbs/client",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "XCiteDB BaaS client SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "prepublishOnly": "npm run build",
10
- "test": "node --test dist/**/*.test.js 2>/dev/null || echo 'No tests'"
10
+ "test": "npm run build && node --test dist/**/*.test.js",
11
+ "test:only": "node --test dist/**/*.test.js"
11
12
  },
12
13
  "keywords": [
13
14
  "xcitedb",
@@ -34,6 +35,7 @@
34
35
  },
35
36
  "dependencies": {},
36
37
  "devDependencies": {
38
+ "@types/node": "^20.14.0",
37
39
  "typescript": "^5.0.0"
38
40
  },
39
41
  "files": [