@vertz/server 0.2.13 → 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.
- package/dist/index.d.ts +671 -102
- package/dist/index.js +2139 -208
- 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
|
-
*
|
|
6
|
+
* rules.* builders — declarative access rule data structures.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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<
|
|
1028
|
-
get(ctx: EntityContext, id: string): Promise<
|
|
1029
|
-
create(ctx: EntityContext, data: Record<string, unknown>): Promise<
|
|
1030
|
-
update(ctx: EntityContext, id: string, data: Record<string, unknown>): Promise<
|
|
1031
|
-
delete(ctx: EntityContext, id: string): Promise<
|
|
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
|
|
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 :
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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<
|
|
1198
|
-
import { ModelDef as
|
|
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
|
|
1211
|
-
import { ModelDef as
|
|
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
|
|
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 };
|