@vxil/cli 0.27.0 → 0.29.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.
@@ -52,6 +52,26 @@ export declare function isOrgChannelTemplate(channel: string): boolean;
52
52
  * `org:<id>`). Null (publish nothing) for a non-template or an org id that is
53
53
  * not a string matching ORG_CHANNEL_ID_RE — fail closed. */
54
54
  export declare function resolveOrgChannel(template: string, orgId: unknown): string | null;
55
+ /** MX-1022-CDC-OWNER-CHANNEL: the owner template token — one WHOLE segment of a
56
+ * cdc `channel` on an owner-scoped (ownerField) collection. Each row publishes
57
+ * to its owner's private channel `user:<owner>:<rest>` (realtime-v1
58
+ * userChannelDenied binds an end-user token for a `user:` channel to its
59
+ * verified id). Never combined with `{org}` in one channel. */
60
+ export declare const CDC_OWNER_TOKEN = "{owner}";
61
+ /** Every resolved owner channel starts with this prefix (realtime-v1
62
+ * token.ts USER_CHANNEL_PREFIX; the `cdc-gated-fields` gate pins both equal). */
63
+ export declare const USER_CHANNEL_PREFIX = "user:";
64
+ /** An owner id that may enter a channel name: no ':' / '@' (realtime refuses
65
+ * such ids every `user:` channel anyway), ≤ 128 chars. */
66
+ export declare const OWNER_CHANNEL_ID_RE: RegExp;
67
+ /** Is `channel` an owner template — exactly one segment equal to `{owner}`,
68
+ * every other segment a non-empty channel-alphabet run, ≤ 128 chars? */
69
+ export declare function isOwnerChannelTemplate(channel: string): boolean;
70
+ /** Resolve an owner template for one row: drop the `{owner}` segment and prefix
71
+ * `user:<id>:` (`meals:{owner}` → `user:<id>:meals`). Null (publish nothing)
72
+ * for a non-template, an owner id that is not a string matching
73
+ * OWNER_CHANNEL_ID_RE, or a result past realtime's 200-char cap — fail closed. */
74
+ export declare function resolveOwnerChannel(template: string, ownerId: unknown): string | null;
55
75
  /** Cross-field rule for CmsConfig.cdc (called from validateFeatureConfig). */
56
76
  export declare function validateCdcConfig(cdc: Record<string, CdcRuleLike> | undefined): string[];
57
77
  /** What a payload:'full' change-data rule must know about its target
@@ -90,4 +110,14 @@ export declare function cdcFullFramesOverGatedCollections(cdc: unknown, collecti
90
110
  * Pure; one problem line per offending ENABLED rule.
91
111
  */
92
112
  export declare function cdcFixedChannelsOverOrgCollections(cdc: unknown, collections: Record<string, CdcCollectionGates>): string[];
113
+ /**
114
+ * MX-1022-CDC-OWNER-CHANNEL: an `{owner}` rule is valid only on an owner-scoped
115
+ * collection (a live `ownerField`) that is NOT a workspace collection: a
116
+ * `user:` channel skips realtime's active-org check, so a member removed from a
117
+ * workspace who still owns rows there would keep receiving them — `{org}`
118
+ * serves workspace collections. The cms runtime floor (cdc.ts cdcChannelFor)
119
+ * publishes nothing for such a rule at every door that cannot check. Pure; one
120
+ * problem line per offending ENABLED rule (an unknown collection is skipped).
121
+ */
122
+ export declare function cdcOwnerRulesOverNonOwnerCollections(cdc: unknown, collections: Record<string, CdcCollectionGates>): string[];
93
123
  export {};
@@ -313,6 +313,7 @@ export declare const AuthConfigSchema: import("@sinclair/typebox").TObject<{
313
313
  resendCooldownSec: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
314
314
  }>>;
315
315
  registryAdopt: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
316
+ userIdFormat: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"ulid">, import("@sinclair/typebox").TLiteral<"uuid">]>>;
316
317
  otp: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
317
318
  enabled: import("@sinclair/typebox").TBoolean;
318
319
  codeTtlMinutes: import("@sinclair/typebox").TInteger;
@@ -710,6 +711,7 @@ export declare const AiConfigSchema: import("@sinclair/typebox").TObject<{
710
711
  system: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
711
712
  schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnknown>>;
712
713
  }>>>;
714
+ testMarkers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
713
715
  }>;
714
716
  export type AiConfig = Static<typeof AiConfigSchema>;
715
717
  export declare const RagConfigSchema: import("@sinclair/typebox").TObject<{
@@ -805,6 +807,13 @@ export declare const PaymentsConfigSchema: import("@sinclair/typebox").TObject<{
805
807
  deferDowngrades: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
806
808
  subPeriodGrants: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
807
809
  }>>;
810
+ refunds: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
811
+ cancelAtProvider: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"off">, import("@sinclair/typebox").TLiteral<"period_end">, import("@sinclair/typebox").TLiteral<"immediately">]>>;
812
+ }>>;
813
+ disputes: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
814
+ cancelAtProvider: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"off">, import("@sinclair/typebox").TLiteral<"period_end">]>>;
815
+ }>>;
816
+ shadow: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
808
817
  }>;
809
818
  export type PaymentsConfig = Static<typeof PaymentsConfigSchema>;
