@xcitedbs/client 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AccessCheckResult, AppAuthConfig, AppEmailConfig, AppEmailTemplates, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BranchInfo, CommitRecord, DatabaseContext, DiffRef, DiffResult, Flags, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, LogEntry, MergeResult, MetaValue, PlatformRegisterResult, PolicySubjectInput, UnqueryResult, PolicyUpdateResponse, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredTriggerResponse, TriggerDefinition, StoredPolicyResponse, SubscriptionOptions, TagRecord, TextSearchQuery, TextSearchResult, OAuthProvidersResponse, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspacesResponse, TokenPair, UserInfo, ApiKeyInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, XCiteQuery } from './types';
1
+ import { AccessCheckResult, AppAuthConfig, AppEmailConfig, AppEmailTemplates, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BranchInfo, CommitRecord, DatabaseContext, DiffRef, DiffResult, Flags, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, LogEntry, MergeResult, MetaValue, PlatformRegisterResult, PolicySubjectInput, UnqueryResult, UnqueryTemplate, PolicyUpdateResponse, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredTriggerResponse, TriggerDefinition, StoredPolicyResponse, SubscriptionOptions, TagRecord, TextSearchQuery, TextSearchResult, OAuthProvidersResponse, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspacesResponse, TokenPair, UserInfo, ApiKeyInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, XCiteQuery } from './types';
2
2
  import { WebSocketSubscription } from './websocket';
