@vxil/cli 0.17.0 → 0.19.0
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.
|
@@ -10,12 +10,16 @@ export declare const HOOK_LIMITS: {
|
|
|
10
10
|
readonly maxReadHooksPerCollection: 10;
|
|
11
11
|
};
|
|
12
12
|
/** The ONLY root variables an expression may reference. `caller` is the VERIFIED
|
|
13
|
-
* end-user principal (https://vxil.com/docs/guide/09-security-and-multitenancy) — read-only
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* end-user principal (https://vxil.com/docs/guide/09-security-and-multitenancy) — read-only.
|
|
14
|
+
* It carries `caller.endUserId` (the verified session sub, or null),
|
|
15
|
+
* `caller.principal` ('end_user' | 'tenant') and `caller.roles` (the VERIFIED
|
|
16
|
+
* session role claims of the signed-in session — never a body or header
|
|
17
|
+
* value; null in server-caller mode). READ hooks always get it
|
|
18
|
+
* (server mode: `{ endUserId: null, principal: 'tenant', roles: null }`).
|
|
19
|
+
* WRITE hooks get it in verified end-user mode only; on a
|
|
20
|
+
* server write it is null, exactly like `before` on a create — so a role rule
|
|
21
|
+
* reads `isNull(caller) || contains(caller.roles, 'manager')`. `contains` on
|
|
22
|
+
* an array is exact element membership. */
|
|
19
23
|
export declare const HOOK_ROOT_VARS: readonly ["item", "before", "now", "caller"];
|
|
20
24
|
/** The ONLY callable functions. Each is pure + deterministic + bounded. */
|
|
21
25
|
export declare const HOOK_FUNCTIONS: readonly ["min", "max", "abs", "round", "floor", "ceil", "sqrt", "pow", "sign", "len", "lower", "upper", "trim", "substr", "contains", "startsWith", "endsWith", "concat", "coalesce", "ifNull", "not", "isNull", "number", "string", "bool", "daysBetween", "yearsBetween"];
|
|
@@ -64,25 +68,41 @@ export declare class HookEvalError extends Error {
|
|
|
64
68
|
/** Thrown by a `validate` hook whose expression is falsy — maps to a clean 422. */
|
|
65
69
|
export declare class HookRejection extends Error {
|
|
66
70
|
}
|
|
71
|
+
/** A WRITE `validate` hook whose expression could not be evaluated (null
|
|
72
|
+
* arithmetic, a missing operand, a bad date …). Still a HookEvalError — the
|
|
73
|
+
* write is refused, never let through — but it carries the hook's id and its
|
|
74
|
+
* declared `message`, so the API can show the end user the tenant's own copy
|
|
75
|
+
* while keeping the engine detail for the developer. `message` stays the
|
|
76
|
+
* engine detail. */
|
|
77
|
+
export declare class HookValidateFault extends HookEvalError {
|
|
78
|
+
readonly hookId: string;
|
|
79
|
+
readonly tenantMessage: string | undefined;
|
|
80
|
+
constructor(hookId: string, tenantMessage: string | undefined, detail: string);
|
|
81
|
+
}
|
|
67
82
|
/** Parse a hook expression into an AST. Throws HookParseError on any malformed input. */
|
|
68
83
|
export declare function parseExpr(src: string): Node;
|
|
69
84
|
/** Walk the AST and reject anything outside the allow-list, plus depth bounds.
|
|
70
85
|
* Returns an array of human-readable errors (empty = safe). Pure, no eval. */
|
|
71
86
|
export declare function validateAst(root: Node): string[];
|
|
72
|
-
/** The VERIFIED caller/session context for
|
|
73
|
-
*
|
|
74
|
-
*
|
|
87
|
+
/** The VERIFIED caller/session context for Lane-A hooks (guide ch. 7).
|
|
88
|
+
* Read-only. Read hooks always get it; write hooks get it in verified end-user
|
|
89
|
+
* mode only (a server write passes null — see HOOK_ROOT_VARS). */
|
|
75
90
|
export interface HookCaller {
|
|
76
91
|
/** the verified end-user session sub, or null in server-caller mode. */
|
|
77
92
|
endUserId: string | null;
|
|
78
93
|
/** 'end_user' when a session was verified at the edge, else 'tenant'. */
|
|
79
94
|
principal: 'end_user' | 'tenant';
|
|
95
|
+
/** the VERIFIED session role claims of the signed-in session; `[]` for a
|
|
96
|
+
* session without roles, null in server-caller mode. Optional so an older
|
|
97
|
+
* caller object still reads
|
|
98
|
+
* as "no roles". */
|
|
99
|
+
roles?: readonly string[] | null;
|
|
80
100
|
}
|
|
81
101
|
export interface HookContext {
|
|
82
102
|
item: Record<string, unknown>;
|
|
83
103
|
before: Record<string, unknown> | null;
|
|
84
104
|
now: string;
|
|
85
|
-
/** verified caller/session context
|
|
105
|
+
/** verified caller/session context; null on a server write. */
|
|
86
106
|
caller?: HookCaller | null;
|
|
87
107
|
}
|
|
88
108
|
/** A mutable step counter SHARED across many evalExpr calls (the read path's
|
|
@@ -116,7 +136,10 @@ export interface HookRunResult {
|
|
|
116
136
|
* Mutates a CLONE: derive hooks set fields; a validate hook that returns falsy
|
|
117
137
|
* throws HookRejection (→ the caller maps to a 422 and the tx rolls back).
|
|
118
138
|
* Throws HookEvalError on a runtime fault (also a clean 422, never a 500). */
|
|
119
|
-
export declare function runWriteHooks(hooks: Record<string, HookDef> | undefined, collection: string, phase: 'create' | 'update', data: Record<string, unknown>, before: Record<string, unknown> | null, now: string
|
|
139
|
+
export declare function runWriteHooks(hooks: Record<string, HookDef> | undefined, collection: string, phase: 'create' | 'update', data: Record<string, unknown>, before: Record<string, unknown> | null, now: string,
|
|
140
|
+
/** the VERIFIED end-user caller (endUserId + principal + roles) in
|
|
141
|
+
* end-user mode; omitted / null on a server write (expressions see null). */
|
|
142
|
+
caller?: HookCaller | null): HookRunResult;
|
|
120
143
|
export interface ReadHookResult {
|
|
121
144
|
/** Surviving rows in input order. Each is a SHALLOW COPY with a copied `data`
|
|
122
145
|
* bag — the caller's rows are never mutated and nothing here is persisted. */
|
|
@@ -143,8 +166,8 @@ export interface ReadHookResult {
|
|
|
143
166
|
* fire here, and read events never fire in runWriteHooks. */
|
|
144
167
|
export declare function runReadHooks(hooks: Record<string, HookDef> | undefined, collection: string, rows: ReadonlyArray<Record<string, unknown>>, now: string,
|
|
145
168
|
/** verified caller/session context (guide ch. 7) — read-only, exposed to
|
|
146
|
-
* expressions as `caller.endUserId` / `caller.principal
|
|
147
|
-
* (server-caller mode). */
|
|
169
|
+
* expressions as `caller.endUserId` / `caller.principal` / `caller.roles`.
|
|
170
|
+
* Omitted ⇒ null (server-caller mode). */
|
|
148
171
|
caller?: HookCaller | null): ReadHookResult;
|
|
149
172
|
export interface ExprRef {
|
|
150
173
|
root: 'item' | 'before' | 'now' | 'caller';
|
|
@@ -157,6 +180,36 @@ export type InferredType = 'number' | 'string' | 'boolean' | 'null' | 'unknown';
|
|
|
157
180
|
/** Best-effort static type of an expression's result (its output). 'unknown' for
|
|
158
181
|
* member access / coalesce / mixed ternaries — never a false-positive mismatch. */
|
|
159
182
|
export declare function inferType(nd: Node): InferredType;
|
|
183
|
+
/** One declared field, as the typing pass needs it (cms field type + required). */
|
|
184
|
+
export interface HookFieldInfo {
|
|
185
|
+
type: string;
|
|
186
|
+
required?: boolean;
|
|
187
|
+
}
|
|
188
|
+
/** collection → field → declared info. */
|
|
189
|
+
export type HookFieldTypes = Record<string, Record<string, HookFieldInfo>>;
|
|
190
|
+
/** Type one parsed expression against one collection's declared fields.
|
|
191
|
+
* `errors` are shapes that can never work (refuse); `warnings` are shapes
|
|
192
|
+
* that work only for some stored values (json vs a scalar, `+` on a text
|
|
193
|
+
* field) and arithmetic on an optional field with no coalesce() — it faults
|
|
194
|
+
* the hook whenever the field is missing. `present` lists fields a derive sets first. */
|
|
195
|
+
export declare function checkHookExprTypes(root: Node, fields: Record<string, HookFieldInfo> | undefined, opts: {
|
|
196
|
+
kind: HookDef['kind'];
|
|
197
|
+
event: HookDef['event'];
|
|
198
|
+
present?: ReadonlySet<string> | undefined;
|
|
199
|
+
}): {
|
|
200
|
+
errors: string[];
|
|
201
|
+
warnings: string[];
|
|
202
|
+
};
|
|
203
|
+
/** Project declared collections (`{ <name>: { fields: { <f>: { type, required? } } } }`
|
|
204
|
+
* — the vxil.config / apply-bundle shape) onto HookFieldTypes. Malformed
|
|
205
|
+
* entries are skipped: no declared field means no verdict. */
|
|
206
|
+
export declare function hookFieldTypesOf(collections: unknown): HookFieldTypes;
|
|
207
|
+
/** Type every hook whose collection the caller declares (see checkHookExprTypes).
|
|
208
|
+
* Unparseable expressions are skipped — validateHookDef already reports them. */
|
|
209
|
+
export declare function checkHookTypes(hooks: Record<string, HookDef> | undefined, fieldTypes: HookFieldTypes): {
|
|
210
|
+
errors: string[];
|
|
211
|
+
warnings: string[];
|
|
212
|
+
};
|
|
160
213
|
export type HlKind = 'num' | 'str' | 'fn' | 'var' | 'kw' | 'op' | 'ident' | 'ws' | 'err';
|
|
161
214
|
/** Token-level highlighter over the FULL source (whitespace + bad chars kept),
|
|
162
215
|
* so a dashboard editor can render a colored overlay behind a textarea. */
|
|
@@ -35,4 +35,22 @@ interface CdcRuleLike {
|
|
|
35
35
|
}
|
|
36
36
|
/** Cross-field rule for CmsConfig.cdc (called from validateFeatureConfig). */
|
|
37
37
|
export declare function validateCdcConfig(cdc: Record<string, CdcRuleLike> | undefined): string[];
|
|
38
|
+
/** What a payload:'full' change-data rule must know about its target
|
|
39
|
+
* collection's read gates. `readRoleFields`: fields with a non-empty
|
|
40
|
+
* `readRoles`; `endUserAccess`: the collection's end-user access mode. */
|
|
41
|
+
export interface CdcCollectionGates {
|
|
42
|
+
readRoleFields?: readonly string[];
|
|
43
|
+
endUserAccess?: 'readwrite' | 'read' | 'none';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A `payload: 'full'` change-data rule inlines the stored row into a realtime
|
|
47
|
+
* frame that every subscriber of the channel receives — it does not go through
|
|
48
|
+
* the read projection. So it must never target a collection whose reads are
|
|
49
|
+
* gated: a field with `readRoles` (the frame would carry the gated value), or
|
|
50
|
+
* an `endUserAccess` of 'read' / 'none' (the frame would bypass the
|
|
51
|
+
* collection's end-user gate). Pure; one problem line per offending ENABLED
|
|
52
|
+
* rule (an unknown collection is skipped). The safe shape is `payload: 'ids'`
|
|
53
|
+
* and a re-read through the cms, where every gate applies.
|
|
54
|
+
*/
|
|
55
|
+
export declare function cdcFullFramesOverGatedCollections(cdc: unknown, collections: Record<string, CdcCollectionGates>): string[];
|
|
38
56
|
export {};
|
|
@@ -78,9 +78,10 @@ export declare const NotificationsConfigSchema: import("@sinclair/typebox").TObj
|
|
|
78
78
|
maxAttempts: import("@sinclair/typebox").TInteger;
|
|
79
79
|
backoff: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"exponential">, import("@sinclair/typebox").TLiteral<"linear">]>;
|
|
80
80
|
}>>;
|
|
81
|
-
suppression: import("@sinclair/typebox").TObject<{
|
|
81
|
+
suppression: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
82
82
|
softBounceThreshold: import("@sinclair/typebox").TInteger;
|
|
83
|
-
|
|
83
|
+
testRecipients: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
84
|
+
}>>;
|
|
84
85
|
rateLimit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
85
86
|
perDay: import("@sinclair/typebox").TInteger;
|
|
86
87
|
perTenantSec: import("@sinclair/typebox").TInteger;
|
|
@@ -217,6 +218,7 @@ declare const OidcProviderSchema: import("@sinclair/typebox").TObject<{
|
|
|
217
218
|
email: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
218
219
|
name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
219
220
|
roles: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
221
|
+
rolePrefix: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
220
222
|
}>>;
|
|
221
223
|
allowedDomains: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
222
224
|
autoLink: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
@@ -274,6 +276,7 @@ export declare const AuthConfigSchema: import("@sinclair/typebox").TObject<{
|
|
|
274
276
|
email: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
275
277
|
name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
276
278
|
roles: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
279
|
+
rolePrefix: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
277
280
|
}>>;
|
|
278
281
|
allowedDomains: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
279
282
|
autoLink: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
@@ -318,6 +321,8 @@ export declare const AuthConfigSchema: import("@sinclair/typebox").TObject<{
|
|
|
318
321
|
breachedPasswords: import("@sinclair/typebox").TBoolean;
|
|
319
322
|
captchaSecretRef: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
320
323
|
allowedRedirectOrigins: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
324
|
+
inviteOnly: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
325
|
+
allowedEmailDomains: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
321
326
|
}>>;
|
|
322
327
|
}>;
|
|
323
328
|
/** The security bag as persisted (present ⇒ leaf defaults applied). */
|
|
@@ -632,6 +637,7 @@ export declare const RagConfigSchema: import("@sinclair/typebox").TObject<{
|
|
|
632
637
|
topK: import("@sinclair/typebox").TInteger;
|
|
633
638
|
mode: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"hybrid">, import("@sinclair/typebox").TLiteral<"vector">, import("@sinclair/typebox").TLiteral<"keyword">]>;
|
|
634
639
|
minScore: import("@sinclair/typebox").TNumber;
|
|
640
|
+
minSimilarity: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
635
641
|
rerank: import("@sinclair/typebox").TBoolean;
|
|
636
642
|
}>;
|
|
637
643
|
boosts: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
@@ -832,6 +838,9 @@ export declare const TEST_RECIPIENT_EMAIL_RE: RegExp;
|
|
|
832
838
|
export declare const TEST_RECIPIENT_GLOB_RE: RegExp;
|
|
833
839
|
export declare const TEST_RECIPIENT_E164_RE: RegExp;
|
|
834
840
|
export declare function isTestRecipientPattern(entry: string): boolean;
|
|
841
|
+
/** The MAIL subset of the grammar (notifications suppression.testRecipients):
|
|
842
|
+
* an exact email or an `*@domain` glob. */
|
|
843
|
+
export declare function isMailTestRecipientPattern(entry: string): boolean;
|
|
835
844
|
/** True iff `identifier` (an email today) matches one configured entry:
|
|
836
845
|
* exact (case-insensitive) or the `*@domain` glob. +E.164 entries never match
|
|
837
846
|
* an email. */
|
package/dist/config.d.ts
CHANGED
|
@@ -121,6 +121,17 @@ export interface CollectionDef {
|
|
|
121
121
|
* `vxil push --allow-destructive`. Any other value fails `vxil plan` /
|
|
122
122
|
* `vxil push` (it never falls back to 'readwrite'). */
|
|
123
123
|
endUserAccess?: 'readwrite' | 'read' | 'none';
|
|
124
|
+
/** Role slugs one of which a VERIFIED end user must hold to WRITE this
|
|
125
|
+
* collection (create, update, $inc, delete, publish, transaction step,
|
|
126
|
+
* filtered delete, batch write, and an end-user delete's cascade into it) —
|
|
127
|
+
* otherwise `403 role_required`. Reads are unaffected. Roles are the
|
|
128
|
+
* session's verified role claims (its orgs roles — the
|
|
129
|
+
* same ones `readRoles` and `caller.roles` in hooks see). Server keys are
|
|
130
|
+
* never affected. Absent / `[]` = ungated. Up to 16 slugs
|
|
131
|
+
* (`^[a-z0-9][a-z0-9_-]{0,31}$`). Stored on the collection (model data, not
|
|
132
|
+
* a config leaf) and carried by BOTH push paths; widening (clearing, or
|
|
133
|
+
* adding a role) needs `vxil push --allow-destructive`. */
|
|
134
|
+
writeRoles?: string[];
|
|
124
135
|
/** Per-record action buttons (guide ch. 4): `[{ key, label, fn }]`, stored on
|
|
125
136
|
* the collection (model data, not a config leaf). The dashboard renders one
|
|
126
137
|
* button per action on each record row; `POST /v1/cms/items/:coll/:id/actions/
|