@vertz/server 0.2.14 → 0.2.15

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +671 -102
  2. package/dist/index.js +2139 -208
  3. package/package.json +4 -4
package/dist/index.d.ts CHANGED
@@ -3,11 +3,68 @@ import { BadRequestException, ConflictException, createEnv, createImmutableProxy
3
3
  import { ModelEntry } from "@vertz/db";
4
4
  import { AuthError, Result } from "@vertz/errors";
5
5
  /**
6
- * defineAccess()Phase 6 RBAC & Access Control configuration
6
+ * rules.* builders declarative access rule data structures.
7
7
  *
8
- * Replaces createAccess() with hierarchical RBAC: resources form trees,
9
- * roles inherit down the hierarchy, and entitlements map to roles/plans/flags.
8
+ * These are pure data structures with no evaluation logic.
9
+ * The access context evaluates them at runtime (sub-phase 4).
10
10
  */
11
+ interface RoleRule {
12
+ readonly type: "role";
13
+ readonly roles: readonly string[];
14
+ }
15
+ interface EntitlementRule {
16
+ readonly type: "entitlement";
17
+ readonly entitlement: string;
18
+ }
19
+ interface WhereRule {
20
+ readonly type: "where";
21
+ readonly conditions: Record<string, unknown>;
22
+ }
23
+ interface AllRule {
24
+ readonly type: "all";
25
+ readonly rules: readonly AccessRule[];
26
+ }
27
+ interface AnyRule {
28
+ readonly type: "any";
29
+ readonly rules: readonly AccessRule[];
30
+ }
31
+ interface AuthenticatedRule {
32
+ readonly type: "authenticated";
33
+ }
34
+ interface PublicRule {
35
+ readonly type: "public";
36
+ }
37
+ interface FvaRule {
38
+ readonly type: "fva";
39
+ readonly maxAge: number;
40
+ }
41
+ type AccessRule = PublicRule | RoleRule | EntitlementRule | WhereRule | AllRule | AnyRule | AuthenticatedRule | FvaRule;
42
+ interface UserMarker {
43
+ readonly __marker: string;
44
+ }
45
+ declare const rules: {
46
+ /** Endpoint is public — no authentication required */
47
+ public: PublicRule;
48
+ /** User has at least one of the specified roles (OR) */
49
+ role(...roleNames: string[]): RoleRule;
50
+ /** User has the specified entitlement (resolves role+plan+flag from defineAccess config) */
51
+ entitlement(name: string): EntitlementRule;
52
+ /** Row-level condition — DB query syntax. Use rules.user.id for dynamic user markers */
53
+ where(conditions: Record<string, unknown>): WhereRule;
54
+ /** All sub-rules must pass (AND) */
55
+ all(...ruleList: AccessRule[]): AllRule;
56
+ /** At least one sub-rule must pass (OR) */
57
+ any(...ruleList: AccessRule[]): AnyRule;
58
+ /** User must be authenticated (no specific role required) */
59
+ authenticated(): AuthenticatedRule;
60
+ /** User must have verified MFA within maxAge seconds */
61
+ fva(maxAge: number): FvaRule;
62
+ /** Declarative user markers — resolved at evaluation time */
63
+ user: {
64
+ readonly id: UserMarker;
65
+ readonly tenantId: UserMarker;
66
+ };
67
+ };
11
68
  /** Denial reasons ordered by actionability (most actionable first) */
12
69
  type DenialReason = "plan_required" | "role_required" | "limit_reached" | "flag_disabled" | "hierarchy_denied" | "step_up_required" | "not_authenticated";
13
70
  /** Metadata attached to denial reasons */
@@ -16,9 +73,12 @@ interface DenialMeta {
16
73
  requiredRoles?: string[];
17
74
  disabledFlags?: string[];
18
75
  limit?: {
76
+ key?: string;
19
77
  max: number;
20
78
  consumed: number;
21
79
  remaining: number;
80
+ /** True when the tenant is consuming beyond the limit under overage billing */
81
+ overage?: boolean;
22
82
  };
23
83
  fvaMaxAge?: number;
24
84
  }
@@ -29,43 +89,120 @@ interface AccessCheckResult {
29
89
  reason?: DenialReason;
30
90
  meta?: DenialMeta;
31
91
  }
32
- /** Entitlement definition — maps to roles, optional plans and flags */
33
- interface EntitlementDef {
34
- roles: string[];
35
- plans?: string[];
36
- flags?: string[];
37
- }
38
92
  /** Billing period for plan limits */
39
- type BillingPeriod = "month" | "day" | "hour";
40
- /** Limit definition within a plan */
93
+ type BillingPeriod = "month" | "day" | "hour" | "quarter" | "year";
94
+ /** Valid price intervals for plans */
95
+ type PriceInterval = "month" | "quarter" | "year" | "one_off";
96
+ /** Plan price definition */
97
+ interface PlanPrice {
98
+ amount: number;
99
+ interval: PriceInterval;
100
+ }
101
+ /** Overage billing configuration for a limit */
102
+ interface OverageConfig {
103
+ /** Price per unit of overage (e.g., 0.01 for $0.01 per extra unit) */
104
+ amount: number;
105
+ /** Units per charge (e.g., 1 = charge per unit, 100 = charge per 100 units) */
106
+ per: number;
107
+ /** Maximum overage charge per period (safety cap). Omit for no cap. */
108
+ cap?: number;
109
+ }
110
+ /** Limit definition within a plan — gates an entitlement with optional scoping */
41
111
  interface LimitDef {
42
- per: BillingPeriod;
43
112
  max: number;
44
- }
45
- /** Plan definition which entitlements are included and their usage limits */
113
+ gates: string;
114
+ /** Billing period for the limit. Omitted = lifetime (no reset). */
115
+ per?: BillingPeriod;
116
+ /** Entity scope — omitted = tenant-level, string = per entity instance */
117
+ scope?: string;
118
+ /** Overage billing: allow usage beyond limit at per-unit cost */
119
+ overage?: OverageConfig;
120
+ }
121
+ /** Add-on compatibility — restricts which base plans an add-on can be attached to */
122
+ interface AddOnRequires {
123
+ group: string;
124
+ plans: readonly string[] | string[];
125
+ }
126
+ /** Grace period duration for grandfathering policy */
127
+ type GraceDuration = "1m" | "3m" | "6m" | "12m" | "indefinite";
128
+ /** Grandfathering policy for a plan */
129
+ interface GrandfatheringPolicy {
130
+ grace?: GraceDuration;
131
+ }
132
+ /** Plan definition — features, limits, metadata, and billing */
46
133
  interface PlanDef {
47
- entitlements: readonly string[] | string[];
134
+ title?: string;
135
+ description?: string;
136
+ group?: string;
137
+ addOn?: boolean;
138
+ price?: PlanPrice;
139
+ features?: readonly string[] | string[];
48
140
  limits?: Record<string, LimitDef>;
141
+ /** Grandfathering policy — how long existing tenants keep old version on plan change */
142
+ grandfathering?: GrandfatheringPolicy;
143
+ /** Add-on only: restricts attachment to tenants on compatible base plans */
144
+ requires?: AddOnRequires;
145
+ }
146
+ /** Entity definition — roles and optional inheritance from parent entity roles */
147
+ interface EntityDef {
148
+ roles: readonly string[] | string[];
149
+ inherits?: Record<string, string>;
150
+ }
151
+ /** Resolved entitlement definition — roles + optional rules */
152
+ interface EntitlementDef {
153
+ roles: string[];
154
+ rules?: AccessRule[];
155
+ flags?: string[];
156
+ /** Plan names that gate this entitlement (evaluated in Layer 4) */
157
+ plans?: string[];
49
158
  }