3
3
  export declare class XCiteDBClient {
4
4
  private baseUrl;
@@ -129,17 +129,131 @@ export declare class XCiteDBClient {
129
129
  deleteAppUser(userId: string): Promise<void>;
130
130
  updateAppUserGroups(userId: string, groups: string[]): Promise<void>;
131
131
  updateAppUserStatus(userId: string, status: 'active' | 'disabled' | 'pending_verification'): Promise<void>;
132
+ /**
133
+ * Create an ABAC policy (`POST /api/v1/security/policies`). Requires admin/editor role.
134
+ * Use `conditions.expression` for attribute-based rules (same predicate syntax as Unquery `?` conditions).
135
+ * Policy actions: `read`, `write`, `delete`, `list`, `meta:read`, `meta:write`, `unquery`.
136
+ *
137
+ * @example Tenant isolation: first path segment must equal app-user attribute `tenant_code`.
138
+ * ```ts
139
+ * await client.createPolicy('tenant_isolation', {
140
+ * effect: 'allow',
141
+ * priority: 100,
142
+ * subjects: { type: 'app_user' },
143
+ * actions: ['read'],
144
+ * resources: { identifiers: [{ match_start: '/' }] },
145
+ * conditions: { expression: 'resource.path[0] = subject.attr.tenant_code' },
146
+ * });
147
+ * ```
148
+ *
149
+ * @example Deny viewers write access under `/admin/`.
150
+ * ```ts
151
+ * await client.createPolicy('deny_viewers_admin', {
152
+ * effect: 'deny',
153
+ * priority: 10,
154
+ * subjects: { type: 'app_user', groups: ['viewers'] },
155
+ * resources: { identifiers: [{ match_start: '/admin/' }] },
156
+ * actions: ['write', 'delete', 'meta:write'],
157
+ * });
158
+ * ```
159
+ *
160
+ * @example Branch-only rule via `conditions.branches`.
161
+ * ```ts
162
+ * await client.createPolicy('staging_only', {
163
+ * effect: 'allow',
164
+ * priority: 50,
165
+ * subjects: { type: 'app_user', groups: ['qa'] },
166
+ * actions: ['read', 'write'],
167
+ * resources: { identifiers: [{ match_start: '/staging/' }] },
168
+ * conditions: { branches: ['stg', 'stg*'] },
169
+ * });
170
+ * ```
171
+ */
132
172
  createPolicy(policyId: string, policy: SecurityPolicy): Promise<StoredPolicyResponse>;
133
173
  listPolicies(): Promise<Record<string, SecurityPolicy>>;
134
174
  getPolicy(policyId: string): Promise<StoredPolicyResponse>;
135
175
  updatePolicy(policyId: string, policy: SecurityPolicy): Promise<PolicyUpdateResponse>;
136
176
  deletePolicy(policyId: string): Promise<void>;
177
+ /**
178
+ * Create or update a trigger (`POST /api/v1/triggers`). Definitions live under `/_xcitedb/triggers`.
179
+ * After matching events, the server runs `action.unquery` over `action.query` and writes JSON to
180
+ * `action.meta_path` on `action.target_identifier` (use `"$trigger_identifier"` for the firing doc).
181
+ * Unquery can use `$trigger_identifier`, `$trigger_meta_path`, `$trigger_operation`, `$trigger_value`.
182
+ *
183
+ * @example On meta change under `/projects/`, append a snapshot entry to an index document.
184
+ * ```ts
185
+ * await client.upsertTrigger('project_meta_index', {
186
+ * event: 'meta_changed',
187
+ * match: {
188
+ * identifiers: [{ match_start: '/projects/' }],
189
+ * match_meta_path: 'status',
190
+ * },
191
+ * action: {
192
+ * query: { match: '$trigger_identifier' },
193
+ * unquery: { lastId: '$trigger_identifier', path: '$trigger_meta_path' },
194
+ * target_identifier: '/indexes/project_meta',
195
+ * meta_path: 'entries',
196
+ * mode: 'append',
197
+ * },
198
+ * });
199
+ * ```
200
+ *
201
+ * @example On document write, write a computed summary onto the same identifier's meta.
202
+ * ```ts
203
+ * await client.upsertTrigger('doc_written_summary', {
204
+ * event: 'document_written',
205
+ * match: { identifiers: [{ match_start: '/articles/' }] },
206
+ * action: {
207
+ * query: { match: '$trigger_identifier' },
208
+ * unquery: { id: '$identifier', nodeCount: '$count' },
209
+ * target_identifier: '$trigger_identifier',
210
+ * meta_path: 'stats',
211
+ * },
212
+ * });
213
+ * ```
214
+ */
137
215
  upsertTrigger(triggerId: string, trigger: TriggerDefinition): Promise<StoredTriggerResponse>;
138
216
  listTriggers(): Promise<Record<string, TriggerDefinition>>;
139
217
  getTrigger(name: string): Promise<StoredTriggerResponse>;
140
218
  deleteTrigger(name: string): Promise<void>;
219
+ /**
220
+ * Dry-run access check (`POST /api/v1/security/check`). Returns `effect` and optional `matched_policy_id`.
221
+ * Useful for debugging policies. Actions: `read`, `write`, `delete`, `list`, `meta:read`, `meta:write`, `unquery`.
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * const r = await client.checkAccess(
226
+ * {
227
+ * type: 'app_user',
228
+ * user_id: 'u1',
229
+ * email: 'alice@example.com',
230
+ * groups: ['editors'],
231
+ * role: 'app_user',
232
+ * attributes: { tenant_code: 'acme', level: 5 },
233
+ * },
234
+ * '/acme/reports/q1',
235
+ * 'read',
236
+ * undefined,
237
+ * 'main'
238
+ * );
239
+ * console.log(r.effect, r.matched_policy_id);
240
+ * ```
241
+ */
141
242
  checkAccess(subject: PolicySubjectInput, identifier: string, action: string, metaPath?: string, branch?: string): Promise<AccessCheckResult>;
142
243
  getSecurityConfig(): Promise<SecurityConfig>;
244
+ /**
245
+ * Update tenant security defaults (`PUT /api/v1/security/config`). When ABAC is enabled, pair
246
+ * `app_user_default_effect: 'deny'` with explicit allow policies for least privilege.
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * await client.updateSecurityConfig({
251
+ * app_user_default_effect: 'deny',
252
+ * default_effect: 'allow',
253
+ * developer_bypass: true,
254
+ * });
255
+ * ```
256
+ */
143
257
  updateSecurityConfig(config: Partial<SecurityConfig>): Promise<void>;
144
258
  createBranch(name: string, fromBranch?: string, fromDate?: string): Promise<void>;
145
259
  deleteBranch(name: string): Promise<void>;
@@ -230,7 +344,32 @@ export declare class XCiteDBClient {
230
344
  acquireLock(identifier: string, expires?: number): Promise<LockInfo>;
231
345
  releaseLock(identifier: string, lockId: string): Promise<boolean>;
232
346
  findLocks(identifier: string): Promise<LockInfo[]>;
233
- unquery<T = UnqueryResult>(query: XCiteQuery, unquery: unknown): Promise<T>;
347
+ /**
348
+ * Run Unquery (`POST /api/v1/unquery`): declarative analytics over documents matching `query`.
349
+ * The `unquery` argument is a JSON template; keys are output fields, string values are expressions.
350
+ *
351
+ * @example Project a meta field into a result key.
352
+ * ```ts
353
+ * const out = await client.unquery<{ tag: string }>(
354
+ * { match_start: '/manual/' },
355
+ * { title: 'chapter_title' }
356
+ * );
357
+ * ```
358
+ *
359
+ * @example Count matching documents.
360
+ * ```ts
361
+ * const out = await client.unquery<{ total: number }>({ match_start: '/orders/' }, { total: '$count' });
362
+ * ```
363
+ *
364
+ * @example XPath from XML (string value must escape quotes for TS).
365
+ * ```ts
366
+ * const out = await client.unquery<{ heading: string }>(
367
+ * { match: '/book/ch1' },
368
+ * { heading: '$xpath("//h1")' }
369
+ * );
370
+ * ```
371
+ */
372
+ unquery<T = UnqueryResult>(query: XCiteQuery, unquery: UnqueryTemplate): Promise<T>;
234
373
  search(q: TextSearchQuery): Promise<TextSearchResult>;
235
374
  reindex(): Promise<{
236
375
  status: string;
package/dist/client.js CHANGED
@@ -502,6 +502,46 @@ class XCiteDBClient {
502
502
  await this.request('PUT', `/api/v1/app/users/${encodeURIComponent(userId)}/status`, { status });
503
503
  }
504
504
  // --- Security policies (developer admin/editor) ---
505
+ /**
506
+ * Create an ABAC policy (`POST /api/v1/security/policies`). Requires admin/editor role.
507
+ * Use `conditions.expression` for attribute-based rules (same predicate syntax as Unquery `?` conditions).
508
+ * Policy actions: `read`, `write`, `delete`, `list`, `meta:read`, `meta:write`, `unquery`.
509
+ *
510
+ * @example Tenant isolation: first path segment must equal app-user attribute `tenant_code`.
511
+ * ```ts
512
+ * await client.createPolicy('tenant_isolation', {
513
+ * effect: 'allow',
514
+ * priority: 100,
515
+ * subjects: { type: 'app_user' },
516
+ * actions: ['read'],
517
+ * resources: { identifiers: [{ match_start: '/' }] },
518
+ * conditions: { expression: 'resource.path[0] = subject.attr.tenant_code' },
519
+ * });
520
+ * ```
521
+ *
522
+ * @example Deny viewers write access under `/admin/`.
523
+ * ```ts
524
+ * await client.createPolicy('deny_viewers_admin', {
525
+ * effect: 'deny',
526
+ * priority: 10,
527
+ * subjects: { type: 'app_user', groups: ['viewers'] },
528
+ * resources: { identifiers: [{ match_start: '/admin/' }] },
529
+ * actions: ['write', 'delete', 'meta:write'],
530
+ * });
531
+ * ```
532
+ *
533
+ * @example Branch-only rule via `conditions.branches`.
534
+ * ```ts
535
+ * await client.createPolicy('staging_only', {
536
+ * effect: 'allow',
537
+ * priority: 50,
538
+ * subjects: { type: 'app_user', groups: ['qa'] },
539
+ * actions: ['read', 'write'],
540
+ * resources: { identifiers: [{ match_start: '/staging/' }] },
541
+ * conditions: { branches: ['stg', 'stg*'] },
542
+ * });
543
+ * ```
544
+ */
505
545
  async createPolicy(policyId, policy) {
506
546
  return this.request('POST', '/api/v1/security/policies', {
507
547
  policy_id: policyId,
@@ -524,6 +564,44 @@ class XCiteDBClient {
524
564
  await this.request('DELETE', `/api/v1/security/policies/${encodeURIComponent(policyId)}`);
525
565
  }
526
566
  // --- Triggers (developer admin/editor) ---
567
+ /**
568
+ * Create or update a trigger (`POST /api/v1/triggers`). Definitions live under `/_xcitedb/triggers`.
569
+ * After matching events, the server runs `action.unquery` over `action.query` and writes JSON to
570
+ * `action.meta_path` on `action.target_identifier` (use `"$trigger_identifier"` for the firing doc).
571
+ * Unquery can use `$trigger_identifier`, `$trigger_meta_path`, `$trigger_operation`, `$trigger_value`.
572
+ *
573
+ * @example On meta change under `/projects/`, append a snapshot entry to an index document.
574
+ * ```ts
575
+ * await client.upsertTrigger('project_meta_index', {
576
+ * event: 'meta_changed',
577
+ * match: {
578
+ * identifiers: [{ match_start: '/projects/' }],
579
+ * match_meta_path: 'status',
580
+ * },
581
+ * action: {
582
+ * query: { match: '$trigger_identifier' },
583
+ * unquery: { lastId: '$trigger_identifier', path: '$trigger_meta_path' },
584
+ * target_identifier: '/indexes/project_meta',
585
+ * meta_path: 'entries',
586
+ * mode: 'append',
587
+ * },
588
+ * });
589
+ * ```
590
+ *
591
+ * @example On document write, write a computed summary onto the same identifier's meta.
592
+ * ```ts
593
+ * await client.upsertTrigger('doc_written_summary', {
594
+ * event: 'document_written',
595
+ * match: { identifiers: [{ match_start: '/articles/' }] },
596
+ * action: {
597
+ * query: { match: '$trigger_identifier' },
598
+ * unquery: { id: '$identifier', nodeCount: '$count' },
599
+ * target_identifier: '$trigger_identifier',
600
+ * meta_path: 'stats',
601
+ * },
602
+ * });
603
+ * ```
604
+ */
527
605
  async upsertTrigger(triggerId, trigger) {
528
606
  return this.request('POST', '/api/v1/triggers', {
529
607
  trigger_id: triggerId,
@@ -544,6 +622,29 @@ class XCiteDBClient {
544
622
  const q = buildQuery({ name });
545
623
  await this.request('DELETE', `/api/v1/triggers${q}`);
546
624
  }
625
+ /**
626
+ * Dry-run access check (`POST /api/v1/security/check`). Returns `effect` and optional `matched_policy_id`.
627
+ * Useful for debugging policies. Actions: `read`, `write`, `delete`, `list`, `meta:read`, `meta:write`, `unquery`.
628
+ *
629
+ * @example
630
+ * ```ts
631
+ * const r = await client.checkAccess(
632
+ * {
633
+ * type: 'app_user',
634
+ * user_id: 'u1',
635
+ * email: 'alice@example.com',
636
+ * groups: ['editors'],
637
+ * role: 'app_user',
638
+ * attributes: { tenant_code: 'acme', level: 5 },
639
+ * },
640
+ * '/acme/reports/q1',
641
+ * 'read',
642
+ * undefined,
643
+ * 'main'
644
+ * );
645
+ * console.log(r.effect, r.matched_policy_id);
646
+ * ```
647
+ */
547
648
  async checkAccess(subject, identifier, action, metaPath, branch) {
548
649
  const body = { subject, identifier, action };
549
650
  if (metaPath !== undefined)
@@ -555,6 +656,19 @@ class XCiteDBClient {
555
656
  async getSecurityConfig() {
556
657
  return this.request('GET', '/api/v1/security/config');
557
658
  }
659
+ /**
660
+ * Update tenant security defaults (`PUT /api/v1/security/config`). When ABAC is enabled, pair
661
+ * `app_user_default_effect: 'deny'` with explicit allow policies for least privilege.
662
+ *
663
+ * @example
664
+ * ```ts
665
+ * await client.updateSecurityConfig({
666
+ * app_user_default_effect: 'deny',
667
+ * default_effect: 'allow',
668
+ * developer_bypass: true,
669
+ * });
670
+ * ```
671
+ */
558
672
  async updateSecurityConfig(config) {
559
673
  await this.request('PUT', '/api/v1/security/config', config);
560
674
  }
@@ -887,6 +1001,31 @@ class XCiteDBClient {
887
1001
  async findLocks(identifier) {
888
1002
  return this.request('GET', `/api/v1/locks${buildQuery({ identifier })}`);
889
1003
  }
1004
+ /**
1005
+ * Run Unquery (`POST /api/v1/unquery`): declarative analytics over documents matching `query`.
1006
+ * The `unquery` argument is a JSON template; keys are output fields, string values are expressions.
1007
+ *
1008
+ * @example Project a meta field into a result key.
1009
+ * ```ts
1010
+ * const out = await client.unquery<{ tag: string }>(
1011
+ * { match_start: '/manual/' },
1012
+ * { title: 'chapter_title' }
1013
+ * );
1014
+ * ```
1015
+ *
1016
+ * @example Count matching documents.
1017
+ * ```ts
1018
+ * const out = await client.unquery<{ total: number }>({ match_start: '/orders/' }, { total: '$count' });
1019
+ * ```
1020
+ *
1021
+ * @example XPath from XML (string value must escape quotes for TS).
1022
+ * ```ts
1023
+ * const out = await client.unquery<{ heading: string }>(
1024
+ * { match: '/book/ch1' },
1025
+ * { heading: '$xpath("//h1")' }
1026
+ * );
1027
+ * ```
1028
+ */
890
1029
  async unquery(query, unquery) {
891
1030
  return this.request('POST', '/api/v1/unquery', { query, unquery });
892
1031
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { XCiteDBClient } from './client';
2
2
  export { WebSocketSubscription } from './websocket';
3
- export type { AccessCheckResult, ApiKeyInfo, AppAuthConfig, AppEmailConfig, AppEmailSmtpConfig, AppEmailTemplateEntry, AppEmailTemplates, AppEmailWebhookConfig, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, DatabaseContext, Flags, JsonDocumentData, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TokenPair, UserInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, UnqueryResult, XCiteQuery, } from './types';
3
+ export type { AccessCheckResult, ApiKeyInfo, AppAuthConfig, AppEmailConfig, AppEmailSmtpConfig, AppEmailTemplateEntry, AppEmailTemplates, AppEmailWebhookConfig, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, DatabaseContext, Flags, JsonDocumentData, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RealtimeEvent, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TokenPair, UserInfo, WriteDocumentOptions, CreateTestSessionOptions, XCiteDBClientOptions, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
4
4
  export { XCiteDBError } from './types';
package/dist/types.d.ts CHANGED
@@ -20,6 +20,13 @@ export type JsonDocumentData = Record<string, unknown>;
20
20
  export type MetaValue = unknown;
21
21
  /** Result of `unquery` (shape depends on the unquery definition). */
22
22
  export type UnqueryResult = unknown;
23
+ /**
24
+ * Unquery DSL template document. Object keys become output field names; string values are
25
+ * expressions (e.g. `"$count"`, `"$xpath(\"//title\")"`, or a meta field name). Supports nested
26
+ * objects, arrays, and `$`-prefixed builtins. See `llms-full.txt` shipped with this package for
27
+ * the full DSL reference.
28
+ */
29
+ export type UnqueryTemplate = Record<string, unknown>;
23
30
  /** Typical `POST /api/v1/platform/auth/register` response (fields vary by registration policy). */
24
31
  export interface PlatformRegisterResult {
25
32
  user_id?: string;
@@ -340,6 +347,12 @@ export interface PolicyResources {
340
347
  }
341
348
  export interface PolicyConditions {
342
349
  branches?: string[];
350
+ /**
351
+ * Unquery condition expression.
352
+ * Policies: evaluated against `{ subject, resource, env }`.
353
+ * Triggers: evaluated against `{ trigger, value, resource, env }` (see server docs).
354
+ */
355
+ expression?: string;
343
356
  }
344
357
  export interface SecurityPolicy {
345
358
  description?: string;
@@ -358,6 +371,8 @@ export interface PolicySubjectInput {
358
371
  groups?: string[];
359
372
  role?: string;
360
373
  username?: string;
374
+ /** Custom app-user attributes (mirrors stored user `attributes`); used by `conditions.expression`. */
375
+ attributes?: Record<string, unknown>;
361
376
  }
362
377
  export interface SecurityConfig {
363
378
  default_effect: 'allow' | 'deny';
@@ -386,8 +401,29 @@ export interface PolicyUpdateResponse {
386
401
  policy: SecurityPolicy;
387
402
  warnings?: string[];
388
403
  }
389
- /** Stored trigger document under /_xcitedb/triggers (server-defined shape). */
390
- export type TriggerDefinition = Record<string, unknown>;
404
+ /** `match` block for stored triggers (`/_xcitedb/triggers`). */
405
+ export interface TriggerMatch {
406
+ /** Non-empty array of identifier patterns (same shape as policy `resources.identifiers`). */
407
+ identifiers: PolicyIdentifierPattern[];
408
+ match_meta_path?: string;
409
+ match_operation?: 'set' | 'append' | 'delete';
410
+ }
411
+ /** `action` block: run unquery and write result to target meta. */
412
+ export interface TriggerAction {
413
+ query: XCiteQuery;
414
+ unquery: UnqueryTemplate;
415
+ target_identifier: string;
416
+ meta_path: string;
417
+ mode?: 'set' | 'append';
418
+ }
419
+ /** Stored trigger document under /_xcitedb/triggers. */
420
+ export interface TriggerDefinition {
421
+ enabled?: boolean;
422
+ event: 'meta_changed' | 'document_written' | 'document_deleted';
423
+ match: TriggerMatch;
424
+ conditions?: PolicyConditions;
425
+ action: TriggerAction;
426
+ }
391
427
  export interface StoredTriggerResponse {
392
428
  trigger_id: string;
393
429
  trigger: TriggerDefinition;
package/llms-full.txt CHANGED
@@ -435,6 +435,122 @@ Executes a structured query document (JSON DSL) for advanced analytics, bulk exp
435
435
 
436
436
  Unquery supports: hierarchical navigation (self, parents, ancestors, children, descendants), XPath queries on XML, JSON path navigation, counts, sums, string operations, type casting, and conditional branching. Results are always structured JSON.
437
437
 
438
+ ## Unquery DSL reference
439
+
440
+ The `unquery` body is a **JSON template**: each **object key** names an output field; each **value** is either a nested object/array (recursed) or a **string parsed as an expression**. Arrays produce JSON arrays in order.
441
+
442
+ ### Core expression builtins (string values)
443
+
444
+ - **Document / path:** `$identifier`, `$identifier(n)` (path segment by index), `$key`, `$index`, `$path`
445
+ - **XML:** `$xpath("expr")`, `$lxpath("expr")` (leaf / no-children variant), `$xml`, `$xml_no_children`, `$node`, `$attr("name")`, `$child("name")`, `$text(expr)`
446
+ - **Aggregates / size:** `$count`, `$sum(expr)`, `$avg(expr)`, `$min(expr)`, `$max(expr)`, `$size(expr)` (array length), `$length(expr)` (string length)
447
+ - **String ops:** `$lower(expr)`, `$upper(expr)`, `$substr(s,start,len?)`, `$replace(src,from,to,all?)`, `$split(s,delim)`, `$join(arr,delim?)`, `$find`, `$ifind`
448
+ - **Time / casts:** `$now`, `$to_time(expr,fmt?)`, `$time_to_str(expr,fmt?)`, `$string`, `$number`, `$int`, `$float`, `$bool(expr)`
449
+ - **Control:** `$if(cond, then, else)`, `$var(name)` or `%name` (template variables), `$call(name)` (user `#func`), `$prev(default)`
450
+ - **Other:** `$node_date`, `$data_date(expr?)`, `$in_filter("name")`, `$file`, `$csv`, `$env`, `$filename` (last three blocked in safety mode for standalone `evaluateCondition`)
451
+
452
+ ### Key-side syntax (JSON object member **names**, not values)
453
+
454
+ - Plain names, dotted paths, `[index]`, `$(expression)` / `$expr` for dynamic keys
455
+ - **`#if`**, **`#var`**, **`#assign`**, **`#func name (a,b,…)`**, **`#exists`**, **`#notexists`**, **`#return`**, **`#returnif`**
456
+ - Suffixes on keys: `?condition` (filter branch), `@ ascending` / `@ descending` / `@ unique_ascending` / `@ unique_descending` on values
457
+ - **Context navigation** after `->` (in key paths): `$self`, `$parent`, `$ancestors`, `$ancestors_and_self`, `$children`, `$descendants`, `$descendants_and_self`, `$date`, `$branch`, `$all`, `$var`, `$file`, `$csv`
458
+
459
+ ### Conditions (same grammar as policy `conditions.expression` and Unquery `?` on keys)
460
+
461
+ Operators: `=`, `!=`, `<`, `>`, `<=`, `>=`, `in`, `not_in`, `contains`, `starts_with`, `ends_with`, `matches`, `is_array`, `is_object`, `is_string`, `is_number`, `is_int`, `is_float`, `is_bool`, `is_literal`. Logic: `&` (AND), `|` (OR), `!` (NOT). Postfix `!` on an expression tests **field exists**.
462
+
463
+ ### Graded examples
464
+
465
+ **1. Project one meta field into an output key** (per matching document, then combined per engine rules):
466
+
467
+ ```json
468
+ {
469
+ "query": { "match_start": "/manual/" },
470
+ "unquery": { "title": "chapter_title" }
471
+ }
472
+ ```
473
+
474
+ **2. Count documents in a prefix:**
475
+
476
+ ```json
477
+ {
478
+ "query": { "match_start": "/orders/" },
479
+ "unquery": { "total": "$count" }
480
+ }
481
+ ```
482
+
483
+ **3. XPath from XML:**
484
+
485
+ ```json
486
+ {
487
+ "query": { "match": "/book/ch1" },
488
+ "unquery": { "heading": "$xpath(\"//h1\")" }
489
+ }
490
+ ```
491
+
492
+ **4. Nested object with conditional key** (illustrative; exact key syntax may use `#if` blocks for complex trees):
493
+
494
+ ```json
495
+ {
496
+ "query": { "match_start": "/items/" },
497
+ "unquery": {
498
+ "id": "$identifier",
499
+ "summary": {
500
+ "len": "$length(title_field)"
501
+ }
502
+ }
503
+ }
504
+ ```
505
+
506
+ SDK: `UnqueryTemplate` in `@xcitedbs/client` types; method `unquery(query, unquery)`.
507
+
508
+ ## ABAC policy expression language
509
+
510
+ Policies may set **`conditions.expression`** (optional) and **`conditions.branches`** (optional array). If `branches` is non-empty, the request branch must match an entry (`"*"`, exact name, or `prefix*`).
511
+
512
+ ### Evaluation context for **policies** (`conditions.expression`)
513
+
514
+ ```json
515
+ {
516
+ "subject": {
517
+ "id": "...",
518
+ "email": "...",
519
+ "type": "app_user|developer|...",
520
+ "role": "...",
521
+ "groups": ["..."],
522
+ "attr": { }
523
+ },
524
+ "resource": {
525
+ "identifier": "/full/path",
526
+ "path": ["segment", "segment"]
527
+ },
528
+ "env": { "branch": "..." }
529
+ }
530
+ ```
531
+
532
+ `subject.attr` mirrors app-user **custom attributes** from the user record. `resource.path` is the identifier split on `/` (non-empty segments only).
533
+
534
+ ### Operators (predicates)
535
+
536
+ `=`, `!=`, `>=`, `<=`, `>`, `<`, `in`, `contains`, `starts_with`, `ends_with`, `&`, `|`, `!`, `+` (string concat), postfix `!` (exists).
537
+
538
+ ### Policy action strings (in `actions` arrays)
539
+
540
+ Use: **`read`**, **`write`**, **`delete`**, **`list`**, **`meta:read`**, **`meta:write`**, **`unquery`**. (Do not use legacy `document:write`-style names in new policies.)
541
+
542
+ ### Copy-pasteable expression recipes (from integration tests)
543
+
544
+ | Scenario | `conditions.expression` |
545
+ |----------|-------------------------|
546
+ | Tenant isolation (first path segment = user attr) | `resource.path[0] = subject.attr.tenant_code` |
547
+ | Numeric level | `subject.attr.level >= 5` |
548
+ | Project membership (second segment in array attr) | `resource.path[1] in subject.attr.projects` |
549
+ | Boolean feature flag | `subject.attr.beta = true` |
550
+ | Compound + branch (also use `conditions.branches` when needed) | `subject.email ends_with '@company.com' \| (subject.email ends_with '@partner.com' & env.branch = 'stg')` |
551
+
552
+ Dry-run: **`POST /api/v1/security/check`** with `subject`, `identifier`, `action`, optional `meta_path`, `branch`.
553
+
438
554
  ---
439
555
 
440
556
  # Branches
@@ -569,27 +685,111 @@ Returns lock info. **`409`** if already locked.
569
685
 
570
686
  **Base path:** `/api/v1/triggers`
571
687
 
688
+ Definitions are stored as JSON under **`/_xcitedb/triggers`**. After a matching event, the server runs **`action.unquery`** over documents selected by **`action.query`**, then writes the resulting JSON to **`action.meta_path`** on **`action.target_identifier`** (elevated privileges inside the same transaction). Nested trigger evaluation is blocked (no infinite loops).
689
+
572
690
  ## Create or update trigger
573
691
 
574
692
  **`POST /api/v1/triggers`**
575
693
 
576
694
  ```json
577
695
  {
578
- "trigger_id": "on_status_review",
696
+ "trigger_id": "on_project_status_meta",
697
+ "trigger": {
698
+ "enabled": true,
699
+ "event": "meta_changed",
700
+ "match": {
701
+ "identifiers": [{ "match_start": "/projects/" }],
702
+ "match_meta_path": "status",
703
+ "match_operation": "set"
704
+ },
705
+ "conditions": {
706
+ "branches": ["*", "main"],
707
+ "expression": "trigger.meta_path = \"status\""
708
+ },
709
+ "action": {
710
+ "query": { "match": "$trigger_identifier" },
711
+ "unquery": { "id": "$identifier", "status": "status" },
712
+ "target_identifier": "/indexes/project_status",
713
+ "meta_path": "entries",
714
+ "mode": "append"
715
+ }
716
+ }
717
+ }
718
+ ```
719
+
720
+ ### Trigger fields
721
+
722
+ | Field | Required | Description |
723
+ |--------|----------|-------------|
724
+ | `enabled` | No (default true) | If false, trigger is skipped. |
725
+ | `event` | Yes | `meta_changed`, `document_written`, or `document_deleted`. |
726
+ | `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). |
727
+ | `conditions` | No | Optional **`branches`** (same as policies) and **`expression`** (see below). |
728
+ | `action` | Yes | **`query`** (`XCiteQuery`), **`unquery`** (Unquery DSL template), **`target_identifier`** (literal or `"$trigger_identifier"`), **`meta_path`**, optional **`mode`**: `set` (default) or `append`. |
729
+
730
+ ### Expression context for **triggers** (`conditions.expression`)
731
+
732
+ Not the same as policies: there is **no `subject`**. Context object:
733
+
734
+ - **`trigger`**: `{ "event", "meta_path", "operation" }` (`operation`: `set` / `append` / `delete`)
735
+ - **`value`**: JSON written at `meta_path` for `meta_changed`, or null
736
+ - **`resource`**: `{ "identifier", "path" }` for the firing identifier
737
+ - **`env`**: `{ "branch" }`
738
+
739
+ ### Unquery variables injected for triggers
740
+
741
+ String template vars: **`$trigger_identifier`**, **`$trigger_meta_path`**, **`$trigger_operation`**. JSON var: **`$trigger_value`** (the written value when applicable).
742
+
743
+ ### Recipes
744
+
745
+ **Meta change → append row to an index document**
746
+
747
+ ```json
748
+ {
749
+ "trigger_id": "meta_index",
750
+ "trigger": {
751
+ "event": "meta_changed",
752
+ "match": {
753
+ "identifiers": [{ "match_start": "/docs/" }],
754
+ "match_meta_path": "reviewed"
755
+ },
756
+ "action": {
757
+ "query": { "match": "$trigger_identifier" },
758
+ "unquery": { "doc": "$trigger_identifier", "at": "$trigger_meta_path" },
759
+ "target_identifier": "/indexes/reviews",
760
+ "meta_path": "log",
761
+ "mode": "append"
762
+ }
763
+ }
764
+ }
765
+ ```
766
+
767
+ **Document written → write stats meta on the same identifier**
768
+
769
+ ```json
770
+ {
771
+ "trigger_id": "article_stats",
579
772
  "trigger": {
580
- "match": { "meta_key": "status", "meta_value": "review" },
581
- "action": { "type": "webhook", "url": "https://hooks.example.com/xcite" }
773
+ "event": "document_written",
774
+ "match": { "identifiers": [{ "match_start": "/articles/" }] },
775
+ "action": {
776
+ "query": { "match": "$trigger_identifier" },
777
+ "unquery": { "id": "$identifier", "nodes": "$count" },
778
+ "target_identifier": "$trigger_identifier",
779
+ "meta_path": "stats",
780
+ "mode": "set"
781
+ }
582
782
  }
583
783
  }
584
784
  ```
585
785
 
586
786
  ## List triggers
587
787
 
588
- **`GET /api/v1/triggers`** — Returns map of `{ trigger_id: trigger_definition }`.
788
+ **`GET /api/v1/triggers`** — Returns map of `{ trigger_id: trigger_definition }`. Optional **`?name=`** returns one trigger as `{ trigger_id, trigger }`.
589
789
 
590
790
  ## Delete trigger
591
791
 
592
- **`DELETE /api/v1/triggers?name=on_status_review`**
792
+ **`DELETE /api/v1/triggers?name=on_project_status_meta`**
593
793
 
594
794
  ---
595
795
 
@@ -609,7 +809,7 @@ Returns lock info. **`409`** if already locked.
609
809
  "priority": 10,
610
810
  "subjects": { "type": "app_user", "groups": ["viewers"] },
611
811
  "resources": { "identifiers": [{ "match_start": "/admin/" }] },
612
- "actions": ["document:write", "document:delete"],
812
+ "actions": ["write", "delete", "meta:write"],
613
813
  "conditions": {}
614
814
  }
615
815
  }
@@ -630,11 +830,11 @@ Returns lock info. **`409`** if already locked.
630
830
  {
631
831
  "subject": { "type": "app_user", "groups": ["editors"] },
632
832
  "identifier": "/admin/config",
633
- "action": "document:write"
833
+ "action": "write"
634
834
  }
635
835
  ```
636
836
 
637
- Returns `{ effect: "allow"|"deny", matched_policy_id? }`.
837
+ Returns `{ effect: "allow"|"deny", matched_policy_id?, expression_context? }`.
638
838
 
639
839
  ## Security config
640
840
 
package/llms.txt CHANGED
@@ -206,6 +206,26 @@ interface XCiteDBClientOptions {
206
206
  **Triggers:**
207
207
  - `upsertTrigger(id, trigger)` / `listTriggers()` / `deleteTrigger(name)` — Automation triggers
208
208
 
209
+ ### Advanced: policy expressions (ABAC)
210
+
211
+ - **Actions** (use in `policy.actions`): `read`, `write`, `delete`, `list`, `meta:read`, `meta:write`, `unquery`.
212
+ - **`conditions.expression`** uses the same predicate syntax as Unquery `?` filters. **Context:** `subject.id`, `subject.email`, `subject.role`, `subject.groups`, `subject.attr.*` (app-user JSON attributes), `resource.identifier`, `resource.path` (array of path segments), `env.branch`.
213
+ - **Operators:** `=`, `!=`, `>=`, `<=`, `>`, `<`, `in`, `contains`, `starts_with`, `ends_with`, `&`, `|`, `!`, `+` (string concat), postfix `!` (exists).
214
+ - **Examples:** `resource.path[0] = subject.attr.tenant_code` — tenant isolation; `subject.attr.level >= 5` — numeric attribute gate.
215
+
216
+ ### Advanced: triggers (stored under `/_xcitedb/triggers`)
217
+
218
+ - **Events:** `meta_changed`, `document_written`, `document_deleted`.
219
+ - **`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`.
220
+ - **`action`:** **`query`** (document query), **`unquery`** (Unquery template), **`target_identifier`** (or `"$trigger_identifier"`), **`meta_path`**, **`mode`:** `set` | `append`.
221
+ - **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`).
222
+
223
+ ### Advanced: Unquery DSL (`unquery(query, unqueryDoc)`)
224
+
225
+ - JSON **keys** = output fields; **string values** = expressions referencing meta/XML (e.g. field name `"title"` reads meta key `title`).
226
+ - **Common builtins:** `$count`, `$sum(expr)`, `$avg(expr)`, `$min(expr)`, `$max(expr)`, `$xpath("…")`, `$identifier`, `$size(expr)`, `$length(expr)`, `$if(cond, a, b)`, `$var(name)` / `%name`.
227
+ - **Example:** `{ "query": { "match_start": "/orders/" }, "unquery": { "n": "$count" } }` — count documents. Full grammar: **`llms-full.txt`** in this package.
228
+
209
229
  **App Users (admin):**
210
230
  - `listAppUsers()` / `createAppUser()` / `deleteAppUser()` — Manage end-user accounts
211
231
  - `registerAppUser(email, password, displayName?, attributes?)` — Self-registration (groups assigned from server config)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcitedbs/client",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "XCiteDB BaaS client SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",