@xcitedbs/client 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +52 -0
- package/dist/client.js +69 -2
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +73 -3
- package/llms-full.txt +41 -4
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -125,6 +125,58 @@ export declare class XCiteDBClient {
|
|
|
125
125
|
destroyTestSessionByToken(token: string): Promise<{
|
|
126
126
|
message: string;
|
|
127
127
|
}>;
|
|
128
|
+
/**
|
|
129
|
+
* Create a long-lived developer sandbox via `POST /api/v1/sandboxes`. Returns the server's
|
|
130
|
+
* `SandboxInfo`. To start using the sandbox immediately, follow up with `client.useSandbox(name)`,
|
|
131
|
+
* or mint a sandbox-bound API key with `mintSandboxApiKey` and route subsequent requests through
|
|
132
|
+
* a fresh client constructed with that key (no header threading needed).
|
|
133
|
+
*/
|
|
134
|
+
createSandbox(opts: import('./types').CreateSandboxOptions): Promise<import('./types').SandboxInfo>;
|
|
135
|
+
/** List sandboxes for the current project (`GET /api/v1/sandboxes`). */
|
|
136
|
+
listSandboxes(): Promise<import('./types').SandboxInfo[]>;
|
|
137
|
+
/** Fetch a sandbox's detail by name (`GET /api/v1/sandboxes/{name}`). */
|
|
138
|
+
getSandbox(name: string): Promise<import('./types').SandboxInfo>;
|
|
139
|
+
/** Update mutable sandbox fields (`PATCH /api/v1/sandboxes/{name}`). */
|
|
140
|
+
updateSandbox(name: string, patch: Partial<Pick<import('./types').SandboxInfo, 'description' | 'pinned' | 'expires_at' | 'effects_policy'>>): Promise<import('./types').SandboxInfo>;
|
|
141
|
+
/** Drop overlay writes for a sandbox while preserving membership and bound keys. */
|
|
142
|
+
resetSandbox(name: string): Promise<{
|
|
143
|
+
message: string;
|
|
144
|
+
session_token: string;
|
|
145
|
+
}>;
|
|
146
|
+
/** Destroy a sandbox; revokes bound API keys and removes membership rows. Owner only. */
|
|
147
|
+
destroySandbox(name: string): Promise<{
|
|
148
|
+
message: string;
|
|
149
|
+
session_token: string;
|
|
150
|
+
}>;
|
|
151
|
+
listSandboxMembers(name: string): Promise<import('./types').SandboxMember[]>;
|
|
152
|
+
addSandboxMember(name: string, member_id: string, role: 'editor' | 'viewer'): Promise<{
|
|
153
|
+
message: string;
|
|
154
|
+
}>;
|
|
155
|
+
removeSandboxMember(name: string, member_id: string): Promise<{
|
|
156
|
+
message: string;
|
|
157
|
+
}>;
|
|
158
|
+
/**
|
|
159
|
+
* Mint an API key bound to the given sandbox. Requests presenting this key automatically route
|
|
160
|
+
* into the sandbox without an `X-Test-Session` header — paste it into your dev `.env` and
|
|
161
|
+
* existing client/curl/SDK calls just work. The `api_key` field is shown once; persist it now.
|
|
162
|
+
*/
|
|
163
|
+
mintSandboxApiKey(name: string, opts?: {
|
|
164
|
+
name?: string;
|
|
165
|
+
role?: 'admin' | 'editor' | 'viewer';
|
|
166
|
+
key_type?: 'secret' | 'public';
|
|
167
|
+
expires_at?: number;
|
|
168
|
+
}): Promise<import('./types').SandboxApiKeyMintResult>;
|
|
169
|
+
/**
|
|
170
|
+
* Pin this client to a named sandbox: subsequent requests carry `X-Test-Session: <token>`.
|
|
171
|
+
* Returns the sandbox info. Throws if the name does not resolve.
|
|
172
|
+
*
|
|
173
|
+
* If you instead want zero-header DX, mint a sandbox-bound API key with
|
|
174
|
+
* `mintSandboxApiKey(name)` and construct a fresh client with that `apiKey` — the server
|
|
175
|
+
* pre-filter resolves the binding without touching the test-session header.
|
|
176
|
+
*/
|
|
177
|
+
useSandbox(name: string): Promise<import('./types').SandboxInfo>;
|
|
178
|
+
/** Clear any pinned test/sandbox session token from this client (subsequent requests hit production). */
|
|
179
|
+
clearSandbox(): void;
|
|
128
180
|
/** True if this client would send API key or Bearer credentials on a normal request. */
|
|
129
181
|
private sentAuthCredentials;
|
|
130
182
|
/** 401 on these paths is an expected auth flow outcome, not a dead session. */
|
package/dist/client.js
CHANGED
|
@@ -699,6 +699,73 @@ class XCiteDBClient {
|
|
|
699
699
|
const enc = encodeURIComponent(token);
|
|
700
700
|
return this.request('DELETE', `/api/v1/test/sessions/${enc}`, undefined, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
701
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* Create a long-lived developer sandbox via `POST /api/v1/sandboxes`. Returns the server's
|
|
704
|
+
* `SandboxInfo`. To start using the sandbox immediately, follow up with `client.useSandbox(name)`,
|
|
705
|
+
* or mint a sandbox-bound API key with `mintSandboxApiKey` and route subsequent requests through
|
|
706
|
+
* a fresh client constructed with that key (no header threading needed).
|
|
707
|
+
*/
|
|
708
|
+
async createSandbox(opts) {
|
|
709
|
+
return this.request('POST', '/api/v1/sandboxes', opts, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
710
|
+
}
|
|
711
|
+
/** List sandboxes for the current project (`GET /api/v1/sandboxes`). */
|
|
712
|
+
async listSandboxes() {
|
|
713
|
+
const r = await this.request('GET', '/api/v1/sandboxes', undefined, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
714
|
+
return r.sandboxes ?? [];
|
|
715
|
+
}
|
|
716
|
+
/** Fetch a sandbox's detail by name (`GET /api/v1/sandboxes/{name}`). */
|
|
717
|
+
async getSandbox(name) {
|
|
718
|
+
return this.request('GET', `/api/v1/sandboxes/${encodeURIComponent(name)}`, undefined, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
719
|
+
}
|
|
720
|
+
/** Update mutable sandbox fields (`PATCH /api/v1/sandboxes/{name}`). */
|
|
721
|
+
async updateSandbox(name, patch) {
|
|
722
|
+
return this.request('PATCH', `/api/v1/sandboxes/${encodeURIComponent(name)}`, patch, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
723
|
+
}
|
|
724
|
+
/** Drop overlay writes for a sandbox while preserving membership and bound keys. */
|
|
725
|
+
async resetSandbox(name) {
|
|
726
|
+
return this.request('POST', `/api/v1/sandboxes/${encodeURIComponent(name)}/reset`, undefined, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
727
|
+
}
|
|
728
|
+
/** Destroy a sandbox; revokes bound API keys and removes membership rows. Owner only. */
|
|
729
|
+
async destroySandbox(name) {
|
|
730
|
+
return this.request('DELETE', `/api/v1/sandboxes/${encodeURIComponent(name)}`, undefined, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
731
|
+
}
|
|
732
|
+
async listSandboxMembers(name) {
|
|
733
|
+
const r = await this.request('GET', `/api/v1/sandboxes/${encodeURIComponent(name)}/members`, undefined, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
734
|
+
return r.members ?? [];
|
|
735
|
+
}
|
|
736
|
+
async addSandboxMember(name, member_id, role) {
|
|
737
|
+
return this.request('POST', `/api/v1/sandboxes/${encodeURIComponent(name)}/members`, { member_id, role }, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
738
|
+
}
|
|
739
|
+
async removeSandboxMember(name, member_id) {
|
|
740
|
+
return this.request('DELETE', `/api/v1/sandboxes/${encodeURIComponent(name)}/members/${encodeURIComponent(member_id)}`, undefined, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Mint an API key bound to the given sandbox. Requests presenting this key automatically route
|
|
744
|
+
* into the sandbox without an `X-Test-Session` header — paste it into your dev `.env` and
|
|
745
|
+
* existing client/curl/SDK calls just work. The `api_key` field is shown once; persist it now.
|
|
746
|
+
*/
|
|
747
|
+
async mintSandboxApiKey(name, opts = {}) {
|
|
748
|
+
return this.request('POST', `/api/v1/sandboxes/${encodeURIComponent(name)}/api-keys`, opts, undefined, { suppressTestSessionHeader: true, no401Retry: true });
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Pin this client to a named sandbox: subsequent requests carry `X-Test-Session: <token>`.
|
|
752
|
+
* Returns the sandbox info. Throws if the name does not resolve.
|
|
753
|
+
*
|
|
754
|
+
* If you instead want zero-header DX, mint a sandbox-bound API key with
|
|
755
|
+
* `mintSandboxApiKey(name)` and construct a fresh client with that `apiKey` — the server
|
|
756
|
+
* pre-filter resolves the binding without touching the test-session header.
|
|
757
|
+
*/
|
|
758
|
+
async useSandbox(name) {
|
|
759
|
+
const info = await this.getSandbox(name);
|
|
760
|
+
if (!info.session_token)
|
|
761
|
+
throw new Error(`useSandbox: server returned no session_token for "${name}"`);
|
|
762
|
+
this.testSessionToken = info.session_token;
|
|
763
|
+
return info;
|
|
764
|
+
}
|
|
765
|
+
/** Clear any pinned test/sandbox session token from this client (subsequent requests hit production). */
|
|
766
|
+
clearSandbox() {
|
|
767
|
+
this.testSessionToken = undefined;
|
|
768
|
+
}
|
|
702
769
|
/** True if this client would send API key or Bearer credentials on a normal request. */
|
|
703
770
|
sentAuthCredentials() {
|
|
704
771
|
return !!(this.apiKey || this.accessToken || this.appUserAccessToken);
|
|
@@ -2497,8 +2564,8 @@ class XCiteDBClient {
|
|
|
2497
2564
|
}
|
|
2498
2565
|
const ct = opts.contentType.trim() || 'application/octet-stream';
|
|
2499
2566
|
const qParts = [];
|
|
2500
|
-
if (opts.scope === '
|
|
2501
|
-
qParts.push('scope=
|
|
2567
|
+
if (opts.scope === 'shared') {
|
|
2568
|
+
qParts.push('scope=shared');
|
|
2502
2569
|
}
|
|
2503
2570
|
if (opts.identifier !== undefined && opts.identifier !== '') {
|
|
2504
2571
|
qParts.push(`identifier=${encodeURIComponent(opts.identifier)}`);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { XCiteDBClient } from './client';
|
|
2
2
|
export { parseAssetUri, formatAssetUri, collectIdentifiersFromText, ASSET_URI_PREFIX } from './assetUri';
|
|
3
3
|
export { WebSocketSubscription } from './websocket';
|
|
4
|
-
export type { AccessCheckResult, ApiKeyInfo, AppAuthConfig, AppEmailConfig, AppEmailSmtpConfig, AppEmailTemplateEntry, AppEmailTemplates, AppEmailWebhookConfig, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BookmarkRecord, BranchInfo, BranchListItem, CheckpointRecord, CommitRecord, CompareEntry, CompareRef, CompareResult, DatabaseContext, DiffEntry, DiffRef, DiffResult, SmartDiffRef, SmartDiffResult, SmartDiffStats, DocumentBatchResponse, DocumentBatchResultRow, DocumentExportFormat, DocumentImportFormat, ExportDocumentResult, Flags, ImportDocumentOptions, ImportDocumentResult, JsonDocumentData, JsonDocumentBatchItem, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, AcquireLockOptions, LockConflictBody, LockExpiredBody, LockUnknownBody, MergeConflict, MergeResult, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, ProjectSearchSettings, ProjectSearchSettingsUpdate, ProjectDocConfResponse, AssetGcDryRunResult, AssetHeadResult, AssetListItem, AssetListResponse, AssetMagicLinkListResponse, AssetMagicLinkRecord, AssetMagicLinkResult, AssetShareListEntry, AssetShareListResponse, AssetShareRequest, AssetStorageImport, AssetStorageMount, AssetStorageTarget, AssetStorageTargetType, AssetUnshareRequest, AssetUploadResult, CreateAssetMagicLinkRequest, ListAssetsOptions, ProjectAssetStorageConfig, UploadAssetOptions, PlatformDefaultDocConfResponse, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PublishConflict, PublishResult, RebaseUserWorkspaceResult, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RagQueryOptions, RagQueryResult, RagStreamEvent, RealtimeEvent, SearchIndexingProgress, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TagRecord, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TriggerEvent, TriggerEventsResponse, TxnOperation, TxnOperationResult, TxnPrecondition, TxnPreconditionResult, TxnRequest, TxnResponse, JwksKey, JwksResponse, VerifyAppUserTokenOptions, TokenPair, UserInfo, UserIsolationConfig, UserIsolationCreateShareParams, UserIsolationOptions, UserIsolationShareMode, UserIsolationShareResult, WorkspaceInfo, WriteDocumentOptions, XmlDocumentBatchItem, CreateTestSessionOptions, TestSessionBootstrap, TestSessionBootstrapSummary, TestSessionInfo, XCiteDBClientOptions, XCiteDBErrorExtras, XCiteDBJwtClaims, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
|
|
4
|
+
export type { AccessCheckResult, ApiKeyInfo, AppAuthConfig, AppEmailConfig, AppEmailSmtpConfig, AppEmailTemplateEntry, AppEmailTemplates, AppEmailWebhookConfig, AppUser, AppUserTokenPair, EmailTestResponse, ForgotPasswordResponse, SendVerificationResponse, BookmarkRecord, BranchInfo, BranchListItem, CheckpointRecord, CommitRecord, CompareEntry, CompareRef, CompareResult, DatabaseContext, DiffEntry, DiffRef, DiffResult, SmartDiffRef, SmartDiffResult, SmartDiffStats, DocumentBatchResponse, DocumentBatchResultRow, DocumentExportFormat, DocumentImportFormat, ExportDocumentResult, Flags, ImportDocumentOptions, ImportDocumentResult, JsonDocumentData, JsonDocumentBatchItem, IdentifierChildNode, ListIdentifierChildrenResult, ListIdentifiersResult, LockInfo, AcquireLockOptions, LockConflictBody, LockExpiredBody, LockUnknownBody, MergeConflict, MergeResult, OAuthProviderInfo, OAuthProvidersResponse, OwnedTenantInfo, ProjectInfo, PlatformRegistrationConfig, PlatformWorkspaceOrg, PlatformWorkspacesResponse, ProjectSearchSettings, ProjectSearchSettingsUpdate, ProjectDocConfResponse, AssetGcDryRunResult, AssetHeadResult, AssetListItem, AssetListResponse, AssetMagicLinkListResponse, AssetMagicLinkRecord, AssetMagicLinkResult, AssetShareListEntry, AssetShareListResponse, AssetShareRequest, AssetStorageImport, AssetStorageMount, AssetStorageTarget, AssetStorageTargetType, AssetUnshareRequest, AssetUploadResult, CreateAssetMagicLinkRequest, ListAssetsOptions, ProjectAssetStorageConfig, UploadAssetOptions, PlatformDefaultDocConfResponse, LogEntry, MetaValue, PlatformRegisterResult, PolicyUpdateResponse, PublishConflict, PublishResult, RebaseUserWorkspaceResult, PolicyConditions, PolicyIdentifierPattern, PolicyResources, PolicySubjectInput, PolicySubjects, RagQueryOptions, RagQueryResult, RagStreamEvent, RealtimeEvent, SandboxApiKeyMintResult, SandboxInfo, SandboxMember, SearchIndexingProgress, SecurityConfig, SecurityPolicy, StoredPolicyResponse, StoredTriggerResponse, SubscriptionOptions, TagRecord, TextSearchHit, TextSearchQuery, TextSearchResult, TriggerDefinition, TriggerEvent, TriggerEventsResponse, TxnOperation, TxnOperationResult, TxnPrecondition, TxnPreconditionResult, TxnRequest, TxnResponse, JwksKey, JwksResponse, VerifyAppUserTokenOptions, TokenPair, UserInfo, UserIsolationConfig, UserIsolationCreateShareParams, UserIsolationOptions, UserIsolationShareMode, UserIsolationShareResult, WorkspaceInfo, WriteDocumentOptions, XmlDocumentBatchItem, CreateSandboxOptions, CreateTestSessionOptions, TestSessionBootstrap, TestSessionBootstrapSummary, TestSessionInfo, XCiteDBClientOptions, XCiteDBErrorExtras, XCiteDBJwtClaims, UnqueryResult, UnqueryTemplate, XCiteQuery, } from './types';
|
|
5
5
|
export { XCiteDBError, XCiteDBForbiddenError, XCiteDBNotFoundError, XCiteDBAuthError, XCiteDBLockConflictError, } from './types';
|
package/dist/types.d.ts
CHANGED
|
@@ -192,8 +192,10 @@ export interface UploadAssetOptions {
|
|
|
192
192
|
contentType: string;
|
|
193
193
|
/** When set (with default scope), uploaded to this path after user-isolation prefixing rules on the server. */
|
|
194
194
|
identifier?: string;
|
|
195
|
-
/** `
|
|
196
|
-
|
|
195
|
+
/** `shared` writes under `/shared/assets/…` (tenant-common, readable by all logged-in app users).
|
|
196
|
+
* Default project/user scope when omitted. To publish anonymously, upload normally and use the
|
|
197
|
+
* share API with `target_user_id: "#anonymous"`. */
|
|
198
|
+
scope?: 'project' | 'shared';
|
|
197
199
|
}
|
|
198
200
|
/** Result of {@link XCiteDBClient.headAsset}. */
|
|
199
201
|
export interface AssetHeadResult {
|
|
@@ -800,6 +802,53 @@ export interface CreateTestSessionOptions {
|
|
|
800
802
|
userIsolation?: UserIsolationOptions;
|
|
801
803
|
requestTimeoutMs?: number;
|
|
802
804
|
}
|
|
805
|
+
/** A long-lived developer sandbox: named overlay on a project + branch. */
|
|
806
|
+
export interface SandboxInfo {
|
|
807
|
+
session_token: string;
|
|
808
|
+
name: string;
|
|
809
|
+
description: string;
|
|
810
|
+
org_id: string;
|
|
811
|
+
project_id: string;
|
|
812
|
+
base_branch: string;
|
|
813
|
+
pinned: boolean;
|
|
814
|
+
effects_policy: 'real' | 'log' | 'mock';
|
|
815
|
+
created_at: number;
|
|
816
|
+
last_used: number;
|
|
817
|
+
expires_at: number;
|
|
818
|
+
reset_generation: number;
|
|
819
|
+
owner_member_id: string;
|
|
820
|
+
/** Present in list responses: the requesting member's role in this sandbox, or "" if not a member. */
|
|
821
|
+
my_role?: 'owner' | 'editor' | 'viewer' | '';
|
|
822
|
+
}
|
|
823
|
+
/** Options for {@link XCiteDBClient.createSandbox}. */
|
|
824
|
+
export interface CreateSandboxOptions {
|
|
825
|
+
/** Required. Kebab-case, 1-64 chars, must start with a-z, no consecutive hyphens. */
|
|
826
|
+
name: string;
|
|
827
|
+
description?: string;
|
|
828
|
+
/** Pinned sandboxes are not garbage-collected by idle TTL. */
|
|
829
|
+
pinned?: boolean;
|
|
830
|
+
/** Unix seconds; 0 = no explicit expiry. */
|
|
831
|
+
expires_at?: number;
|
|
832
|
+
base_branch?: string;
|
|
833
|
+
/** Defaults to "log" — webhooks/auth-emails get captured instead of firing. */
|
|
834
|
+
effects_policy?: 'real' | 'log' | 'mock';
|
|
835
|
+
}
|
|
836
|
+
/** Response from {@link XCiteDBClient.mintSandboxApiKey}. The `api_key` value is shown once. */
|
|
837
|
+
export interface SandboxApiKeyMintResult {
|
|
838
|
+
api_key: string;
|
|
839
|
+
key_id: string;
|
|
840
|
+
prefix: string;
|
|
841
|
+
key_type: 'secret' | 'public';
|
|
842
|
+
bound_sandbox_id: string;
|
|
843
|
+
sandbox_name: string;
|
|
844
|
+
message: string;
|
|
845
|
+
}
|
|
846
|
+
export interface SandboxMember {
|
|
847
|
+
member_id: string;
|
|
848
|
+
role: 'owner' | 'editor' | 'viewer';
|
|
849
|
+
added_at: number;
|
|
850
|
+
added_by: string;
|
|
851
|
+
}
|
|
803
852
|
/** Application user (tenant-scoped), distinct from developer users. */
|
|
804
853
|
export interface AppUser {
|
|
805
854
|
user_id: string;
|
|
@@ -978,7 +1027,17 @@ export interface PolicyUpdateResponse {
|
|
|
978
1027
|
export interface TriggerMatch {
|
|
979
1028
|
/** Non-empty array of identifier patterns (same shape as policy `resources.identifiers`). */
|
|
980
1029
|
identifiers: PolicyIdentifierPattern[];
|
|
1030
|
+
/**
|
|
1031
|
+
* Single meta path pattern (exact, or `prefix*`). Mutually exclusive with `match_meta_paths`;
|
|
1032
|
+
* the server rejects writes that set both fields.
|
|
1033
|
+
*/
|
|
981
1034
|
match_meta_path?: string;
|
|
1035
|
+
/**
|
|
1036
|
+
* Array of meta path patterns (anyOf). Each entry is exact or `prefix*`. Useful for one
|
|
1037
|
+
* trigger gating multiple fields of the same shape (e.g. role-restricted profile fields).
|
|
1038
|
+
* Empty array means no constraint. Mutually exclusive with `match_meta_path`.
|
|
1039
|
+
*/
|
|
1040
|
+
match_meta_paths?: string[];
|
|
982
1041
|
match_operation?: 'set' | 'append' | 'delete';
|
|
983
1042
|
}
|
|
984
1043
|
/** `action` block: run unquery and write result to target meta. */
|
|
@@ -992,10 +1051,21 @@ export interface TriggerAction {
|
|
|
992
1051
|
/** Stored trigger document under /_xcitedb/triggers. */
|
|
993
1052
|
export interface TriggerDefinition {
|
|
994
1053
|
enabled?: boolean;
|
|
1054
|
+
/**
|
|
1055
|
+
* `"after"` (default): run after the write commits, may run actions. `"before"`:
|
|
1056
|
+
* predicate-only — runs before the write applies and rejects with HTTP 422 when matched.
|
|
1057
|
+
* BEFORE triggers must not have `action` or `actions`.
|
|
1058
|
+
*/
|
|
1059
|
+
phase?: 'before' | 'after';
|
|
995
1060
|
event: 'meta_changed' | 'document_written' | 'document_deleted';
|
|
996
1061
|
match: TriggerMatch;
|
|
997
1062
|
conditions?: PolicyConditions;
|
|
998
|
-
action
|
|
1063
|
+
/** AFTER-trigger single action (legacy). Mutually exclusive with `actions`. Forbidden on BEFORE triggers. */
|
|
1064
|
+
action?: TriggerAction;
|
|
1065
|
+
/** AFTER-trigger multi-action variant. Mutually exclusive with `action`. Forbidden on BEFORE triggers. */
|
|
1066
|
+
actions?: TriggerAction[];
|
|
1067
|
+
/** BEFORE-trigger reject message surfaced to clients on HTTP 422. */
|
|
1068
|
+
reject_reason?: string;
|
|
999
1069
|
}
|
|
1000
1070
|
export interface StoredTriggerResponse {
|
|
1001
1071
|
trigger_id: string;
|
package/llms-full.txt
CHANGED
|
@@ -1157,18 +1157,55 @@ Definitions are stored as JSON under **`/_xcitedb/triggers`**. After a matching
|
|
|
1157
1157
|
| Field | Required | Description |
|
|
1158
1158
|
|--------|----------|-------------|
|
|
1159
1159
|
| `enabled` | No (default true) | If false, trigger is skipped. |
|
|
1160
|
+
| `phase` | No (default `"after"`) | `"after"` runs post-write actions; `"before"` is predicate-only and rejects the write with HTTP 422 when matched. BEFORE triggers must not have `action` or `actions`. |
|
|
1160
1161
|
| `event` | Yes | `meta_changed`, `document_written`, or `document_deleted`. |
|
|
1161
|
-
| `match` | Yes | Must include **`identifiers`**: non-empty array of identifier patterns (`exact`, `match_start`, `match_end`, `contains`, `regex`). Optional **`match_meta_path
|
|
1162
|
+
| `match` | Yes | Must include **`identifiers`**: non-empty array of identifier patterns (`exact`, `match_start`, `match_end`, `contains`, `regex`). Optional **`match_meta_path`** (single exact path or `prefix*`) **or** **`match_meta_paths`** (array of patterns, anyOf — useful for one trigger gating multiple fields; mutually exclusive with `match_meta_path`). Optional **`match_operation`**: `set`, `append`, or `delete`. |
|
|
1162
1163
|
| `conditions` | No | Optional **`branches`** (same as policies) and **`expression`** (see below). |
|
|
1163
|
-
| `action` |
|
|
1164
|
+
| `action` | AFTER only | **`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). Mutually exclusive with `actions`. |
|
|
1165
|
+
| `actions` | AFTER only | Array of action objects for multi-op fan-out. Each `kind` is one of `WriteMeta`, `AppendMeta`, `WriteJson`, `DeleteIdentifier`, `AddIdentifier`, `ClearMetaPath`. Mutually exclusive with `action`. |
|
|
1166
|
+
| `reject_reason` | BEFORE only | Message returned to clients on HTTP 422 when the trigger matches. |
|
|
1164
1167
|
|
|
1165
|
-
###
|
|
1168
|
+
### BEFORE triggers (predicate-only)
|
|
1169
|
+
|
|
1170
|
+
`phase: "before"` triggers run *before* the write applies and reject it with **HTTP 422** when the match and conditions both hold. They are predicate-only — no `action` / `actions`. Fired only on writes routed through **`/api/v1/txn`** (regular `/api/v1/meta` and `/api/v1/json-documents` writes do not invoke BEFORE triggers).
|
|
1171
|
+
|
|
1172
|
+
The expression sees **`prior`** alongside `value`:
|
|
1173
|
+
|
|
1174
|
+
- **`prior`** (BEFORE + `meta_changed` only) — value at the same `meta_path` before the write. Bound only when at least one BEFORE trigger's expression references `prior`. JSON `null` when the path is currently absent.
|
|
1175
|
+
|
|
1176
|
+
Common patterns (the expression grammar uses `=` / `!=` / `<` / `>`, and single `&` / `|` for boolean):
|
|
1177
|
+
|
|
1178
|
+
- **Immutability after first set**: `prior is_literal & value != prior` — rejects only when a literal value already exists and differs.
|
|
1179
|
+
- **Monotonic non-decreasing**: `prior is_literal & value < prior`.
|
|
1180
|
+
- **Role-gated field**: `subject.role != 'system'` (no `prior` needed).
|
|
1181
|
+
|
|
1182
|
+
Example (`song.id` immutable once set):
|
|
1183
|
+
|
|
1184
|
+
```json
|
|
1185
|
+
{
|
|
1186
|
+
"trigger_id": "song_id_immutable",
|
|
1187
|
+
"trigger": {
|
|
1188
|
+
"phase": "before",
|
|
1189
|
+
"event": "meta_changed",
|
|
1190
|
+
"match": {
|
|
1191
|
+
"identifiers": [{ "match_start": "/spaces/" }],
|
|
1192
|
+
"match_meta_path": "id"
|
|
1193
|
+
},
|
|
1194
|
+
"conditions": {
|
|
1195
|
+
"expression": "prior is_literal & value != prior"
|
|
1196
|
+
},
|
|
1197
|
+
"reject_reason": "song id is immutable once set"
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
```
|
|
1166
1201
|
|
|
1167
|
-
|
|
1202
|
+
### Expression context for **triggers** (`conditions.expression`)
|
|
1168
1203
|
|
|
1169
1204
|
- **`trigger`**: `{ "event", "meta_path", "operation" }` (`operation`: `set` / `append` / `delete`)
|
|
1170
1205
|
- **`value`**: JSON written at `meta_path` for `meta_changed`, or null
|
|
1206
|
+
- **`prior`** (BEFORE + `meta_changed` only): value at `meta_path` before the write, or `null` if absent
|
|
1171
1207
|
- **`resource`**: `{ "identifier", "path" }` for the firing identifier
|
|
1208
|
+
- **`subject`**: `{ "id", "user_id", "email", "type", "role", "groups", "attr" }` (BEFORE only — AFTER triggers historically have no subject)
|
|
1172
1209
|
- **`env`**: `{ "branch" }`
|
|
1173
1210
|
|
|
1174
1211
|
### Unquery variables injected for triggers
|