159
+ /** Entitlement callback context — provides `where()` and `user` for attribute rules */
160
+ interface RuleContext {
161
+ where(conditions: Record<string, unknown>): AccessRule;
162
+ user: {
163
+ readonly id: {
164
+ readonly __marker: string;
165
+ };
166
+ readonly tenantId: {
167
+ readonly __marker: string;
168
+ };
169
+ };
170
+ }
171
+ /** Entitlement value: object or callback returning object */
172
+ type EntitlementValue = EntitlementDef | ((r: RuleContext) => EntitlementDef);
50
173
  /** The input config for defineAccess() */
51
174
  interface DefineAccessInput {
52
- hierarchy: string[];
53
- roles: Record<string, string[]>;
54
- inheritance?: Record<string, Record<string, string>>;
55
- entitlements: Record<string, EntitlementDef>;
175
+ entities: Record<string, EntityDef>;
176
+ entitlements: Record<string, EntitlementValue>;
56
177
  plans?: Record<string, PlanDef>;
57
178
  /** Fallback plan name when an org's plan expires. Defaults to 'free'. */
58
179
  defaultPlan?: string;
59
180
  }
60
181
  /** The frozen config returned by defineAccess() */
61
182
  interface AccessDefinition {
183
+ /** Inferred hierarchy from inherits declarations */
62
184
  readonly hierarchy: readonly string[];
185
+ /** Entities keyed by name */
186
+ readonly entities: Readonly<Record<string, Readonly<EntityDef>>>;
187
+ /** Roles per entity (derived from entities for downstream compat) */
63
188
  readonly roles: Readonly<Record<string, readonly string[]>>;
189
+ /** Inheritance map per entity (derived from entities for downstream compat) */
64
190
  readonly inheritance: Readonly<Record<string, Readonly<Record<string, string>>>>;
191
+ /** Resolved entitlements */
65
192
  readonly entitlements: Readonly<Record<string, Readonly<EntitlementDef>>>;
66
193
  readonly plans?: Readonly<Record<string, Readonly<PlanDef>>>;
67
194
  /** Fallback plan name when an org's plan expires. Defaults to 'free'. */
68
195
  readonly defaultPlan?: string;
196
+ /**
197
+ * Computed: Set of entitlements that are gated by at least one plan's `features`.
198
+ * If an entitlement is in this set, a plan check is required during `can()`.
199
+ */
200
+ readonly _planGatedEntitlements: ReadonlySet<string>;
201
+ /**
202
+ * Computed: Map from entitlement to all limit keys that gate it.
203
+ * Used during can() to find all limits that need to pass for an entitlement.
204
+ */
205
+ readonly _entitlementToLimitKeys: Readonly<Record<string, readonly string[]>>;
69
206
  }
70
207
  declare function defineAccess(input: DefineAccessInput): AccessDefinition;
71
208
  /**
@@ -153,12 +290,40 @@ declare class InMemoryFlagStore implements FlagStore {
153
290
  getFlag(orgId: string, flag: string): boolean;
154
291
  getFlags(orgId: string): Record<string, boolean>;
155
292
  }
293
+ /** Limit override: add (additive) or max (hard cap) */
294
+ interface LimitOverrideDef {
295
+ add?: number;
296
+ max?: number;
297
+ }
298
+ /** Per-tenant overrides */
299
+ interface TenantOverrides {
300
+ features?: string[];
301
+ limits?: Record<string, LimitOverrideDef>;
302
+ }
303
+ interface OverrideStore {
304
+ set(tenantId: string, overrides: TenantOverrides): Promise<void>;
305
+ remove(tenantId: string, keys: {
306
+ features?: string[];
307
+ limits?: string[];
308
+ }): Promise<void>;
309
+ get(tenantId: string): Promise<TenantOverrides | null>;
310
+ dispose(): void;
311
+ }
312
+ declare class InMemoryOverrideStore implements OverrideStore {
313
+ private overrides;
314
+ set(tenantId: string, overrides: TenantOverrides): Promise<void>;
315
+ remove(tenantId: string, keys: {
316
+ features?: string[];
317
+ limits?: string[];
318
+ }): Promise<void>;
319
+ get(tenantId: string): Promise<TenantOverrides | null>;
320
+ dispose(): void;
321
+ }
156
322
  /**
157
- * PlanStore org-level plan assignments with overrides.
158
- *
159
- * Stores which plan an organization is on, when it started,
160
- * optional expiration, and per-customer limit overrides.
323
+ * Validate tenant overrides against the access definition.
324
+ * Throws on invalid limit keys, invalid feature names, or invalid max values.
161
325
  */
326
+ declare function validateOverrides(accessDef: AccessDefinition, overrides: TenantOverrides): void;
162
327
  /** Per-customer limit override. Only affects the cap, not the billing period. */
163
328
  interface LimitOverride {
164
329
  max: number;
@@ -179,14 +344,85 @@ interface PlanStore {
179
344
  getPlan(orgId: string): Promise<OrgPlan | null>;
180
345
  updateOverrides(orgId: string, overrides: Record<string, LimitOverride>): Promise<void>;
181
346
  removePlan(orgId: string): Promise<void>;
347
+ /** Attach an add-on to an org. */
348
+ attachAddOn?(orgId: string, addOnId: string): Promise<void>;
349
+ /** Detach an add-on from an org. */
350
+ detachAddOn?(orgId: string, addOnId: string): Promise<void>;
351
+ /** Get all active add-on IDs for an org. */
352
+ getAddOns?(orgId: string): Promise<string[]>;
353
+ /** List all org IDs assigned to a specific plan. */
354
+ listByPlan?(planId: string): Promise<string[]>;
182
355
  dispose(): void;
183
356
  }
184
357
  declare class InMemoryPlanStore implements PlanStore {
185
358
  private plans;
359
+ private addOns;
186
360
  assignPlan(orgId: string, planId: string, startedAt?: Date, expiresAt?: Date | null): Promise<void>;
187
361
  getPlan(orgId: string): Promise<OrgPlan | null>;
188
362
  updateOverrides(orgId: string, overrides: Record<string, LimitOverride>): Promise<void>;
189
363
  removePlan(orgId: string): Promise<void>;
364
+ attachAddOn(orgId: string, addOnId: string): Promise<void>;
365
+ detachAddOn(orgId: string, addOnId: string): Promise<void>;
366
+ getAddOns(orgId: string): Promise<string[]>;
367
+ listByPlan(planId: string): Promise<string[]>;
368
+ dispose(): void;
369
+ }
370
+ /**
371
+ * Check if an add-on is compatible with a given base plan.
372
+ * Returns true if the add-on has no `requires` or if the plan is in the requires list.
373
+ */
374
+ declare function checkAddOnCompatibility(accessDef: AccessDefinition, addOnId: string, currentPlanId: string): boolean;
375
+ /**
376
+ * Get add-ons that are incompatible with a target plan.
377
+ * Used to flag incompatible add-ons when a tenant downgrades.
378
+ */
379
+ declare function getIncompatibleAddOns(accessDef: AccessDefinition, activeAddOnIds: string[], targetPlanId: string): string[];
380
+ /**
381
+ * Plan Version Store — tracks versioned snapshots of plan configurations.
382
+ *
383
+ * When a plan's config changes (hash differs), a new version is created
384
+ * with a snapshot of the plan's features, limits, and price at that point.
385
+ */
386
+ interface PlanSnapshot {
387
+ features: readonly string[] | string[];
388
+ limits: Record<string, unknown>;
389
+ price: {
390
+ amount: number;
391
+ interval: string;
392
+ } | null;
393
+ }
394
+ interface PlanVersionInfo {
395
+ planId: string;
396
+ version: number;
397
+ hash: string;
398
+ snapshot: PlanSnapshot;
399
+ createdAt: Date;
400
+ }
401
+ interface PlanVersionStore {
402
+ /** Create a new version for a plan. Returns the version number. */
403
+ createVersion(planId: string, hash: string, snapshot: PlanSnapshot): Promise<number>;
404
+ /** Get the current (latest) version number for a plan. Returns null if no versions exist. */
405
+ getCurrentVersion(planId: string): Promise<number | null>;
406
+ /** Get a specific version's info. Returns null if not found. */
407
+ getVersion(planId: string, version: number): Promise<PlanVersionInfo | null>;
408
+ /** Get the version number a tenant is on for a given plan. Returns null if not set. */
409
+ getTenantVersion(tenantId: string, planId: string): Promise<number | null>;
410
+ /** Set the version number a tenant is on for a given plan. */
411
+ setTenantVersion(tenantId: string, planId: string, version: number): Promise<void>;
412
+ /** Get the hash of the current (latest) version for a plan. Returns null if no versions. */
413
+ getCurrentHash(planId: string): Promise<string | null>;
414
+ /** Clean up resources. */
415
+ dispose(): void;
416
+ }
417
+ declare class InMemoryPlanVersionStore implements PlanVersionStore {
418
+ private versions;
419
+ private tenantVersions;
420
+ createVersion(planId: string, hash: string, snapshot: PlanSnapshot): Promise<number>;
421
+ getCurrentVersion(planId: string): Promise<number | null>;
422
+ getVersion(planId: string, version: number): Promise<PlanVersionInfo | null>;
423
+ getTenantVersion(tenantId: string, planId: string): Promise<number | null>;
424
+ setTenantVersion(tenantId: string, planId: string, version: number): Promise<void>;
425
+ getCurrentHash(planId: string): Promise<string | null>;
190
426
  dispose(): void;
191
427
  }
192
428
  /**
@@ -239,8 +475,12 @@ interface AccessContextConfig {
239
475
  planStore?: PlanStore;
240
476
  /** Wallet store — required for Layer 5 wallet checks and canAndConsume() */
241
477
  walletStore?: WalletStore;
478
+ /** Override store — per-tenant feature and limit overrides */
479
+ overrideStore?: OverrideStore;
242
480
  /** Resolves an org ID from a resource. Required for plan/wallet checks. */
243
481
  orgResolver?: (resource?: ResourceRef) => Promise<string | null>;
482
+ /** Plan version store — required for versioned plan resolution (grandfathered tenants) */
483
+ planVersionStore?: PlanVersionStore;
244
484
  }
