@vertz/server 0.2.14 → 0.2.16
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 +962 -191
- package/dist/index.js +3273 -422
- package/package.json +5 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,71 @@
|
|
|
1
|
-
import { AccumulateProvides, AppBuilder as AppBuilder2, AppConfig as AppConfig2, CorsConfig, Ctx, DeepReadonly, Deps, EnvConfig, HandlerCtx, HttpMethod, HttpStatusCode, Infer, InferSchema, ListenOptions, MiddlewareDef, NamedMiddlewareDef, RawRequest, ServerAdapter, ServerHandle } from "@vertz/core";
|
|
1
|
+
import { AccumulateProvides, AppBuilder as AppBuilder2, AppConfig as AppConfig2, CorsConfig, Ctx, DeepReadonly, Deps, EnvConfig, HandlerCtx, HttpMethod, HttpStatusCode, Infer as Infer2, InferSchema, ListenOptions, MiddlewareDef, NamedMiddlewareDef, RawRequest, ServerAdapter, ServerHandle } from "@vertz/core";
|
|
2
2
|
import { BadRequestException, ConflictException, createEnv, createImmutableProxy, createMiddleware, deepFreeze, ForbiddenException, InternalServerErrorException, makeImmutable, NotFoundException, ServiceUnavailableException, UnauthorizedException, ValidationException, VertzException, vertz } from "@vertz/core";
|
|
3
3
|
import { ModelEntry } from "@vertz/db";
|
|
4
4
|
import { AuthError, Result } from "@vertz/errors";
|
|
5
|
+
import { Infer, ObjectSchema, StringSchema } from "@vertz/schema";
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
+
* rules.* builders — declarative access rule data structures.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* These are pure data structures with no evaluation logic.
|
|
10
|
+
* The access context evaluates them at runtime (sub-phase 4).
|
|
10
11
|
*/
|
|
12
|
+
interface RoleRule {
|
|
13
|
+
readonly type: "role";
|
|
14
|
+
readonly roles: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
interface EntitlementRule {
|
|
17
|
+
readonly type: "entitlement";
|
|
18
|
+
readonly entitlement: string;
|
|
19
|
+
}
|
|
20
|
+
interface WhereRule {
|
|
21
|
+
readonly type: "where";
|
|
22
|
+
readonly conditions: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
interface AllRule {
|
|
25
|
+
readonly type: "all";
|
|
26
|
+
readonly rules: readonly AccessRule[];
|
|
27
|
+
}
|
|
28
|
+
interface AnyRule {
|
|
29
|
+
readonly type: "any";
|
|
30
|
+
readonly rules: readonly AccessRule[];
|
|
31
|
+
}
|
|
32
|
+
interface AuthenticatedRule {
|
|
33
|
+
readonly type: "authenticated";
|
|
34
|
+
}
|
|
35
|
+
interface PublicRule {
|
|
36
|
+
readonly type: "public";
|
|
37
|
+
}
|
|
38
|
+
interface FvaRule {
|
|
39
|
+
readonly type: "fva";
|
|
40
|
+
readonly maxAge: number;
|
|
41
|
+
}
|
|
42
|
+
type AccessRule = PublicRule | RoleRule | EntitlementRule | WhereRule | AllRule | AnyRule | AuthenticatedRule | FvaRule;
|
|
43
|
+
interface UserMarker {
|
|
44
|
+
readonly __marker: string;
|
|
45
|
+
}
|
|
46
|
+
declare const rules: {
|
|
47
|
+
/** Endpoint is public — no authentication required */
|
|
48
|
+
public: PublicRule;
|
|
49
|
+
/** User has at least one of the specified roles (OR) */
|
|
50
|
+
role(...roleNames: string[]): RoleRule;
|
|
51
|
+
/** User has the specified entitlement (resolves role+plan+flag from defineAccess config) */
|
|
52
|
+
entitlement(name: string): EntitlementRule;
|
|
53
|
+
/** Row-level condition — DB query syntax. Use rules.user.id for dynamic user markers */
|
|
54
|
+
where(conditions: Record<string, unknown>): WhereRule;
|
|
55
|
+
/** All sub-rules must pass (AND) */
|
|
56
|
+
all(...ruleList: AccessRule[]): AllRule;
|
|
57
|
+
/** At least one sub-rule must pass (OR) */
|
|
58
|
+
any(...ruleList: AccessRule[]): AnyRule;
|
|
59
|
+
/** User must be authenticated (no specific role required) */
|
|
60
|
+
authenticated(): AuthenticatedRule;
|
|
61
|
+
/** User must have verified MFA within maxAge seconds */
|
|
62
|
+
fva(maxAge: number): FvaRule;
|
|
63
|
+
/** Declarative user markers — resolved at evaluation time */
|
|
64
|
+
user: {
|
|
65
|
+
readonly id: UserMarker;
|
|
66
|
+
readonly tenantId: UserMarker;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
11
69
|
/** Denial reasons ordered by actionability (most actionable first) */
|
|
12
70
|
type DenialReason = "plan_required" | "role_required" | "limit_reached" | "flag_disabled" | "hierarchy_denied" | "step_up_required" | "not_authenticated";
|
|
13
71
|
/** Metadata attached to denial reasons */
|
|
@@ -16,9 +74,12 @@ interface DenialMeta {
|
|
|
16
74
|
requiredRoles?: string[];
|
|
17
75
|
disabledFlags?: string[];
|
|
18
76
|
limit?: {
|
|
77
|
+
key?: string;
|
|
19
78
|
max: number;
|
|
20
79
|
consumed: number;
|
|
21
80
|
remaining: number;
|
|
81
|
+
/** True when the tenant is consuming beyond the limit under overage billing */
|
|
82
|
+
overage?: boolean;
|
|
22
83
|
};
|
|
23
84
|
fvaMaxAge?: number;
|
|
24
85
|
}
|
|
@@ -29,43 +90,120 @@ interface AccessCheckResult {
|
|
|
29
90
|
reason?: DenialReason;
|
|
30
91
|
meta?: DenialMeta;
|
|
31
92
|
}
|
|
32
|
-
/** Entitlement definition — maps to roles, optional plans and flags */
|
|
33
|
-
interface EntitlementDef {
|
|
34
|
-
roles: string[];
|
|
35
|
-
plans?: string[];
|
|
36
|
-
flags?: string[];
|
|
37
|
-
}
|
|
38
93
|
/** Billing period for plan limits */
|
|
39
|
-
type BillingPeriod = "month" | "day" | "hour";
|
|
40
|
-
/**
|
|
94
|
+
type BillingPeriod = "month" | "day" | "hour" | "quarter" | "year";
|
|
95
|
+
/** Valid price intervals for plans */
|
|
96
|
+
type PriceInterval = "month" | "quarter" | "year" | "one_off";
|
|
97
|
+
/** Plan price definition */
|
|
98
|
+
interface PlanPrice {
|
|
99
|
+
amount: number;
|
|
100
|
+
interval: PriceInterval;
|
|
101
|
+
}
|
|
102
|
+
/** Overage billing configuration for a limit */
|
|
103
|
+
interface OverageConfig {
|
|
104
|
+
/** Price per unit of overage (e.g., 0.01 for $0.01 per extra unit) */
|
|
105
|
+
amount: number;
|
|
106
|
+
/** Units per charge (e.g., 1 = charge per unit, 100 = charge per 100 units) */
|
|
107
|
+
per: number;
|
|
108
|
+
/** Maximum overage charge per period (safety cap). Omit for no cap. */
|
|
109
|
+
cap?: number;
|
|
110
|
+
}
|
|
111
|
+
/** Limit definition within a plan — gates an entitlement with optional scoping */
|
|
41
112
|
interface LimitDef {
|
|
42
|
-
per: BillingPeriod;
|
|
43
113
|
max: number;
|
|
44
|
-
|
|
45
|
-
/**
|
|
114
|
+
gates: string;
|
|
115
|
+
/** Billing period for the limit. Omitted = lifetime (no reset). */
|
|
116
|
+
per?: BillingPeriod;
|
|
117
|
+
/** Entity scope — omitted = tenant-level, string = per entity instance */
|
|
118
|
+
scope?: string;
|
|
119
|
+
/** Overage billing: allow usage beyond limit at per-unit cost */
|
|
120
|
+
overage?: OverageConfig;
|
|
121
|
+
}
|
|
122
|
+
/** Add-on compatibility — restricts which base plans an add-on can be attached to */
|
|
123
|
+
interface AddOnRequires {
|
|
124
|
+
group: string;
|
|
125
|
+
plans: readonly string[] | string[];
|
|
126
|
+
}
|
|
127
|
+
/** Grace period duration for grandfathering policy */
|
|
128
|
+
type GraceDuration = "1m" | "3m" | "6m" | "12m" | "indefinite";
|
|
129
|
+
/** Grandfathering policy for a plan */
|
|
130
|
+
interface GrandfatheringPolicy {
|
|
131
|
+
grace?: GraceDuration;
|
|
132
|
+
}
|
|
133
|
+
/** Plan definition — features, limits, metadata, and billing */
|
|
46
134
|
interface PlanDef {
|
|
47
|
-
|
|
135
|
+
title?: string;
|
|
136
|
+
description?: string;
|
|
137
|
+
group?: string;
|
|
138
|
+
addOn?: boolean;
|
|
139
|
+
price?: PlanPrice;
|
|
140
|
+
features?: readonly string[] | string[];
|
|
48
141
|
limits?: Record<string, LimitDef>;
|
|
142
|
+
/** Grandfathering policy — how long existing tenants keep old version on plan change */
|
|
143
|
+
grandfathering?: GrandfatheringPolicy;
|
|
144
|
+
/** Add-on only: restricts attachment to tenants on compatible base plans */
|
|
145
|
+
requires?: AddOnRequires;
|
|
146
|
+
}
|
|
147
|
+
/** Entity definition — roles and optional inheritance from parent entity roles */
|
|
148
|
+
interface EntityDef {
|
|
149
|
+
roles: readonly string[] | string[];
|
|
150
|
+
inherits?: Record<string, string>;
|
|
151
|
+
}
|
|
152
|
+
/** Resolved entitlement definition — roles + optional rules */
|
|
153
|
+
interface EntitlementDef {
|
|
154
|
+
roles: string[];
|
|
155
|
+
rules?: AccessRule[];
|
|
156
|
+
flags?: string[];
|
|
157
|
+
/** Plan names that gate this entitlement (evaluated in Layer 4) */
|
|
158
|
+
plans?: string[];
|
|
49
159
|
}
|
|
160
|
+
/** Entitlement callback context — provides `where()` and `user` for attribute rules */
|
|
161
|
+
interface RuleContext {
|
|
162
|
+
where(conditions: Record<string, unknown>): AccessRule;
|
|
163
|
+
user: {
|
|
164
|
+
readonly id: {
|
|
165
|
+
readonly __marker: string;
|
|
166
|
+
};
|
|
167
|
+
readonly tenantId: {
|
|
168
|
+
readonly __marker: string;
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** Entitlement value: object or callback returning object */
|
|
173
|
+
type EntitlementValue = EntitlementDef | ((r: RuleContext) => EntitlementDef);
|
|
50
174
|
/** The input config for defineAccess() */
|
|
51
175
|
interface DefineAccessInput {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
inheritance?: Record<string, Record<string, string>>;
|
|
55
|
-
entitlements: Record<string, EntitlementDef>;
|
|
176
|
+
entities: Record<string, EntityDef>;
|
|
177
|
+
entitlements: Record<string, EntitlementValue>;
|
|
56
178
|
plans?: Record<string, PlanDef>;
|
|
57
179
|
/** Fallback plan name when an org's plan expires. Defaults to 'free'. */
|
|
58
180
|
defaultPlan?: string;
|
|
59
181
|
}
|
|
60
182
|
/** The frozen config returned by defineAccess() */
|
|
61
183
|
interface AccessDefinition {
|
|
184
|
+
/** Inferred hierarchy from inherits declarations */
|
|
62
185
|
readonly hierarchy: readonly string[];
|
|
186
|
+
/** Entities keyed by name */
|
|
187
|
+
readonly entities: Readonly<Record<string, Readonly<EntityDef>>>;
|
|
188
|
+
/** Roles per entity (derived from entities for downstream compat) */
|
|
63
189
|
readonly roles: Readonly<Record<string, readonly string[]>>;
|
|
190
|
+
/** Inheritance map per entity (derived from entities for downstream compat) */
|
|
64
191
|
readonly inheritance: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
192
|
+
/** Resolved entitlements */
|
|
65
193
|
readonly entitlements: Readonly<Record<string, Readonly<EntitlementDef>>>;
|
|
66
194
|
readonly plans?: Readonly<Record<string, Readonly<PlanDef>>>;
|
|
67
195
|
/** Fallback plan name when an org's plan expires. Defaults to 'free'. */
|
|
68
196
|
readonly defaultPlan?: string;
|
|
197
|
+
/**
|
|
198
|
+
* Computed: Set of entitlements that are gated by at least one plan's `features`.
|
|
199
|
+
* If an entitlement is in this set, a plan check is required during `can()`.
|
|
200
|
+
*/
|
|
201
|
+
readonly _planGatedEntitlements: ReadonlySet<string>;
|
|
202
|
+
/**
|
|
203
|
+
* Computed: Map from entitlement to all limit keys that gate it.
|
|
204
|
+
* Used during can() to find all limits that need to pass for an entitlement.
|
|
205
|
+
*/
|
|
206
|
+
readonly _entitlementToLimitKeys: Readonly<Record<string, readonly string[]>>;
|
|
69
207
|
}
|
|
70
208
|
declare function defineAccess(input: DefineAccessInput): AccessDefinition;
|
|
71
209
|
/**
|
|
@@ -143,60 +281,77 @@ declare class InMemoryRoleAssignmentStore implements RoleAssignmentStore {
|
|
|
143
281
|
* Used by Layer 1 of access context to gate entitlements on feature flags.
|
|
144
282
|
*/
|
|
145
283
|
interface FlagStore {
|
|
146
|
-
setFlag(
|
|
147
|
-
getFlag(
|
|
148
|
-
getFlags(
|
|
284
|
+
setFlag(tenantId: string, flag: string, enabled: boolean): void;
|
|
285
|
+
getFlag(tenantId: string, flag: string): boolean;
|
|
286
|
+
getFlags(tenantId: string): Record<string, boolean>;
|
|
149
287
|
}
|
|
150
288
|
declare class InMemoryFlagStore implements FlagStore {
|
|
151
289
|
private flags;
|
|
152
|
-
setFlag(
|
|
153
|
-
getFlag(
|
|
154
|
-
getFlags(
|
|
290
|
+
setFlag(tenantId: string, flag: string, enabled: boolean): void;
|
|
291
|
+
getFlag(tenantId: string, flag: string): boolean;
|
|
292
|
+
getFlags(tenantId: string): Record<string, boolean>;
|
|
155
293
|
}
|
|
156
|
-
/**
|
|
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.
|
|
161
|
-
*/
|
|
162
|
-
/** Per-customer limit override. Only affects the cap, not the billing period. */
|
|
294
|
+
/** Per-tenant limit override. Only affects the cap, not the billing period. */
|
|
163
295
|
interface LimitOverride {
|
|
164
296
|
max: number;
|
|
165
297
|
}
|
|
166
|
-
interface
|
|
167
|
-
|
|
298
|
+
interface Subscription {
|
|
299
|
+
tenantId: string;
|
|
168
300
|
planId: string;
|
|
169
301
|
startedAt: Date;
|
|
170
302
|
expiresAt: Date | null;
|
|
171
303
|
overrides: Record<string, LimitOverride>;
|
|
172
304
|
}
|
|
173
|
-
interface
|
|
305
|
+
interface SubscriptionStore {
|
|
174
306
|
/**
|
|
175
|
-
* Assign a plan to
|
|
176
|
-
* To preserve overrides across plan changes, re-apply them after calling
|
|
307
|
+
* Assign a plan to a tenant. Resets per-tenant overrides (overrides are plan-specific).
|
|
308
|
+
* To preserve overrides across plan changes, re-apply them after calling assign().
|
|
177
309
|
*/
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
updateOverrides(
|
|
181
|
-
|
|
310
|
+
assign(tenantId: string, planId: string, startedAt?: Date, expiresAt?: Date | null): Promise<void>;
|
|
311
|
+
get(tenantId: string): Promise<Subscription | null>;
|
|
312
|
+
updateOverrides(tenantId: string, overrides: Record<string, LimitOverride>): Promise<void>;
|
|
313
|
+
remove(tenantId: string): Promise<void>;
|
|
314
|
+
/** Attach an add-on to a tenant. */
|
|
315
|
+
attachAddOn?(tenantId: string, addOnId: string): Promise<void>;
|
|
316
|
+
/** Detach an add-on from a tenant. */
|
|
317
|
+
detachAddOn?(tenantId: string, addOnId: string): Promise<void>;
|
|
318
|
+
/** Get all active add-on IDs for a tenant. */
|
|
319
|
+
getAddOns?(tenantId: string): Promise<string[]>;
|
|
320
|
+
/** List all tenant IDs assigned to a specific plan. */
|
|
321
|
+
listByPlan?(planId: string): Promise<string[]>;
|
|
182
322
|
dispose(): void;
|
|
183
323
|
}
|
|
184
|
-
declare class
|
|
185
|
-
private
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
324
|
+
declare class InMemorySubscriptionStore implements SubscriptionStore {
|
|
325
|
+
private subscriptions;
|
|
326
|
+
private addOns;
|
|
327
|
+
assign(tenantId: string, planId: string, startedAt?: Date, expiresAt?: Date | null): Promise<void>;
|
|
328
|
+
get(tenantId: string): Promise<Subscription | null>;
|
|
329
|
+
updateOverrides(tenantId: string, overrides: Record<string, LimitOverride>): Promise<void>;
|
|
330
|
+
remove(tenantId: string): Promise<void>;
|
|
331
|
+
attachAddOn(tenantId: string, addOnId: string): Promise<void>;
|
|
332
|
+
detachAddOn(tenantId: string, addOnId: string): Promise<void>;
|
|
333
|
+
getAddOns(tenantId: string): Promise<string[]>;
|
|
334
|
+
listByPlan(planId: string): Promise<string[]>;
|
|
190
335
|
dispose(): void;
|
|
191
336
|
}
|
|
192
337
|
/**
|
|
338
|
+
* Check if an add-on is compatible with a given base plan.
|
|
339
|
+
* Returns true if the add-on has no `requires` or if the plan is in the requires list.
|
|
340
|
+
*/
|
|
341
|
+
declare function checkAddOnCompatibility(accessDef: AccessDefinition, addOnId: string, currentPlanId: string): boolean;
|
|
342
|
+
/**
|
|
343
|
+
* Get add-ons that are incompatible with a target plan.
|
|
344
|
+
* Used to flag incompatible add-ons when a tenant downgrades.
|
|
345
|
+
*/
|
|
346
|
+
declare function getIncompatibleAddOns(accessDef: AccessDefinition, activeAddOnIds: string[], targetPlanId: string): string[];
|
|
347
|
+
/**
|
|
193
348
|
* WalletStore — consumption tracking for plan-limited entitlements.
|
|
194
349
|
*
|
|
195
|
-
* Tracks per-
|
|
350
|
+
* Tracks per-tenant usage within billing periods with atomic
|
|
196
351
|
* check-and-increment operations.
|
|
197
352
|
*/
|
|
198
353
|
interface WalletEntry {
|
|
199
|
-
|
|
354
|
+
tenantId: string;
|
|
200
355
|
entitlement: string;
|
|
201
356
|
periodStart: Date;
|
|
202
357
|
periodEnd: Date;
|
|
@@ -209,17 +364,99 @@ interface ConsumeResult {
|
|
|
209
364
|
remaining: number;
|
|
210
365
|
}
|
|
211
366
|
interface WalletStore {
|
|
212
|
-
consume(
|
|
213
|
-
unconsume(
|
|
214
|
-
getConsumption(
|
|
367
|
+
consume(tenantId: string, entitlement: string, periodStart: Date, periodEnd: Date, limit: number, amount?: number): Promise<ConsumeResult>;
|
|
368
|
+
unconsume(tenantId: string, entitlement: string, periodStart: Date, periodEnd: Date, amount?: number): Promise<void>;
|
|
369
|
+
getConsumption(tenantId: string, entitlement: string, periodStart: Date, periodEnd: Date): Promise<number>;
|
|
215
370
|
dispose(): void;
|
|
216
371
|
}
|
|
217
372
|
declare class InMemoryWalletStore implements WalletStore {
|
|
218
373
|
private entries;
|
|
219
374
|
private key;
|
|
220
|
-
consume(
|
|
221
|
-
unconsume(
|
|
222
|
-
getConsumption(
|
|
375
|
+
consume(tenantId: string, entitlement: string, periodStart: Date, periodEnd: Date, limit: number, amount?: number): Promise<ConsumeResult>;
|
|
376
|
+
unconsume(tenantId: string, entitlement: string, periodStart: Date, _periodEnd: Date, amount?: number): Promise<void>;
|
|
377
|
+
getConsumption(tenantId: string, entitlement: string, periodStart: Date, _periodEnd: Date): Promise<number>;
|
|
378
|
+
dispose(): void;
|
|
379
|
+
}
|
|
380
|
+
/** Limit override: add (additive) or max (hard cap) */
|
|
381
|
+
interface LimitOverrideDef {
|
|
382
|
+
add?: number;
|
|
383
|
+
max?: number;
|
|
384
|
+
}
|
|
385
|
+
/** Per-tenant overrides */
|
|
386
|
+
interface TenantOverrides {
|
|
387
|
+
features?: string[];
|
|
388
|
+
limits?: Record<string, LimitOverrideDef>;
|
|
389
|
+
}
|
|
390
|
+
interface OverrideStore {
|
|
391
|
+
set(tenantId: string, overrides: TenantOverrides): Promise<void>;
|
|
392
|
+
remove(tenantId: string, keys: {
|
|
393
|
+
features?: string[];
|
|
394
|
+
limits?: string[];
|
|
395
|
+
}): Promise<void>;
|
|
396
|
+
get(tenantId: string): Promise<TenantOverrides | null>;
|
|
397
|
+
dispose(): void;
|
|
398
|
+
}
|
|
399
|
+
declare class InMemoryOverrideStore implements OverrideStore {
|
|
400
|
+
private overrides;
|
|
401
|
+
set(tenantId: string, overrides: TenantOverrides): Promise<void>;
|
|
402
|
+
remove(tenantId: string, keys: {
|
|
403
|
+
features?: string[];
|
|
404
|
+
limits?: string[];
|
|
405
|
+
}): Promise<void>;
|
|
406
|
+
get(tenantId: string): Promise<TenantOverrides | null>;
|
|
407
|
+
dispose(): void;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Validate tenant overrides against the access definition.
|
|
411
|
+
* Throws on invalid limit keys, invalid feature names, or invalid max values.
|
|
412
|
+
*/
|
|
413
|
+
declare function validateOverrides(accessDef: AccessDefinition, overrides: TenantOverrides): void;
|
|
414
|
+
/**
|
|
415
|
+
* Plan Version Store — tracks versioned snapshots of plan configurations.
|
|
416
|
+
*
|
|
417
|
+
* When a plan's config changes (hash differs), a new version is created
|
|
418
|
+
* with a snapshot of the plan's features, limits, and price at that point.
|
|
419
|
+
*/
|
|
420
|
+
interface PlanSnapshot {
|
|
421
|
+
features: readonly string[] | string[];
|
|
422
|
+
limits: Record<string, unknown>;
|
|
423
|
+
price: {
|
|
424
|
+
amount: number;
|
|
425
|
+
interval: string;
|
|
426
|
+
} | null;
|
|
427
|
+
}
|
|
428
|
+
interface PlanVersionInfo {
|
|
429
|
+
planId: string;
|
|
430
|
+
version: number;
|
|
431
|
+
hash: string;
|
|
432
|
+
snapshot: PlanSnapshot;
|
|
433
|
+
createdAt: Date;
|
|
434
|
+
}
|
|
435
|
+
interface PlanVersionStore {
|
|
436
|
+
/** Create a new version for a plan. Returns the version number. */
|
|
437
|
+
createVersion(planId: string, hash: string, snapshot: PlanSnapshot): Promise<number>;
|
|
438
|
+
/** Get the current (latest) version number for a plan. Returns null if no versions exist. */
|
|
439
|
+
getCurrentVersion(planId: string): Promise<number | null>;
|
|
440
|
+
/** Get a specific version's info. Returns null if not found. */
|
|
441
|
+
getVersion(planId: string, version: number): Promise<PlanVersionInfo | null>;
|
|
442
|
+
/** Get the version number a tenant is on for a given plan. Returns null if not set. */
|
|
443
|
+
getTenantVersion(tenantId: string, planId: string): Promise<number | null>;
|
|
444
|
+
/** Set the version number a tenant is on for a given plan. */
|
|
445
|
+
setTenantVersion(tenantId: string, planId: string, version: number): Promise<void>;
|
|
446
|
+
/** Get the hash of the current (latest) version for a plan. Returns null if no versions. */
|
|
447
|
+
getCurrentHash(planId: string): Promise<string | null>;
|
|
448
|
+
/** Clean up resources. */
|
|
449
|
+
dispose(): void;
|
|
450
|
+
}
|
|
451
|
+
declare class InMemoryPlanVersionStore implements PlanVersionStore {
|
|
452
|
+
private versions;
|
|
453
|
+
private tenantVersions;
|
|
454
|
+
createVersion(planId: string, hash: string, snapshot: PlanSnapshot): Promise<number>;
|
|
455
|
+
getCurrentVersion(planId: string): Promise<number | null>;
|
|
456
|
+
getVersion(planId: string, version: number): Promise<PlanVersionInfo | null>;
|
|
457
|
+
getTenantVersion(tenantId: string, planId: string): Promise<number | null>;
|
|
458
|
+
setTenantVersion(tenantId: string, planId: string, version: number): Promise<void>;
|
|
459
|
+
getCurrentHash(planId: string): Promise<string | null>;
|
|
223
460
|
dispose(): void;
|
|
224
461
|
}
|
|
225
462
|
interface ResourceRef {
|
|
@@ -235,25 +472,39 @@ interface AccessContextConfig {
|
|
|
235
472
|
fva?: number;
|
|
236
473
|
/** Flag store — required for Layer 1 feature flag checks */
|
|
237
474
|
flagStore?: FlagStore;
|
|
238
|
-
/**
|
|
239
|
-
|
|
475
|
+
/** Subscription store — required for Layer 4 plan checks */
|
|
476
|
+
subscriptionStore?: SubscriptionStore;
|
|
240
477
|
/** Wallet store — required for Layer 5 wallet checks and canAndConsume() */
|
|
241
478
|
walletStore?: WalletStore;
|
|
479
|
+
/** Override store — per-tenant feature and limit overrides */
|
|
480
|
+
overrideStore?: OverrideStore;
|
|
242
481
|
/** Resolves an org ID from a resource. Required for plan/wallet checks. */
|
|
243
482
|
orgResolver?: (resource?: ResourceRef) => Promise<string | null>;
|
|
483
|
+
/** Plan version store — required for versioned plan resolution (grandfathered tenants) */
|
|
484
|
+
planVersionStore?: PlanVersionStore;
|
|
244
485
|
}
|
|
486
|
+
/**
|
|
487
|
+
* Entitlement registry — augmented by @vertz/codegen to narrow entitlement strings.
|
|
488
|
+
* When empty (no codegen), Entitlement falls back to `string`.
|
|
489
|
+
* When codegen runs, it populates this with `{ 'entity:action': true }` entries.
|
|
490
|
+
*/
|
|
491
|
+
interface EntitlementRegistry {}
|
|
492
|
+
/** Entitlement type — narrows to literal union when codegen populates EntitlementRegistry. */
|
|
493
|
+
type Entitlement = keyof EntitlementRegistry extends never ? string : Extract<keyof EntitlementRegistry, string>;
|
|
245
494
|
interface AccessContext {
|
|
246
|
-
can(entitlement:
|
|
247
|
-
check(entitlement:
|
|
248
|
-
authorize(entitlement:
|
|
495
|
+
can(entitlement: Entitlement, resource?: ResourceRef): Promise<boolean>;
|
|
496
|
+
check(entitlement: Entitlement, resource?: ResourceRef): Promise<AccessCheckResult>;
|
|
497
|
+
authorize(entitlement: Entitlement, resource?: ResourceRef): Promise<void>;
|
|
249
498
|
canAll(checks: Array<{
|
|
250
|
-
entitlement:
|
|
499
|
+
entitlement: Entitlement;
|
|
251
500
|
resource?: ResourceRef;
|
|
252
501
|
}>): Promise<Map<string, boolean>>;
|
|
502
|
+
/** Batch check: single entitlement across multiple entities. Returns Map<entityId, boolean>. */
|
|
503
|
+
canBatch(entitlement: Entitlement, resources: ResourceRef[]): Promise<Map<string, boolean>>;
|
|
253
504
|
/** Atomic check + consume. Runs full can() then increments wallet if all layers pass. */
|
|
254
|
-
canAndConsume(entitlement:
|
|
505
|
+
canAndConsume(entitlement: Entitlement, resource?: ResourceRef, amount?: number): Promise<boolean>;
|
|
255
506
|
/** Rollback a previous canAndConsume(). Use when the operation fails after consumption. */
|
|
256
|
-
unconsume(entitlement:
|
|
507
|
+
unconsume(entitlement: Entitlement, resource?: ResourceRef, amount?: number): Promise<void>;
|
|
257
508
|
}
|
|
258
509
|
declare function createAccessContext(config: AccessContextConfig): AccessContext;
|
|
259
510
|
interface AccessCheckData {
|
|
@@ -273,17 +524,16 @@ interface ComputeAccessSetConfig {
|
|
|
273
524
|
accessDef: AccessDefinition;
|
|
274
525
|
roleStore: RoleAssignmentStore;
|
|
275
526
|
closureStore: ClosureStore;
|
|
276
|
-
plan?: string | null;
|
|
277
527
|
/** Flag store — for feature flag state in access set */
|
|
278
528
|
flagStore?: FlagStore;
|
|
279
|
-
/**
|
|
280
|
-
|
|
529
|
+
/** Subscription store — for limit info in access set */
|
|
530
|
+
subscriptionStore?: SubscriptionStore;
|
|
281
531
|
/** Wallet store — for consumption info in access set */
|
|
282
532
|
walletStore?: WalletStore;
|
|
283
533
|
/** Org resolver — for plan/wallet lookups */
|
|
284
534
|
orgResolver?: (resource?: ResourceRef) => Promise<string | null>;
|
|
285
|
-
/**
|
|
286
|
-
|
|
535
|
+
/** Tenant ID — direct tenant ID for global access set (bypass orgResolver) */
|
|
536
|
+
tenantId?: string | null;
|
|
287
537
|
}
|
|
288
538
|
declare function computeAccessSet(config: ComputeAccessSetConfig): Promise<AccessSet>;
|
|
289
539
|
/** Sparse encoding for JWT. Only includes allowed + denied-with-meta entries. */
|
|
@@ -351,6 +601,7 @@ interface SessionStore {
|
|
|
351
601
|
expiresAt: Date;
|
|
352
602
|
currentTokens?: AuthTokens;
|
|
353
603
|
}): Promise<StoredSession>;
|
|
604
|
+
findActiveSessionById(id: string): Promise<StoredSession | null>;
|
|
354
605
|
findByRefreshHash(hash: string): Promise<StoredSession | null>;
|
|
355
606
|
findByPreviousRefreshHash(hash: string): Promise<StoredSession | null>;
|
|
356
607
|
revokeSession(id: string): Promise<void>;
|
|
@@ -390,6 +641,8 @@ interface UserStore {
|
|
|
390
641
|
findById(id: string): Promise<AuthUser | null>;
|
|
391
642
|
updatePasswordHash(userId: string, passwordHash: string): Promise<void>;
|
|
392
643
|
updateEmailVerified(userId: string, verified: boolean): Promise<void>;
|
|
644
|
+
/** Delete a user by id. Used for rollback when onUserCreated fails. */
|
|
645
|
+
deleteUser(id: string): Promise<void>;
|
|
393
646
|
}
|
|
394
647
|
interface MfaConfig {
|
|
395
648
|
enabled?: boolean;
|
|
@@ -431,8 +684,7 @@ interface OAuthUserInfo {
|
|
|
431
684
|
providerId: string;
|
|
432
685
|
email: string;
|
|
433
686
|
emailVerified: boolean;
|
|
434
|
-
|
|
435
|
-
avatarUrl?: string;
|
|
687
|
+
raw: Record<string, unknown>;
|
|
436
688
|
}
|
|
437
689
|
interface OAuthProvider {
|
|
438
690
|
id: string;
|
|
@@ -453,6 +705,45 @@ interface OAuthAccountStore {
|
|
|
453
705
|
unlinkAccount(userId: string, provider: string): Promise<void>;
|
|
454
706
|
dispose(): void;
|
|
455
707
|
}
|
|
708
|
+
/** Context provided to auth lifecycle callbacks. */
|
|
709
|
+
interface AuthCallbackContext {
|
|
710
|
+
/**
|
|
711
|
+
* System-level entity access — bypasses access rules.
|
|
712
|
+
* During sign-up, the user isn't authenticated yet,
|
|
713
|
+
* so access rules like rules.authenticated() would block the callback.
|
|
714
|
+
*/
|
|
715
|
+
entities: Record<string, AuthEntityProxy>;
|
|
716
|
+
}
|
|
717
|
+
/** Minimal CRUD interface for entity access within auth callbacks. */
|
|
718
|
+
interface AuthEntityProxy {
|
|
719
|
+
get(id: string): Promise<unknown>;
|
|
720
|
+
list(options?: unknown): Promise<unknown>;
|
|
721
|
+
create(data: Record<string, unknown>): Promise<unknown>;
|
|
722
|
+
update(id: string, data: Record<string, unknown>): Promise<unknown>;
|
|
723
|
+
delete(id: string): Promise<void>;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Discriminated union for onUserCreated callback payload.
|
|
727
|
+
* OAuth and email/password sign-ups provide different data shapes.
|
|
728
|
+
*/
|
|
729
|
+
type OnUserCreatedPayload = {
|
|
730
|
+
/** The auth user that was just created. */
|
|
731
|
+
user: AuthUser;
|
|
732
|
+
/** The OAuth provider that created this user. */
|
|
733
|
+
provider: {
|
|
734
|
+
id: string;
|
|
735
|
+
name: string;
|
|
736
|
+
};
|
|
737
|
+
/** Full provider API response (cast to GithubProfile, GoogleProfile, etc.). */
|
|
738
|
+
profile: Record<string, unknown>;
|
|
739
|
+
} | {
|
|
740
|
+
/** The auth user that was just created. */
|
|
741
|
+
user: AuthUser;
|
|
742
|
+
/** null for email/password sign-up. */
|
|
743
|
+
provider: null;
|
|
744
|
+
/** Extra fields from the sign-up form (via schema passthrough). */
|
|
745
|
+
signUpData: Record<string, unknown>;
|
|
746
|
+
};
|
|
456
747
|
interface EmailVerificationConfig {
|
|
457
748
|
enabled: boolean;
|
|
458
749
|
tokenTtl?: string | number;
|
|
@@ -548,6 +839,21 @@ interface AuthConfig {
|
|
|
548
839
|
passwordResetStore?: PasswordResetStore;
|
|
549
840
|
/** Access control configuration — enables ACL claim in JWT */
|
|
550
841
|
access?: AuthAccessConfig;
|
|
842
|
+
/** Tenant switching configuration — enables POST /auth/switch-tenant */
|
|
843
|
+
tenant?: TenantConfig;
|
|
844
|
+
/**
|
|
845
|
+
* Called after a new user is created in the auth system.
|
|
846
|
+
* Fires before the session is created.
|
|
847
|
+
* If this throws, the auth user is rolled back (deleted).
|
|
848
|
+
*/
|
|
849
|
+
onUserCreated?: (payload: OnUserCreatedPayload, ctx: AuthCallbackContext) => Promise<void>;
|
|
850
|
+
/** @internal Entity proxy for onUserCreated callback. Set by createServer(). */
|
|
851
|
+
_entityProxy?: Record<string, AuthEntityProxy>;
|
|
852
|
+
}
|
|
853
|
+
/** Configuration for multi-tenant session switching. */
|
|
854
|
+
interface TenantConfig {
|
|
855
|
+
/** Verify that user has membership in the target tenant. Return false to deny. */
|
|
856
|
+
verifyMembership: (userId: string, tenantId: string) => Promise<boolean>;
|
|
551
857
|
}
|
|
552
858
|
/** Access control configuration for JWT acl claim computation. */
|
|
553
859
|
interface AuthAccessConfig {
|
|
@@ -555,16 +861,16 @@ interface AuthAccessConfig {
|
|
|
555
861
|
roleStore: RoleAssignmentStore;
|
|
556
862
|
closureStore: ClosureStore;
|
|
557
863
|
flagStore?: FlagStore;
|
|
864
|
+
subscriptionStore?: SubscriptionStore;
|
|
865
|
+
walletStore?: WalletStore;
|
|
558
866
|
}
|
|
559
867
|
interface AuthUser {
|
|
560
868
|
id: string;
|
|
561
869
|
email: string;
|
|
562
870
|
role: string;
|
|
563
|
-
plan?: string;
|
|
564
871
|
emailVerified?: boolean;
|
|
565
872
|
createdAt: Date;
|
|
566
873
|
updatedAt: Date;
|
|
567
|
-
[key: string]: unknown;
|
|
568
874
|
}
|
|
569
875
|
interface SessionPayload {
|
|
570
876
|
sub: string;
|
|
@@ -574,6 +880,7 @@ interface SessionPayload {
|
|
|
574
880
|
exp: number;
|
|
575
881
|
jti: string;
|
|
576
882
|
sid: string;
|
|
883
|
+
tenantId?: string;
|
|
577
884
|
claims?: Record<string, unknown>;
|
|
578
885
|
fva?: number;
|
|
579
886
|
acl?: AclClaim;
|
|
@@ -608,16 +915,18 @@ interface SessionInfo {
|
|
|
608
915
|
expiresAt: Date;
|
|
609
916
|
isCurrent: boolean;
|
|
610
917
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
email:
|
|
619
|
-
password:
|
|
620
|
-
}
|
|
918
|
+
type ReservedSignUpField = "role" | "emailVerified" | "id" | "createdAt" | "updatedAt";
|
|
919
|
+
type ReservedSignUpFields = { [K in ReservedSignUpField]? : never };
|
|
920
|
+
declare const signUpInputSchema: ObjectSchema<{
|
|
921
|
+
email: StringSchema;
|
|
922
|
+
password: StringSchema;
|
|
923
|
+
}>;
|
|
924
|
+
declare const signInInputSchema: ObjectSchema<{
|
|
925
|
+
email: StringSchema;
|
|
926
|
+
password: StringSchema;
|
|
927
|
+
}>;
|
|
928
|
+
type SignUpInput = Infer<typeof signUpInputSchema> & ReservedSignUpFields & Record<string, unknown>;
|
|
929
|
+
type SignInInput = Infer<typeof signInInputSchema>;
|
|
621
930
|
interface AuthApi {
|
|
622
931
|
signUp: (data: SignUpInput, ctx?: {
|
|
623
932
|
headers: Headers;
|
|
@@ -647,6 +956,19 @@ interface AuthInstance {
|
|
|
647
956
|
initialize: () => Promise<void>;
|
|
648
957
|
/** Dispose stores and cleanup intervals */
|
|
649
958
|
dispose: () => void;
|
|
959
|
+
/**
|
|
960
|
+
* JWT-only session resolver for SSR injection.
|
|
961
|
+
* Reads the session cookie, verifies JWT (no DB lookup), and returns
|
|
962
|
+
* minimal session data + optional access set for client hydration.
|
|
963
|
+
*/
|
|
964
|
+
resolveSessionForSSR: (request: Request) => Promise<{
|
|
965
|
+
session: {
|
|
966
|
+
user: Record<string, unknown>;
|
|
967
|
+
expiresAt: number;
|
|
968
|
+
};
|
|
969
|
+
/** AccessSet | null at runtime; typed as unknown to avoid cross-package dependency on @vertz/ui */
|
|
970
|
+
accessSet?: unknown;
|
|
971
|
+
} | null>;
|
|
650
972
|
}
|
|
651
973
|
interface AuthContext {
|
|
652
974
|
headers: Headers;
|
|
@@ -672,9 +994,6 @@ interface UserTableEntry extends ModelEntry<any, any> {
|
|
|
672
994
|
role: {
|
|
673
995
|
type: string;
|
|
674
996
|
};
|
|
675
|
-
plan?: {
|
|
676
|
-
type: string;
|
|
677
|
-
};
|
|
678
997
|
createdAt: {
|
|
679
998
|
type: Date;
|
|
680
999
|
};
|
|
@@ -703,9 +1022,8 @@ import { AuthValidationError } from "@vertz/errors";
|
|
|
703
1022
|
declare function hashPassword(password: string): Promise<string>;
|
|
704
1023
|
declare function verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
705
1024
|
declare function validatePassword(password: string, requirements?: PasswordRequirements): AuthValidationError | null;
|
|
706
|
-
type Entitlement = string;
|
|
707
1025
|
interface RoleDefinition {
|
|
708
|
-
entitlements:
|
|
1026
|
+
entitlements: string[];
|
|
709
1027
|
}
|
|
710
1028
|
interface EntitlementDefinition {
|
|
711
1029
|
roles: string[];
|
|
@@ -718,20 +1036,20 @@ interface AccessConfig {
|
|
|
718
1036
|
}
|
|
719
1037
|
interface AccessInstance {
|
|
720
1038
|
/** Check if user has a specific entitlement */
|
|
721
|
-
can(entitlement:
|
|
1039
|
+
can(entitlement: string, user: AuthUser | null): Promise<boolean>;
|
|
722
1040
|
/** Check with resource context */
|
|
723
|
-
canWithResource(entitlement:
|
|
1041
|
+
canWithResource(entitlement: string, resource: Resource, user: AuthUser | null): Promise<boolean>;
|
|
724
1042
|
/** Throws if not authorized */
|
|
725
|
-
authorize(entitlement:
|
|
1043
|
+
authorize(entitlement: string, user: AuthUser | null): Promise<void>;
|
|
726
1044
|
/** Authorize with resource context */
|
|
727
|
-
authorizeWithResource(entitlement:
|
|
1045
|
+
authorizeWithResource(entitlement: string, resource: Resource, user: AuthUser | null): Promise<void>;
|
|
728
1046
|
/** Check multiple entitlements at once */
|
|
729
1047
|
canAll(checks: Array<{
|
|
730
|
-
entitlement:
|
|
1048
|
+
entitlement: string;
|
|
731
1049
|
resource?: Resource;
|
|
732
1050
|
}>, user: AuthUser | null): Promise<Map<string, boolean>>;
|
|
733
1051
|
/** Get all entitlements for a role */
|
|
734
|
-
getEntitlementsForRole(role: string):
|
|
1052
|
+
getEntitlementsForRole(role: string): string[];
|
|
735
1053
|
/** Middleware that adds ctx.can() and ctx.authorize() to context */
|
|
736
1054
|
middleware: () => any;
|
|
737
1055
|
}
|
|
@@ -742,9 +1060,9 @@ interface Resource {
|
|
|
742
1060
|
[key: string]: unknown;
|
|
743
1061
|
}
|
|
744
1062
|
declare class AuthorizationError extends Error {
|
|
745
|
-
readonly entitlement:
|
|
1063
|
+
readonly entitlement: string;
|
|
746
1064
|
readonly userId?: string | undefined;
|
|
747
|
-
constructor(message: string, entitlement:
|
|
1065
|
+
constructor(message: string, entitlement: string, userId?: string | undefined);
|
|
748
1066
|
}
|
|
749
1067
|
declare function createAccess(config: AccessConfig): AccessInstance;
|
|
750
1068
|
declare const defaultAccess: AccessInstance;
|
|
@@ -766,6 +1084,23 @@ type AccessEvent = {
|
|
|
766
1084
|
} | {
|
|
767
1085
|
type: "access:plan_changed";
|
|
768
1086
|
orgId: string;
|
|
1087
|
+
} | {
|
|
1088
|
+
type: "access:plan_assigned";
|
|
1089
|
+
orgId: string;
|
|
1090
|
+
planId: string;
|
|
1091
|
+
} | {
|
|
1092
|
+
type: "access:addon_attached";
|
|
1093
|
+
orgId: string;
|
|
1094
|
+
addonId: string;
|
|
1095
|
+
} | {
|
|
1096
|
+
type: "access:addon_detached";
|
|
1097
|
+
orgId: string;
|
|
1098
|
+
addonId: string;
|
|
1099
|
+
} | {
|
|
1100
|
+
type: "access:limit_reset";
|
|
1101
|
+
orgId: string;
|
|
1102
|
+
entitlement: string;
|
|
1103
|
+
max: number;
|
|
769
1104
|
};
|
|
770
1105
|
interface AccessWsData {
|
|
771
1106
|
userId: string;
|
|
@@ -790,6 +1125,10 @@ interface AccessEventBroadcaster {
|
|
|
790
1125
|
broadcastLimitUpdate(orgId: string, entitlement: string, consumed: number, remaining: number, max: number): void;
|
|
791
1126
|
broadcastRoleChange(userId: string): void;
|
|
792
1127
|
broadcastPlanChange(orgId: string): void;
|
|
1128
|
+
broadcastPlanAssigned(orgId: string, planId: string): void;
|
|
1129
|
+
broadcastAddonAttached(orgId: string, addonId: string): void;
|
|
1130
|
+
broadcastAddonDetached(orgId: string, addonId: string): void;
|
|
1131
|
+
broadcastLimitReset(orgId: string, entitlement: string, max: number): void;
|
|
793
1132
|
getConnectionCount: number;
|
|
794
1133
|
}
|
|
795
1134
|
/** Minimal Bun.Server interface for WebSocket upgrade */
|
|
@@ -806,6 +1145,156 @@ interface BunWebSocket<T> {
|
|
|
806
1145
|
ping(): void;
|
|
807
1146
|
}
|
|
808
1147
|
declare function createAccessEventBroadcaster(config: AccessEventBroadcasterConfig): AccessEventBroadcaster;
|
|
1148
|
+
import { ModelDef } from "@vertz/db";
|
|
1149
|
+
declare const authModels: Record<string, ModelDef>;
|
|
1150
|
+
import { DatabaseClient } from "@vertz/db";
|
|
1151
|
+
type AuthModels = typeof authModels;
|
|
1152
|
+
/**
|
|
1153
|
+
* Minimal database interface for auth stores.
|
|
1154
|
+
*
|
|
1155
|
+
* Keeps raw query support for legacy stores, but exposes the typed
|
|
1156
|
+
* `auth_sessions` delegate so session lookups can go through the generated
|
|
1157
|
+
* client instead of ad hoc SQL strings. Includes `transaction` for atomic
|
|
1158
|
+
* multi-statement writes in plan and closure stores.
|
|
1159
|
+
*/
|
|
1160
|
+
type AuthDbClient = Pick<DatabaseClient<AuthModels>, "auth_sessions" | "query" | "_internals" | "transaction">;
|
|
1161
|
+
/**
|
|
1162
|
+
* Dialect-aware DDL helpers for auth table creation.
|
|
1163
|
+
*
|
|
1164
|
+
* Produces SQL column type fragments for SQLite and PostgreSQL,
|
|
1165
|
+
* enabling portable CREATE TABLE statements.
|
|
1166
|
+
*/
|
|
1167
|
+
type DbDialectName = "sqlite" | "postgres";
|
|
1168
|
+
/**
|
|
1169
|
+
* Names of all auth tables — used for model validation.
|
|
1170
|
+
*/
|
|
1171
|
+
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"];
|
|
1172
|
+
/**
|
|
1173
|
+
* Validate that all required auth models are registered in the DatabaseClient.
|
|
1174
|
+
*
|
|
1175
|
+
* Throws a prescriptive error when models are missing, telling the developer
|
|
1176
|
+
* exactly what to add to their createDb() call.
|
|
1177
|
+
*/
|
|
1178
|
+
declare function validateAuthModels(db: AuthDbClient): void;
|
|
1179
|
+
/**
|
|
1180
|
+
* Initialize auth tables in the database.
|
|
1181
|
+
*
|
|
1182
|
+
* Executes CREATE TABLE IF NOT EXISTS for all 9 auth tables.
|
|
1183
|
+
* Idempotent — safe to call on every server start.
|
|
1184
|
+
*/
|
|
1185
|
+
declare function initializeAuthTables(db: AuthDbClient): Promise<void>;
|
|
1186
|
+
interface BillingAdapter {
|
|
1187
|
+
/** Push plan definitions to the payment processor. Idempotent. */
|
|
1188
|
+
syncPlans(plans: Record<string, PlanDef>): Promise<void>;
|
|
1189
|
+
/** Create a subscription for a tenant. */
|
|
1190
|
+
createSubscription(tenantId: string, planId: string): Promise<string>;
|
|
1191
|
+
/** Cancel a tenant's subscription. */
|
|
1192
|
+
cancelSubscription(tenantId: string): Promise<void>;
|
|
1193
|
+
/** Attach an add-on to a tenant's subscription. */
|
|
1194
|
+
attachAddOn(tenantId: string, addOnId: string): Promise<void>;
|
|
1195
|
+
/** Report metered usage (overage) for a tenant. */
|
|
1196
|
+
reportOverage(tenantId: string, limitKey: string, amount: number): Promise<void>;
|
|
1197
|
+
/** Get the billing portal URL for a tenant. */
|
|
1198
|
+
getPortalUrl(tenantId: string): Promise<string>;
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Billing Event Emitter — typed event system for billing lifecycle events.
|
|
1202
|
+
*
|
|
1203
|
+
* Events: subscription:created, subscription:canceled, billing:payment_failed
|
|
1204
|
+
*/
|
|
1205
|
+
type BillingEventType = "subscription:created" | "subscription:canceled" | "billing:payment_failed";
|
|
1206
|
+
interface BillingEvent {
|
|
1207
|
+
tenantId: string;
|
|
1208
|
+
planId: string;
|
|
1209
|
+
attempt?: number;
|
|
1210
|
+
}
|
|
1211
|
+
type BillingEventHandler = (event: BillingEvent) => void;
|
|
1212
|
+
interface BillingEventEmitter {
|
|
1213
|
+
on(eventType: BillingEventType, handler: BillingEventHandler): void;
|
|
1214
|
+
off(eventType: BillingEventType, handler: BillingEventHandler): void;
|
|
1215
|
+
emit(eventType: BillingEventType, event: BillingEvent): void;
|
|
1216
|
+
}
|
|
1217
|
+
declare function createBillingEventEmitter(): BillingEventEmitter;
|
|
1218
|
+
/**
|
|
1219
|
+
* Overage Computation — calculates overage charges for limit usage.
|
|
1220
|
+
*
|
|
1221
|
+
* Formula: (consumed - max) * rate, capped if cap is specified.
|
|
1222
|
+
* Returns 0 if usage is within the limit.
|
|
1223
|
+
*/
|
|
1224
|
+
interface OverageInput {
|
|
1225
|
+
consumed: number;
|
|
1226
|
+
max: number;
|
|
1227
|
+
rate: number;
|
|
1228
|
+
cap?: number;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Compute overage charge for a given usage period.
|
|
1232
|
+
*
|
|
1233
|
+
* @returns The overage charge amount (0 if within limit)
|
|
1234
|
+
*/
|
|
1235
|
+
declare function computeOverage(input: OverageInput): number;
|
|
1236
|
+
interface StripeProduct {
|
|
1237
|
+
id: string;
|
|
1238
|
+
name: string;
|
|
1239
|
+
metadata: Record<string, string>;
|
|
1240
|
+
}
|
|
1241
|
+
interface StripePrice {
|
|
1242
|
+
id: string;
|
|
1243
|
+
product: string;
|
|
1244
|
+
unit_amount: number;
|
|
1245
|
+
recurring: {
|
|
1246
|
+
interval: string;
|
|
1247
|
+
} | null;
|
|
1248
|
+
active: boolean;
|
|
1249
|
+
metadata: Record<string, string>;
|
|
1250
|
+
}
|
|
1251
|
+
interface StripeClient {
|
|
1252
|
+
products: {
|
|
1253
|
+
list(): Promise<{
|
|
1254
|
+
data: StripeProduct[];
|
|
1255
|
+
}>;
|
|
1256
|
+
create(params: {
|
|
1257
|
+
name: string;
|
|
1258
|
+
metadata: Record<string, string>;
|
|
1259
|
+
description?: string;
|
|
1260
|
+
}): Promise<StripeProduct>;
|
|
1261
|
+
update(id: string, params: {
|
|
1262
|
+
name?: string;
|
|
1263
|
+
metadata?: Record<string, string>;
|
|
1264
|
+
}): Promise<StripeProduct>;
|
|
1265
|
+
};
|
|
1266
|
+
prices: {
|
|
1267
|
+
list(params: {
|
|
1268
|
+
product: string;
|
|
1269
|
+
}): Promise<{
|
|
1270
|
+
data: StripePrice[];
|
|
1271
|
+
}>;
|
|
1272
|
+
create(params: {
|
|
1273
|
+
product: string;
|
|
1274
|
+
unit_amount: number;
|
|
1275
|
+
currency: string;
|
|
1276
|
+
recurring?: {
|
|
1277
|
+
interval: string;
|
|
1278
|
+
};
|
|
1279
|
+
metadata: Record<string, string>;
|
|
1280
|
+
}): Promise<StripePrice>;
|
|
1281
|
+
update(id: string, params: {
|
|
1282
|
+
active: boolean;
|
|
1283
|
+
}): Promise<StripePrice>;
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
interface StripeBillingAdapterConfig {
|
|
1287
|
+
stripe: StripeClient;
|
|
1288
|
+
currency?: string;
|
|
1289
|
+
}
|
|
1290
|
+
declare function createStripeBillingAdapter(config: StripeBillingAdapterConfig): BillingAdapter;
|
|
1291
|
+
interface WebhookHandlerConfig {
|
|
1292
|
+
subscriptionStore: SubscriptionStore;
|
|
1293
|
+
emitter: BillingEventEmitter;
|
|
1294
|
+
defaultPlan: string;
|
|
1295
|
+
webhookSecret: string;
|
|
1296
|
+
}
|
|
1297
|
+
declare function createWebhookHandler(config: WebhookHandlerConfig): (request: Request) => Promise<Response>;
|
|
809
1298
|
interface Period {
|
|
810
1299
|
periodStart: Date;
|
|
811
1300
|
periodEnd: Date;
|
|
@@ -817,6 +1306,107 @@ interface Period {
|
|
|
817
1306
|
* the period containing now. For 'day'/'hour': uses fixed-duration periods.
|
|
818
1307
|
*/
|
|
819
1308
|
declare function calculateBillingPeriod(startedAt: Date, per: BillingPeriod, now?: Date): Period;
|
|
1309
|
+
declare class DbClosureStore implements ClosureStore {
|
|
1310
|
+
private db;
|
|
1311
|
+
constructor(db: AuthDbClient);
|
|
1312
|
+
addResource(type: string, id: string, parent?: ParentRef): Promise<void>;
|
|
1313
|
+
removeResource(type: string, id: string): Promise<void>;
|
|
1314
|
+
getAncestors(type: string, id: string): Promise<ClosureEntry[]>;
|
|
1315
|
+
getDescendants(type: string, id: string): Promise<ClosureEntry[]>;
|
|
1316
|
+
hasPath(ancestorType: string, ancestorId: string, descendantType: string, descendantId: string): Promise<boolean>;
|
|
1317
|
+
dispose(): void;
|
|
1318
|
+
}
|
|
1319
|
+
declare class DbFlagStore implements FlagStore {
|
|
1320
|
+
private db;
|
|
1321
|
+
private cache;
|
|
1322
|
+
constructor(db: AuthDbClient);
|
|
1323
|
+
/**
|
|
1324
|
+
* Load all flags from DB into memory. Call once on initialization.
|
|
1325
|
+
*/
|
|
1326
|
+
loadFlags(): Promise<void>;
|
|
1327
|
+
setFlag(orgId: string, flag: string, enabled: boolean): void;
|
|
1328
|
+
getFlag(orgId: string, flag: string): boolean;
|
|
1329
|
+
getFlags(orgId: string): Record<string, boolean>;
|
|
1330
|
+
}
|
|
1331
|
+
declare class DbOAuthAccountStore implements OAuthAccountStore {
|
|
1332
|
+
private db;
|
|
1333
|
+
constructor(db: AuthDbClient);
|
|
1334
|
+
linkAccount(userId: string, provider: string, providerId: string, email?: string): Promise<void>;
|
|
1335
|
+
findByProviderAccount(provider: string, providerId: string): Promise<string | null>;
|
|
1336
|
+
findByUserId(userId: string): Promise<{
|
|
1337
|
+
provider: string;
|
|
1338
|
+
providerId: string;
|
|
1339
|
+
}[]>;
|
|
1340
|
+
unlinkAccount(userId: string, provider: string): Promise<void>;
|
|
1341
|
+
dispose(): void;
|
|
1342
|
+
}
|
|
1343
|
+
declare class DbRoleAssignmentStore implements RoleAssignmentStore {
|
|
1344
|
+
private db;
|
|
1345
|
+
constructor(db: AuthDbClient);
|
|
1346
|
+
assign(userId: string, resourceType: string, resourceId: string, role: string): Promise<void>;
|
|
1347
|
+
revoke(userId: string, resourceType: string, resourceId: string, role: string): Promise<void>;
|
|
1348
|
+
getRoles(userId: string, resourceType: string, resourceId: string): Promise<string[]>;
|
|
1349
|
+
getRolesForUser(userId: string): Promise<RoleAssignment[]>;
|
|
1350
|
+
getEffectiveRole(userId: string, resourceType: string, resourceId: string, accessDef: AccessDefinition, closureStore: ClosureStore): Promise<string | null>;
|
|
1351
|
+
dispose(): void;
|
|
1352
|
+
}
|
|
1353
|
+
declare class DbSessionStore implements SessionStore {
|
|
1354
|
+
private db;
|
|
1355
|
+
constructor(db: AuthDbClient);
|
|
1356
|
+
createSessionWithId(id: string, data: {
|
|
1357
|
+
userId: string;
|
|
1358
|
+
refreshTokenHash: string;
|
|
1359
|
+
ipAddress: string;
|
|
1360
|
+
userAgent: string;
|
|
1361
|
+
expiresAt: Date;
|
|
1362
|
+
currentTokens?: AuthTokens;
|
|
1363
|
+
}): Promise<StoredSession>;
|
|
1364
|
+
findByRefreshHash(hash: string): Promise<StoredSession | null>;
|
|
1365
|
+
findActiveSessionById(id: string): Promise<StoredSession | null>;
|
|
1366
|
+
findByPreviousRefreshHash(hash: string): Promise<StoredSession | null>;
|
|
1367
|
+
revokeSession(id: string): Promise<void>;
|
|
1368
|
+
listActiveSessions(userId: string): Promise<StoredSession[]>;
|
|
1369
|
+
countActiveSessions(userId: string): Promise<number>;
|
|
1370
|
+
getCurrentTokens(sessionId: string): Promise<AuthTokens | null>;
|
|
1371
|
+
updateSession(id: string, data: {
|
|
1372
|
+
refreshTokenHash: string;
|
|
1373
|
+
previousRefreshHash: string;
|
|
1374
|
+
lastActiveAt: Date;
|
|
1375
|
+
currentTokens?: AuthTokens;
|
|
1376
|
+
}): Promise<void>;
|
|
1377
|
+
dispose(): void;
|
|
1378
|
+
/** Map a raw SQL row (snake_case) to StoredSession. */
|
|
1379
|
+
private rowToSession;
|
|
1380
|
+
/** Map an ORM record (camelCase) to StoredSession. */
|
|
1381
|
+
private recordToSession;
|
|
1382
|
+
}
|
|
1383
|
+
declare class DbSubscriptionStore implements SubscriptionStore {
|
|
1384
|
+
private db;
|
|
1385
|
+
constructor(db: AuthDbClient);
|
|
1386
|
+
assign(tenantId: string, planId: string, startedAt?: Date, expiresAt?: Date | null): Promise<void>;
|
|
1387
|
+
get(tenantId: string): Promise<Subscription | null>;
|
|
1388
|
+
updateOverrides(tenantId: string, overrides: Record<string, LimitOverride>): Promise<void>;
|
|
1389
|
+
remove(tenantId: string): Promise<void>;
|
|
1390
|
+
attachAddOn(tenantId: string, addOnId: string): Promise<void>;
|
|
1391
|
+
detachAddOn(tenantId: string, addOnId: string): Promise<void>;
|
|
1392
|
+
getAddOns(tenantId: string): Promise<string[]>;
|
|
1393
|
+
dispose(): void;
|
|
1394
|
+
private loadOverrides;
|
|
1395
|
+
}
|
|
1396
|
+
declare class DbUserStore implements UserStore {
|
|
1397
|
+
private db;
|
|
1398
|
+
constructor(db: AuthDbClient);
|
|
1399
|
+
createUser(user: AuthUser, passwordHash: string | null): Promise<void>;
|
|
1400
|
+
findByEmail(email: string): Promise<{
|
|
1401
|
+
user: AuthUser;
|
|
1402
|
+
passwordHash: string | null;
|
|
1403
|
+
} | null>;
|
|
1404
|
+
findById(id: string): Promise<AuthUser | null>;
|
|
1405
|
+
updatePasswordHash(userId: string, passwordHash: string): Promise<void>;
|
|
1406
|
+
updateEmailVerified(userId: string, verified: boolean): Promise<void>;
|
|
1407
|
+
deleteUser(id: string): Promise<void>;
|
|
1408
|
+
private rowToUser;
|
|
1409
|
+
}
|
|
820
1410
|
declare class InMemoryEmailVerificationStore implements EmailVerificationStore {
|
|
821
1411
|
private byId;
|
|
822
1412
|
private byTokenHash;
|
|
@@ -848,6 +1438,38 @@ declare function computeEntityAccess(entitlements: string[], entity: {
|
|
|
848
1438
|
* Returns true if `fva` exists and (now - fva) < maxAgeSeconds.
|
|
849
1439
|
*/
|
|
850
1440
|
declare function checkFva(payload: SessionPayload, maxAgeSeconds: number): boolean;
|
|
1441
|
+
/**
|
|
1442
|
+
* Grandfathering Store — tracks which tenants are grandfathered on old plan versions.
|
|
1443
|
+
*
|
|
1444
|
+
* When a plan version changes, existing tenants keep their old version
|
|
1445
|
+
* during a grace period. This store tracks that state.
|
|
1446
|
+
*/
|
|
1447
|
+
interface GrandfatheringState {
|
|
1448
|
+
tenantId: string;
|
|
1449
|
+
planId: string;
|
|
1450
|
+
version: number;
|
|
1451
|
+
graceEnds: Date | null;
|
|
1452
|
+
}
|
|
1453
|
+
interface GrandfatheringStore {
|
|
1454
|
+
/** Mark a tenant as grandfathered on a specific plan version. */
|
|
1455
|
+
setGrandfathered(tenantId: string, planId: string, version: number, graceEnds: Date | null): Promise<void>;
|
|
1456
|
+
/** Get grandfathering state for a tenant on a plan. Returns null if not grandfathered. */
|
|
1457
|
+
getGrandfathered(tenantId: string, planId: string): Promise<GrandfatheringState | null>;
|
|
1458
|
+
/** List all grandfathered tenants for a plan. */
|
|
1459
|
+
listGrandfathered(planId: string): Promise<GrandfatheringState[]>;
|
|
1460
|
+
/** Remove grandfathering state (after migration). */
|
|
1461
|
+
removeGrandfathered(tenantId: string, planId: string): Promise<void>;
|
|
1462
|
+
/** Clean up resources. */
|
|
1463
|
+
dispose(): void;
|
|
1464
|
+
}
|
|
1465
|
+
declare class InMemoryGrandfatheringStore implements GrandfatheringStore {
|
|
1466
|
+
private states;
|
|
1467
|
+
setGrandfathered(tenantId: string, planId: string, version: number, graceEnds: Date | null): Promise<void>;
|
|
1468
|
+
getGrandfathered(tenantId: string, planId: string): Promise<GrandfatheringState | null>;
|
|
1469
|
+
listGrandfathered(planId: string): Promise<GrandfatheringState[]>;
|
|
1470
|
+
removeGrandfathered(tenantId: string, planId: string): Promise<void>;
|
|
1471
|
+
dispose(): void;
|
|
1472
|
+
}
|
|
851
1473
|
declare class InMemoryMFAStore implements MFAStore {
|
|
852
1474
|
private secrets;
|
|
853
1475
|
private backupCodes;
|
|
@@ -887,6 +1509,71 @@ declare class InMemoryPasswordResetStore implements PasswordResetStore {
|
|
|
887
1509
|
deleteByUserId(userId: string): Promise<void>;
|
|
888
1510
|
dispose(): void;
|
|
889
1511
|
}
|
|
1512
|
+
interface PlanHashInput {
|
|
1513
|
+
features?: readonly string[] | string[];
|
|
1514
|
+
limits?: Record<string, unknown>;
|
|
1515
|
+
price?: {
|
|
1516
|
+
amount: number;
|
|
1517
|
+
interval: string;
|
|
1518
|
+
} | null;
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Compute SHA-256 hash of canonical JSON representation of plan config.
|
|
1522
|
+
* Keys are sorted recursively for deterministic output regardless of object key order.
|
|
1523
|
+
*/
|
|
1524
|
+
declare function computePlanHash(config: PlanHashInput): Promise<string>;
|
|
1525
|
+
type PlanEventType = "plan:version_created" | "plan:grace_approaching" | "plan:grace_expiring" | "plan:migrated";
|
|
1526
|
+
interface PlanEvent {
|
|
1527
|
+
type: PlanEventType;
|
|
1528
|
+
planId: string;
|
|
1529
|
+
tenantId?: string;
|
|
1530
|
+
version?: number;
|
|
1531
|
+
previousVersion?: number;
|
|
1532
|
+
currentVersion?: number;
|
|
1533
|
+
graceEnds?: Date | null;
|
|
1534
|
+
timestamp: Date;
|
|
1535
|
+
}
|
|
1536
|
+
type PlanEventHandler = (event: PlanEvent) => void;
|
|
1537
|
+
interface TenantPlanState {
|
|
1538
|
+
planId: string;
|
|
1539
|
+
version: number;
|
|
1540
|
+
currentVersion: number;
|
|
1541
|
+
grandfathered: boolean;
|
|
1542
|
+
graceEnds: Date | null;
|
|
1543
|
+
snapshot: PlanSnapshot;
|
|
1544
|
+
}
|
|
1545
|
+
interface MigrateOpts {
|
|
1546
|
+
tenantId?: string;
|
|
1547
|
+
}
|
|
1548
|
+
interface ScheduleOpts {
|
|
1549
|
+
at: Date | string;
|
|
1550
|
+
}
|
|
1551
|
+
interface PlanManagerConfig {
|
|
1552
|
+
plans: Record<string, PlanDef>;
|
|
1553
|
+
versionStore: PlanVersionStore;
|
|
1554
|
+
grandfatheringStore: GrandfatheringStore;
|
|
1555
|
+
subscriptionStore: SubscriptionStore;
|
|
1556
|
+
clock?: () => Date;
|
|
1557
|
+
}
|
|
1558
|
+
interface PlanManager {
|
|
1559
|
+
/** Hash plan configs, compare with stored, create new versions if different. Idempotent. */
|
|
1560
|
+
initialize(): Promise<void>;
|
|
1561
|
+
/** Migrate tenants past grace period (or specific tenant immediately). */
|
|
1562
|
+
migrate(planId: string, opts?: MigrateOpts): Promise<void>;
|
|
1563
|
+
/** Schedule future migration date for all grandfathered tenants. */
|
|
1564
|
+
schedule(planId: string, opts: ScheduleOpts): Promise<void>;
|
|
1565
|
+
/** Return tenant's plan state (planId, version, grandfathered, snapshot). */
|
|
1566
|
+
resolve(tenantId: string): Promise<TenantPlanState | null>;
|
|
1567
|
+
/** List all grandfathered tenants for a plan. */
|
|
1568
|
+
grandfathered(planId: string): Promise<GrandfatheringState[]>;
|
|
1569
|
+
/** Check all grandfathered tenants and emit grace_approaching / grace_expiring events. */
|
|
1570
|
+
checkGraceEvents(): Promise<void>;
|
|
1571
|
+
/** Register an event handler. */
|
|
1572
|
+
on(handler: PlanEventHandler): void;
|
|
1573
|
+
/** Remove an event handler. */
|
|
1574
|
+
off(handler: PlanEventHandler): void;
|
|
1575
|
+
}
|
|
1576
|
+
declare function createPlanManager(config: PlanManagerConfig): PlanManager;
|
|
890
1577
|
declare function discord(config: OAuthProviderConfig): OAuthProvider;
|
|
891
1578
|
declare function github(config: OAuthProviderConfig): OAuthProvider;
|
|
892
1579
|
declare function google(config: OAuthProviderConfig): OAuthProvider;
|
|
@@ -898,64 +1585,6 @@ declare class InMemoryRateLimitStore implements RateLimitStore {
|
|
|
898
1585
|
dispose(): void;
|
|
899
1586
|
private cleanup;
|
|
900
1587
|
}
|
|
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
1588
|
declare class InMemorySessionStore implements SessionStore {
|
|
960
1589
|
private sessions;
|
|
961
1590
|
private currentTokens;
|
|
@@ -978,6 +1607,7 @@ declare class InMemorySessionStore implements SessionStore {
|
|
|
978
1607
|
currentTokens?: AuthTokens;
|
|
979
1608
|
}): Promise<StoredSession>;
|
|
980
1609
|
findByRefreshHash(hash: string): Promise<StoredSession | null>;
|
|
1610
|
+
findActiveSessionById(id: string): Promise<StoredSession | null>;
|
|
981
1611
|
findByPreviousRefreshHash(hash: string): Promise<StoredSession | null>;
|
|
982
1612
|
revokeSession(id: string): Promise<void>;
|
|
983
1613
|
listActiveSessions(userId: string): Promise<StoredSession[]>;
|
|
@@ -1003,14 +1633,78 @@ declare class InMemoryUserStore implements UserStore {
|
|
|
1003
1633
|
findById(id: string): Promise<AuthUser | null>;
|
|
1004
1634
|
updatePasswordHash(userId: string, passwordHash: string): Promise<void>;
|
|
1005
1635
|
updateEmailVerified(userId: string, verified: boolean): Promise<void>;
|
|
1636
|
+
deleteUser(id: string): Promise<void>;
|
|
1006
1637
|
}
|
|
1007
1638
|
declare function createAuth(config: AuthConfig): AuthInstance;
|
|
1639
|
+
import { SchemaLike } from "@vertz/db";
|
|
1640
|
+
/**
|
|
1641
|
+
* A content type descriptor that implements SchemaLike.
|
|
1642
|
+
* Carries HTTP metadata alongside parse/validate behavior.
|
|
1643
|
+
*/
|
|
1644
|
+
interface ContentDescriptor<T> extends SchemaLike<T> {
|
|
1645
|
+
/** Discriminator — distinguishes from plain SchemaLike */
|
|
1646
|
+
readonly _kind: "content";
|
|
1647
|
+
/** MIME type for HTTP headers */
|
|
1648
|
+
readonly _contentType: string;
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Runtime check: is the given SchemaLike a ContentDescriptor?
|
|
1652
|
+
*/
|
|
1653
|
+
declare function isContentDescriptor(value: SchemaLike<unknown>): value is ContentDescriptor<unknown>;
|
|
1654
|
+
declare const content: {
|
|
1655
|
+
/** application/xml → string */
|
|
1656
|
+
readonly xml: () => ContentDescriptor<string>;
|
|
1657
|
+
/** text/html → string */
|
|
1658
|
+
readonly html: () => ContentDescriptor<string>;
|
|
1659
|
+
/** text/plain → string */
|
|
1660
|
+
readonly text: () => ContentDescriptor<string>;
|
|
1661
|
+
/** application/octet-stream → Uint8Array */
|
|
1662
|
+
readonly binary: () => ContentDescriptor<Uint8Array>;
|
|
1663
|
+
};
|
|
1008
1664
|
import { AppBuilder, AppConfig } from "@vertz/core";
|
|
1009
|
-
import { DatabaseClient, EntityDbAdapter as EntityDbAdapter3, ModelEntry as ModelEntry2 } from "@vertz/db";
|
|
1010
|
-
import { ModelDef as
|
|
1011
|
-
import { ModelDef } from "@vertz/db";
|
|
1012
|
-
import { EntityDbAdapter, ListOptions } from "@vertz/db";
|
|
1013
|
-
import { EntityError, Result as
|
|
1665
|
+
import { DatabaseClient as DatabaseClient2, EntityDbAdapter as EntityDbAdapter3, ModelEntry as ModelEntry2 } from "@vertz/db";
|
|
1666
|
+
import { ModelDef as ModelDef4, RelationDef as RelationDef2, SchemaLike as SchemaLike2, TableDef } from "@vertz/db";
|
|
1667
|
+
import { ListOptions as ListOptions3, ModelDef as ModelDef3 } from "@vertz/db";
|
|
1668
|
+
import { EntityDbAdapter, GetOptions, ListOptions, ModelDef as ModelDef2 } from "@vertz/db";
|
|
1669
|
+
import { EntityError, Result as Result3 } from "@vertz/errors";
|
|
1670
|
+
import { RelationDef, TenantGraph } from "@vertz/db";
|
|
1671
|
+
/** One hop in the relation chain from entity to tenant root. */
|
|
1672
|
+
interface TenantChainHop {
|
|
1673
|
+
/** Target table name (e.g., 'projects') */
|
|
1674
|
+
readonly tableName: string;
|
|
1675
|
+
/** FK column on the current table (e.g., 'projectId') */
|
|
1676
|
+
readonly foreignKey: string;
|
|
1677
|
+
/** PK column on the target table (e.g., 'id') */
|
|
1678
|
+
readonly targetColumn: string;
|
|
1679
|
+
}
|
|
1680
|
+
/** Full chain from an indirectly scoped entity to the tenant root. */
|
|
1681
|
+
interface TenantChain {
|
|
1682
|
+
/** Ordered hops from entity → ... → directly-scoped table */
|
|
1683
|
+
readonly hops: readonly TenantChainHop[];
|
|
1684
|
+
/** The tenant FK column on the final hop's target table (e.g., 'organizationId') */
|
|
1685
|
+
readonly tenantColumn: string;
|
|
1686
|
+
}
|
|
1687
|
+
interface ModelRegistryEntry {
|
|
1688
|
+
readonly table: {
|
|
1689
|
+
readonly _name: string;
|
|
1690
|
+
readonly _columns: Record<string, unknown>;
|
|
1691
|
+
};
|
|
1692
|
+
readonly relations: Record<string, RelationDef>;
|
|
1693
|
+
readonly _tenant?: string | null;
|
|
1694
|
+
}
|
|
1695
|
+
type ModelRegistry = Record<string, ModelRegistryEntry>;
|
|
1696
|
+
/**
|
|
1697
|
+
* Resolves the relation chain from an indirectly scoped entity back to the
|
|
1698
|
+
* tenant root.
|
|
1699
|
+
*
|
|
1700
|
+
* Returns null if the entity is not indirectly scoped (i.e., it's the root,
|
|
1701
|
+
* directly scoped, shared, or unscoped).
|
|
1702
|
+
*
|
|
1703
|
+
* The chain is computed by walking `ref.one` relations from the entity until
|
|
1704
|
+
* we reach a directly-scoped model. The tenant column is read from the
|
|
1705
|
+
* directly-scoped model's `_tenant` relation FK.
|
|
1706
|
+
*/
|
|
1707
|
+
declare function resolveTenantChain(entityKey: string, tenantGraph: TenantGraph, registry: ModelRegistry): TenantChain | null;
|
|
1014
1708
|
import { EntityDbAdapter as EntityDbAdapter2, ListOptions as ListOptions2 } from "@vertz/db";
|
|
1015
1709
|
interface ListResult<T = Record<string, unknown>> {
|
|
1016
1710
|
items: T[];
|
|
@@ -1023,29 +1717,38 @@ interface CrudResult<T = unknown> {
|
|
|
1023
1717
|
status: number;
|
|
1024
1718
|
body: T;
|
|
1025
1719
|
}
|
|
1026
|
-
interface CrudHandlers {
|
|
1027
|
-
list(ctx: EntityContext
|
|
1028
|
-
get(ctx: EntityContext
|
|
1029
|
-
create(ctx: EntityContext
|
|
1030
|
-
update(ctx: EntityContext
|
|
1031
|
-
delete(ctx: EntityContext
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1720
|
+
interface CrudHandlers<TModel extends ModelDef2 = ModelDef2> {
|
|
1721
|
+
list(ctx: EntityContext<TModel>, options?: ListOptions): Promise<Result3<CrudResult<ListResult<TModel["table"]["$response"]>>, EntityError>>;
|
|
1722
|
+
get(ctx: EntityContext<TModel>, id: string, options?: GetOptions): Promise<Result3<CrudResult<TModel["table"]["$response"]>, EntityError>>;
|
|
1723
|
+
create(ctx: EntityContext<TModel>, data: Record<string, unknown>): Promise<Result3<CrudResult<TModel["table"]["$response"]>, EntityError>>;
|
|
1724
|
+
update(ctx: EntityContext<TModel>, id: string, data: Record<string, unknown>): Promise<Result3<CrudResult<TModel["table"]["$response"]>, EntityError>>;
|
|
1725
|
+
delete(ctx: EntityContext<TModel>, id: string): Promise<Result3<CrudResult<null>, EntityError>>;
|
|
1726
|
+
}
|
|
1727
|
+
/** Resolves IDs from a table matching a where condition. Used for indirect tenant filtering. */
|
|
1728
|
+
type QueryParentIdsFn = (tableName: string, where: Record<string, unknown>) => Promise<string[]>;
|
|
1729
|
+
/** Options for the CRUD pipeline factory. */
|
|
1730
|
+
interface CrudPipelineOptions {
|
|
1731
|
+
/** Tenant chain for indirectly scoped entities. */
|
|
1732
|
+
tenantChain?: TenantChain | null;
|
|
1733
|
+
/** Resolves parent IDs for indirect tenant chain traversal. */
|
|
1734
|
+
queryParentIds?: QueryParentIdsFn;
|
|
1735
|
+
}
|
|
1736
|
+
declare function createCrudHandlers<TModel extends ModelDef2 = ModelDef2>(def: EntityDefinition<TModel>, db: EntityDbAdapter, options?: CrudPipelineOptions): CrudHandlers<TModel>;
|
|
1034
1737
|
/**
|
|
1035
1738
|
* EntityOperations — typed CRUD facade for a single entity.
|
|
1036
1739
|
*
|
|
1037
1740
|
* When used as `ctx.entity`, TModel fills in actual column types.
|
|
1038
1741
|
* When used as `ctx.entities.*`, TModel defaults to `ModelDef` (loose typing).
|
|
1039
1742
|
*/
|
|
1040
|
-
interface EntityOperations<TModel extends
|
|
1743
|
+
interface EntityOperations<TModel extends ModelDef3 = ModelDef3> {
|
|
1041
1744
|
get(id: string): Promise<TModel["table"]["$response"]>;
|
|
1042
|
-
list(options?:
|
|
1745
|
+
list(options?: ListOptions3<TModel>): Promise<ListResult<TModel["table"]["$response"]>>;
|
|
1043
1746
|
create(data: TModel["table"]["$create_input"]): Promise<TModel["table"]["$response"]>;
|
|
1044
1747
|
update(id: string, data: TModel["table"]["$update_input"]): Promise<TModel["table"]["$response"]>;
|
|
1045
1748
|
delete(id: string): Promise<void>;
|
|
1046
1749
|
}
|
|
1047
1750
|
/** Extracts the model type from an EntityDefinition */
|
|
1048
|
-
type ExtractModel<T> = T extends EntityDefinition<infer M> ? M :
|
|
1751
|
+
type ExtractModel<T> = T extends EntityDefinition<infer M> ? M : ModelDef4;
|
|
1049
1752
|
/**
|
|
1050
1753
|
* Maps an inject config `{ key: EntityDefinition<TModel> }` to
|
|
1051
1754
|
* `{ key: EntityOperations<TModel> }` for typed ctx.entities access.
|
|
@@ -1053,12 +1756,13 @@ type ExtractModel<T> = T extends EntityDefinition<infer M> ? M : ModelDef2;
|
|
|
1053
1756
|
type InjectToOperations<TInject extends Record<string, EntityDefinition> = {}> = { readonly [K in keyof TInject] : EntityOperations<ExtractModel<TInject[K]>> };
|
|
1054
1757
|
interface BaseContext {
|
|
1055
1758
|
readonly userId: string | null;
|
|
1759
|
+
readonly tenantId: string | null;
|
|
1056
1760
|
authenticated(): boolean;
|
|
1057
1761
|
tenant(): boolean;
|
|
1058
1762
|
role(...roles: string[]): boolean;
|
|
1059
1763
|
}
|
|
1060
1764
|
interface EntityContext<
|
|
1061
|
-
TModel extends
|
|
1765
|
+
TModel extends ModelDef4 = ModelDef4,
|
|
1062
1766
|
TInject extends Record<string, EntityDefinition> = {}
|
|
1063
1767
|
> extends BaseContext {
|
|
1064
1768
|
/** Typed CRUD on the current entity */
|
|
@@ -1066,7 +1770,7 @@ interface EntityContext<
|
|
|
1066
1770
|
/** Typed access to injected entities only */
|
|
1067
1771
|
readonly entities: InjectToOperations<TInject>;
|
|
1068
1772
|
}
|
|
1069
|
-
type AccessRule2 = false | ((ctx: BaseContext, row: Record<string, unknown>) => boolean | Promise<boolean>);
|
|
1773
|
+
type AccessRule2 = false | AccessRule | ((ctx: BaseContext, row: Record<string, unknown>) => boolean | Promise<boolean>);
|
|
1070
1774
|
interface EntityBeforeHooks<
|
|
1071
1775
|
TCreateInput = unknown,
|
|
1072
1776
|
TUpdateInput = unknown
|
|
@@ -1087,20 +1791,35 @@ interface EntityActionDef<
|
|
|
1087
1791
|
> {
|
|
1088
1792
|
readonly method?: string;
|
|
1089
1793
|
readonly path?: string;
|
|
1090
|
-
readonly body:
|
|
1091
|
-
readonly response:
|
|
1794
|
+
readonly body: SchemaLike2<TInput>;
|
|
1795
|
+
readonly response: SchemaLike2<TOutput>;
|
|
1092
1796
|
readonly handler: (input: TInput, ctx: TCtx, row: TResponse | null) => Promise<TOutput>;
|
|
1093
1797
|
}
|
|
1094
1798
|
/** Extract column keys from a RelationDef's target table. */
|
|
1095
|
-
type RelationColumnKeys<R> = R extends
|
|
1096
|
-
|
|
1799
|
+
type RelationColumnKeys<R> = R extends RelationDef2<infer TTarget> ? TTarget extends TableDef<infer TCols> ? Extract<keyof TCols, string> : string : string;
|
|
1800
|
+
/** Structured relation config with select, allowWhere, allowOrderBy, maxLimit. */
|
|
1801
|
+
interface RelationConfigObject<TColumnKeys extends string = string> {
|
|
1802
|
+
/** Which fields can be selected. Record<field, true>. */
|
|
1803
|
+
readonly select?: { readonly [F in TColumnKeys]? : true };
|
|
1804
|
+
/** Which fields can be filtered via `where`. */
|
|
1805
|
+
readonly allowWhere?: readonly string[];
|
|
1806
|
+
/** Which fields can be sorted via `orderBy`. */
|
|
1807
|
+
readonly allowOrderBy?: readonly string[];
|
|
1808
|
+
/** Max items per parent row. Defaults to DEFAULT_RELATION_LIMIT (100). */
|
|
1809
|
+
readonly maxLimit?: number;
|
|
1810
|
+
}
|
|
1811
|
+
type EntityRelationsConfig<TRelations extends Record<string, RelationDef2> = Record<string, RelationDef2>> = { [K in keyof TRelations]? : true | false | RelationConfigObject<RelationColumnKeys<TRelations[K]>> };
|
|
1097
1812
|
interface EntityConfig<
|
|
1098
|
-
TModel extends
|
|
1813
|
+
TModel extends ModelDef4 = ModelDef4,
|
|
1099
1814
|
TActions extends Record<string, EntityActionDef<any, any, any, any>> = {},
|
|
1100
1815
|
TInject extends Record<string, EntityDefinition> = {}
|
|
1101
1816
|
> {
|
|
1102
1817
|
readonly model: TModel;
|
|
1103
1818
|
readonly inject?: TInject;
|
|
1819
|
+
/** Override the DB table name (defaults to entity name). For admin entities over shared tables. */
|
|
1820
|
+
readonly table?: string;
|
|
1821
|
+
/** Whether CRUD auto-filters by tenantId. Defaults to true when model has tenantId column. */
|
|
1822
|
+
readonly tenantScoped?: boolean;
|
|
1104
1823
|
readonly access?: Partial<Record<"list" | "get" | "create" | "update" | "delete" | Extract<keyof NoInfer<TActions>, string>, AccessRule2>>;
|
|
1105
1824
|
readonly before?: {
|
|
1106
1825
|
readonly create?: (data: TModel["table"]["$create_input"], ctx: EntityContext<TModel, TInject>) => TModel["table"]["$create_input"] | Promise<TModel["table"]["$create_input"]>;
|
|
@@ -1114,7 +1833,7 @@ interface EntityConfig<
|
|
|
1114
1833
|
readonly actions?: { readonly [K in keyof TActions] : TActions[K] };
|
|
1115
1834
|
readonly relations?: EntityRelationsConfig<TModel["relations"]>;
|
|
1116
1835
|
}
|
|
1117
|
-
interface EntityDefinition<TModel extends
|
|
1836
|
+
interface EntityDefinition<TModel extends ModelDef4 = ModelDef4> {
|
|
1118
1837
|
readonly kind: "entity";
|
|
1119
1838
|
readonly name: string;
|
|
1120
1839
|
readonly model: TModel;
|
|
@@ -1124,8 +1843,14 @@ interface EntityDefinition<TModel extends ModelDef2 = ModelDef2> {
|
|
|
1124
1843
|
readonly after: EntityAfterHooks;
|
|
1125
1844
|
readonly actions: Record<string, EntityActionDef>;
|
|
1126
1845
|
readonly relations: EntityRelationsConfig<TModel["relations"]>;
|
|
1127
|
-
|
|
1128
|
-
|
|
1846
|
+
/** DB table name (defaults to entity name). */
|
|
1847
|
+
readonly table: string;
|
|
1848
|
+
/** Whether CRUD auto-filters by tenantId. */
|
|
1849
|
+
readonly tenantScoped: boolean;
|
|
1850
|
+
/** Relation chain for indirect tenant scoping. Null for direct or unscoped. */
|
|
1851
|
+
readonly tenantChain: TenantChain | null;
|
|
1852
|
+
}
|
|
1853
|
+
import { SchemaLike as SchemaLike3 } from "@vertz/db";
|
|
1129
1854
|
/** Extracts the model type from an EntityDefinition */
|
|
1130
1855
|
type ExtractModel2<T> = T extends EntityDefinition<infer M> ? M : never;
|
|
1131
1856
|
/**
|
|
@@ -1133,9 +1858,18 @@ type ExtractModel2<T> = T extends EntityDefinition<infer M> ? M : never;
|
|
|
1133
1858
|
* `{ key: EntityOperations<TModel> }` for typed ctx.entities access.
|
|
1134
1859
|
*/
|
|
1135
1860
|
type InjectToOperations2<TInject extends Record<string, EntityDefinition> = {}> = { readonly [K in keyof TInject] : EntityOperations<ExtractModel2<TInject[K]>> };
|
|
1861
|
+
/** Raw request metadata exposed to service handlers */
|
|
1862
|
+
interface ServiceRequestInfo {
|
|
1863
|
+
readonly url: string;
|
|
1864
|
+
readonly method: string;
|
|
1865
|
+
readonly headers: Headers;
|
|
1866
|
+
readonly body: unknown;
|
|
1867
|
+
}
|
|
1136
1868
|
interface ServiceContext<TInject extends Record<string, EntityDefinition> = {}> extends BaseContext {
|
|
1137
1869
|
/** Typed access to injected entities only */
|
|
1138
1870
|
readonly entities: InjectToOperations2<TInject>;
|
|
1871
|
+
/** Raw request metadata — URL, method, headers, pre-parsed body */
|
|
1872
|
+
readonly request: ServiceRequestInfo;
|
|
1139
1873
|
}
|
|
1140
1874
|
interface ServiceActionDef<
|
|
1141
1875
|
TInput = unknown,
|
|
@@ -1144,8 +1878,8 @@ interface ServiceActionDef<
|
|
|
1144
1878
|
> {
|
|
1145
1879
|
readonly method?: string;
|
|
1146
1880
|
readonly path?: string;
|
|
1147
|
-
readonly body
|
|
1148
|
-
readonly response:
|
|
1881
|
+
readonly body?: SchemaLike3<TInput>;
|
|
1882
|
+
readonly response: SchemaLike3<TOutput>;
|
|
1149
1883
|
readonly handler: (input: TInput, ctx: TCtx) => Promise<TOutput>;
|
|
1150
1884
|
}
|
|
1151
1885
|
interface ServiceConfig<
|
|
@@ -1163,6 +1897,12 @@ interface ServiceDefinition {
|
|
|
1163
1897
|
readonly access: Partial<Record<string, AccessRule2>>;
|
|
1164
1898
|
readonly actions: Record<string, ServiceActionDef>;
|
|
1165
1899
|
}
|
|
1900
|
+
interface ServerInstance extends AppBuilder {
|
|
1901
|
+
auth: AuthInstance;
|
|
1902
|
+
initialize(): Promise<void>;
|
|
1903
|
+
/** Routes auth requests (/api/auth/*) to auth.handler, everything else to entity handler */
|
|
1904
|
+
readonly requestHandler: (request: Request) => Promise<Response>;
|
|
1905
|
+
}
|
|
1166
1906
|
interface ServerConfig extends Omit<AppConfig, "_entityDbFactory" | "entities"> {
|
|
1167
1907
|
/** Entity definitions created via entity() from @vertz/server */
|
|
1168
1908
|
entities?: EntityDefinition[];
|
|
@@ -1174,16 +1914,37 @@ interface ServerConfig extends Omit<AppConfig, "_entityDbFactory" | "entities">
|
|
|
1174
1914
|
* - A DatabaseClient from createDb() (recommended — auto-bridged per entity)
|
|
1175
1915
|
* - An EntityDbAdapter (deprecated — simple adapter with get/list/create/update/delete)
|
|
1176
1916
|
*/
|
|
1177
|
-
db?:
|
|
1917
|
+
db?: DatabaseClient2<Record<string, ModelEntry2>> | EntityDbAdapter3;
|
|
1178
1918
|
/** @internal Factory to create a DB adapter for each entity. Prefer `db` instead. */
|
|
1179
1919
|
_entityDbFactory?: (entityDef: EntityDefinition) => EntityDbAdapter3;
|
|
1920
|
+
/** @internal Resolves parent IDs for indirect tenant chain traversal. For testing only. */
|
|
1921
|
+
_queryParentIds?: QueryParentIdsFn;
|
|
1922
|
+
/** @internal Tenant chains for indirect scoping. For testing without DatabaseClient. */
|
|
1923
|
+
_tenantChains?: Map<string, TenantChain>;
|
|
1924
|
+
/** Auth configuration — when combined with db, auto-wires DB-backed stores */
|
|
1925
|
+
auth?: AuthConfig;
|
|
1180
1926
|
}
|
|
1181
1927
|
/**
|
|
1182
1928
|
* Creates an HTTP server with entity route generation.
|
|
1183
1929
|
* Wraps @vertz/core's createServer to inject entity CRUD handlers.
|
|
1930
|
+
*
|
|
1931
|
+
* When both `db` (DatabaseClient) and `auth` are provided:
|
|
1932
|
+
* - Validates auth models are registered in the DatabaseClient
|
|
1933
|
+
* - Auto-wires DB-backed UserStore and SessionStore
|
|
1934
|
+
* - Returns ServerInstance with `.auth` and `.initialize()`
|
|
1184
1935
|
*/
|
|
1936
|
+
declare function createServer(config: ServerConfig & {
|
|
1937
|
+
db: DatabaseClient2<Record<string, ModelEntry2>>;
|
|
1938
|
+
auth: AuthConfig;
|
|
1939
|
+
}): ServerInstance;
|
|
1185
1940
|
declare function createServer(config: ServerConfig): AppBuilder;
|
|
1186
|
-
import { EntityForbiddenError, Result as
|
|
1941
|
+
import { EntityForbiddenError, Result as Result4 } from "@vertz/errors";
|
|
1942
|
+
interface EnforceAccessOptions {
|
|
1943
|
+
/** Evaluate an entitlement via the access context (defineAccess) */
|
|
1944
|
+
readonly can?: (entitlement: string) => Promise<boolean>;
|
|
1945
|
+
/** Seconds since last MFA verification (for rules.fva) */
|
|
1946
|
+
readonly fvaAge?: number;
|
|
1947
|
+
}
|
|
1187
1948
|
/**
|
|
1188
1949
|
* Evaluates an access rule for the given operation.
|
|
1189
1950
|
* Returns err(EntityForbiddenError) if access is denied.
|
|
@@ -1192,10 +1953,16 @@ import { EntityForbiddenError, Result as Result3 } from "@vertz/errors";
|
|
|
1192
1953
|
*
|
|
1193
1954
|
* - No rule defined → deny (deny by default)
|
|
1194
1955
|
* - Rule is false → operation is disabled
|
|
1195
|
-
* -
|
|
1956
|
+
* - Descriptor rule → dispatch by type
|
|
1957
|
+
* - Function rule → evaluate and deny if returns false
|
|
1958
|
+
*
|
|
1959
|
+
* When `skipWhere` is true, `where` rules are treated as ok() — used
|
|
1960
|
+
* when where conditions have already been pushed to the DB query.
|
|
1196
1961
|
*/
|
|
1197
|
-
declare function enforceAccess(operation: string, accessRules: Partial<Record<string, AccessRule2>>, ctx: BaseContext, row?: Record<string, unknown
|
|
1198
|
-
|
|
1962
|
+
declare function enforceAccess(operation: string, accessRules: Partial<Record<string, AccessRule2>>, ctx: BaseContext, row?: Record<string, unknown>, options?: EnforceAccessOptions & {
|
|
1963
|
+
skipWhere?: boolean;
|
|
1964
|
+
}): Promise<Result4<void, EntityForbiddenError>>;
|
|
1965
|
+
import { ModelDef as ModelDef6 } from "@vertz/db";
|
|
1199
1966
|
/**
|
|
1200
1967
|
* Request info extracted from HTTP context / auth middleware.
|
|
1201
1968
|
*/
|
|
@@ -1207,10 +1974,10 @@ interface RequestInfo {
|
|
|
1207
1974
|
/**
|
|
1208
1975
|
* Creates an EntityContext from request info, entity operations, and registry proxy.
|
|
1209
1976
|
*/
|
|
1210
|
-
declare function createEntityContext<TModel extends
|
|
1211
|
-
import { ModelDef as
|
|
1977
|
+
declare function createEntityContext<TModel extends ModelDef6 = ModelDef6>(request: RequestInfo, entityOps: EntityOperations<TModel>, registryProxy: Record<string, EntityOperations>): EntityContext<TModel>;
|
|
1978
|
+
import { ModelDef as ModelDef7 } from "@vertz/db";
|
|
1212
1979
|
declare function entity<
|
|
1213
|
-
TModel extends
|
|
1980
|
+
TModel extends ModelDef7,
|
|
1214
1981
|
TInject extends Record<string, EntityDefinition> = {},
|
|
1215
1982
|
TActions extends Record<string, EntityActionDef<any, any, TModel["table"]["$response"], EntityContext<TModel, TInject>>> = {}
|
|
1216
1983
|
>(name: string, config: EntityConfig<TModel, TActions, TInject>): EntityDefinition<TModel>;
|
|
@@ -1258,6 +2025,10 @@ declare function stripReadOnlyFields(table: TableDef2, data: Record<string, unkn
|
|
|
1258
2025
|
import { EntityRouteEntry } from "@vertz/core";
|
|
1259
2026
|
interface EntityRouteOptions {
|
|
1260
2027
|
apiPrefix?: string;
|
|
2028
|
+
/** Tenant chain for indirectly scoped entities. */
|
|
2029
|
+
tenantChain?: TenantChain | null;
|
|
2030
|
+
/** Resolves parent IDs for indirect tenant chain traversal. */
|
|
2031
|
+
queryParentIds?: QueryParentIdsFn;
|
|
1261
2032
|
}
|
|
1262
2033
|
/**
|
|
1263
2034
|
* Generates HTTP route entries for a single entity definition.
|
|
@@ -1271,4 +2042,4 @@ declare function service<
|
|
|
1271
2042
|
TInject extends Record<string, EntityDefinition> = {},
|
|
1272
2043
|
TActions extends Record<string, ServiceActionDef<any, any, any>> = Record<string, ServiceActionDef<any, any, any>>
|
|
1273
2044
|
>(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,
|
|
2045
|
+
export { vertz, verifyPassword, validatePassword, validateOverrides, validateAuthModels, stripReadOnlyFields, stripHiddenFields, service, rules, resolveTenantChain, makeImmutable, isContentDescriptor, 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, content, computePlanHash, computeOverage, computeEntityAccess, computeAccessSet, checkFva, checkAddOnCompatibility, calculateBillingPeriod, authModels, WebhookHandlerConfig, WalletStore, WalletEntry, VertzException, ValidationException, UserTableEntry, UserStore, UnauthorizedException, TenantOverrides, TenantChainHop, TenantChain, SubscriptionStore, Subscription, StripeProduct, StripePrice, StripeClient, StripeBillingAdapterConfig, StoredSession, StoredPasswordReset, StoredEmailVerification, SignUpInput, SignInInput, SessionStrategy, SessionStore, SessionPayload, SessionInfo, SessionConfig, Session, ServiceUnavailableException, ServiceRequestInfo, ServiceDefinition, ServiceContext, ServiceConfig, ServiceActionDef, ServerInstance, ServerHandle, ServerConfig, ServerAdapter, RuleContext, RoleAssignmentTableEntry, RoleAssignmentStore, RoleAssignment, ResourceRef, Resource, RequestInfo, RawRequest, RateLimitStore, RateLimitResult, RateLimitConfig, PriceInterval, PlanVersionStore, PlanVersionInfo, PlanSnapshot, PlanPrice, PlanHashInput, PlanDef, Period, PasswordResetStore, PasswordResetConfig, PasswordRequirements, ParentRef, OverrideStore, OverageInput, OverageConfig, OnUserCreatedPayload, OAuthUserInfo, OAuthTokens, OAuthProviderConfig, OAuthProvider, OAuthAccountStore, NotFoundException, NamedMiddlewareDef, MiddlewareDef, MfaSetupData, MfaConfig, MfaChallengeData, MFAStore, ListenOptions, ListResult, ListOptions2 as ListOptions, LimitOverrideDef, LimitOverride, LimitDef, InternalServerErrorException, InferSchema, Infer2 as Infer, InMemoryWalletStore, InMemoryUserStore, InMemorySubscriptionStore, InMemorySessionStore, InMemoryRoleAssignmentStore, InMemoryRateLimitStore, InMemoryPlanVersionStore, 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, EntitlementRegistry, EntitlementDefinition, EntitlementDef, Entitlement, EncodedAccessSet, EmailVerificationStore, EmailVerificationConfig, EmailPasswordConfig, Deps, DenialReason, DenialMeta, DefineAccessInput, DeepReadonly, DbUserStore, DbSubscriptionStore, DbSessionStore, DbRoleAssignmentStore, DbOAuthAccountStore, DbFlagStore, DbDialectName, DbClosureStore, Ctx, CrudResult, CrudHandlers, CorsConfig, CookieConfig, ContentDescriptor, ConsumeResult, ConflictException, ComputeAccessSetConfig, ClosureStore, ClosureRow, ClosureEntry, BillingPeriod, BillingEventType, BillingEventHandler, BillingEventEmitter, BillingEvent, BillingAdapter, BaseContext, BadRequestException, AuthorizationError, AuthUser, AuthTokens, AuthInstance, AuthEntityProxy, AuthDbClient, AuthContext, AuthConfig, AuthCallbackContext, 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 };
|