@vxil/cli 0.16.1 → 0.18.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.
@@ -64,6 +64,17 @@ export declare class HookEvalError extends Error {
64
64
  /** Thrown by a `validate` hook whose expression is falsy — maps to a clean 422. */
65
65
  export declare class HookRejection extends Error {
66
66
  }
67
+ /** A WRITE `validate` hook whose expression could not be evaluated (null
68
+ * arithmetic, a missing operand, a bad date …). Still a HookEvalError — the
69
+ * write is refused, never let through — but it carries the hook's id and its
70
+ * declared `message`, so the API can show the end user the tenant's own copy
71
+ * while keeping the engine detail for the developer. `message` stays the
72
+ * engine detail. */
73
+ export declare class HookValidateFault extends HookEvalError {
74
+ readonly hookId: string;
75
+ readonly tenantMessage: string | undefined;
76
+ constructor(hookId: string, tenantMessage: string | undefined, detail: string);
77
+ }
67
78
  /** Parse a hook expression into an AST. Throws HookParseError on any malformed input. */
68
79
  export declare function parseExpr(src: string): Node;
69
80
  /** Walk the AST and reject anything outside the allow-list, plus depth bounds.
@@ -157,6 +168,36 @@ export type InferredType = 'number' | 'string' | 'boolean' | 'null' | 'unknown';
157
168
  /** Best-effort static type of an expression's result (its output). 'unknown' for
158
169
  * member access / coalesce / mixed ternaries — never a false-positive mismatch. */
159
170
  export declare function inferType(nd: Node): InferredType;
171
+ /** One declared field, as the typing pass needs it (cms field type + required). */
172
+ export interface HookFieldInfo {
173
+ type: string;
174
+ required?: boolean;
175
+ }
176
+ /** collection → field → declared info. */
177
+ export type HookFieldTypes = Record<string, Record<string, HookFieldInfo>>;
178
+ /** Type one parsed expression against one collection's declared fields.
179
+ * `errors` are shapes that can never work (refuse); `warnings` are shapes
180
+ * that work only for some stored values (json vs a scalar, `+` on a text
181
+ * field) and arithmetic on an optional field with no coalesce() — it faults
182
+ * the hook whenever the field is missing. `present` lists fields a derive sets first. */
183
+ export declare function checkHookExprTypes(root: Node, fields: Record<string, HookFieldInfo> | undefined, opts: {
184
+ kind: HookDef['kind'];
185
+ event: HookDef['event'];
186
+ present?: ReadonlySet<string> | undefined;
187
+ }): {
188
+ errors: string[];
189
+ warnings: string[];
190
+ };
191
+ /** Project declared collections (`{ <name>: { fields: { <f>: { type, required? } } } }`
192
+ * — the vxil.config / apply-bundle shape) onto HookFieldTypes. Malformed
193
+ * entries are skipped: no declared field means no verdict. */
194
+ export declare function hookFieldTypesOf(collections: unknown): HookFieldTypes;
195
+ /** Type every hook whose collection the caller declares (see checkHookExprTypes).
196
+ * Unparseable expressions are skipped — validateHookDef already reports them. */
197
+ export declare function checkHookTypes(hooks: Record<string, HookDef> | undefined, fieldTypes: HookFieldTypes): {
198
+ errors: string[];
199
+ warnings: string[];
200
+ };
160
201
  export type HlKind = 'num' | 'str' | 'fn' | 'var' | 'kw' | 'op' | 'ident' | 'ws' | 'err';
161
202
  /** Token-level highlighter over the FULL source (whitespace + bad chars kept),
162
203
  * so a dashboard editor can render a colored overlay behind a textarea. */
@@ -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;
@@ -832,6 +833,9 @@ export declare const TEST_RECIPIENT_EMAIL_RE: RegExp;
832
833
  export declare const TEST_RECIPIENT_GLOB_RE: RegExp;
833
834
  export declare const TEST_RECIPIENT_E164_RE: RegExp;
834
835
  export declare function isTestRecipientPattern(entry: string): boolean;
836
+ /** The MAIL subset of the grammar (notifications suppression.testRecipients):
837
+ * an exact email or an `*@domain` glob. */
838
+ export declare function isMailTestRecipientPattern(entry: string): boolean;
835
839
  /** True iff `identifier` (an email today) matches one configured entry:
836
840
  * exact (case-insensitive) or the `*@domain` glob. +E.164 entries never match
837
841
  * an email. */
package/dist/config.d.ts CHANGED
@@ -106,6 +106,21 @@ export interface CollectionDef {
106
106
  * with a regression test on each — an attribute read by no enforcing path ships
107
107
  * inert (the validation.unique / owner_field precedent). */
108
108
  public?: boolean;
109
+ /** What a VERIFIED end user (a thin-client key + a session) may do on this
110
+ * collection (https://vxil.com/docs/guide/09-security-and-multitenancy):
111
+ * 'readwrite' (default) — read and write (owner-scoped when ownerField is set);
112
+ * 'read' — read only (owner-scoped as above); every end-user write
113
+ * (create, update, $inc, delete, publish, transaction step,
114
+ * filtered delete, batch write — and a delete elsewhere
115
+ * whose on_delete cascade / set_null would change a row
116
+ * here) is `403 server_only`;
117
+ * 'none' — no end-user reads or writes at all, even with ownerField.
118
+ * Server keys (and platform writes such as a jobs status mirror) are never
119
+ * affected. Stored on the collection (model data, not a config leaf) and
120
+ * carried by BOTH push paths; widening ('read'/'none' → 'readwrite') needs
121
+ * `vxil push --allow-destructive`. Any other value fails `vxil plan` /
122
+ * `vxil push` (it never falls back to 'readwrite'). */
123
+ endUserAccess?: 'readwrite' | 'read' | 'none';
109
124
  /** Per-record action buttons (guide ch. 4): `[{ key, label, fn }]`, stored on
110
125
  * the collection (model data, not a config leaf). The dashboard renders one
111
126
  * button per action on each record row; `POST /v1/cms/items/:coll/:id/actions/