245
485
  interface AccessContext {
246
486
  can(entitlement: string, resource?: ResourceRef): Promise<boolean>;
@@ -250,6 +490,8 @@ interface AccessContext {
250
490
  entitlement: string;
251
491
  resource?: ResourceRef;
252
492
  }>): Promise<Map<string, boolean>>;
493
+ /** Batch check: single entitlement across multiple entities. Returns Map<entityId, boolean>. */
494
+ canBatch(entitlement: string, resources: ResourceRef[]): Promise<Map<string, boolean>>;
253
495
  /** Atomic check + consume. Runs full can() then increments wallet if all layers pass. */
254
496
  canAndConsume(entitlement: string, resource?: ResourceRef, amount?: number): Promise<boolean>;
255
497
  /** Rollback a previous canAndConsume(). Use when the operation fails after consumption. */
@@ -766,6 +1008,23 @@ type AccessEvent = {
766
1008
  } | {
767
1009
  type: "access:plan_changed";
768
1010
  orgId: string;
1011
+ } | {
1012
+ type: "access:plan_assigned";
1013
+ orgId: string;
1014
+ planId: string;
1015
+ } | {
1016
+ type: "access:addon_attached";
1017
+ orgId: string;
1018
+ addonId: string;
1019
+ } | {
1020
+ type: "access:addon_detached";
1021
+ orgId: string;
1022
+ addonId: string;
1023
+ } | {
1024
+ type: "access:limit_reset";
1025
+ orgId: string;
1026
+ entitlement: string;
1027
+ max: number;
769
1028
  };