810
819
  export declare const FunctionsConfigSchema: import("@sinclair/typebox").TObject<{
@@ -916,12 +925,12 @@ export type AuthHookEvent = (typeof AUTH_HOOK_EVENTS)[number];
916
925
  /** The events a `cmsHook` function binding may name — the closed union
917
926
  * `packages/config` types on the `cmsHook` trigger, and the keys of the
918
927
  * functions-v1 receiver's CMS_HOOK_EVENTS_FOR (beforeCreate → created,
919
- * beforeUpdate → updated, beforeWrite → both, afterPublish → published). Omitted = beforeWrite. Anything
928
+ * beforeUpdate → updated, beforeWrite → both, afterPublish → published, afterDelete → deleted). Omitted = beforeWrite. Anything
920
929
  * else is refused at config write (E-CMSHOOK, 2026-10-01): an unknown name used
921
930
  * to fall back to beforeWrite at delivery, so a typo like 'afterCreate' or
922
931
  * 'beforeDelete' silently subscribed the function to creates AND updates.
923
932
  * A CI gate pins every copy to this list. */
924
- export declare const CMS_HOOK_EVENTS: readonly ["beforeCreate", "beforeUpdate", "beforeWrite", "afterPublish"];
933
+ export declare const CMS_HOOK_EVENTS: readonly ["beforeCreate", "beforeUpdate", "beforeWrite", "afterPublish", "afterDelete"];
925
934
  export type CmsHookEvent = (typeof CMS_HOOK_EVENTS)[number];
926
935
  /** The ONE refusal text for an unknown cmsHook binding event (the config
927
936
  * validator and the deploy route's early 422 both use it). */
@@ -1014,3 +1023,11 @@ export declare function isDeclaredRetainDays(v: unknown): v is number | null | u
1014
1023
  * collection that change applies only under allow-destructive; lengthening
1015
1024
  * or clearing keeps more and always applies. */
1016
1025
  export declare function retainDaysShortens(declared: number | null, remote: number | null): boolean;
1026
+ /** = cms-v1 core.ts DELETED_RETAIN_DAYS_MAX (a parity test pins the two). */
1027
+ export declare const CMS_DELETED_RETAIN_DAYS_MAX = 29;
1028
+ export declare function isDeclaredDeletedRetainDays(v: unknown): v is number | null | undefined;
1029
+ export declare function isDeclaredOnOwnerErase(v: unknown): v is 'delete' | 'keep' | undefined;
1030
+ /** Does moving from `remote` to `declared` make an erase delete MORE (turning
1031
+ * 'delete' on)? On an existing collection that applies only under
1032
+ * allow-destructive; turning it off always applies. */
1033
+ export declare function onOwnerEraseWidens(declared: 'delete' | 'keep', remote: 'delete' | 'keep'): boolean;
package/dist/config.d.ts CHANGED
@@ -140,6 +140,24 @@ export interface CollectionDef {
140
140
  * shortening it needs `vxil push --allow-destructive` (lengthening or
141
141
  * `null` always applies). Carried by BOTH push paths. */
142
142
  retainDays?: number | null;
143
+ /** Recovery window for DELETED items, in days (guide ch. 4): a soft-deleted
144
+ * item stays restorable this long before the nightly cleanup purges it for
145
+ * good. An integer 0..29 (0 = purged at the next nightly run, ≤ 24 h), or
146
+ * `null` for the platform default of 30 days. ABSENT = not managed by
147
+ * config. Shortening it on an existing collection needs
148
+ * `vxil push --allow-destructive`. Carried by BOTH push paths. */
149
+ deletedRetainDays?: number | null;
150
+ /** What a GDPR erase of a user does to the items they OWN here (guide ch. 9;
151
+ * needs `ownerField` — "owner" is whoever that field names):
152
+ * 'keep' (default) — the items stay; the owner id resolves to the
153
+ * erased tombstone user;
154
+ * 'delete' — the erase hard-deletes them (live and deleted, unique values
155
+ * freed) in the same transaction. Relations elsewhere are not
156
+ * cascaded (they keep a dangling id).
157
+ * ABSENT = not managed by config. Turning 'delete' on for an existing
158
+ * collection needs `vxil push --allow-destructive`. Carried by BOTH push
159
+ * paths. */
160
+ onOwnerErase?: 'delete' | 'keep';
143
161
  /** Per-record action buttons (guide ch. 4): `[{ key, label, fn }]`, stored on
144
162
  * the collection (model data, not a config leaf). The dashboard renders one
145
163
  * button per action on each record row; `POST /v1/cms/items/:coll/:id/actions/
@@ -303,12 +321,14 @@ export type FunctionTrigger =
303
321
  * `beforeUpdate` → updated only, `beforeWrite` → created|updated,
304
322
  * `afterPublish` → published (a draft→published transition is its own event,
305
323
  * not an update); a write to another collection never wakes the function.
306
- * Deleted/bulk events are not delivered to a cmsHook binding. `event`
324
+ * `afterDelete` → deleted (one event per deleted root row — cascaded children
325
+ * and GDPR erase emit none; the payload carries `id`, `owner` (+ `org`), never
326
+ * the row body). Bulk-summary events are not delivered to a cmsHook binding. `event`
307
327
  * defaults to `beforeWrite`. */
308
328
  | {
309
329
  kind: 'cmsHook';
310
330
  collection: string;
311
- event?: 'beforeCreate' | 'beforeUpdate' | 'beforeWrite' | 'afterPublish';
331
+ event?: 'beforeCreate' | 'beforeUpdate' | 'beforeWrite' | 'afterPublish' | 'afterDelete';
312
332
  retry?: FunctionTriggerRetry;
313
333
  } | {
314
334
  kind: 'authHook';