@vxil/cli 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 techmaker.io
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # vxil
2
+
3
+ The [Vxil](https://vxil.com) CLI — config-as-code for backend building blocks you enable in one line.
4
+
5
+ ```bash
6
+ npx @vxil/cli quickstart # create a tenant + scaffold vxil.config.ts
7
+ npx @vxil/cli push # apply your config (features, cms collections, functions)
8
+ npx @vxil/cli gen # generate a typed client + MCP manifest for YOUR backend
9
+ npx @vxil/cli functions dev fn # run a tenant function locally against the remote edge
10
+ npx @vxil/cli doctor # sanity-check keys, URLs, reachability
11
+ ```
12
+
13
+ Config-as-code lives in `vxil.config.ts`, typed via the bundled `vxil/config` subpath:
14
+
15
+ ```ts
16
+ import { defineConfig } from '@vxil/config';
17
+
18
+ export default defineConfig({
19
+ features: {
20
+ auth: { enabled: true, methods: ['email_otp'] },
21
+ cms: { enabled: true, collections: { tasks: { fields: { title: 'text', done: 'boolean' } } } },
22
+ notifications: { enabled: true, provider: 'resend' },
23
+ },
24
+ });
25
+ ```
26
+
27
+ Verbs: `init quickstart push pull diff gen secrets env seed export import functions cms dev doctor versions rollback link apply migrate try`.
28
+
29
+ Docs: [vxil.com](https://vxil.com) · Dashboard: [vxil.com/dashboard](https://vxil.com/dashboard)
30
+
31
+ MIT © techmaker.io
@@ -0,0 +1,167 @@
1
+ export declare const HOOK_LIMITS: {
2
+ readonly maxSourceLen: 2000;
3
+ readonly maxTokens: 600;
4
+ readonly maxNodes: 250;
5
+ readonly maxDepth: 40;
6
+ readonly maxEvalSteps: 5000;
7
+ readonly maxStringLen: 4096;
8
+ readonly maxNumberMagnitude: 1000000000000000;
9
+ readonly maxReadEvalStepsPerRequest: 250000;
10
+ readonly maxReadHooksPerCollection: 10;
11
+ };
12
+ /** The ONLY root variables an expression may reference. `caller` is the VERIFIED
13
+ * end-user principal (docs/end-user-principals-design.md §5.6) — read-only and
14
+ * populated ONLY on the READ path (runReadHooks); on the write path and in
15
+ * server-caller mode it is null, exactly like `before` on a create. It carries
16
+ * `caller.endUserId` (the verified session sub, or null) and `caller.principal`
17
+ * ('end_user' | 'tenant'). It is the missing "caller/session context" a read
18
+ * hook keys on to redact/derive per-viewer. */
19
+ export declare const HOOK_ROOT_VARS: readonly ["item", "before", "now", "caller"];
20
+ /** The ONLY callable functions. Each is pure + deterministic + bounded. */
21
+ 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"];
22
+ export type Node = {
23
+ t: 'num';
24
+ v: number;
25
+ } | {
26
+ t: 'str';
27
+ v: string;
28
+ } | {
29
+ t: 'bool';
30
+ v: boolean;
31
+ } | {
32
+ t: 'null';
33
+ } | {
34
+ t: 'var';
35
+ name: string;
36
+ } | {
37
+ t: 'member';
38
+ obj: Node;
39
+ prop: string;
40
+ } | {
41
+ t: 'unary';
42
+ op: '!' | '-';
43
+ arg: Node;
44
+ } | {
45
+ t: 'bin';
46
+ op: BinOp;
47
+ l: Node;
48
+ r: Node;
49
+ } | {
50
+ t: 'tern';
51
+ c: Node;
52
+ a: Node;
53
+ b: Node;
54
+ } | {
55
+ t: 'call';
56
+ fn: string;
57
+ args: Node[];
58
+ };
59
+ type BinOp = '||' | '&&' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '+' | '-' | '*' | '/' | '%';
60
+ export declare class HookParseError extends Error {
61
+ }
62
+ export declare class HookEvalError extends Error {
63
+ }
64
+ /** Thrown by a `validate` hook whose expression is falsy — maps to a clean 422. */
65
+ export declare class HookRejection extends Error {
66
+ }
67
+ /** Parse a hook expression into an AST. Throws HookParseError on any malformed input. */
68
+ export declare function parseExpr(src: string): Node;
69
+ /** Walk the AST and reject anything outside the allow-list, plus depth bounds.
70
+ * Returns an array of human-readable errors (empty = safe). Pure, no eval. */
71
+ export declare function validateAst(root: Node): string[];
72
+ /** The VERIFIED caller/session context for read hooks (design §5.6). Read-only;
73
+ * present only on the read path in end-user mode. In server mode / write path
74
+ * the whole object is null. */
75
+ export interface HookCaller {
76
+ /** the verified end-user session sub, or null in server-caller mode. */
77
+ endUserId: string | null;
78
+ /** 'end_user' when a session was verified at the edge, else 'tenant'. */
79
+ principal: 'end_user' | 'tenant';
80
+ }
81
+ export interface HookContext {
82
+ item: Record<string, unknown>;
83
+ before: Record<string, unknown> | null;
84
+ now: string;
85
+ /** verified caller/session context (read path only); null otherwise. */
86
+ caller?: HookCaller | null;
87
+ }
88
+ /** A mutable step counter SHARED across many evalExpr calls (the read path's
89
+ * whole-page budget). Checked against maxReadEvalStepsPerRequest. */
90
+ export interface EvalBudget {
91
+ steps: number;
92
+ }
93
+ /** Evaluate a validated AST against the context. Bounded by a per-expression
94
+ * eval-step budget, plus an optional SHARED budget spanning many evaluations
95
+ * (runReadHooks passes one per request so a page of rows shares one bound). */
96
+ export declare function evalExpr(root: Node, ctx: HookContext, shared?: EvalBudget): unknown;
97
+ export interface HookDef {
98
+ collection: string;
99
+ event: 'beforeCreate' | 'beforeUpdate' | 'beforeWrite' | 'beforeRead' | 'afterRead';
100
+ kind: 'validate' | 'derive' | 'redact';
101
+ expr: string;
102
+ field?: string;
103
+ message?: string;
104
+ enabled?: boolean;
105
+ }
106
+ /** Config-WRITE-time validation of one hook (the anti-malice gate). Returns
107
+ * errors (empty = safe to publish). Does NOT need the collection's field list. */
108
+ export declare function validateHookDef(id: string, def: HookDef): string[];
109
+ /** Validate a whole hooks bag (CmsConfig.hooks) at config time. */
110
+ export declare function validateHooksConfig(hooks: Record<string, HookDef> | undefined): string[];
111
+ export interface HookRunResult {
112
+ data: Record<string, unknown>;
113
+ derived: string[];
114
+ }
115
+ /** Run all matching write hooks for (collection, phase) over the candidate data.
116
+ * Mutates a CLONE: derive hooks set fields; a validate hook that returns falsy
117
+ * throws HookRejection (→ the caller maps to a 422 and the tx rolls back).
118
+ * 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): HookRunResult;
120
+ export interface ReadHookResult {
121
+ /** Surviving rows in input order. Each is a SHALLOW COPY with a copied `data`
122
+ * bag — the caller's rows are never mutated and nothing here is persisted. */
123
+ rows: Record<string, unknown>[];
124
+ /** Rows removed by a falsy beforeRead validate (the visibility filter). */
125
+ dropped: number;
126
+ }
127
+ /** Run the tenant's READ hooks for a collection over a page of PROJECTED rows
128
+ * (each row an item envelope whose `data` already carries computed fields and
129
+ * $expand results — that is the cross-field surface an expression reads).
130
+ *
131
+ * Per row, in order:
132
+ * 1. beforeRead `validate` — a falsy expression DROPS the row (visibility
133
+ * filter; a dropped row is indistinguishable from a missing one).
134
+ * 2. afterRead `derive` — sets data[field] on the OUTPUT copy only
135
+ * (scalar-only, same guard as the write path; NEVER persisted).
136
+ * 3. afterRead `redact` — deletes data[field] when the expression is truthy.
137
+ *
138
+ * Context per row: { item: the projected data, before: null, now: ONE ISO
139
+ * string for the whole request } — deterministic across the page. The entire
140
+ * page shares ONE eval-step budget (HOOK_LIMITS.maxReadEvalStepsPerRequest),
141
+ * which is the list path's per-request CPU bound; exhausting it throws
142
+ * HookEvalError (→ a clean 422, never a pinned isolate). Write events never
143
+ * fire here, and read events never fire in runWriteHooks. */
144
+ export declare function runReadHooks(hooks: Record<string, HookDef> | undefined, collection: string, rows: ReadonlyArray<Record<string, unknown>>, now: string,
145
+ /** verified caller/session context (design §5.6) — read-only, exposed to
146
+ * expressions as `caller.endUserId` / `caller.principal`. Omitted ⇒ null
147
+ * (server-caller mode). */
148
+ caller?: HookCaller | null): ReadHookResult;
149
+ export interface ExprRef {
150
+ root: 'item' | 'before' | 'now' | 'caller';
151
+ /** dotted path under the root, e.g. item.profile.age → ['profile','age']. */
152
+ path: string[];
153
+ }
154
+ /** Collect every variable reference an expression reads (its inputs). */
155
+ export declare function extractRefs(root: Node): ExprRef[];
156
+ export type InferredType = 'number' | 'string' | 'boolean' | 'null' | 'unknown';
157
+ /** Best-effort static type of an expression's result (its output). 'unknown' for
158
+ * member access / coalesce / mixed ternaries — never a false-positive mismatch. */
159
+ export declare function inferType(nd: Node): InferredType;
160
+ export type HlKind = 'num' | 'str' | 'fn' | 'var' | 'kw' | 'op' | 'ident' | 'ws' | 'err';
161
+ /** Token-level highlighter over the FULL source (whitespace + bad chars kept),
162
+ * so a dashboard editor can render a colored overlay behind a textarea. */
163
+ export declare function highlightTokens(src: string): Array<{
164
+ value: string;
165
+ kind: HlKind;
166
+ }>;
167
+ export {};