@xcitedbs/client 0.2.26 → 0.2.27
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 +32 -5
- package/dist/client.js +32 -5
- package/llms-full.txt +43 -16
- package/llms.txt +7 -5
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -443,7 +443,13 @@ export declare class XCiteDBClient {
|
|
|
443
443
|
listUserWorkspaces(): Promise<{
|
|
444
444
|
user_workspaces: Record<string, unknown>[];
|
|
445
445
|
}>;
|
|
446
|
-
/**
|
|
446
|
+
/**
|
|
447
|
+
* Create a user workspace (`POST /api/v1/user-workspaces`). Returns the workspace record (top-level JSON).
|
|
448
|
+
*
|
|
449
|
+
* `sourceBranch` defaults to the root timeline (`""`) — works on a fresh project with no other
|
|
450
|
+
* workspaces. `"main"` is normalized to `""` server-side; the persisted record stores `""`. If
|
|
451
|
+
* you pass an explicit non-existent `sourceBranch`, the server returns 400 with the offending name.
|
|
452
|
+
*/
|
|
447
453
|
createUserWorkspace(name: string, options?: {
|
|
448
454
|
sourceBranch?: string;
|
|
449
455
|
sourceDate?: string;
|
|
@@ -566,17 +572,38 @@ export declare class XCiteDBClient {
|
|
|
566
572
|
listIdentifierChildren(parentPath?: string): Promise<ListIdentifierChildrenResult>;
|
|
567
573
|
queryLog(query: XCiteQuery, fromDate: string, toDate: string): Promise<LogEntry[]>;
|
|
568
574
|
addMeta(identifier: string, value: unknown, path?: string, opts?: {
|
|
569
|
-
mode?: 'set' | 'append';
|
|
575
|
+
mode?: 'set' | 'append' | 'merge_append';
|
|
570
576
|
overwrite?: boolean;
|
|
571
577
|
}): Promise<boolean>;
|
|
572
578
|
addMetaByQuery(query: XCiteQuery, value: unknown, path?: string, firstMatch?: boolean, opts?: {
|
|
573
|
-
mode?: 'set' | 'append';
|
|
579
|
+
mode?: 'set' | 'append' | 'merge_append';
|
|
580
|
+
overwrite?: boolean;
|
|
581
|
+
}): Promise<boolean>;
|
|
582
|
+
/**
|
|
583
|
+
* Strict array-extend: `value` must be an array; the stored target at `path` must be an array
|
|
584
|
+
* (or absent). Server returns 400 `append_payload_not_array` or 409 `append_target_not_array`
|
|
585
|
+
* otherwise. To merge an object payload (recursing into nested arrays), use {@link mergeAppendMeta}.
|
|
586
|
+
* To push a single item, use {@link appendItem}.
|
|
587
|
+
*/
|
|
588
|
+
appendMeta(identifier: string, value: unknown[], path?: string, opts?: {
|
|
589
|
+
overwrite?: boolean;
|
|
590
|
+
}): Promise<boolean>;
|
|
591
|
+
appendMetaByQuery(query: XCiteQuery, value: unknown[], path?: string, firstMatch?: boolean, opts?: {
|
|
592
|
+
overwrite?: boolean;
|
|
593
|
+
}): Promise<boolean>;
|
|
594
|
+
/**
|
|
595
|
+
* Deep-extend: walks the payload and extends any nested arrays found in storage. Scalars and
|
|
596
|
+
* non-array objects in the payload are written as `set` at their respective paths. Use this
|
|
597
|
+
* when you intentionally want recursive array-extend; otherwise prefer {@link appendMeta}.
|
|
598
|
+
*/
|
|
599
|
+
mergeAppendMeta(identifier: string, value: unknown, path?: string, opts?: {
|
|
574
600
|
overwrite?: boolean;
|
|
575
601
|
}): Promise<boolean>;
|
|
576
|
-
|
|
602
|
+
mergeAppendMetaByQuery(query: XCiteQuery, value: unknown, path?: string, firstMatch?: boolean, opts?: {
|
|
577
603
|
overwrite?: boolean;
|
|
578
604
|
}): Promise<boolean>;
|
|
579
|
-
|
|
605
|
+
/** Convenience: push a single item. Wraps `item` in `[item]` and calls {@link appendMeta}. */
|
|
606
|
+
appendItem(identifier: string, item: unknown, path?: string, opts?: {
|
|
580
607
|
overwrite?: boolean;
|
|
581
608
|
}): Promise<boolean>;
|
|
582
609
|
queryMeta<T = MetaValue>(identifier: string, path?: string): Promise<T>;
|
package/dist/client.js
CHANGED
|
@@ -1462,7 +1462,13 @@ class XCiteDBClient {
|
|
|
1462
1462
|
async listUserWorkspaces() {
|
|
1463
1463
|
return this.request('GET', '/api/v1/user-workspaces');
|
|
1464
1464
|
}
|
|
1465
|
-
/**
|
|
1465
|
+
/**
|
|
1466
|
+
* Create a user workspace (`POST /api/v1/user-workspaces`). Returns the workspace record (top-level JSON).
|
|
1467
|
+
*
|
|
1468
|
+
* `sourceBranch` defaults to the root timeline (`""`) — works on a fresh project with no other
|
|
1469
|
+
* workspaces. `"main"` is normalized to `""` server-side; the persisted record stores `""`. If
|
|
1470
|
+
* you pass an explicit non-existent `sourceBranch`, the server returns 400 with the offending name.
|
|
1471
|
+
*/
|
|
1466
1472
|
async createUserWorkspace(name, options) {
|
|
1467
1473
|
const body = { name };
|
|
1468
1474
|
if (options?.sourceBranch)
|
|
@@ -1800,8 +1806,8 @@ class XCiteDBClient {
|
|
|
1800
1806
|
}
|
|
1801
1807
|
async addMeta(identifier, value, path = '', opts) {
|
|
1802
1808
|
const body = { identifier: this.isoPrefixId(identifier), value, path };
|
|
1803
|
-
if (opts?.mode === 'append')
|
|
1804
|
-
body.mode =
|
|
1809
|
+
if (opts?.mode === 'append' || opts?.mode === 'merge_append')
|
|
1810
|
+
body.mode = opts.mode;
|
|
1805
1811
|
if (opts?.overwrite)
|
|
1806
1812
|
body.overwrite = true;
|
|
1807
1813
|
const r = await this.request('POST', '/api/v1/meta', body);
|
|
@@ -1814,19 +1820,40 @@ class XCiteDBClient {
|
|
|
1814
1820
|
path,
|
|
1815
1821
|
first_match: firstMatch,
|
|
1816
1822
|
};
|
|
1817
|
-
if (opts?.mode === 'append')
|
|
1818
|
-
body.mode =
|
|
1823
|
+
if (opts?.mode === 'append' || opts?.mode === 'merge_append')
|
|
1824
|
+
body.mode = opts.mode;
|
|
1819
1825
|
if (opts?.overwrite)
|
|
1820
1826
|
body.overwrite = true;
|
|
1821
1827
|
const r = await this.request('POST', '/api/v1/meta', body);
|
|
1822
1828
|
return r?.ok !== false;
|
|
1823
1829
|
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Strict array-extend: `value` must be an array; the stored target at `path` must be an array
|
|
1832
|
+
* (or absent). Server returns 400 `append_payload_not_array` or 409 `append_target_not_array`
|
|
1833
|
+
* otherwise. To merge an object payload (recursing into nested arrays), use {@link mergeAppendMeta}.
|
|
1834
|
+
* To push a single item, use {@link appendItem}.
|
|
1835
|
+
*/
|
|
1824
1836
|
async appendMeta(identifier, value, path = '', opts) {
|
|
1825
1837
|
return this.addMeta(identifier, value, path, { mode: 'append', ...opts });
|
|
1826
1838
|
}
|
|
1827
1839
|
async appendMetaByQuery(query, value, path = '', firstMatch = false, opts) {
|
|
1828
1840
|
return this.addMetaByQuery(query, value, path, firstMatch, { mode: 'append', ...opts });
|
|
1829
1841
|
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Deep-extend: walks the payload and extends any nested arrays found in storage. Scalars and
|
|
1844
|
+
* non-array objects in the payload are written as `set` at their respective paths. Use this
|
|
1845
|
+
* when you intentionally want recursive array-extend; otherwise prefer {@link appendMeta}.
|
|
1846
|
+
*/
|
|
1847
|
+
async mergeAppendMeta(identifier, value, path = '', opts) {
|
|
1848
|
+
return this.addMeta(identifier, value, path, { mode: 'merge_append', ...opts });
|
|
1849
|
+
}
|
|
1850
|
+
async mergeAppendMetaByQuery(query, value, path = '', firstMatch = false, opts) {
|
|
1851
|
+
return this.addMetaByQuery(query, value, path, firstMatch, { mode: 'merge_append', ...opts });
|
|
1852
|
+
}
|
|
1853
|
+
/** Convenience: push a single item. Wraps `item` in `[item]` and calls {@link appendMeta}. */
|
|
1854
|
+
async appendItem(identifier, item, path = '', opts) {
|
|
1855
|
+
return this.appendMeta(identifier, [item], path, opts);
|
|
1856
|
+
}
|
|
1830
1857
|
async queryMeta(identifier, path = '') {
|
|
1831
1858
|
return this.request('GET', `/api/v1/meta${buildQuery({ identifier: this.isoPrefixId(identifier), path })}`);
|
|
1832
1859
|
}
|
package/llms-full.txt
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
Before reading the full reference, note these critical differences from typical databases:
|
|
10
10
|
|
|
11
|
-
1. **The default workspace is the
|
|
11
|
+
1. **The default workspace is the root timeline (`""`); `"main"` is its alias.** When no `X-Workspace` header is sent (or `context.workspace` / `context.branch` is omitted/empty), the server operates on the root timeline. `"main"` passed in any header, body field (`source_branch`, `from_branch`, `target_branch`, `branch`, `source_workspace`, `target_workspace`), or query param is normalized server-side to `""`. **Wrinkle to know:** the canonical stored form is `""`. If you pass `source_branch: "main"` to `createUserWorkspace`, the persisted record reads back as `source_branch: ""` — `""` is the source of truth; `"main"` is just a convenience input. `X-Branch` is a supported alias of `X-Workspace`. Attempting to **create** a branch literally named `"main"` (e.g. `POST /api/v1/workspaces {"name": "main"}`) returns 400 — the name is reserved.
|
|
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
|
|
|
@@ -225,7 +225,7 @@ For **integration and wet tests** against a shared BaaS host without touching pr
|
|
|
225
225
|
|
|
226
226
|
| Step | What to do |
|
|
227
227
|
|------|------------|
|
|
228
|
-
| **Create** | **`POST /api/v1/test/sessions`** with normal **`Authorization: Bearer …`** or **`X-API-Key`**. Response includes
|
|
228
|
+
| **Create** | **`POST /api/v1/test/sessions`** with normal **`Authorization: Bearer …`** or **`X-API-Key`**. Response includes **`session_token`** (UUID), **`tenant_id`** (the session's logical tenant id, formatted as `xcitedbtest-<session_token>` — useful for ABAC group strings like `project:<tenant_id>:editor`), `expires_at`, and `session_ttl_seconds`. Server enforces per-credential limits (`test.max_sessions_per_key`, `test.session_ttl_seconds`, `test.max_test_db_size_bytes` in server config). Optional JSON body **`{"overlay":true}`** provisions a **read-through production** session (writable delta only under `_test/<uuid>/`; production LMDB is read-only base). |
|
|
229
229
|
| **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. |
|
|
230
230
|
| **Auth** | **Default:** developer auth (API key / platform JWT) is **bypassed** with a synthetic admin identity. However, **app-user identity is still recognized**: if `X-App-User-Token` or a Bearer app-user JWT is present, the request runs as that app user (for routes like `/app/auth/me`). **`X-Test-Auth: required`:** all auth is validated normally; ABAC applies, but data still comes from the test session DB. |
|
|
231
231
|
| **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. **SDKs:** JS `listTestSessions` / `destroyAllTestSessions` / `destroyTestSessionByToken`; Python `list_test_sessions` / `destroy_all_test_sessions` / `destroy_test_session_by_token`; C++ same snake_case; MCP tools `list_test_sessions`, `destroy_all_test_sessions`, `destroy_test_session_by_token`. |
|
|
@@ -606,8 +606,8 @@ Attach structured **JSON metadata** to documents or nodes.
|
|
|
606
606
|
| `identifier` **or** `query` | one required | Target document id or document query |
|
|
607
607
|
| `value` | yes | JSON to write (omit only for string-specific query batch paths handled by the server) |
|
|
608
608
|
| `path` | no (default `""`) | Meta path (dot-separated keys; `[i]` for array indices) |
|
|
609
|
-
| `mode` | no (default `"set"`) | `"set"` — write/replace at `path`. For **arrays** at `path`, indices `0..n-1` are written and any previous tail beyond the new length is cleared. `"append"` — array-extend
|
|
610
|
-
| `overwrite` | no (default `false`) | When `true`, delete existing metadata under `path` **before** applying `value`. That runs before array logic: with `mode: "append"` it clears the stored `[n]` marker, so the subsequent write always starts at index `0` (you do not keep prior elements). To extend an existing array, use **`overwrite: false`**. For “clear then replace the whole array”, use **`overwrite: true`** with **`mode: "set"
|
|
609
|
+
| `mode` | no (default `"set"`) | `"set"` — write/replace at `path`. For **arrays** at `path`, indices `0..n-1` are written and any previous tail beyond the new length is cleared. `"append"` — **strict** array-extend: `value` **must** be a JSON array, and the stored value at `path` must be an array (or absent). The new elements are written after the stored length. `"merge_append"` — **deep** array-extend: walks the payload and extends any nested arrays in storage; scalars and non-array objects in the payload are written as `set` at their own paths. Use `merge_append` only when you intentionally want recursion. See **Append semantics** below. |
|
|
610
|
+
| `overwrite` | no (default `false`) | When `true`, delete existing metadata under `path` **before** applying `value`. That runs before array logic: with `mode: "append"` or `"merge_append"` it clears the stored `[n]` marker, so the subsequent write always starts at index `0` (you do not keep prior elements). To extend an existing array, use **`overwrite: false`**. For “clear then replace the whole array”, use **`overwrite: true`** with **`mode: "set"`**. |
|
|
611
611
|
| `first_match` | no | With `query`, only the first matching identifier is updated when `true`. |
|
|
612
612
|
|
|
613
613
|
```json
|
|
@@ -622,23 +622,47 @@ Attach structured **JSON metadata** to documents or nodes.
|
|
|
622
622
|
|
|
623
623
|
### Append semantics
|
|
624
624
|
|
|
625
|
-
- **`mode: "append"
|
|
626
|
-
-
|
|
627
|
-
- **`overwrite: true` +
|
|
625
|
+
- **`mode: "append"` (strict).** The `value` **must** be a JSON array. Non-array payloads (objects, scalars, strings) are rejected with **`400 append_payload_not_array`**. The stored value at `path` must be an array (or absent). If a non-array value is already stored at `path`, the request is rejected with **`409 append_target_not_array`** to avoid silently overwriting it. Empty array `[]` is a no-op success. Missing path is OK — a fresh array is created.
|
|
626
|
+
- **`mode: "merge_append"` (deep).** Walks the payload as if it were a tree of writes. Object payloads recurse into their fields; at each leaf where the stored value is an array and the payload is an array, elements are extended after the stored length. Scalars and non-array objects at leaves are written as `set` at their own paths. Use this only when you intentionally want a single request to extend several nested arrays at once. There is **no** strict precheck — the recursive semantics is the contract.
|
|
627
|
+
- **`overwrite: true` + append modes:** overwrite removes everything under `path` first, so append always indexes from **`0`**. Prefer **`overwrite: false`** + **`append`** to grow a list; use **`overwrite: true`** + **`set`** when replacing the whole subtree/array.
|
|
628
628
|
|
|
629
|
-
**Example — extend `tags
|
|
629
|
+
**Example — strict append: extend `tags`** (stored `tags` is `["a","b"]`; result `["a","b","c","d"]`):
|
|
630
630
|
|
|
631
631
|
```json
|
|
632
632
|
{
|
|
633
633
|
"identifier": "/book/ch1",
|
|
634
634
|
"path": "tags",
|
|
635
635
|
"mode": "append",
|
|
636
|
-
"overwrite": false,
|
|
637
636
|
"value": ["c", "d"]
|
|
638
637
|
}
|
|
639
638
|
```
|
|
640
639
|
|
|
641
|
-
**Example —
|
|
640
|
+
**Example — push a single item.** Strict append requires an array; wrap a single item:
|
|
641
|
+
|
|
642
|
+
```json
|
|
643
|
+
{
|
|
644
|
+
"identifier": "/book/ch1",
|
|
645
|
+
"path": "comments",
|
|
646
|
+
"mode": "append",
|
|
647
|
+
"value": [{"author": "alice", "text": "lgtm"}]
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
The JS SDK exposes `appendItem(id, item, path)` as sugar for this; Python has `append_item`; C++ has `append_item`.
|
|
652
|
+
|
|
653
|
+
**Example — merge_append: extend several arrays in one shot** (stored `tags` is `["a"]`, `reviewers` is `["alice"]`):
|
|
654
|
+
|
|
655
|
+
```json
|
|
656
|
+
{
|
|
657
|
+
"identifier": "/book/ch1",
|
|
658
|
+
"mode": "merge_append",
|
|
659
|
+
"value": {"tags": ["b","c"], "reviewers": ["bob"]}
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
Result: `tags` becomes `["a","b","c"]`; `reviewers` becomes `["alice","bob"]`.
|
|
664
|
+
|
|
665
|
+
**Example — overwrite + append** (clears `tags` first; result only `["c","d"]`):
|
|
642
666
|
|
|
643
667
|
```json
|
|
644
668
|
{
|
|
@@ -650,7 +674,7 @@ Attach structured **JSON metadata** to documents or nodes.
|
|
|
650
674
|
}
|
|
651
675
|
```
|
|
652
676
|
|
|
653
|
-
Can also use `"query"` instead of `"identifier"` to target multiple documents by query filter.
|
|
677
|
+
Can also use `"query"` instead of `"identifier"` to target multiple documents by query filter. With strict `mode: "append"`, the target precheck runs per matched identifier; on the first one whose stored value at `path` is non-array, the response is `409 append_target_not_array` with `detail.identifier` naming the offender.
|
|
654
678
|
|
|
655
679
|
## Query metadata (GET)
|
|
656
680
|
|
|
@@ -863,7 +887,7 @@ Dry-run: **`POST /api/v1/security/check`** with `subject`, `identifier`, `action
|
|
|
863
887
|
**Preferred base path:** `/api/v1/workspaces`
|
|
864
888
|
**Deprecated alias:** `/api/v1/branches` (same handlers)
|
|
865
889
|
|
|
866
|
-
**IMPORTANT: The default workspace is the empty string `""`.** When no workspace is specified, the server uses the root timeline.
|
|
890
|
+
**IMPORTANT: The default workspace is the empty string `""`.** When no workspace is specified, the server uses the root timeline. `"main"` is normalized to `""` server-side wherever a workspace name appears (headers, body fields, query params); the canonical stored form is always `""`. Attempting to create a branch literally named `"main"` returns 400 (reserved name).
|
|
867
891
|
|
|
868
892
|
## List workspaces
|
|
869
893
|
|
|
@@ -1082,7 +1106,7 @@ Definitions are stored as JSON under **`/_xcitedb/triggers`**. After a matching
|
|
|
1082
1106
|
| `event` | Yes | `meta_changed`, `document_written`, or `document_deleted`. |
|
|
1083
1107
|
| `match` | Yes | Must include **`identifiers`**: non-empty array of identifier patterns (`exact`, `match_start`, `match_end`, `contains`, `regex`). Optional **`match_meta_path`**: exact path or prefix ending with `*`. Optional **`match_operation`**: `set`, `append`, or `delete` (meta / delete ops). |
|
|
1084
1108
|
| `conditions` | No | Optional **`branches`** (same as policies) and **`expression`** (see below). |
|
|
1085
|
-
| `action` | Yes | **`query`** (`XCiteQuery`), **`unquery`** (Unquery DSL template), **`target_identifier`** (literal or `"$trigger_identifier"`), **`meta_path`**, optional **`mode`**: `set` (default) or `
|
|
1109
|
+
| `action` | Yes | **`query`** (`XCiteQuery`), **`unquery`** (Unquery DSL template), **`target_identifier`** (literal or `"$trigger_identifier"`), **`meta_path`**, optional **`mode`**: `set` (default), `append` (strict — unquery output must be a JSON array, stored target must be an array; trigger logs and skips on misuse), or `merge_append` (deep — recurses into objects to extend nested arrays). |
|
|
1086
1110
|
|
|
1087
1111
|
### Expression context for **triggers** (`conditions.expression`)
|
|
1088
1112
|
|
|
@@ -1608,10 +1632,13 @@ interface DatabaseContext {
|
|
|
1608
1632
|
- `put(identifier, data, opts?)` / `get<T>(identifier)` / `remove(identifier)` / `list(match?, limit?, offset?)` — JSON CRUD aliases (`opts` same as `writeJsonDocument`)
|
|
1609
1633
|
|
|
1610
1634
|
### Metadata
|
|
1611
|
-
- `addMeta(identifier, value, path?, opts?)` → `boolean` — `opts`: `mode?: 'set'|'append'`, `overwrite?: boolean`
|
|
1635
|
+
- `addMeta(identifier, value, path?, opts?)` → `boolean` — `opts`: `mode?: 'set'|'append'|'merge_append'`, `overwrite?: boolean`
|
|
1612
1636
|
- `addMetaByQuery(query, value, path?, firstMatch?, opts?)` → `boolean`
|
|
1613
|
-
- `appendMeta(identifier, value, path?, opts?)` → `boolean` —
|
|
1614
|
-
- `appendMetaByQuery(query, value, path?, firstMatch?, opts?)` → `boolean`
|
|
1637
|
+
- `appendMeta(identifier, value: unknown[], path?, opts?)` → `boolean` — **strict** array-extend; `value` typed as array. Server returns `400 append_payload_not_array` / `409 append_target_not_array` on misuse.
|
|
1638
|
+
- `appendMetaByQuery(query, value: unknown[], path?, firstMatch?, opts?)` → `boolean` — strict.
|
|
1639
|
+
- `mergeAppendMeta(identifier, value, path?, opts?)` → `boolean` — **deep** array-extend (recurses into objects, extends nested arrays). Use only when you intentionally want recursion.
|
|
1640
|
+
- `mergeAppendMetaByQuery(query, value, path?, firstMatch?, opts?)` → `boolean`
|
|
1641
|
+
- `appendItem(identifier, item, path?, opts?)` → `boolean` — sugar for `appendMeta(id, [item], path, opts)`.
|
|
1615
1642
|
- `queryMeta<T>(identifier, path?)` → `T`
|
|
1616
1643
|
- `queryMetaByQuery<T>(query, path?)` → `T`
|
|
1617
1644
|
- `clearMeta(query)` → `boolean`
|
package/llms.txt
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
These are the most common sources of confusion for developers and AI assistants:
|
|
8
8
|
|
|
9
|
-
1. **The default workspace is the
|
|
9
|
+
1. **The default workspace is the root timeline (`""`); `"main"` is its alias.** 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. `"main"` passed in any header, body field (`source_branch`, `from_branch`, `target_branch`, `branch`, `source_workspace`, `target_workspace`), or query param is normalized server-side to `""`. **Wrinkle to know:** the canonical stored form is `""`. If you pass `source_branch: "main"` to `createUserWorkspace`, the persisted record reads back as `source_branch: ""` — `""` is the source of truth; `"main"` is just a convenience input. `X-Branch` is a supported alias of `X-Workspace`. Attempting to **create** a branch literally named `"main"` (e.g. `POST /api/v1/workspaces {"name": "main"}`) returns 400 — the name is reserved.
|
|
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
|
|
|
@@ -159,7 +159,7 @@ When you build a backend that calls XCiteDB on behalf of users:
|
|
|
159
159
|
## JSON documents and metadata (merge, overwrite, efficiency)
|
|
160
160
|
|
|
161
161
|
- **JSON documents merge by default.** `POST /api/v1/json-documents` merges the posted `data` into the existing document (object fields combined per XCiteDB meta merge rules). Send **`overwrite: true`** to clear all stored JSON under that document root first, then write only the new payload. SDKs accept the same flag (e.g. `writeJsonDocument(id, data, { overwrite: true })` in JavaScript).
|
|
162
|
-
- **Metadata `mode` and arrays.** **`POST /api/v1/meta`** uses **`mode`**: default **`set`** writes or replaces at `path`
|
|
162
|
+
- **Metadata `mode` and arrays.** **`POST /api/v1/meta`** uses **`mode`**: default **`set`** writes or replaces at `path`; **`append`** is **strict** array-extend — `value` must be a JSON array AND the stored target at `path` must already be an array (or absent). Otherwise the server returns **`400 append_payload_not_array`** or **`409 append_target_not_array`**. **`merge_append`** is the legacy "deep" behavior — recurses into object payloads and extends nested arrays in storage; use only when you intentionally want recursion. Optional **`overwrite: true`** clears metadata under `path` before writing. JavaScript: **`appendMeta(id, [items], path)`** (strict, types `value` as array), **`mergeAppendMeta(id, value, path)`** (deep), **`appendItem(id, item, path)`** (sugar for a single item). Python/C++: **`append_meta` / `merge_append_meta` / `append_item`**.
|
|
163
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.
|
|
164
164
|
|
|
165
165
|
## XML subtree writes (shredded model)
|
|
@@ -349,8 +349,10 @@ interface XCiteDBClientOptions {
|
|
|
349
349
|
- **Quick JSON aliases:** `put`, `get`, `remove`, `list` — same as the four methods above
|
|
350
350
|
|
|
351
351
|
**Metadata (JSON on XML):**
|
|
352
|
-
- `addMeta(identifier, value, path?, opts?)` — Set or append metadata (`opts?.mode`: `set`|`append`; `opts?.overwrite`)
|
|
353
|
-
- `appendMeta(identifier, value, path?, opts?)` —
|
|
352
|
+
- `addMeta(identifier, value, path?, opts?)` — Set or append metadata (`opts?.mode`: `set`|`append`|`merge_append`; `opts?.overwrite`)
|
|
353
|
+
- `appendMeta(identifier, value: unknown[], path?, opts?)` — **Strict** array-extend at `path`; `value` typed as array. Server returns `400 append_payload_not_array` / `409 append_target_not_array` on misuse.
|
|
354
|
+
- `mergeAppendMeta(identifier, value, path?, opts?)` — **Deep** array-extend (recurses into objects, extends nested arrays). Use only when you intentionally want recursion.
|
|
355
|
+
- `appendItem(identifier, item, path?, opts?)` — Convenience: wraps `item` in `[item]` and calls `appendMeta`.
|
|
354
356
|
- `queryMeta(identifier, path?)` — Read metadata
|
|
355
357
|
- `clearMeta(query)` — Remove metadata
|
|
356
358
|
|
|
@@ -402,7 +404,7 @@ interface XCiteDBClientOptions {
|
|
|
402
404
|
|
|
403
405
|
- **Events:** `meta_changed`, `document_written`, `document_deleted`.
|
|
404
406
|
- **`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`.
|
|
405
|
-
- **`action`:** **`query`** (document query), **`unquery`** (Unquery template), **`target_identifier`** (or `"$trigger_identifier"`), **`meta_path`**, **`mode`:** `set` | `append
|
|
407
|
+
- **`action`:** **`query`** (document query), **`unquery`** (Unquery template), **`target_identifier`** (or `"$trigger_identifier"`), **`meta_path`**, **`mode`:** `set` | `append` (strict — unquery output must be a JSON array) | `merge_append` (deep — recurses into objects to extend nested arrays).
|
|
406
408
|
- **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`).
|
|
407
409
|
|
|
408
410
|
### Advanced: Unquery DSL (`unquery(query, unqueryDoc)`)
|