@xcitedbs/client 0.2.21 → 0.2.24
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/assetUri.d.ts +14 -0
- package/dist/assetUri.js +119 -0
- package/dist/assetUri.test.d.ts +1 -0
- package/dist/assetUri.test.js +20 -0
- package/dist/client.d.ts +94 -1
- package/dist/client.js +544 -7
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -1
- package/dist/types.d.ts +161 -0
- package/llms-full.txt +94 -7
- package/llms.txt +64 -3
- package/package.json +1 -1
- package/unquery-ai-guide.md +2 -0
package/dist/types.d.ts
CHANGED
|
@@ -170,6 +170,125 @@ export interface ProjectDocConfResponse {
|
|
|
170
170
|
has_project_override: boolean;
|
|
171
171
|
doc_conf_text: string | null;
|
|
172
172
|
}
|
|
173
|
+
/** `POST /api/v1/assets` success body (`201`). */
|
|
174
|
+
export interface AssetUploadResult {
|
|
175
|
+
identifier: string;
|
|
176
|
+
uri: string;
|
|
177
|
+
target_name?: string;
|
|
178
|
+
content_type?: string;
|
|
179
|
+
size?: number;
|
|
180
|
+
sha256?: string;
|
|
181
|
+
etag?: string;
|
|
182
|
+
}
|
|
183
|
+
/** Options for {@link XCiteDBClient.uploadAsset}. */
|
|
184
|
+
export interface UploadAssetOptions {
|
|
185
|
+
contentType: string;
|
|
186
|
+
/** When set (with default scope), uploaded to this path after user-isolation prefixing rules on the server. */
|
|
187
|
+
identifier?: string;
|
|
188
|
+
/** `public` writes under `/public/assets/…`. Default project/user scope when omitted. */
|
|
189
|
+
scope?: 'project' | 'public';
|
|
190
|
+
}
|
|
191
|
+
/** Result of {@link XCiteDBClient.headAsset}. */
|
|
192
|
+
export interface AssetHeadResult {
|
|
193
|
+
contentType: string;
|
|
194
|
+
size: number;
|
|
195
|
+
etag?: string;
|
|
196
|
+
}
|
|
197
|
+
/** `POST /api/v1/admin/assets/gc/dry-run` (admin). */
|
|
198
|
+
export interface AssetGcDryRunResult {
|
|
199
|
+
manifest_count: number;
|
|
200
|
+
referenced_asset_count: number;
|
|
201
|
+
orphan_count: number;
|
|
202
|
+
orphan_sample: string[];
|
|
203
|
+
}
|
|
204
|
+
export type AssetStorageTargetType = 'internal' | 's3';
|
|
205
|
+
/** One named storage target in `asset_storage_v2` (`GET/PUT /api/v1/project/settings/asset-storage`). */
|
|
206
|
+
export interface AssetStorageTarget {
|
|
207
|
+
type: AssetStorageTargetType;
|
|
208
|
+
writable?: boolean;
|
|
209
|
+
s3_endpoint_url?: string;
|
|
210
|
+
s3_bucket?: string;
|
|
211
|
+
s3_region?: string;
|
|
212
|
+
access_key_id?: string;
|
|
213
|
+
/** On GET, redacted as `***` when set. */
|
|
214
|
+
secret_access_key?: string;
|
|
215
|
+
small_file_threshold_bytes?: number;
|
|
216
|
+
redirect_threshold_bytes?: number;
|
|
217
|
+
import_head_cache_ttl_seconds?: number;
|
|
218
|
+
[key: string]: unknown;
|
|
219
|
+
}
|
|
220
|
+
export interface AssetStorageMount {
|
|
221
|
+
path: string;
|
|
222
|
+
target: string;
|
|
223
|
+
key_template: string;
|
|
224
|
+
}
|
|
225
|
+
export interface AssetStorageImport {
|
|
226
|
+
path: string;
|
|
227
|
+
target: string;
|
|
228
|
+
key: string;
|
|
229
|
+
tree?: boolean;
|
|
230
|
+
content_type?: string;
|
|
231
|
+
}
|
|
232
|
+
/** Project asset storage v2 (`GET/PUT /api/v1/project/settings/asset-storage`). */
|
|
233
|
+
export interface ProjectAssetStorageConfig {
|
|
234
|
+
targets: Record<string, AssetStorageTarget>;
|
|
235
|
+
mounts: AssetStorageMount[];
|
|
236
|
+
imports: AssetStorageImport[];
|
|
237
|
+
}
|
|
238
|
+
/** Body for `POST /api/v1/security/user-isolation/asset-shares`. */
|
|
239
|
+
export interface AssetShareRequest {
|
|
240
|
+
/** Canonical or app-relative asset path (subject to user-isolation prefixing when enabled). */
|
|
241
|
+
identifier?: string;
|
|
242
|
+
/** `xcitedb:asset:/…` or raw `/…` path; not prefixed by the client. */
|
|
243
|
+
source_uri?: string;
|
|
244
|
+
target_user_id: string;
|
|
245
|
+
mode: UserIsolationShareMode;
|
|
246
|
+
}
|
|
247
|
+
/** Body for `DELETE /api/v1/security/user-isolation/asset-shares`. */
|
|
248
|
+
export interface AssetUnshareRequest {
|
|
249
|
+
identifier?: string;
|
|
250
|
+
source_uri?: string;
|
|
251
|
+
target_user_id?: string;
|
|
252
|
+
sharer_user_id?: string;
|
|
253
|
+
}
|
|
254
|
+
export interface AssetShareListEntry {
|
|
255
|
+
identifier: string;
|
|
256
|
+
mode: string;
|
|
257
|
+
}
|
|
258
|
+
/** `GET /api/v1/security/user-isolation/asset-shares` */
|
|
259
|
+
export interface AssetShareListResponse {
|
|
260
|
+
direction: string;
|
|
261
|
+
identifiers: AssetShareListEntry[];
|
|
262
|
+
scope?: string;
|
|
263
|
+
note?: string;
|
|
264
|
+
}
|
|
265
|
+
/** `POST /api/v1/security/asset-magic-links` */
|
|
266
|
+
export interface CreateAssetMagicLinkRequest {
|
|
267
|
+
source_uri: string;
|
|
268
|
+
actions: ('read' | 'write')[];
|
|
269
|
+
expires_in_seconds: number;
|
|
270
|
+
max_uses?: number;
|
|
271
|
+
}
|
|
272
|
+
export interface AssetMagicLinkResult {
|
|
273
|
+
token: string;
|
|
274
|
+
token_id?: string;
|
|
275
|
+
expires_at: string;
|
|
276
|
+
link_url: string;
|
|
277
|
+
}
|
|
278
|
+
export interface AssetMagicLinkRecord {
|
|
279
|
+
token_id: string;
|
|
280
|
+
identifier: string;
|
|
281
|
+
actions?: unknown;
|
|
282
|
+
issued_by_user_id?: string;
|
|
283
|
+
issued_at?: string;
|
|
284
|
+
expires_at?: string;
|
|
285
|
+
max_uses?: number;
|
|
286
|
+
uses?: number;
|
|
287
|
+
revoked?: boolean;
|
|
288
|
+
}
|
|
289
|
+
export interface AssetMagicLinkListResponse {
|
|
290
|
+
tokens: AssetMagicLinkRecord[];
|
|
291
|
+
}
|
|
173
292
|
/** `GET /api/v1/platform/default-doc-conf` */
|
|
174
293
|
export interface PlatformDefaultDocConfResponse {
|
|
175
294
|
doc_conf_text: string;
|
|
@@ -399,11 +518,41 @@ export interface WriteDocumentOptions {
|
|
|
399
518
|
is_top?: boolean;
|
|
400
519
|
compare_attributes?: boolean;
|
|
401
520
|
}
|
|
521
|
+
/** Supported source formats for `POST /api/v1/documents/import` (server detects from bytes / filename). */
|
|
522
|
+
export type DocumentImportFormat = 'docx' | 'odt' | 'rtf' | 'pdf' | 'txt' | 'md' | 'adoc';
|
|
523
|
+
/** JSON body on successful import (`201 Created`). */
|
|
524
|
+
export interface ImportDocumentResult {
|
|
525
|
+
identifier: string;
|
|
526
|
+
section_count: number;
|
|
527
|
+
source_format: string;
|
|
528
|
+
}
|
|
529
|
+
export interface ImportDocumentOptions {
|
|
530
|
+
/** Override root `db:identifier`; default is `/imported/<sanitized-filename>`. */
|
|
531
|
+
identifier?: string;
|
|
532
|
+
/** Original filename (helps sniff format when `File` is not available, e.g. Node `Blob` only). */
|
|
533
|
+
filename?: string;
|
|
534
|
+
}
|
|
535
|
+
/** `GET /api/v1/documents/export?format=…` */
|
|
536
|
+
export type DocumentExportFormat = 'xml' | 'docx' | 'odt' | 'pdf' | 'txt' | 'md' | 'adoc';
|
|
537
|
+
export interface ExportDocumentResult {
|
|
538
|
+
bytes: Uint8Array;
|
|
539
|
+
contentType: string;
|
|
540
|
+
filename: string;
|
|
541
|
+
/** Present for txt/md/adoc when server sends `X-XciteDB-Export-RoundTrip`. */
|
|
542
|
+
roundTrip?: 'exact' | 'lossy';
|
|
543
|
+
/** From `X-XciteDB-Export-Warning` when present. */
|
|
544
|
+
warning?: string;
|
|
545
|
+
}
|
|
402
546
|
/** One row in `POST /api/v1/documents/batch` or `POST /api/v1/json-documents/batch` response `results`. */
|
|
403
547
|
export interface DocumentBatchResultRow {
|
|
404
548
|
index: number;
|
|
405
549
|
identifier: string;
|
|
406
550
|
ok: boolean;
|
|
551
|
+
/**
|
|
552
|
+
* When `ok` is false, server batch precheck / write reason. Includes:
|
|
553
|
+
* `duplicate_identifier_in_batch` (409), `parent_inlines_batch_member` (409),
|
|
554
|
+
* `lock_conflict` (423), and other parse/policy strings.
|
|
555
|
+
*/
|
|
407
556
|
error?: string;
|
|
408
557
|
code?: number;
|
|
409
558
|
/** Present when `error === 'lock_conflict'` (HTTP-style code 423 in batch row). */
|
|
@@ -835,6 +984,8 @@ export interface CompareResult {
|
|
|
835
984
|
date_key?: string;
|
|
836
985
|
};
|
|
837
986
|
total_changes: number;
|
|
987
|
+
/** Echo of request `match_start` when path-scoped compare was used. */
|
|
988
|
+
match_start?: string;
|
|
838
989
|
}
|
|
839
990
|
/** @deprecated Use {@link CompareResult}. */
|
|
840
991
|
export type DiffResult = CompareResult;
|
|
@@ -857,6 +1008,16 @@ export interface PublishResult {
|
|
|
857
1008
|
}
|
|
858
1009
|
/** @deprecated Use {@link PublishResult}. */
|
|
859
1010
|
export type MergeResult = PublishResult;
|
|
1011
|
+
/** `POST /api/v1/user-workspaces/{id}/rebase` */
|
|
1012
|
+
export interface RebaseUserWorkspaceResult {
|
|
1013
|
+
status: 'completed' | 'conflicts';
|
|
1014
|
+
message?: string;
|
|
1015
|
+
old_from_date_key?: string;
|
|
1016
|
+
new_from_date_key?: string;
|
|
1017
|
+
conflicts?: PublishConflict[];
|
|
1018
|
+
auto_mergeable?: string[];
|
|
1019
|
+
would_expose?: string[];
|
|
1020
|
+
}
|
|
860
1021
|
export type XCiteDBErrorExtras = {
|
|
861
1022
|
reason?: string;
|
|
862
1023
|
policyId?: string;
|
package/llms-full.txt
CHANGED
|
@@ -12,7 +12,7 @@ Before reading the full reference, note these critical differences from typical
|
|
|
12
12
|
|
|
13
13
|
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.
|
|
14
14
|
|
|
15
|
-
3. **Documents are XML or JSON, not arbitrary blobs.** XML documents are the primary document type. JSON documents are a parallel store. Both are fully versioned.
|
|
15
|
+
3. **Documents are XML or JSON, not arbitrary blobs.** XML documents are the primary document type. JSON documents are a parallel store. Both are fully versioned. **Binary files** use the separate **assets** subsystem (not document shredding): canonical paths like `/assets/<uuid>`, stable **`xcitedb:asset:/…`** URIs, **`POST` / `GET` / `HEAD` / `DELETE /api/v1/assets/…`**, project **`asset-storage` v2** config, optional user-isolation **asset-shares**, and **asset magic links** (`?ml=`).
|
|
16
16
|
|
|
17
17
|
4. **Context (workspace + date) travels as HTTP headers** (`X-Workspace` preferred, `X-Branch` alias, `X-Date`, and optionally `X-Unversioned` for explicit flat writes), not URL path segments.
|
|
18
18
|
|
|
@@ -28,14 +28,16 @@ Before reading the full reference, note these critical differences from typical
|
|
|
28
28
|
|
|
29
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
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`**.
|
|
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`**. **Locks:** cooperative lock metadata is read from the **base** layer as well as the overlay, but lock acquire/release only affects the overlay—so **`lock_conflict`** during overlay tests can reflect a lock still held in production; use **`findLocks` / `forceReleaseLock`** on your paths or a non-overlay session when testing locks.
|
|
32
|
+
|
|
33
|
+
12. **SDK asset helpers.** JavaScript: `uploadAsset`, `getAsset`, `headAsset`, `deleteAsset`, `resolveAssetIdentifier`, `formatAssetUri`, `collectIdentifiersFromText`, plus `createAssetShare` / `createAssetMagicLink` and related APIs. Python: `upload_asset`, `get_asset`, `head_asset`, `xcitedb.asset_uri`. C++: `xcitedb/asset_uri.hpp`, `XCiteDBClient::upload_asset`, `get_asset_raw`, `head_asset_raw`, `delete_asset`, and security helpers mirroring the other SDKs.
|
|
32
34
|
|
|
33
35
|
## Choosing the Right Versioning Approach
|
|
34
36
|
|
|
35
37
|
- **Write at a specific date:** Set `X-Date` / `context.date` and write — every dated write is a **temporal revision**; no workspace or checkpoint required.
|
|
36
38
|
- **Read as-of a date:** Set `X-Date` and read; the engine returns the revision at or before that instant.
|
|
37
39
|
- **Isolated editing (draft → publish):** Create a **workspace**, edit, then **publish** to the target timeline; optional **checkpoints** for named snapshots.
|
|
38
|
-
- **App-user user workspaces (`_uw/<owner>/<slug>`):** **`GET/POST /api/v1/user-workspaces`**, grants, publish — see repository **`web/src/docs/content/user-workspaces.md`**.
|
|
40
|
+
- **App-user user workspaces (`_uw/<owner>/<slug>`):** **`GET/POST /api/v1/user-workspaces`**, grants, publish, **rebase** — see repository **`web/src/docs/content/user-workspaces.md`**.
|
|
39
41
|
- **Audit trail:** Checkpoints carry messages and affected identifiers; **bookmarks** name a checkpoint.
|
|
40
42
|
- **Undo a batch:** **Revert** to a prior checkpoint on that workspace.
|
|
41
43
|
|
|
@@ -63,6 +65,8 @@ Legacy REST paths under `/api/v1/branches`, `/commits`, `/tags`, `/diff` remain
|
|
|
63
65
|
|
|
64
66
|
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.
|
|
65
67
|
|
|
68
|
+
8. **Identifier strings (minting).** The engine rejects identifiers containing byte **`0x01`**. The API canonicalizes hierarchical paths (**leading `/`**, **no trailing `/`**). Reserved namespaces include **`/_xcitedb/`**, **`/assets/`**, **`/public/assets/`**, and **`/public/shared/<tenant>/assets/`**. **`identifier_hierarchy_max_depth`** (default **4**) tunes hierarchy indexing; deeper paths still work but **`GET …/identifier-children`** may scan.
|
|
69
|
+
|
|
66
70
|
---
|
|
67
71
|
|
|
68
72
|
# Part 1: Product Overview
|
|
@@ -434,6 +438,57 @@ The identifier is extracted from the `db:identifier` attribute in the root XML e
|
|
|
434
438
|
|
|
435
439
|
Query parameters: `identifier` (required), `flags` (optional: `FirstMatch`, `IncludeChildren`, `NoChildren`, `KeepIndexNodes`), `filter`, `path_filter`.
|
|
436
440
|
|
|
441
|
+
### Shallow node + subtree navigation
|
|
442
|
+
|
|
443
|
+
- **`flags=NoChildren,KeepIndexNodes,FirstMatch`**: returns the matching node with inline leaf content plus **`db:N1`**, **`db:N2`**, … placeholders for shredded child slots (avoids loading the full deep subtree in one response).
|
|
444
|
+
- Combine with **`GET /api/v1/documents/identifier-children?parent_path=…`** for the next level of hierarchy (`segment`, `full_path`, `is_identifier`, `has_children`).
|
|
445
|
+
- **JavaScript SDK:** `queryByIdentifierFull(id)` loads **`FirstMatch,IncludeChildren`** for editor round-trips; **`queryByIdentifierShallow(id)`** + **`listChildIdentifiers(id)`** (e.g. `Promise.all` client-side) for sidebar / tree expansion.
|
|
446
|
+
|
|
447
|
+
### Standalone subtree write (shredded model)
|
|
448
|
+
|
|
449
|
+
You may `POST /api/v1/documents` with a root element whose `db:identifier` is a nested path (e.g. `/spaces/u/docs/doc1/sec-1`) without re-posting the parent document: the engine writes that subtree only and updates the identifier hierarchy index for the parent automatically. Keep `is_top: true` (default) on the write.
|
|
450
|
+
|
|
451
|
+
### XML shredding semantics (must read)
|
|
452
|
+
|
|
453
|
+
- **Auto-shred.** Any element with **`db:identifier`** is stored as its own LMDB row. The parent’s stored XML keeps a **`<db:N…/>`** placeholder for that slot. There is **no** size threshold and **no** opt-out.
|
|
454
|
+
- **`is_top` is a marker only.** It registers a **`TOP:<xcitepath>`** alias for tooling; it does **not** change shredding, merge, or overwrite behavior.
|
|
455
|
+
- **Batch ordering.** **`POST /api/v1/documents/batch`** processes **`items[]`** **sequentially**. Each item’s XML is shredded so **every** `db:identifier` in that payload becomes (or overwrites) its own row. Precheck returns per-row **`409`**: **`duplicate_identifier_in_batch`** when the same canonical root id appears in more than one item (the **later** row is rejected), and **`parent_inlines_batch_member`** when one item’s XML still contains **full** markup for another item’s **root** id in the same batch (prevents silently overwriting a fresher child row with a stale parent).
|
|
456
|
+
- **Save the smallest dirty subtree.** Do not include live descendant XML for ids you also flush as standalone batch rows; use placeholders from a shallow read when you must touch the parent in the same request.
|
|
457
|
+
|
|
458
|
+
**Round-trip:** Element structure, child order, and text are preserved. The server may add **`db:order`**, **`db:xcitepath`**, **`xmlns:db`**. Attribute order and insignificant whitespace are **not** byte-stable—compare by DOM, not raw bytes.
|
|
459
|
+
|
|
460
|
+
### Batch XML writes
|
|
461
|
+
|
|
462
|
+
**`POST /api/v1/documents/batch`** — JSON body **`{ "items": [ { "xml": "<…>", "is_top": true, "compare_attributes": false, "identifier": "/optional/match" }, … ] }`**. Response **`200`** with **`{ "results": [ { "index", "ok", "identifier", "error"?, "code"?, "current_lock"? } ] }`** (best-effort: later rows still run when earlier rows fail). JavaScript: **`writeXmlDocumentsBatch`**. Python: **`write_xml_documents_batch`**. C++: **`write_xml_documents_batch`**.
|
|
463
|
+
|
|
464
|
+
### Minimal SPA editor recipe
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
await client.enableUserIsolation();
|
|
468
|
+
client.setContext({ workspace: '', project_id: projectId });
|
|
469
|
+
|
|
470
|
+
async function loadDocForEditor(id: string) {
|
|
471
|
+
return client.queryByIdentifierFull(id);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function sidebarRow(id: string) {
|
|
475
|
+
const [node, listed] = await Promise.all([
|
|
476
|
+
client.queryByIdentifierShallow(id),
|
|
477
|
+
client.listIdentifierChildren(id),
|
|
478
|
+
]);
|
|
479
|
+
return { node, children: listed.children };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function saveDirty(items: XmlDocumentBatchItem[]) {
|
|
483
|
+
const { results } = await client.writeXmlDocumentsBatch(items);
|
|
484
|
+
results.forEach((r, i) => {
|
|
485
|
+
if (!r.ok) console.warn(i, r.error, r.code);
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Save rules:** (1) At most **one** batch item per flush may use a given root **`db:identifier`**. (2) Never ship a parent that **inlines** another item’s root id in the same batch. (3) **`is_top`** does not relax (1) or (2).
|
|
491
|
+
|
|
437
492
|
## Delete document
|
|
438
493
|
|
|
439
494
|
**`DELETE /api/v1/documents/by-id?identifier=/book/ch1`**
|
|
@@ -526,7 +581,7 @@ Returns `{ identifiers: string[], total, offset, limit }`.
|
|
|
526
581
|
|
|
527
582
|
## Export document
|
|
528
583
|
|
|
529
|
-
**`GET /api/v1/documents/export?
|
|
584
|
+
**`GET /api/v1/documents/export?identifier=/book/ch1&format=...`**
|
|
530
585
|
|
|
531
586
|
## Export provision
|
|
532
587
|
|
|
@@ -847,6 +902,22 @@ Returns `{ status: "completed"|"conflicts", checkpoint?, commit?, merged_identif
|
|
|
847
902
|
|
|
848
903
|
---
|
|
849
904
|
|
|
905
|
+
# App-user workspaces (`/api/v1/user-workspaces`)
|
|
906
|
+
|
|
907
|
+
Per-user draft branches (`_uw/<owner>/<slug>`). See also **`web/src/docs/content/user-workspaces.md`**.
|
|
908
|
+
|
|
909
|
+
## Rebase user workspace
|
|
910
|
+
|
|
911
|
+
**`POST /api/v1/user-workspaces/{id}/rebase`**
|
|
912
|
+
|
|
913
|
+
```json
|
|
914
|
+
{ "auto_resolve": "none" }
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
Advances the workspace branch fork metadata to the **current tip** of its parent branch. Returns **`200`** with `{ status: "completed", message?, old_from_date_key, new_from_date_key, would_expose[], auto_mergeable[], conflicts: [] }`, or **`409`** with `{ status: "conflicts", conflicts: [...], auto_mergeable?, would_expose? }` when the same identifiers changed on both parent and workspace since the fork (same conflict shape as workspace publish).
|
|
918
|
+
|
|
919
|
+
---
|
|
920
|
+
|
|
850
921
|
# Checkpoints, bookmarks & compare
|
|
851
922
|
|
|
852
923
|
## Create checkpoint
|
|
@@ -896,13 +967,16 @@ Returns `{ checkpoints: [...], commits: [...], total, branch }` (mirrors).
|
|
|
896
967
|
{
|
|
897
968
|
"from": { "branch": "", "date": "2024-01-15T00:00:00" },
|
|
898
969
|
"to": { "branch": "feature-x" },
|
|
899
|
-
"include_content": true
|
|
970
|
+
"include_content": true,
|
|
971
|
+
"match_start": "/spaces/u/docs/doc1"
|
|
900
972
|
}
|
|
901
973
|
```
|
|
902
974
|
|
|
975
|
+
Optional **`match_start`**: when set, only identifiers equal to that path or under it (prefix + `/`) are included in **`changes`** (tenant-wide noise is dropped).
|
|
976
|
+
|
|
903
977
|
(Deprecated: **`POST /api/v1/diff`**; `date_key` still accepted internally but prefer **`date`**.)
|
|
904
978
|
|
|
905
|
-
Returns `{ changes: [{ identifier, action: "added"|"modified"|"deleted", from_content?, to_content? }], total_changes }
|
|
979
|
+
Returns `{ changes: [{ identifier, action: "added"|"modified"|"deleted", from_content?, to_content? }], total_changes, match_start? }` ( **`match_start`** echoed when the filter was used).
|
|
906
980
|
|
|
907
981
|
---
|
|
908
982
|
|
|
@@ -1506,7 +1580,13 @@ interface DatabaseContext {
|
|
|
1506
1580
|
### XML Documents
|
|
1507
1581
|
- `writeXmlDocument(xml, options?)` → `void` — XML via JSON body (recommended). Deprecated: `writeDocumentJson`.
|
|
1508
1582
|
- `writeXML(xml)` → `void` — Raw XML body
|
|
1583
|
+
- `importDocument(file, options?)` → `ImportDocumentResult` — multipart `POST /api/v1/documents/import`
|
|
1584
|
+
- `exportDocument(identifier, format?, options?)` → `ExportDocumentResult` — binary `GET /api/v1/documents/export`
|
|
1509
1585
|
- `queryByIdentifier(identifier, flags?, filter?, pathFilter?)` → `string[]`
|
|
1586
|
+
- `queryByIdentifierFull(identifier, filter?, pathFilter?)` → `string[]` — `FirstMatch,IncludeChildren` (editor load)
|
|
1587
|
+
- `queryByIdentifierShallow(identifier, filter?, pathFilter?)` → `string[]` — `NoChildren,KeepIndexNodes,FirstMatch`
|
|
1588
|
+
- `listChildIdentifiers(parentPath?)` → `ListIdentifierChildrenResult` — alias of `listIdentifierChildren`
|
|
1589
|
+
- `writeXmlDocumentsBatch(items)` → `DocumentBatchResponse` — `POST /api/v1/documents/batch` (per-row `409`: `duplicate_identifier_in_batch`, `parent_inlines_batch_member`)
|
|
1510
1590
|
- `queryDocuments(query: XCiteQuery, flags?, filter?, pathFilter?)` → `string[]`
|
|
1511
1591
|
- `deleteDocument(identifier)` → `void`
|
|
1512
1592
|
- `listIdentifiers(query: XCiteQuery)` → `ListIdentifiersResult`
|
|
@@ -1544,6 +1624,13 @@ interface DatabaseContext {
|
|
|
1544
1624
|
- `publishWorkspace(targetWorkspace, sourceWorkspace, options?)` → `PublishResult`
|
|
1545
1625
|
- **Deprecated:** `withBranch`, `createBranch`, `listBranches`, `getBranch`, `deleteBranch`, `mergeBranch` (same HTTP behavior)
|
|
1546
1626
|
|
|
1627
|
+
### App-user user workspaces (`_uw/…`)
|
|
1628
|
+
- `listUserWorkspaces()` → `{ user_workspaces: unknown[] }`
|
|
1629
|
+
- `createUserWorkspace(name, options?)` → `Record<string, unknown>`
|
|
1630
|
+
- `getUserWorkspace(id)` / `deleteUserWorkspace(id)` / `addUserWorkspaceGrant(id, body)` / `removeUserWorkspaceGrant(id, body)`
|
|
1631
|
+
- `publishUserWorkspace(id, options?)` → `PublishResult`-shaped JSON
|
|
1632
|
+
- `rebaseUserWorkspace(id, options?)` → `RebaseUserWorkspaceResult` — advances fork onto parent tip; **409** + `conflicts` on overlap (optional `autoResolve`: `none` | `source` | `target`)
|
|
1633
|
+
|
|
1547
1634
|
### Checkpoints
|
|
1548
1635
|
- `createCheckpoint(message, author?)` → `CheckpointRecord`
|
|
1549
1636
|
- `listCheckpoints(options?)` → `{ checkpoints, total, branch }` (wire JSON may also include `commits` mirror)
|
|
@@ -1560,7 +1647,7 @@ interface DatabaseContext {
|
|
|
1560
1647
|
- **Deprecated:** `createTag`, `listTags`, `getTag`, `deleteTag`
|
|
1561
1648
|
|
|
1562
1649
|
### Compare
|
|
1563
|
-
- `compare(from: CompareRef, to: CompareRef, includeContent?)` → `CompareResult`
|
|
1650
|
+
- `compare(from: CompareRef, to: CompareRef, includeContent?)` or `compare(from, to, { includeContent?, matchStart? })` → `CompareResult`
|
|
1564
1651
|
- **Deprecated:** `diff(from: DiffRef, …)` — alias of `compare`
|
|
1565
1652
|
|
|
1566
1653
|
### Locks
|
package/llms.txt
CHANGED
|
@@ -22,7 +22,9 @@ 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). 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).
|
|
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). **Overlay + locks:** cooperative locks are stored in LMDB meta; overlay sessions **read through** to **base-layer** locks, but **`acquireLock` / `releaseLock`** only mutate the overlay—so a `lock_conflict` in an overlay test can still mean a lock held in production; clear with **`findLocks`** / **`forceReleaseLock`** or use a non-overlay session for lock-sensitive tests.
|
|
26
|
+
|
|
27
|
+
10. **Binary assets (v2) are not XML/JSON documents.** Files live under canonical asset identifiers (e.g. `/assets/<uuid>`) and stable **`xcitedb:asset:/…`** URIs. **`POST /api/v1/assets`** uploads raw bytes; **`GET` / `HEAD` / `DELETE /api/v1/assets/…`** use per-segment URL encoding (optional **`?ml=`** magic-link bearer). Project routing is **`GET/PUT /api/v1/project/settings/asset-storage`** (`targets`, `mounts`, `imports`). User-isolation **asset-shares** and **asset magic links** live under **`/api/v1/security/user-isolation/asset-shares`** and **`/api/v1/security/asset-magic-links`**. JS/Python/C++ SDKs expose upload/get/head/delete + URI helpers (`parseAssetUri`, `asset_uri.parse`, `asset_uri::parse`) and share/magic-link clients.
|
|
26
28
|
|
|
27
29
|
## Choosing the Right Versioning Approach
|
|
28
30
|
|
|
@@ -72,6 +74,8 @@ Legacy REST paths (`/api/v1/branches`, `/commits`, `/tags`, `/diff`) remain as *
|
|
|
72
74
|
|
|
73
75
|
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).
|
|
74
76
|
|
|
77
|
+
13. **Identifier strings (minting).** The engine rejects identifiers containing byte **`0x01`** (LMDB key separator). The API canonicalizes paths (**leading `/`**, **no trailing `/`**). Reserved namespaces include **`/_xcitedb/`**, **`/assets/`**, **`/public/assets/`**, and **`/public/shared/<tenant>/assets/`**. Server config **`identifier_hierarchy_max_depth`** defaults to **4**—deeper paths still work, but **`identifier-children`** listing may fall back to a scan.
|
|
78
|
+
|
|
75
79
|
## API key capability matrix (typical)
|
|
76
80
|
|
|
77
81
|
| Capability | API key `role` | Public key allowed? |
|
|
@@ -158,6 +162,50 @@ When you build a backend that calls XCiteDB on behalf of users:
|
|
|
158
162
|
- **Metadata `mode` and arrays.** **`POST /api/v1/meta`** uses **`mode`**: default **`set`** writes or replaces at `path` (arrays are replaced in range; excess old indices cleared); **`append`** appends array elements after existing ones at `path`. Optional **`overwrite: true`** clears metadata under `path` before writing. JavaScript: **`appendMeta`** or **`addMeta(..., { mode: 'append' })`**; Python/C++: **`append_meta`** or **`add_meta`** with **`mode`** / **`overwrite`** (see SDK sections below).
|
|
159
163
|
- **Prefer dictionary-style objects.** **Shredded** JSON metadata is stored in **fragment-level** keys (e.g. per field name). **Object maps** get per-field storage; when an object accumulates enough distinct field names (server default threshold **32**), XCiteDB switches automatically to **dictionary storage** (`{*}` plus per-field keys) for efficient indexed access. For lookup-heavy or wide records, use **`{ "key": value, ... }`** shapes (or one document per logical row) rather than opaque arrays when you need keyed reads.
|
|
160
164
|
|
|
165
|
+
## XML subtree writes (shredded model)
|
|
166
|
+
|
|
167
|
+
- **Standalone subtree root.** Because XCiteDB shreds XML per identifier, you can `writeXmlDocument('<sec db:identifier="/spaces/u/docs/doc1/sec-1">…</sec>')` and the engine writes only that subtree — you do **not** need to re-send the parent document. The parent's children index (`id_hier`) is updated automatically. Keep `is_top: true` (default) on `POST /api/v1/documents` even when the `db:identifier` is nested under another path. Use `compare_attributes: true` only when you need attribute-level diffing for triggers.
|
|
168
|
+
|
|
169
|
+
## XML shredding semantics (must read)
|
|
170
|
+
|
|
171
|
+
- **Auto-shred.** Any element with **`db:identifier`** is stored as its own LMDB row. The parent’s stored XML keeps a **`<db:N…/>`** placeholder for that slot. There is **no** size threshold and **no** opt-out.
|
|
172
|
+
- **`is_top` is a marker only.** It registers a **`TOP:<xcitepath>`** alias for tooling; it does **not** change shredding, merge, or overwrite behavior. Leaving **`true`** on every write is normal.
|
|
173
|
+
- **`POST /api/v1/documents/batch`** runs items **in order**; each item’s write replaces LMDB rows for **every** `db:identifier` present in that item’s XML. The server prechecks each row and may return **`409`** with **`duplicate_identifier_in_batch`** (same root id in two items) or **`parent_inlines_batch_member`** (a parent’s XML still contains another item’s root id—a stale inlined child). Check **`results[i].ok`** on the JSON response.
|
|
174
|
+
- **Save the smallest dirty subtree.** Do not flush a parent that still contains full XML for an identified child you also write in the same batch. Prefer placeholders from **`queryByIdentifierShallow`**, or write only the changed child documents.
|
|
175
|
+
|
|
176
|
+
**Round-trip:** Element structure, child order, and text are preserved. The server may add **`db:order`**, **`db:xcitepath`**, **`xmlns:db`**. Attribute order and insignificant whitespace are **not** byte-stable—compare by DOM, not raw bytes.
|
|
177
|
+
|
|
178
|
+
## Minimal SPA editor recipe
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
await client.enableUserIsolation(); // if app users edit under namespaced paths
|
|
182
|
+
client.setContext({ workspace: '', project_id: projectId });
|
|
183
|
+
|
|
184
|
+
/** Full document for the editor (all shredded children inlined). */
|
|
185
|
+
async function loadDocForEditor(id: string) {
|
|
186
|
+
return client.queryByIdentifierFull(id);
|
|
187
|
+
// same as: queryByIdentifier(id, 'FirstMatch,IncludeChildren')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Sidebar / tree: shallow shell + one hierarchy level (two parallel requests). */
|
|
191
|
+
async function sidebarRow(id: string) {
|
|
192
|
+
const [node, listed] = await Promise.all([
|
|
193
|
+
client.queryByIdentifierShallow(id),
|
|
194
|
+
client.listIdentifierChildren(id),
|
|
195
|
+
]);
|
|
196
|
+
return { node, children: listed.children };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function saveDirty(items: XmlDocumentBatchItem[]) {
|
|
200
|
+
const { results } = await client.writeXmlDocumentsBatch(items);
|
|
201
|
+
results.forEach((r, i) => {
|
|
202
|
+
if (!r.ok) console.warn(i, r.error, r.code);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Three save rules:** (1) Each intended root **`db:identifier`** appears as the **root** of **at most one** batch item per flush. (2) Never ship a parent’s **full** subtree for a child id that is also its **own** batch row in the same request. (3) **`is_top`** does not relax (1) or (2).
|
|
208
|
+
|
|
161
209
|
## JavaScript/TypeScript SDK (`@xcitedbs/client`)
|
|
162
210
|
|
|
163
211
|
Install: `npm install @xcitedbs/client`
|
|
@@ -191,6 +239,11 @@ await client.writeXmlDocument(
|
|
|
191
239
|
'<chapter db:identifier="/manual/v1/intro"><title>Introduction</title></chapter>'
|
|
192
240
|
);
|
|
193
241
|
|
|
242
|
+
// Import DOCX/PDF (etc.) server-side — shredded into XML (`POST /api/v1/documents/import`, multipart field `file`)
|
|
243
|
+
// const fileBlob = …; // Blob from <input type="file"> or fetch
|
|
244
|
+
// await client.importDocument(fileBlob, { identifier: '/manual/imported/spec', filename: 'spec.docx' });
|
|
245
|
+
// const out = await client.exportDocument('/manual/imported/spec', 'docx'); // bytes in `out.bytes`
|
|
246
|
+
|
|
194
247
|
// Write a JSON document
|
|
195
248
|
await client.writeJsonDocument('app.settings', { theme: 'dark', locale: 'en' });
|
|
196
249
|
|
|
@@ -273,8 +326,14 @@ interface XCiteDBClientOptions {
|
|
|
273
326
|
|
|
274
327
|
**XML Documents:**
|
|
275
328
|
- `writeXmlDocument(xml, options?)` — Store XML via JSON body (identifier inside XML). Deprecated alias: `writeDocumentJson`.
|
|
329
|
+
- `writeXmlDocumentsBatch(items)` — `POST /api/v1/documents/batch`; per-row `ok` / `error` / `code` (see shredding section for `409` batch conflicts)
|
|
276
330
|
- `writeXML(xml)` — Store raw XML with `Content-Type: application/xml`
|
|
331
|
+
- `importDocument(file, options?)` — `POST /api/v1/documents/import` (multipart `file`). Server converts DOCX, ODF, RTF, PDF, Markdown, AsciiDoc, or plain text into shredded XML. Optional `identifier` query override; optional `filename` for sniffing when not a `File`.
|
|
332
|
+
- `exportDocument(id, format?, options?)` — `GET /api/v1/documents/export` binary (`xml` | `docx` | `odt` | `pdf` | `txt` | `md` | `adoc`). Optional `strict` for text exports.
|
|
277
333
|
- `queryByIdentifier(id, flags?, filter?)` — Get document(s) by identifier
|
|
334
|
+
- `queryByIdentifierFull(id, filter?, pathFilter?)` — Same as `flags=FirstMatch,IncludeChildren` (editor load: full subtree)
|
|
335
|
+
- `queryByIdentifierShallow(id, filter?, pathFilter?)` — Same as `flags=NoChildren,KeepIndexNodes,FirstMatch` (skeleton: placeholders `db:N*` for shredded slots; avoids full subtree)
|
|
336
|
+
- `listChildIdentifiers(parentPath?)` — Alias of `listIdentifierChildren`; next level of identifier hierarchy (pair with `queryByIdentifierShallow` for sidebars—`Promise.all` both if you want one round-trip client-side)
|
|
278
337
|
- `queryDocuments(query, flags?)` — List/filter documents
|
|
279
338
|
- `deleteDocument(identifier)` — Delete by identifier
|
|
280
339
|
- `listIdentifiers(query)` — List known identifiers with pagination
|
|
@@ -299,12 +358,12 @@ interface XCiteDBClientOptions {
|
|
|
299
358
|
- `listWorkspaces()` — List workspaces
|
|
300
359
|
- `publishWorkspace(target, source, options?)` — Publish workspace changes to a target timeline
|
|
301
360
|
- `deleteWorkspace(name)` — Delete workspace
|
|
302
|
-
- `listUserWorkspaces()` / `createUserWorkspace(name, options?)` / `getUserWorkspace(id)` / `deleteUserWorkspace(id)` / `addUserWorkspaceGrant(id, body)` / `removeUserWorkspaceGrant(id, body)` / `publishUserWorkspace(id, options?)` — App-user **`_uw/…`** workspaces
|
|
361
|
+
- `listUserWorkspaces()` / `createUserWorkspace(name, options?)` / `getUserWorkspace(id)` / `deleteUserWorkspace(id)` / `addUserWorkspaceGrant(id, body)` / `removeUserWorkspaceGrant(id, body)` / `publishUserWorkspace(id, options?)` / `rebaseUserWorkspace(id, options?)` — App-user **`_uw/…`** workspaces (`rebase` advances the fork onto the parent tip; **409** + `conflicts` on overlap, like publish)
|
|
303
362
|
- `createCheckpoint(message, author?)` — Named snapshot of current state
|
|
304
363
|
- `listCheckpoints(options?)` — List checkpoints
|
|
305
364
|
- `revertToCheckpoint(checkpointId)` — Revert workspace to a prior checkpoint
|
|
306
365
|
- `applyCheckpoint(checkpointId, message?)` — Apply another checkpoint’s changes here
|
|
307
|
-
- `compare(from, to, includeContent?)` — Compare revisions
|
|
366
|
+
- `compare(from, to, includeContent?)` or `compare(from, to, { includeContent?, matchStart? })` — Compare revisions; optional **`matchStart`** restricts results to that identifier and descendants (same as document `match_start` query semantics)
|
|
308
367
|
- `createBookmark(name, checkpointId, message?)` — Bookmark a checkpoint
|
|
309
368
|
- `listBookmarks()` / `deleteBookmark(name)` — Manage bookmarks
|
|
310
369
|
- **Deprecated aliases:** `withBranch`, `createBranch`, `listBranches`, `mergeBranch`, `deleteBranch`, `createCommit`, `listCommits`, `rollbackToCommit`, `cherryPick`, `diff`, `createTag`, `listTags`, `deleteTag` (same HTTP behavior)
|
|
@@ -369,6 +428,8 @@ When calling `queryByIdentifier` or `queryDocuments`, the `flags` parameter cont
|
|
|
369
428
|
- `'NoChildren'` — Exclude children
|
|
370
429
|
- `'KeepIndexNodes'` — Include index/structural nodes
|
|
371
430
|
|
|
431
|
+
**Sidebar / AST navigation:** combine **`'NoChildren,KeepIndexNodes,FirstMatch'`** (or `queryByIdentifierShallow`) with **`listChildIdentifiers`** / **`listIdentifierChildren`** so each expansion is one shallow XML read plus one child list, instead of `IncludeChildren` loading the entire subtree.
|
|
432
|
+
|
|
372
433
|
### Errors
|
|
373
434
|
|
|
374
435
|
All API errors throw `XCiteDBError` with `.status` (HTTP code) and `.body` (parsed response). Common codes: `401` unauthenticated, `403` forbidden by policy or RBAC, `404` not found, `409` conflict (lock), `422` validation, `423` project encrypted and locked, `429` rate limited. Many **ABAC** denials return `403` with `"message":"Forbidden"` plus optional **`policy_id`** and **`hint`** (check JWT `tenant_id` vs `project:` group middle segment).
|
package/package.json
CHANGED
package/unquery-ai-guide.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Unquery is a declarative language for querying and transforming structured documents (JSON and XML). It is the query language of XCiteDB and the `unq` command-line tool. Every query is itself a JSON document. The result is also JSON.
|
|
6
6
|
|
|
7
|
+
> **Maintainers — parse regression tests:** Fenced `json` / `text` examples in this file, plus Unquery listings in [`web/src/docs/content/unquery-tutorial.html`](../web/src/docs/content/unquery-tutorial.html), feed the generated fixture [`XCiteDB2/tests/fixtures/unquery_tutorial_parse_cases.json`](../XCiteDB2/tests/fixtures/unquery_tutorial_parse_cases.json) (do not edit by hand). After changing examples here or in that HTML file, regenerate from the repo root with `python3 scripts/gen_unquery_tutorial_parse_cases.py`, then commit the updated JSON. CI / local builds run `unquery_tutorial_parse_test` (see [`XCiteDB2/tests/run_tests.sh`](../XCiteDB2/tests/run_tests.sh)).
|
|
8
|
+
|
|
7
9
|
---
|
|
8
10
|
|
|
9
11
|
## 0. AI preamble — must-read before writing a query
|