@vxil/config 0.2.0 → 0.3.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/dist/index.d.ts +45 -6
- package/package.json +2 -2
- package/src/index.ts +47 -6
package/dist/index.d.ts
CHANGED
|
@@ -23,9 +23,34 @@ export interface FieldDef {
|
|
|
23
23
|
* cannot reconcile surface as a visible plan warning, never a silent no-op. */
|
|
24
24
|
unique?: boolean;
|
|
25
25
|
/** Per-relation-field delete behavior (cms.md §11): bounded, atomic fan-out
|
|
26
|
-
* when the referenced item is deleted
|
|
27
|
-
*
|
|
28
|
-
|
|
26
|
+
* when the referenced item is deleted — or `restrict` (§11.1), which REFUSES
|
|
27
|
+
* the delete with 409 `referenced` while a live reference exists. Same
|
|
28
|
+
* carriage + reconcile behavior as `unique`. */
|
|
29
|
+
onDelete?: 'cascade' | 'set_null' | 'restrict';
|
|
30
|
+
/** FIELD-LEVEL READ SECURITY (cms.md §18): the end-user ORG ROLE slugs allowed
|
|
31
|
+
* to READ this field. Omitted/`[]` = ungated (every caller sees it — today's
|
|
32
|
+
* behavior). A non-empty list is FAIL-SAFE: in VERIFIED end-user mode the
|
|
33
|
+
* field is OMITTED from every read (get / list / query / `$expand` at any
|
|
34
|
+
* depth / the write-response echo / read-hook payloads) unless the session's
|
|
35
|
+
* verified `roles` claim intersects the list, it is UNQUERYABLE (a filter /
|
|
36
|
+
* sort / group-by naming it is a 422, so it cannot be a value oracle), and it
|
|
37
|
+
* is NEVER served on the anonymous public lane. A trusted SERVER caller (an
|
|
38
|
+
* API key with no end-user session) still sees every field — this gates END
|
|
39
|
+
* USERS, not the tenant. ≤16 entries, each `^[a-z0-9][a-z0-9_-]{0,31}$` (the
|
|
40
|
+
* `orgs` role alphabet). The gate
|
|
41
|
+
* never affects WRITES. Carried by BOTH push paths and alterable in place. */
|
|
42
|
+
readRoles?: string[];
|
|
43
|
+
}
|
|
44
|
+
/** One config-declared per-record ACTION button (cms.md §17): exactly ONE
|
|
45
|
+
* human-initiated step — pressing it invokes the deployed tenant function `fn`
|
|
46
|
+
* with `{ collection, item_id, action, actor, item }` and returns its result.
|
|
47
|
+
* No conditions, no chaining, no scheduling. `key` matches
|
|
48
|
+
* /^[a-z][a-z0-9_]{0,31}$/, `fn` is a function name (/^[a-z][a-z0-9-]{0,47}$/),
|
|
49
|
+
* ≤8 per collection. */
|
|
50
|
+
export interface CmsActionDef {
|
|
51
|
+
key: string;
|
|
52
|
+
label: string;
|
|
53
|
+
fn: string;
|
|
29
54
|
}
|
|
30
55
|
/** The CANONICAL field-attribute name set (config-side spelling), DERIVED from
|
|
31
56
|
* `FieldDef` itself so it can never drift from the authoring type: it is every
|
|
@@ -66,10 +91,24 @@ export interface CollectionDef {
|
|
|
66
91
|
* with a regression test on each — an attribute read by no enforcing path ships
|
|
67
92
|
* inert (the validation.unique / owner_field precedent). */
|
|
68
93
|
public?: boolean;
|
|
94
|
+
/** Per-record action buttons (cms.md §17): `[{ key, label, fn }]`, stored on
|
|
95
|
+
* the collection (model data, not a config leaf). The dashboard renders one
|
|
96
|
+
* button per action on each record row; `POST /v1/cms/items/:coll/:id/actions/
|
|
97
|
+
* :key` invokes `fn` once. Carried by BOTH push paths (create + reconcile on
|
|
98
|
+
* existing collections — config is the source of truth; `[]`/absent clears). */
|
|
99
|
+
actions?: CmsActionDef[];
|
|
69
100
|
}
|
|
101
|
+
/** The auth lifecycle events an `authHook` binding may subscribe to (F4-30,
|
|
102
|
+
* auth wave 2026-09-10) — a CLOSED union; ONE event per binding, one audit
|
|
103
|
+
* event → one function invocation, no branching inside vxil:
|
|
104
|
+
* user.created — auth.user.created { user_id, method, is_anonymous }
|
|
105
|
+
* session.created — auth.session.created { user_id, session_id }
|
|
106
|
+
* session.revoked — auth.session.revoked { user_id, session_id, reason }
|
|
107
|
+
* signin.failure — auth.signin.failure { email_hash | user_id, reason } */
|
|
108
|
+
export type AuthHookEvent = 'user.created' | 'session.created' | 'session.revoked' | 'signin.failure';
|
|
70
109
|
/** A function trigger (the §7.3 crossing). cmsHook fires on a CMS write;
|
|
71
|
-
* authHook
|
|
72
|
-
* at-least-once, ~1min fanout latency). */
|
|
110
|
+
* authHook fires on ONE auth lifecycle event (default `user.created` — the
|
|
111
|
+
* post-signup hook; at-least-once, ~1min fanout latency). */
|
|
73
112
|
export type FunctionTrigger = {
|
|
74
113
|
kind: 'http';
|
|
75
114
|
path?: string;
|
|
@@ -88,7 +127,7 @@ export type FunctionTrigger = {
|
|
|
88
127
|
event: 'beforeCreate' | 'beforeUpdate' | 'beforeWrite';
|
|
89
128
|
} | {
|
|
90
129
|
kind: 'authHook';
|
|
91
|
-
event?:
|
|
130
|
+
event?: AuthHookEvent;
|
|
92
131
|
};
|
|
93
132
|
/** A deployed tenant function — source in functions/, deployed on `vxil push`. */
|
|
94
133
|
export interface FunctionDef {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vxil/config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "defineConfig() — one typed source of truth for a whole vxil backend (features + CMS schema + functions + secret refs + seed). INTERNAL workspace package: bundled into the published `vxil` package's `vxil/config` subpath, not published separately.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://vxil.com",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"src"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@vxil/feature-configs": "0.
|
|
25
|
+
"@vxil/feature-configs": "0.3.0"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -42,9 +42,35 @@ export interface FieldDef {
|
|
|
42
42
|
* cannot reconcile surface as a visible plan warning, never a silent no-op. */
|
|
43
43
|
unique?: boolean;
|
|
44
44
|
/** Per-relation-field delete behavior (cms.md §11): bounded, atomic fan-out
|
|
45
|
-
* when the referenced item is deleted
|
|
46
|
-
*
|
|
47
|
-
|
|
45
|
+
* when the referenced item is deleted — or `restrict` (§11.1), which REFUSES
|
|
46
|
+
* the delete with 409 `referenced` while a live reference exists. Same
|
|
47
|
+
* carriage + reconcile behavior as `unique`. */
|
|
48
|
+
onDelete?: 'cascade' | 'set_null' | 'restrict';
|
|
49
|
+
/** FIELD-LEVEL READ SECURITY (cms.md §18): the end-user ORG ROLE slugs allowed
|
|
50
|
+
* to READ this field. Omitted/`[]` = ungated (every caller sees it — today's
|
|
51
|
+
* behavior). A non-empty list is FAIL-SAFE: in VERIFIED end-user mode the
|
|
52
|
+
* field is OMITTED from every read (get / list / query / `$expand` at any
|
|
53
|
+
* depth / the write-response echo / read-hook payloads) unless the session's
|
|
54
|
+
* verified `roles` claim intersects the list, it is UNQUERYABLE (a filter /
|
|
55
|
+
* sort / group-by naming it is a 422, so it cannot be a value oracle), and it
|
|
56
|
+
* is NEVER served on the anonymous public lane. A trusted SERVER caller (an
|
|
57
|
+
* API key with no end-user session) still sees every field — this gates END
|
|
58
|
+
* USERS, not the tenant. ≤16 entries, each `^[a-z0-9][a-z0-9_-]{0,31}$` (the
|
|
59
|
+
* `orgs` role alphabet). The gate
|
|
60
|
+
* never affects WRITES. Carried by BOTH push paths and alterable in place. */
|
|
61
|
+
readRoles?: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** One config-declared per-record ACTION button (cms.md §17): exactly ONE
|
|
65
|
+
* human-initiated step — pressing it invokes the deployed tenant function `fn`
|
|
66
|
+
* with `{ collection, item_id, action, actor, item }` and returns its result.
|
|
67
|
+
* No conditions, no chaining, no scheduling. `key` matches
|
|
68
|
+
* /^[a-z][a-z0-9_]{0,31}$/, `fn` is a function name (/^[a-z][a-z0-9-]{0,47}$/),
|
|
69
|
+
* ≤8 per collection. */
|
|
70
|
+
export interface CmsActionDef {
|
|
71
|
+
key: string;
|
|
72
|
+
label: string;
|
|
73
|
+
fn: string;
|
|
48
74
|
}
|
|
49
75
|
|
|
50
76
|
/** The CANONICAL field-attribute name set (config-side spelling), DERIVED from
|
|
@@ -87,18 +113,33 @@ export interface CollectionDef {
|
|
|
87
113
|
* with a regression test on each — an attribute read by no enforcing path ships
|
|
88
114
|
* inert (the validation.unique / owner_field precedent). */
|
|
89
115
|
public?: boolean;
|
|
116
|
+
/** Per-record action buttons (cms.md §17): `[{ key, label, fn }]`, stored on
|
|
117
|
+
* the collection (model data, not a config leaf). The dashboard renders one
|
|
118
|
+
* button per action on each record row; `POST /v1/cms/items/:coll/:id/actions/
|
|
119
|
+
* :key` invokes `fn` once. Carried by BOTH push paths (create + reconcile on
|
|
120
|
+
* existing collections — config is the source of truth; `[]`/absent clears). */
|
|
121
|
+
actions?: CmsActionDef[];
|
|
90
122
|
}
|
|
91
123
|
|
|
124
|
+
/** The auth lifecycle events an `authHook` binding may subscribe to (F4-30,
|
|
125
|
+
* auth wave 2026-09-10) — a CLOSED union; ONE event per binding, one audit
|
|
126
|
+
* event → one function invocation, no branching inside vxil:
|
|
127
|
+
* user.created — auth.user.created { user_id, method, is_anonymous }
|
|
128
|
+
* session.created — auth.session.created { user_id, session_id }
|
|
129
|
+
* session.revoked — auth.session.revoked { user_id, session_id, reason }
|
|
130
|
+
* signin.failure — auth.signin.failure { email_hash | user_id, reason } */
|
|
131
|
+
export type AuthHookEvent = 'user.created' | 'session.created' | 'session.revoked' | 'signin.failure';
|
|
132
|
+
|
|
92
133
|
/** A function trigger (the §7.3 crossing). cmsHook fires on a CMS write;
|
|
93
|
-
* authHook
|
|
94
|
-
* at-least-once, ~1min fanout latency). */
|
|
134
|
+
* authHook fires on ONE auth lifecycle event (default `user.created` — the
|
|
135
|
+
* post-signup hook; at-least-once, ~1min fanout latency). */
|
|
95
136
|
export type FunctionTrigger =
|
|
96
137
|
| { kind: 'http'; path?: string }
|
|
97
138
|
| { kind: 'cron'; schedule: string }
|
|
98
139
|
| { kind: 'queue'; source: string }
|
|
99
140
|
| { kind: 'webhook'; source: string }
|
|
100
141
|
| { kind: 'cmsHook'; collection: string; event: 'beforeCreate' | 'beforeUpdate' | 'beforeWrite' }
|
|
101
|
-
| { kind: 'authHook'; event?:
|
|
142
|
+
| { kind: 'authHook'; event?: AuthHookEvent };
|
|
102
143
|
|
|
103
144
|
/** A deployed tenant function — source in functions/, deployed on `vxil push`. */
|
|
104
145
|
export interface FunctionDef {
|