770
1029
  interface AccessWsData {
771
1030
  userId: string;
@@ -790,6 +1049,10 @@ interface AccessEventBroadcaster {
790
1049
  broadcastLimitUpdate(orgId: string, entitlement: string, consumed: number, remaining: number, max: number): void;
791
1050
  broadcastRoleChange(userId: string): void;
792
1051
  broadcastPlanChange(orgId: string): void;
1052
+ broadcastPlanAssigned(orgId: string, planId: string): void;
1053
+ broadcastAddonAttached(orgId: string, addonId: string): void;
1054
+ broadcastAddonDetached(orgId: string, addonId: string): void;
1055
+ broadcastLimitReset(orgId: string, entitlement: string, max: number): void;
793
1056
  getConnectionCount: number;
794
1057
  }
795
1058
  /** Minimal Bun.Server interface for WebSocket upgrade */
@@ -806,6 +1069,161 @@ interface BunWebSocket<T> {
806
1069
  ping(): void;
807
1070
  }
808
1071
  declare function createAccessEventBroadcaster(config: AccessEventBroadcasterConfig): AccessEventBroadcaster;
1072
+ import { ModelDef } from "@vertz/db";
1073
+ declare const authModels: Record<string, ModelDef>;
1074
+ import { QueryResult, ReadError } from "@vertz/db";
1075
+ import { SqlFragment } from "@vertz/db/sql";
1076
+ import { Result as Result2 } from "@vertz/errors";
1077
+ /**
1078
+ * Minimal database interface for auth stores.
1079
+ * Only requires raw query capability and dialect info.
1080
+ */
1081
+ interface AuthDbClient {
1082
+ query<T = Record<string, unknown>>(fragment: SqlFragment): Promise<Result2<QueryResult<T>, ReadError>>;
1083
+ _internals: {
1084
+ readonly models: Record<string, unknown>;
1085
+ readonly dialect: {
1086
+ readonly name: "postgres" | "sqlite";
1087
+ };
1088
+ };
1089
+ }
1090
+ /**
1091
+ * Dialect-aware DDL helpers for auth table creation.
1092
+ *
1093
+ * Produces SQL column type fragments for SQLite and PostgreSQL,
1094
+ * enabling portable CREATE TABLE statements.
1095
+ */
1096
+ type DbDialectName = "sqlite" | "postgres";
1097
+ /**
1098
+ * Names of all auth tables — used for model validation.
1099
+ */
1100
+ declare const AUTH_TABLE_NAMES: readonly ["auth_users", "auth_sessions", "auth_oauth_accounts", "auth_role_assignments", "auth_closure", "auth_plans", "auth_plan_addons", "auth_flags", "auth_overrides"];
1101
+ /**
1102
+ * Validate that all required auth models are registered in the DatabaseClient.
1103
+ *
1104
+ * Throws a prescriptive error when models are missing, telling the developer
1105
+ * exactly what to add to their createDb() call.
1106
+ */
1107
+ declare function validateAuthModels(db: AuthDbClient): void;
1108
+ /**
1109
+ * Initialize auth tables in the database.
1110
+ *
1111
+ * Executes CREATE TABLE IF NOT EXISTS for all 9 auth tables.
1112
+ * Idempotent — safe to call on every server start.
1113
+ */
1114
+ declare function initializeAuthTables(db: AuthDbClient): Promise<void>;
1115
+ interface BillingAdapter {
1116
+ /** Push plan definitions to the payment processor. Idempotent. */
1117
+ syncPlans(plans: Record<string, PlanDef>): Promise<void>;
1118
+ /** Create a subscription for a tenant. */
1119
+ createSubscription(tenantId: string, planId: string): Promise<string>;
1120
+ /** Cancel a tenant's subscription. */
1121
+ cancelSubscription(tenantId: string): Promise<void>;
1122
+ /** Attach an add-on to a tenant's subscription. */
1123
+ attachAddOn(tenantId: string, addOnId: string): Promise<void>;
1124
+ /** Report metered usage (overage) for a tenant. */
1125
+ reportOverage(tenantId: string, limitKey: string, amount: number): Promise<void>;
1126
+ /** Get the billing portal URL for a tenant. */
1127
+ getPortalUrl(tenantId: string): Promise<string>;
1128
+ }
1129
+ /**
1130
+ * Billing Event Emitter — typed event system for billing lifecycle events.
1131
+ *
1132
+ * Events: subscription:created, subscription:canceled, billing:payment_failed
1133
+ */
1134
+ type BillingEventType = "subscription:created" | "subscription:canceled" | "billing:payment_failed";
1135
+ interface BillingEvent {
1136
+ tenantId: string;
1137
+ planId: string;
1138
+ attempt?: number;
1139
+ }
1140
+ type BillingEventHandler = (event: BillingEvent) => void;
1141
+ interface BillingEventEmitter {
1142
+ on(eventType: BillingEventType, handler: BillingEventHandler): void;
1143
+ off(eventType: BillingEventType, handler: BillingEventHandler): void;
1144
+ emit(eventType: BillingEventType, event: BillingEvent): void;
1145
+ }
1146
+ declare function createBillingEventEmitter(): BillingEventEmitter;
1147
+ /**
1148
+ * Overage Computation — calculates overage charges for limit usage.
1149
+ *
1150
+ * Formula: (consumed - max) * rate, capped if cap is specified.
1151
+ * Returns 0 if usage is within the limit.
1152
+ */
1153
+ interface OverageInput {
1154
+ consumed: number;
1155
+ max: number;
1156
+ rate: number;
1157
+ cap?: number;
1158
+ }
1159
+ /**
1160
+ * Compute overage charge for a given usage period.
1161
+ *
1162
+ * @returns The overage charge amount (0 if within limit)
1163
+ */
1164
+ declare function computeOverage(input: OverageInput): number;
1165
+ interface StripeProduct {
1166
+ id: string;
1167
+ name: string;
1168
+ metadata: Record<string, string>;
1169
+ }
1170
+ interface StripePrice {
1171
+ id: string;
1172
+ product: string;
1173
+ unit_amount: number;
1174
+ recurring: {
1175
+ interval: string;
1176
+ } | null;
1177
+ active: boolean;
1178
+ metadata: Record<string, string>;
1179
+ }
1180
+ interface StripeClient {
1181
+ products: {
1182
+ list(): Promise<{
1183
+ data: StripeProduct[];
1184
+ }>;
1185
+ create(params: {
1186
+ name: string;
1187
+ metadata: Record<string, string>;
1188
+ description?: string;
1189
+ }): Promise<StripeProduct>;
1190
+ update(id: string, params: {
1191
+ name?: string;
1192
+ metadata?: Record<string, string>;
1193
+ }): Promise<StripeProduct>;
1194
+ };
1195
+ prices: {
1196
+ list(params: {
1197
+ product: string;
1198
+ }): Promise<{
1199
+ data: StripePrice[];
1200
+ }>;
1201
+ create(params: {
1202
+ product: string;
1203
+ unit_amount: number;
1204
+ currency: string;
1205
+ recurring?: {
1206
+ interval: string;
1207
+ };
1208
+ metadata: Record<string, string>;
1209
+ }): Promise<StripePrice>;
1210
+ update(id: string, params: {
1211
+ active: boolean;
1212
+ }): Promise<StripePrice>;
1213
+ };
1214
+ }
1215
+ interface StripeBillingAdapterConfig {
1216
+ stripe: StripeClient;
1217
+ currency?: string;
1218
+ }
1219
+ declare function createStripeBillingAdapter(config: StripeBillingAdapterConfig): BillingAdapter;
1220
+ interface WebhookHandlerConfig {
1221
+ planStore: PlanStore;
1222
+ emitter: BillingEventEmitter;
1223
+ defaultPlan: string;
1224
+ webhookSecret: string;
1225
+ }
1226
+ declare function createWebhookHandler(config: WebhookHandlerConfig): (request: Request) => Promise<Response>;
809
1227
  interface Period {
810
1228
  periodStart: Date;
811
1229
  periodEnd: Date;
@@ -817,6 +1235,102 @@ interface Period {
817
1235
  * the period containing now. For 'day'/'hour': uses fixed-duration periods.
818
1236
  */
819
1237
  declare function calculateBillingPeriod(startedAt: Date, per: BillingPeriod, now?: Date): Period;
1238
+ declare class DbClosureStore implements ClosureStore {
1239
+ private db;
1240
+ constructor(db: AuthDbClient);
1241
+ addResource(type: string, id: string, parent?: ParentRef): Promise<void>;
1242
+ removeResource(type: string, id: string): Promise<void>;
1243
+ getAncestors(type: string, id: string): Promise<ClosureEntry[]>;
1244
+ getDescendants(type: string, id: string): Promise<ClosureEntry[]>;
1245
+ hasPath(ancestorType: string, ancestorId: string, descendantType: string, descendantId: string): Promise<boolean>;
1246
+ dispose(): void;
1247
+ }
1248
+ declare class DbFlagStore implements FlagStore {
1249
+ private db;
1250
+ private cache;
1251
+ constructor(db: AuthDbClient);
1252
+ /**
1253
+ * Load all flags from DB into memory. Call once on initialization.
1254
+ */
1255
+ loadFlags(): Promise<void>;
1256
+ setFlag(orgId: string, flag: string, enabled: boolean): void;
1257
+ getFlag(orgId: string, flag: string): boolean;
1258
+ getFlags(orgId: string): Record<string, boolean>;
1259
+ }
1260
+ declare class DbOAuthAccountStore implements OAuthAccountStore {
1261
+ private db;
1262
+ constructor(db: AuthDbClient);
1263
+ linkAccount(userId: string, provider: string, providerId: string, email?: string): Promise<void>;
1264
+ findByProviderAccount(provider: string, providerId: string): Promise<string | null>;
1265
+ findByUserId(userId: string): Promise<{
1266
+ provider: string;
1267
+ providerId: string;
1268
+ }[]>;
1269
+ unlinkAccount(userId: string, provider: string): Promise<void>;
1270
+ dispose(): void;
1271
+ }
1272
+ declare class DbPlanStore implements PlanStore {
1273
+ private db;
1274
+ constructor(db: AuthDbClient);
1275
+ assignPlan(orgId: string, planId: string, startedAt?: Date, expiresAt?: Date | null): Promise<void>;
1276
+ getPlan(orgId: string): Promise<OrgPlan | null>;
1277
+ updateOverrides(orgId: string, overrides: Record<string, LimitOverride>): Promise<void>;
1278
+ removePlan(orgId: string): Promise<void>;
1279
+ attachAddOn(orgId: string, addOnId: string): Promise<void>;
1280
+ detachAddOn(orgId: string, addOnId: string): Promise<void>;
1281
+ getAddOns(orgId: string): Promise<string[]>;
1282
+ dispose(): void;
1283
+ private loadOverrides;
1284
+ }
1285
+ declare class DbRoleAssignmentStore implements RoleAssignmentStore {
1286
+ private db;
1287
+ constructor(db: AuthDbClient);
1288
+ assign(userId: string, resourceType: string, resourceId: string, role: string): Promise<void>;
1289
+ revoke(userId: string, resourceType: string, resourceId: string, role: string): Promise<void>;
1290
+ getRoles(userId: string, resourceType: string, resourceId: string): Promise<string[]>;
1291
+ getRolesForUser(userId: string): Promise<RoleAssignment[]>;
1292
+ getEffectiveRole(userId: string, resourceType: string, resourceId: string, accessDef: AccessDefinition, closureStore: ClosureStore): Promise<string | null>;
1293
+ dispose(): void;
1294
+ }
1295
+ declare class DbSessionStore implements SessionStore {
1296
+ private db;
1297
+ constructor(db: AuthDbClient);
1298
+ createSessionWithId(id: string, data: {
1299
+ userId: string;
1300
+ refreshTokenHash: string;
1301
+ ipAddress: string;
1302
+ userAgent: string;
1303
+ expiresAt: Date;
1304
+ currentTokens?: AuthTokens;
1305
+ }): Promise<StoredSession>;
1306
+ findByRefreshHash(hash: string): Promise<StoredSession | null>;
1307
+ findByPreviousRefreshHash(hash: string): Promise<StoredSession | null>;
1308
+ revokeSession(id: string): Promise<void>;
1309
+ listActiveSessions(userId: string): Promise<StoredSession[]>;
1310
+ countActiveSessions(userId: string): Promise<number>;
1311
+ getCurrentTokens(sessionId: string): Promise<AuthTokens | null>;
1312
+ updateSession(id: string, data: {
1313
+ refreshTokenHash: string;
1314
+ previousRefreshHash: string;
1315
+ lastActiveAt: Date;
1316
+ currentTokens?: AuthTokens;
1317
+ }): Promise<void>;
1318
+ dispose(): void;
1319
+ private rowToSession;
1320
+ }
1321
+ declare class DbUserStore implements UserStore {
1322
+ private db;
1323
+ constructor(db: AuthDbClient);
1324
+ createUser(user: AuthUser, passwordHash: string | null): Promise<void>;
1325
+ findByEmail(email: string): Promise<{
1326
+ user: AuthUser;
1327
+ passwordHash: string | null;
1328
+ } | null>;
1329
+ findById(id: string): Promise<AuthUser | null>;
1330
+ updatePasswordHash(userId: string, passwordHash: string): Promise<void>;
1331
+ updateEmailVerified(userId: string, verified: boolean): Promise<void>;
1332
+ private rowToUser;
1333
+ }
820
1334
  declare class InMemoryEmailVerificationStore implements EmailVerificationStore {
821
1335
  private byId;
822
1336
  private byTokenHash;
@@ -848,6 +1362,38 @@ declare function computeEntityAccess(entitlements: string[], entity: {
848
1362
  * Returns true if `fva` exists and (now - fva) < maxAgeSeconds.
849
1363
  */
850
1364
  declare function checkFva(payload: SessionPayload, maxAgeSeconds: number): boolean;
1365
+ /**
1366
+ * Grandfathering Store — tracks which tenants are grandfathered on old plan versions.
1367
+ *
1368
+ * When a plan version changes, existing tenants keep their old version
1369
+ * during a grace period. This store tracks that state.
1370
+ */
1371
+ interface GrandfatheringState {
1372
+ tenantId: string;
1373
+ planId: string;
1374
+ version: number;
1375
+ graceEnds: Date | null;
1376
+ }
1377
+ interface GrandfatheringStore {
1378
+ /** Mark a tenant as grandfathered on a specific plan version. */
1379
+ setGrandfathered(tenantId: string, planId: string, version: number, graceEnds: Date | null): Promise<void>;
1380
+ /** Get grandfathering state for a tenant on a plan. Returns null if not grandfathered. */
1381
+ getGrandfathered(tenantId: string, planId: string): Promise<GrandfatheringState | null>;
1382
+ /** List all grandfathered tenants for a plan. */
1383
+ listGrandfathered(planId: string): Promise<GrandfatheringState[]>;
1384
+ /** Remove grandfathering state (after migration). */
1385
+ removeGrandfathered(tenantId: string, planId: string): Promise<void>;
1386
+ /** Clean up resources. */
1387
+ dispose(): void;
1388
+ }
1389
+ declare class InMemoryGrandfatheringStore implements GrandfatheringStore {
1390
+ private states;
1391
+ setGrandfathered(tenantId: string, planId: string, version: number, graceEnds: Date | null): Promise<void>;
1392
+ getGrandfathered(tenantId: string, planId: string): Promise<GrandfatheringState | null>;
1393
+ listGrandfathered(planId: string): Promise<GrandfatheringState[]>;
1394
+ removeGrandfathered(tenantId: string, planId: string): Promise<void>;
1395
+ dispose(): void;
1396
+ }
851
1397
  declare class InMemoryMFAStore implements MFAStore {
852
1398
  private secrets;
853
1399
  private backupCodes;
@@ -887,6 +1433,71 @@ declare class InMemoryPasswordResetStore implements PasswordResetStore {
887
1433
  deleteByUserId(userId: string): Promise<void>;
888
1434
  dispose(): void;
889
1435
  }
1436
+ interface PlanHashInput {
1437
+ features?: readonly string[] | string[];
1438
+ limits?: Record<string, unknown>;
1439
+ price?: {
1440
+ amount: number;
1441
+ interval: string;
1442
+ } | null;
1443
+ }
1444
+ /**
1445
+ * Compute SHA-256 hash of canonical JSON representation of plan config.
1446
+ * Keys are sorted recursively for deterministic output regardless of object key order.
1447
+ */
1448
+ declare function computePlanHash(config: PlanHashInput): Promise<string>;
1449
+ type PlanEventType = "plan:version_created" | "plan:grace_approaching" | "plan:grace_expiring" | "plan:migrated";
1450
+ interface PlanEvent {
1451
+ type: PlanEventType;
1452
+ planId: string;
1453
+ tenantId?: string;
1454
+ version?: number;
1455
+ previousVersion?: number;
1456
+ currentVersion?: number;
1457
+ graceEnds?: Date | null;
1458
+ timestamp: Date;
1459
+ }
1460
+ type PlanEventHandler = (event: PlanEvent) => void;
1461
+ interface TenantPlanState {
1462
+ planId: string;
1463
+ version: number;
1464
+ currentVersion: number;
1465
+ grandfathered: boolean;
1466
+ graceEnds: Date | null;
1467
+ snapshot: PlanSnapshot;
1468
+ }
1469
+ interface MigrateOpts {
1470
+ tenantId?: string;
1471
+ }
1472
+ interface ScheduleOpts {
1473
+ at: Date | string;
1474
+ }
1475
+ interface PlanManagerConfig {
1476
+ plans: Record<string, PlanDef>;
1477
+ versionStore: PlanVersionStore;
1478
+ grandfatheringStore: GrandfatheringStore;
1479
+ planStore: PlanStore;
1480
+ clock?: () => Date;
1481
+ }
1482
+ interface PlanManager {
1483
+ /** Hash plan configs, compare with stored, create new versions if different. Idempotent. */
1484
+ initialize(): Promise<void>;
1485
+ /** Migrate tenants past grace period (or specific tenant immediately). */
1486
+ migrate(planId: string, opts?: MigrateOpts): Promise<void>;
1487
+ /** Schedule future migration date for all grandfathered tenants. */
1488
+ schedule(planId: string, opts: ScheduleOpts): Promise<void>;
1489
+ /** Return tenant's plan state (planId, version, grandfathered, snapshot). */
1490
+ resolve(tenantId: string): Promise<TenantPlanState | null>;
1491
+ /** List all grandfathered tenants for a plan. */
1492
+ grandfathered(planId: string): Promise<GrandfatheringState[]>;
1493
+ /** Check all grandfathered tenants and emit grace_approaching / grace_expiring events. */
1494
+ checkGraceEvents(): Promise<void>;
1495
+ /** Register an event handler. */
1496
+ on(handler: PlanEventHandler): void;
1497
+ /** Remove an event handler. */
1498
+ off(handler: PlanEventHandler): void;
1499
+ }
1500
+ declare function createPlanManager(config: PlanManagerConfig): PlanManager;
890
1501
  declare function discord(config: OAuthProviderConfig): OAuthProvider;
891
1502
  declare function github(config: OAuthProviderConfig): OAuthProvider;
892
1503
  declare function google(config: OAuthProviderConfig): OAuthProvider;
@@ -898,64 +1509,6 @@ declare class InMemoryRateLimitStore implements RateLimitStore {
898
1509
  dispose(): void;
899
1510
  private cleanup;
900
1511
  }
901
- /**
902
- * rules.* builders — declarative access rule data structures.
903
- *
904
- * These are pure data structures with no evaluation logic.
905
- * The access context evaluates them at runtime (sub-phase 4).
906
- */
907
- interface RoleRule {
908
- readonly type: "role";
909
- readonly roles: readonly string[];
910
- }
911
- interface EntitlementRule {
912
- readonly type: "entitlement";
913
- readonly entitlement: string;
914
- }
915
- interface WhereRule {
916
- readonly type: "where";
917
- readonly conditions: Record<string, unknown>;
918
- }
919
- interface AllRule {
920
- readonly type: "all";
921
- readonly rules: readonly AccessRule[];
922
- }
923
- interface AnyRule {
924
- readonly type: "any";
925
- readonly rules: readonly AccessRule[];
926
- }
927
- interface AuthenticatedRule {
928
- readonly type: "authenticated";
929
- }
930
- interface FvaRule {
931
- readonly type: "fva";
932
- readonly maxAge: number;
933
- }
934
- type AccessRule = RoleRule | EntitlementRule | WhereRule | AllRule | AnyRule | AuthenticatedRule | FvaRule;
935
- interface UserMarker {
936
- readonly __marker: string;
937
- }
938
- declare const rules: {
939
- /** User has at least one of the specified roles (OR) */
940
- role(...roleNames: string[]): RoleRule;
941
- /** User has the specified entitlement (resolves role+plan+flag from defineAccess config) */
942
- entitlement(name: string): EntitlementRule;
943
- /** Row-level condition — DB query syntax. Use rules.user.id for dynamic user markers */
944
- where(conditions: Record<string, unknown>): WhereRule;
945
- /** All sub-rules must pass (AND) */
946
- all(...ruleList: AccessRule[]): AllRule;
947
- /** At least one sub-rule must pass (OR) */
948
- any(...ruleList: AccessRule[]): AnyRule;
949
- /** User must be authenticated (no specific role required) */
950
- authenticated(): AuthenticatedRule;
951
- /** User must have verified MFA within maxAge seconds */
952
- fva(maxAge: number): FvaRule;
953
- /** Declarative user markers — resolved at evaluation time */
954
- user: {
955
- readonly id: UserMarker;
956
- readonly tenantId: UserMarker;
957
- };
958
- };
959
1512
  declare class InMemorySessionStore implements SessionStore {
960
1513
  private sessions;
961
1514
  private currentTokens;
@@ -1007,10 +1560,10 @@ declare class InMemoryUserStore implements UserStore {
1007
1560
  declare function createAuth(config: AuthConfig): AuthInstance;
1008
1561
  import { AppBuilder, AppConfig } from "@vertz/core";
1009
1562
  import { DatabaseClient, EntityDbAdapter as EntityDbAdapter3, ModelEntry as ModelEntry2 } from "@vertz/db";
1010
- import { ModelDef as ModelDef2, RelationDef, SchemaLike, TableDef } from "@vertz/db";
1011
- import { ModelDef } from "@vertz/db";
1563
+ import { ModelDef as ModelDef3, RelationDef, SchemaLike, TableDef } from "@vertz/db";
1564
+ import { ModelDef as ModelDef2 } from "@vertz/db";
1012
1565
  import { EntityDbAdapter, ListOptions } from "@vertz/db";
1013
- import { EntityError, Result as Result2 } from "@vertz/errors";
1566
+ import { EntityError, Result as Result3 } from "@vertz/errors";
1014
1567
  import { EntityDbAdapter as EntityDbAdapter2, ListOptions as ListOptions2 } from "@vertz/db";
1015
1568
  interface ListResult<T = Record<string, unknown>> {
1016
1569
  items: T[];
@@ -1024,11 +1577,11 @@ interface CrudResult<T = unknown> {
1024
1577
  body: T;
1025
1578
  }
1026
1579
  interface CrudHandlers {
1027
- list(ctx: EntityContext, options?: ListOptions): Promise<Result2<CrudResult<ListResult>, EntityError>>;
1028
- get(ctx: EntityContext, id: string): Promise<Result2<CrudResult<Record<string, unknown>>, EntityError>>;
1029
- create(ctx: EntityContext, data: Record<string, unknown>): Promise<Result2<CrudResult<Record<string, unknown>>, EntityError>>;
1030
- update(ctx: EntityContext, id: string, data: Record<string, unknown>): Promise<Result2<CrudResult<Record<string, unknown>>, EntityError>>;
1031
- delete(ctx: EntityContext, id: string): Promise<Result2<CrudResult<null>, EntityError>>;
1580
+ list(ctx: EntityContext, options?: ListOptions): Promise<Result3<CrudResult<ListResult>, EntityError>>;
1581
+ get(ctx: EntityContext, id: string): Promise<Result3<CrudResult<Record<string, unknown>>, EntityError>>;
1582
+ create(ctx: EntityContext, data: Record<string, unknown>): Promise<Result3<CrudResult<Record<string, unknown>>, EntityError>>;
1583
+ update(ctx: EntityContext, id: string, data: Record<string, unknown>): Promise<Result3<CrudResult<Record<string, unknown>>, EntityError>>;
1584
+ delete(ctx: EntityContext, id: string): Promise<Result3<CrudResult<null>, EntityError>>;
1032
1585
  }
1033
1586
  declare function createCrudHandlers(def: EntityDefinition, db: EntityDbAdapter): CrudHandlers;
1034
1587
  /**
@@ -1037,7 +1590,7 @@ declare function createCrudHandlers(def: EntityDefinition, db: EntityDbAdapter):
1037
1590
  * When used as `ctx.entity`, TModel fills in actual column types.
1038
1591
  * When used as `ctx.entities.*`, TModel defaults to `ModelDef` (loose typing).
1039
1592
  */
1040
- interface EntityOperations<TModel extends ModelDef = ModelDef> {
1593
+ interface EntityOperations<TModel extends ModelDef2 = ModelDef2> {
1041
1594
  get(id: string): Promise<TModel["table"]["$response"]>;
1042
1595
  list(options?: ListOptions2): Promise<ListResult<TModel["table"]["$response"]>>;
1043
1596
  create(data: TModel["table"]["$create_input"]): Promise<TModel["table"]["$response"]>;
@@ -1045,7 +1598,7 @@ interface EntityOperations<TModel extends ModelDef = ModelDef> {
1045
1598
  delete(id: string): Promise<void>;
1046
1599
  }
1047
1600
  /** Extracts the model type from an EntityDefinition */
1048
- type ExtractModel<T> = T extends EntityDefinition<infer M> ? M : ModelDef2;
1601
+ type ExtractModel<T> = T extends EntityDefinition<infer M> ? M : ModelDef3;
1049
1602
  /**
1050
1603
  * Maps an inject config `{ key: EntityDefinition<TModel> }` to
1051
1604
  * `{ key: EntityOperations<TModel> }` for typed ctx.entities access.
@@ -1058,7 +1611,7 @@ interface BaseContext {
1058
1611
  role(...roles: string[]): boolean;
1059
1612
  }
1060
1613
  interface EntityContext<
1061
- TModel extends ModelDef2 = ModelDef2,
1614
+ TModel extends ModelDef3 = ModelDef3,
1062
1615
  TInject extends Record<string, EntityDefinition> = {}
1063
1616
  > extends BaseContext {
1064
1617
  /** Typed CRUD on the current entity */
@@ -1066,7 +1619,7 @@ interface EntityContext<
1066
1619
  /** Typed access to injected entities only */
1067
1620
  readonly entities: InjectToOperations<TInject>;
1068
1621
  }
1069
- type AccessRule2 = false | ((ctx: BaseContext, row: Record<string, unknown>) => boolean | Promise<boolean>);
1622
+ type AccessRule2 = false | PublicRule | ((ctx: BaseContext, row: Record<string, unknown>) => boolean | Promise<boolean>);
1070
1623
  interface EntityBeforeHooks<
1071
1624
  TCreateInput = unknown,
1072
1625
  TUpdateInput = unknown
@@ -1095,7 +1648,7 @@ interface EntityActionDef<
1095
1648
  type RelationColumnKeys<R> = R extends RelationDef<infer TTarget> ? TTarget extends TableDef<infer TCols> ? Extract<keyof TCols, string> : string : string;
1096
1649
  type EntityRelationsConfig<TRelations extends Record<string, RelationDef> = Record<string, RelationDef>> = { [K in keyof TRelations]? : true | false | { [F in RelationColumnKeys<TRelations[K]>]? : true } };
1097
1650
  interface EntityConfig<
1098
- TModel extends ModelDef2 = ModelDef2,
1651
+ TModel extends ModelDef3 = ModelDef3,
1099
1652
  TActions extends Record<string, EntityActionDef<any, any, any, any>> = {},
1100
1653
  TInject extends Record<string, EntityDefinition> = {}
1101
1654
  > {
@@ -1114,7 +1667,7 @@ interface EntityConfig<
1114
1667
  readonly actions?: { readonly [K in keyof TActions] : TActions[K] };
1115
1668
  readonly relations?: EntityRelationsConfig<TModel["relations"]>;
1116
1669
  }
1117
- interface EntityDefinition<TModel extends ModelDef2 = ModelDef2> {
1670
+ interface EntityDefinition<TModel extends ModelDef3 = ModelDef3> {
1118
1671
  readonly kind: "entity";
1119
1672
  readonly name: string;
1120
1673
  readonly model: TModel;
@@ -1163,6 +1716,10 @@ interface ServiceDefinition {
1163
1716
  readonly access: Partial<Record<string, AccessRule2>>;
1164
1717
  readonly actions: Record<string, ServiceActionDef>;
1165
1718
  }
1719
+ interface ServerInstance extends AppBuilder {
1720
+ auth: AuthInstance;
1721
+ initialize(): Promise<void>;
1722
+ }
1166
1723
  interface ServerConfig extends Omit<AppConfig, "_entityDbFactory" | "entities"> {
1167
1724
  /** Entity definitions created via entity() from @vertz/server */
1168
1725
  entities?: EntityDefinition[];
@@ -1177,13 +1734,24 @@ interface ServerConfig extends Omit<AppConfig, "_entityDbFactory" | "entities">
1177
1734
  db?: DatabaseClient<Record<string, ModelEntry2>> | EntityDbAdapter3;
1178
1735
  /** @internal Factory to create a DB adapter for each entity. Prefer `db` instead. */
1179
1736
  _entityDbFactory?: (entityDef: EntityDefinition) => EntityDbAdapter3;
1737
+ /** Auth configuration — when combined with db, auto-wires DB-backed stores */
1738
+ auth?: AuthConfig;
1180
1739
  }
1181
1740
  /**
1182
1741
  * Creates an HTTP server with entity route generation.
1183
1742
  * Wraps @vertz/core's createServer to inject entity CRUD handlers.
1743
+ *
1744
+ * When both `db` (DatabaseClient) and `auth` are provided:
1745
+ * - Validates auth models are registered in the DatabaseClient
1746
+ * - Auto-wires DB-backed UserStore and SessionStore
1747
+ * - Returns ServerInstance with `.auth` and `.initialize()`
1184
1748
  */
1749
+ declare function createServer(config: ServerConfig & {
1750
+ db: DatabaseClient<Record<string, ModelEntry2>>;
1751
+ auth: AuthConfig;
1752
+ }): ServerInstance;
1185
1753
  declare function createServer(config: ServerConfig): AppBuilder;
1186
- import { EntityForbiddenError, Result as Result3 } from "@vertz/errors";
1754
+ import { EntityForbiddenError, Result as Result4 } from "@vertz/errors";
1187
1755
  /**
1188
1756
  * Evaluates an access rule for the given operation.
1189
1757
  * Returns err(EntityForbiddenError) if access is denied.
@@ -1192,10 +1760,11 @@ import { EntityForbiddenError, Result as Result3 } from "@vertz/errors";
1192
1760
  *
1193
1761
  * - No rule defined → deny (deny by default)
1194
1762
  * - Rule is false → operation is disabled
1763
+ * - Rule is { type: 'public' } → always allow
1195
1764
  * - Rule is a function → evaluate and deny if returns false
1196
1765
  */
1197
- declare function enforceAccess(operation: string, accessRules: Partial<Record<string, AccessRule2>>, ctx: BaseContext, row?: Record<string, unknown>): Promise<Result3<void, EntityForbiddenError>>;
1198
- import { ModelDef as ModelDef3 } from "@vertz/db";
1766
+ declare function enforceAccess(operation: string, accessRules: Partial<Record<string, AccessRule2>>, ctx: BaseContext, row?: Record<string, unknown>): Promise<Result4<void, EntityForbiddenError>>;
1767
+ import { ModelDef as ModelDef4 } from "@vertz/db";
1199
1768
  /**
1200
1769
  * Request info extracted from HTTP context / auth middleware.
1201
1770
  */
@@ -1207,10 +1776,10 @@ interface RequestInfo {
1207
1776
  /**
1208
1777
  * Creates an EntityContext from request info, entity operations, and registry proxy.
1209
1778
  */
1210
- declare function createEntityContext<TModel extends ModelDef3 = ModelDef3>(request: RequestInfo, entityOps: EntityOperations<TModel>, registryProxy: Record<string, EntityOperations>): EntityContext<TModel>;
1211
- import { ModelDef as ModelDef4 } from "@vertz/db";
1779
+ declare function createEntityContext<TModel extends ModelDef4 = ModelDef4>(request: RequestInfo, entityOps: EntityOperations<TModel>, registryProxy: Record<string, EntityOperations>): EntityContext<TModel>;
1780
+ import { ModelDef as ModelDef5 } from "@vertz/db";
1212
1781
  declare function entity<
1213
- TModel extends ModelDef4,
1782
+ TModel extends ModelDef5,
1214
1783
  TInject extends Record<string, EntityDefinition> = {},
1215
1784
  TActions extends Record<string, EntityActionDef<any, any, TModel["table"]["$response"], EntityContext<TModel, TInject>>> = {}
1216
1785
  >(name: string, config: EntityConfig<TModel, TActions, TInject>): EntityDefinition<TModel>;
@@ -1271,4 +1840,4 @@ declare function service<
1271
1840
  TInject extends Record<string, EntityDefinition> = {},
1272
1841
  TActions extends Record<string, ServiceActionDef<any, any, any>> = Record<string, ServiceActionDef<any, any, any>>
1273
1842
  >(name: string, config: ServiceConfig<TActions, TInject>): ServiceDefinition;
1274
- export { vertz, verifyPassword, validatePassword, stripReadOnlyFields, stripHiddenFields, service, rules, makeImmutable, hashPassword, google, github, generateEntityRoutes, entityErrorHandler, entity, enforceAccess, encodeAccessSet, discord, defineAccess, defaultAccess, deepFreeze, decodeAccessSet, createServer, createMiddleware, createImmutableProxy, createEnv, createEntityContext, createCrudHandlers, createAuth, createAccessEventBroadcaster, createAccessContext, createAccess, computeEntityAccess, computeAccessSet, checkFva, calculateBillingPeriod, WalletStore, WalletEntry, VertzException, ValidationException, UserTableEntry, UserStore, UnauthorizedException, StoredSession, StoredPasswordReset, StoredEmailVerification, SignUpInput, SignInInput, SessionStrategy, SessionStore, SessionPayload, SessionInfo, SessionConfig, Session, ServiceUnavailableException, ServiceDefinition, ServiceContext, ServiceConfig, ServiceActionDef, ServerHandle, ServerConfig, ServerAdapter, RoleAssignmentTableEntry, RoleAssignmentStore, RoleAssignment, ResourceRef, Resource, RequestInfo, RawRequest, RateLimitStore, RateLimitResult, RateLimitConfig, PlanStore, PlanDef, Period, PasswordResetStore, PasswordResetConfig, PasswordRequirements, ParentRef, OrgPlan, OAuthUserInfo, OAuthTokens, OAuthProviderConfig, OAuthProvider, OAuthAccountStore, NotFoundException, NamedMiddlewareDef, MiddlewareDef, MfaSetupData, MfaConfig, MfaChallengeData, MFAStore, ListenOptions, ListResult, ListOptions2 as ListOptions, LimitOverride, LimitDef, InternalServerErrorException, InferSchema, Infer, InMemoryWalletStore, InMemoryUserStore, InMemorySessionStore, InMemoryRoleAssignmentStore, InMemoryRateLimitStore, InMemoryPlanStore, InMemoryPasswordResetStore, InMemoryOAuthAccountStore, InMemoryMFAStore, InMemoryFlagStore, InMemoryEmailVerificationStore, InMemoryClosureStore, HttpStatusCode, HttpMethod, HandlerCtx, ForbiddenException, FlagStore, EnvConfig, EntityRouteOptions, EntityRelationsConfig, EntityRegistry, EntityOperations, EntityErrorResult, EntityDefinition, EntityDbAdapter2 as EntityDbAdapter, EntityContext, EntityConfig, EntityActionDef, EntitlementDefinition, EntitlementDef, Entitlement, EncodedAccessSet, EmailVerificationStore, EmailVerificationConfig, EmailPasswordConfig, Deps, DenialReason, DenialMeta, DefineAccessInput, DeepReadonly, Ctx, CrudResult, CrudHandlers, CorsConfig, CookieConfig, ConsumeResult, ConflictException, ComputeAccessSetConfig, ClosureStore, ClosureRow, ClosureEntry, BillingPeriod, BaseContext, BadRequestException, AuthorizationError, AuthUser, AuthTokens, AuthInstance, AuthContext, AuthConfig, AuthApi, AppConfig2 as AppConfig, AppBuilder2 as AppBuilder, AclClaim, AccumulateProvides, AccessWsData, AccessSet, AccessRule2 as AccessRule, AccessInstance, AccessEventBroadcasterConfig, AccessEventBroadcaster, AccessEvent, AccessDefinition, AccessContextConfig, AccessContext, AccessConfig, AccessCheckResult, AccessCheckData };
1843
+ export { vertz, verifyPassword, validatePassword, validateOverrides, validateAuthModels, stripReadOnlyFields, stripHiddenFields, service, rules, makeImmutable, initializeAuthTables, hashPassword, google, github, getIncompatibleAddOns, generateEntityRoutes, entityErrorHandler, entity, enforceAccess, encodeAccessSet, discord, defineAccess, defaultAccess, deepFreeze, decodeAccessSet, createWebhookHandler, createStripeBillingAdapter, createServer, createPlanManager, createMiddleware, createImmutableProxy, createEnv, createEntityContext, createCrudHandlers, createBillingEventEmitter, createAuth, createAccessEventBroadcaster, createAccessContext, createAccess, computePlanHash, computeOverage, computeEntityAccess, computeAccessSet, checkFva, checkAddOnCompatibility, calculateBillingPeriod, authModels, WebhookHandlerConfig, WalletStore, WalletEntry, VertzException, ValidationException, UserTableEntry, UserStore, UnauthorizedException, TenantOverrides, StripeProduct, StripePrice, StripeClient, StripeBillingAdapterConfig, StoredSession, StoredPasswordReset, StoredEmailVerification, SignUpInput, SignInInput, SessionStrategy, SessionStore, SessionPayload, SessionInfo, SessionConfig, Session, ServiceUnavailableException, ServiceDefinition, ServiceContext, ServiceConfig, ServiceActionDef, ServerInstance, ServerHandle, ServerConfig, ServerAdapter, RuleContext, RoleAssignmentTableEntry, RoleAssignmentStore, RoleAssignment, ResourceRef, Resource, RequestInfo, RawRequest, RateLimitStore, RateLimitResult, RateLimitConfig, PriceInterval, PlanVersionStore, PlanVersionInfo, PlanStore, PlanSnapshot, PlanPrice, PlanHashInput, PlanDef, Period, PasswordResetStore, PasswordResetConfig, PasswordRequirements, ParentRef, OverrideStore, OverageInput, OverageConfig, OrgPlan, OAuthUserInfo, OAuthTokens, OAuthProviderConfig, OAuthProvider, OAuthAccountStore, NotFoundException, NamedMiddlewareDef, MiddlewareDef, MfaSetupData, MfaConfig, MfaChallengeData, MFAStore, ListenOptions, ListResult, ListOptions2 as ListOptions, LimitOverrideDef, LimitOverride, LimitDef, InternalServerErrorException, InferSchema, Infer, InMemoryWalletStore, InMemoryUserStore, InMemorySessionStore, InMemoryRoleAssignmentStore, InMemoryRateLimitStore, InMemoryPlanVersionStore, InMemoryPlanStore, InMemoryPasswordResetStore, InMemoryOverrideStore, InMemoryOAuthAccountStore, InMemoryMFAStore, InMemoryGrandfatheringStore, InMemoryFlagStore, InMemoryEmailVerificationStore, InMemoryClosureStore, HttpStatusCode, HttpMethod, HandlerCtx, GrandfatheringStore, GrandfatheringState, ForbiddenException, FlagStore, EnvConfig, EntityRouteOptions, EntityRelationsConfig, EntityRegistry, EntityOperations, EntityErrorResult, EntityDefinition, EntityDef, EntityDbAdapter2 as EntityDbAdapter, EntityContext, EntityConfig, EntityActionDef, EntitlementValue, EntitlementDefinition, EntitlementDef, Entitlement, EncodedAccessSet, EmailVerificationStore, EmailVerificationConfig, EmailPasswordConfig, Deps, DenialReason, DenialMeta, DefineAccessInput, DeepReadonly, DbUserStore, DbSessionStore, DbRoleAssignmentStore, DbPlanStore, DbOAuthAccountStore, DbFlagStore, DbDialectName, DbClosureStore, Ctx, CrudResult, CrudHandlers, CorsConfig, CookieConfig, ConsumeResult, ConflictException, ComputeAccessSetConfig, ClosureStore, ClosureRow, ClosureEntry, BillingPeriod, BillingEventType, BillingEventHandler, BillingEventEmitter, BillingEvent, BillingAdapter, BaseContext, BadRequestException, AuthorizationError, AuthUser, AuthTokens, AuthInstance, AuthDbClient, AuthContext, AuthConfig, AuthApi, AppConfig2 as AppConfig, AppBuilder2 as AppBuilder, AddOnRequires, AclClaim, AccumulateProvides, AccessWsData, AccessSet, AccessRule2 as AccessRule, AccessInstance, AccessEventBroadcasterConfig, AccessEventBroadcaster, AccessEvent, AccessDefinition, AccessContextConfig, AccessContext, AccessConfig, AccessCheckResult, AccessCheckData, AUTH_TABLE_NAMES };