@kysera/rls 0.8.1 → 0.8.3

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 CHANGED
@@ -1,9 +1,9 @@
1
- import { R as RLSSchema, O as Operation, P as PolicyCondition, a as PolicyHints, b as PolicyDefinition, F as FilterCondition, T as TableRLSConfig, C as CompiledPolicy, c as CompiledFilterPolicy, d as RLSContext, e as RLSAuthContext, f as RLSRequestContext, g as PolicyEvaluationContext } from './types-Dowjd6zG.js';
2
- export { h as PolicyType } from './types-Dowjd6zG.js';
1
+ import { R as RLSSchema, O as Operation, P as PolicyCondition, a as PolicyHints, b as PolicyActivationCondition, C as ConditionalPolicyDefinition, F as FilterCondition, T as TableRLSConfig, c as PolicyDefinition, d as CompiledPolicy, e as CompiledFilterPolicy, f as RLSContext, g as RLSAuthContext, h as RLSRequestContext, i as PolicyEvaluationContext } from './types-CyqksFKU.js';
2
+ export { k as PolicyActivationContext, j as PolicyType } from './types-CyqksFKU.js';
3
3
  import { KyseraLogger, DatabaseError, ErrorCode } from '@kysera/core';
4
4
  import { Plugin } from '@kysera/executor';
5
5
  import { z } from 'zod';
6
- import 'kysely';
6
+ import { SelectQueryBuilder } from 'kysely';
7
7
 
8
8
  /**
9
9
  * RLS schema definition and validation
@@ -95,6 +95,24 @@ interface PolicyOptions {
95
95
  priority?: number;
96
96
  /** Performance optimization hints */
97
97
  hints?: PolicyHints;
98
+ /**
99
+ * Condition that determines if this policy is active
100
+ * The policy will only be evaluated if this returns true
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * // Only apply in production
105
+ * allow('read', () => true, {
106
+ * condition: ctx => ctx.meta?.environment === 'production'
107
+ * })
108
+ *
109
+ * // Feature-gated policy
110
+ * filter('read', ctx => ({ strict: true }), {
111
+ * condition: ctx => ctx.meta?.features?.strictMode
112
+ * })
113
+ * ```
114
+ */
115
+ condition?: PolicyActivationCondition;
98
116
  }
99
117
  /**
100
118
  * Create an allow policy
@@ -118,7 +136,7 @@ interface PolicyOptions {
118
136
  * })
119
137
  * ```
120
138
  */
121
- declare function allow(operation: Operation | Operation[], condition: PolicyCondition, options?: PolicyOptions): PolicyDefinition;
139
+ declare function allow(operation: Operation | Operation[], condition: PolicyCondition, options?: PolicyOptions): ConditionalPolicyDefinition;
122
140
  /**
123
141
  * Create a deny policy
124
142
  * Blocks access when condition evaluates to true (overrides allow)
@@ -142,7 +160,7 @@ declare function allow(operation: Operation | Operation[], condition: PolicyCond
142
160
  * })
143
161
  * ```
144
162
  */
145
- declare function deny(operation: Operation | Operation[], condition?: PolicyCondition, options?: PolicyOptions): PolicyDefinition;
163
+ declare function deny(operation: Operation | Operation[], condition?: PolicyCondition, options?: PolicyOptions): ConditionalPolicyDefinition;
146
164
  /**
147
165
  * Create a filter policy
148
166
  * Adds WHERE conditions to SELECT queries
@@ -179,7 +197,7 @@ declare function deny(operation: Operation | Operation[], condition?: PolicyCond
179
197
  * })
180
198
  * ```
181
199
  */
182
- declare function filter(operation: 'read' | 'all', condition: FilterCondition, options?: PolicyOptions): PolicyDefinition;
200
+ declare function filter(operation: 'read' | 'all', condition: FilterCondition, options?: PolicyOptions): ConditionalPolicyDefinition;
183
201
  /**
184
202
  * Create a validate policy
185
203
  * Validates mutation data before execution
@@ -204,7 +222,78 @@ declare function filter(operation: 'read' | 'all', condition: FilterCondition, o
204
222
  * })
205
223
  * ```
206
224
  */
207
- declare function validate(operation: 'create' | 'update' | 'all', condition: PolicyCondition, options?: PolicyOptions): PolicyDefinition;
225
+ declare function validate(operation: 'create' | 'update' | 'all', condition: PolicyCondition, options?: PolicyOptions): ConditionalPolicyDefinition;
226
+ /**
227
+ * Create a policy that is only active in specific environments
228
+ *
229
+ * @param environments - Environments where the policy is active
230
+ * @param policyFn - Function that creates the policy
231
+ * @returns Policy with environment condition
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * // Policy only active in production
236
+ * const prodPolicy = whenEnvironment(['production'], () =>
237
+ * allow('read', () => true, { name: 'prod-read' })
238
+ * );
239
+ *
240
+ * // Policy active in staging and production
241
+ * const nonDevPolicy = whenEnvironment(['staging', 'production'], () =>
242
+ * filter('read', ctx => ({ tenant_id: ctx.auth.tenantId }))
243
+ * );
244
+ * ```
245
+ */
246
+ declare function whenEnvironment(environments: string[], policyFn: () => ConditionalPolicyDefinition): ConditionalPolicyDefinition;
247
+ /**
248
+ * Create a policy that is only active when a feature flag is enabled
249
+ *
250
+ * @param feature - Feature flag name
251
+ * @param policyFn - Function that creates the policy
252
+ * @returns Policy with feature flag condition
253
+ *
254
+ * @example
255
+ * ```typescript
256
+ * // Policy only active when 'strict_rls' feature is enabled
257
+ * const strictPolicy = whenFeature('strict_rls', () =>
258
+ * deny('delete', () => true, { name: 'strict-no-delete' })
259
+ * );
260
+ * ```
261
+ */
262
+ declare function whenFeature(feature: string, policyFn: () => ConditionalPolicyDefinition): ConditionalPolicyDefinition;
263
+ /**
264
+ * Create a policy that is only active during specific hours
265
+ *
266
+ * @param startHour - Start hour (0-23)
267
+ * @param endHour - End hour (0-23)
268
+ * @param policyFn - Function that creates the policy
269
+ * @returns Policy with time-based condition
270
+ *
271
+ * @example
272
+ * ```typescript
273
+ * // Policy only active during business hours (9 AM - 5 PM)
274
+ * const businessHoursPolicy = whenTimeRange(9, 17, () =>
275
+ * allow('update', () => true, { name: 'business-hours-update' })
276
+ * );
277
+ * ```
278
+ */
279
+ declare function whenTimeRange(startHour: number, endHour: number, policyFn: () => ConditionalPolicyDefinition): ConditionalPolicyDefinition;
280
+ /**
281
+ * Create a policy that is only active when a custom condition is met
282
+ *
283
+ * @param condition - Custom activation condition
284
+ * @param policyFn - Function that creates the policy
285
+ * @returns Policy with custom condition
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * // Policy only active when user is in beta program
290
+ * const betaPolicy = whenCondition(
291
+ * ctx => ctx.meta?.betaUser === true,
292
+ * () => allow('read', () => true, { name: 'beta-read' })
293
+ * );
294
+ * ```
295
+ */
296
+ declare function whenCondition(condition: PolicyActivationCondition, policyFn: () => ConditionalPolicyDefinition): ConditionalPolicyDefinition;
208
297
 
209
298
  /**
210
299
  * Policy Registry
@@ -630,13 +719,27 @@ interface RLSPluginOptions<DB = unknown> {
630
719
  * Note: 'schema' and 'onViolation' are not included as they are complex runtime objects.
631
720
  */
632
721
  declare const RLSPluginOptionsSchema: z.ZodObject<{
633
- excludeTables: z.ZodOptional<z.ZodArray<z.ZodString>>;
634
- bypassRoles: z.ZodOptional<z.ZodArray<z.ZodString>>;
722
+ excludeTables: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
723
+ bypassRoles: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
635
724
  requireContext: z.ZodOptional<z.ZodBoolean>;
636
725
  allowUnfilteredQueries: z.ZodOptional<z.ZodBoolean>;
637
726
  auditDecisions: z.ZodOptional<z.ZodBoolean>;
638
727
  primaryKeyColumn: z.ZodOptional<z.ZodString>;
639
- }, z.core.$strip>;
728
+ }, "strip", z.ZodTypeAny, {
729
+ excludeTables?: string[] | undefined;
730
+ bypassRoles?: string[] | undefined;
731
+ requireContext?: boolean | undefined;
732
+ allowUnfilteredQueries?: boolean | undefined;
733
+ auditDecisions?: boolean | undefined;
734
+ primaryKeyColumn?: string | undefined;
735
+ }, {
736
+ excludeTables?: string[] | undefined;
737
+ bypassRoles?: string[] | undefined;
738
+ requireContext?: boolean | undefined;
739
+ allowUnfilteredQueries?: boolean | undefined;
740
+ auditDecisions?: boolean | undefined;
741
+ primaryKeyColumn?: string | undefined;
742
+ }>;
640
743
  /**
641
744
  * Create RLS plugin for Kysera
642
745
  *
@@ -806,4 +909,2731 @@ declare function hashString(str: string): string;
806
909
  */
807
910
  declare function normalizeOperations(operation: Operation | Operation[]): Operation[];
808
911
 
809
- export { CompiledFilterPolicy, CompiledPolicy, type CreateRLSContextOptions, FilterCondition, Operation, PolicyCondition, PolicyDefinition, PolicyEvaluationContext, PolicyHints, type PolicyOptions, PolicyRegistry, RLSAuthContext, RLSContext, RLSContextError, RLSContextValidationError, RLSError, type RLSErrorCode, RLSErrorCodes, type RLSPluginOptions, RLSPluginOptionsSchema, RLSPolicyEvaluationError, RLSPolicyViolation, RLSRequestContext, RLSSchema, RLSSchemaError, TableRLSConfig, allow, createEvaluationContext, createRLSContext, deepMerge, defineRLSSchema, deny, filter, hashString, isAsyncFunction, mergeRLSSchemas, normalizeOperations, rlsContext, rlsPlugin, safeEvaluate, validate, withRLSContext, withRLSContextAsync };
912
+ /**
913
+ * Context Resolver Types
914
+ *
915
+ * Provides infrastructure for pre-resolving async data before RLS policy evaluation.
916
+ * This allows synchronous filters to access data that would otherwise require async lookups.
917
+ *
918
+ * @module @kysera/rls/resolvers/types
919
+ */
920
+
921
+ /**
922
+ * Base interface for resolved data that can be added to RLS context
923
+ *
924
+ * @example
925
+ * ```typescript
926
+ * interface MyResolvedData extends ResolvedData {
927
+ * organizationIds: string[];
928
+ * permissions: Set<string>;
929
+ * employeeRoles: Map<string, string[]>;
930
+ * }
931
+ * ```
932
+ */
933
+ interface ResolvedData {
934
+ /**
935
+ * Timestamp when data was resolved
936
+ * Used for cache validation
937
+ */
938
+ resolvedAt: Date;
939
+ /**
940
+ * Cache key used for this resolution (if cached)
941
+ */
942
+ cacheKey?: string;
943
+ }
944
+ /**
945
+ * Extended auth context with pre-resolved data
946
+ *
947
+ * @typeParam TUser - Custom user type
948
+ * @typeParam TResolved - Type of pre-resolved data
949
+ *
950
+ * @example
951
+ * ```typescript
952
+ * interface OrgPermissions extends ResolvedData {
953
+ * organizationIds: string[];
954
+ * orgPermissions: Map<string, Set<string>>;
955
+ * isOrgOwner: (orgId: string) => boolean;
956
+ * hasOrgPermission: (orgId: string, permission: string) => boolean;
957
+ * }
958
+ *
959
+ * type EnhancedAuth = EnhancedRLSAuthContext<User, OrgPermissions>;
960
+ *
961
+ * // Use in policy
962
+ * filter('read', ctx => ({
963
+ * organization_id: ctx.auth.resolved.organizationIds
964
+ * }));
965
+ * ```
966
+ */
967
+ interface EnhancedRLSAuthContext<TUser = unknown, TResolved extends ResolvedData = ResolvedData> extends RLSAuthContext<TUser> {
968
+ /**
969
+ * Pre-resolved data available synchronously in policies
970
+ *
971
+ * This data is populated by ContextResolvers before entering the RLS context.
972
+ * Use this for async data lookups that policies need synchronously.
973
+ */
974
+ resolved: TResolved;
975
+ }
976
+ /**
977
+ * Extended RLS context with enhanced auth containing resolved data
978
+ *
979
+ * @typeParam TUser - Custom user type
980
+ * @typeParam TResolved - Type of pre-resolved data
981
+ * @typeParam TMeta - Custom metadata type
982
+ */
983
+ interface EnhancedRLSContext<TUser = unknown, TResolved extends ResolvedData = ResolvedData, TMeta = unknown> extends Omit<RLSContext<TUser, TMeta>, 'auth'> {
984
+ auth: EnhancedRLSAuthContext<TUser, TResolved>;
985
+ }
986
+ /**
987
+ * Base context for resolver input (before resolution)
988
+ */
989
+ interface BaseResolverContext {
990
+ auth: {
991
+ userId: string | number;
992
+ roles: string[];
993
+ tenantId?: string | number;
994
+ organizationIds?: (string | number)[];
995
+ permissions?: string[];
996
+ attributes?: Record<string, unknown>;
997
+ isSystem?: boolean;
998
+ };
999
+ timestamp: Date;
1000
+ meta?: unknown;
1001
+ }
1002
+ /**
1003
+ * Context resolver that enriches base context with pre-resolved data
1004
+ *
1005
+ * Resolvers are responsible for fetching async data and making it available
1006
+ * synchronously in policy evaluation contexts.
1007
+ *
1008
+ * @typeParam TResolved - Type of resolved data this resolver produces
1009
+ *
1010
+ * @example
1011
+ * ```typescript
1012
+ * const orgPermissionResolver: ContextResolver<OrgPermissions> = {
1013
+ * name: 'org-permissions',
1014
+ *
1015
+ * async resolve(base) {
1016
+ * const employments = await db.selectFrom('employees')
1017
+ * .where('user_id', '=', base.auth.userId)
1018
+ * .where('status', '=', 'active')
1019
+ * .execute();
1020
+ *
1021
+ * const orgPermissions = new Map<string, Set<string>>();
1022
+ * // ... resolve permissions ...
1023
+ *
1024
+ * return {
1025
+ * resolvedAt: new Date(),
1026
+ * organizationIds: employments.map(e => e.organization_id),
1027
+ * orgPermissions,
1028
+ * isOrgOwner: (orgId) => employments.some(e => e.organization_id === orgId && e.is_owner),
1029
+ * hasOrgPermission: (orgId, permission) => {
1030
+ * const perms = orgPermissions.get(orgId);
1031
+ * return perms?.has('*') || perms?.has(permission) || false;
1032
+ * }
1033
+ * };
1034
+ * },
1035
+ *
1036
+ * cacheKey: (base) => `rls:org-perms:${base.auth.userId}`,
1037
+ * cacheTtl: 300 // 5 minutes
1038
+ * };
1039
+ * ```
1040
+ */
1041
+ interface ContextResolver<TResolved extends ResolvedData = ResolvedData> {
1042
+ /**
1043
+ * Unique name for this resolver
1044
+ * Used for logging and debugging
1045
+ */
1046
+ name: string;
1047
+ /**
1048
+ * Resolve async data for the context
1049
+ *
1050
+ * @param base - Base context with user info
1051
+ * @returns Pre-resolved data to be added to context
1052
+ */
1053
+ resolve(base: BaseResolverContext): Promise<TResolved>;
1054
+ /**
1055
+ * Generate cache key for this context
1056
+ * Return undefined to disable caching for this resolver
1057
+ *
1058
+ * @param base - Base context
1059
+ * @returns Cache key string or undefined
1060
+ */
1061
+ cacheKey?(base: BaseResolverContext): string | undefined;
1062
+ /**
1063
+ * Cache TTL in seconds
1064
+ * @default 300 (5 minutes)
1065
+ */
1066
+ cacheTtl?: number;
1067
+ /**
1068
+ * Whether this resolver is required
1069
+ * If true, resolution failure will throw an error
1070
+ * If false, the resolver will be skipped on failure
1071
+ *
1072
+ * @default true
1073
+ */
1074
+ required?: boolean;
1075
+ /**
1076
+ * Dependencies on other resolvers (by name)
1077
+ * This resolver will wait for dependencies to complete first
1078
+ */
1079
+ dependsOn?: string[];
1080
+ /**
1081
+ * Priority for resolver execution order (higher = earlier)
1082
+ * @default 0
1083
+ */
1084
+ priority?: number;
1085
+ }
1086
+ /**
1087
+ * Combined result of multiple resolvers
1088
+ *
1089
+ * @typeParam T - Union type of all resolved data types
1090
+ */
1091
+ interface CompositeResolvedData<T extends Record<string, ResolvedData>> extends ResolvedData {
1092
+ /**
1093
+ * Individual resolver results keyed by resolver name
1094
+ */
1095
+ resolvers: T;
1096
+ }
1097
+ /**
1098
+ * Cache provider interface for storing resolved context data
1099
+ */
1100
+ interface ResolverCacheProvider {
1101
+ /**
1102
+ * Get cached data
1103
+ * @param key - Cache key
1104
+ * @returns Cached data or null if not found/expired
1105
+ */
1106
+ get<T>(key: string): Promise<T | null>;
1107
+ /**
1108
+ * Set cached data
1109
+ * @param key - Cache key
1110
+ * @param value - Data to cache
1111
+ * @param ttlSeconds - Time to live in seconds
1112
+ */
1113
+ set<T>(key: string, value: T, ttlSeconds: number): Promise<void>;
1114
+ /**
1115
+ * Delete cached data
1116
+ * @param key - Cache key
1117
+ */
1118
+ delete(key: string): Promise<void>;
1119
+ /**
1120
+ * Delete all cached data matching a pattern
1121
+ * @param pattern - Pattern to match (e.g., "rls:org-perms:*")
1122
+ */
1123
+ deletePattern?(pattern: string): Promise<void>;
1124
+ }
1125
+ /**
1126
+ * In-memory cache provider implementation
1127
+ *
1128
+ * Suitable for single-instance deployments or testing.
1129
+ * For distributed systems, use a Redis-based provider.
1130
+ */
1131
+ declare class InMemoryCacheProvider implements ResolverCacheProvider {
1132
+ private cache;
1133
+ get<T>(key: string): Promise<T | null>;
1134
+ set<T>(key: string, value: T, ttlSeconds: number): Promise<void>;
1135
+ delete(key: string): Promise<void>;
1136
+ deletePattern(pattern: string): Promise<void>;
1137
+ /**
1138
+ * Clear all cached entries
1139
+ */
1140
+ clear(): void;
1141
+ /**
1142
+ * Get current cache size
1143
+ */
1144
+ get size(): number;
1145
+ }
1146
+ /**
1147
+ * Options for ResolverManager
1148
+ */
1149
+ interface ResolverManagerOptions {
1150
+ /**
1151
+ * Cache provider for storing resolved data
1152
+ * @default InMemoryCacheProvider
1153
+ */
1154
+ cacheProvider?: ResolverCacheProvider;
1155
+ /**
1156
+ * Default cache TTL in seconds
1157
+ * @default 300 (5 minutes)
1158
+ */
1159
+ defaultCacheTtl?: number;
1160
+ /**
1161
+ * Whether to run resolvers in parallel when possible
1162
+ * @default true
1163
+ */
1164
+ parallelResolution?: boolean;
1165
+ /**
1166
+ * Maximum time (ms) to wait for a single resolver
1167
+ * @default 5000 (5 seconds)
1168
+ */
1169
+ resolverTimeout?: number;
1170
+ /**
1171
+ * Logger for resolver operations
1172
+ */
1173
+ logger?: {
1174
+ debug?: (message: string, context?: Record<string, unknown>) => void;
1175
+ info?: (message: string, context?: Record<string, unknown>) => void;
1176
+ warn?: (message: string, context?: Record<string, unknown>) => void;
1177
+ error?: (message: string, context?: Record<string, unknown>) => void;
1178
+ };
1179
+ }
1180
+ /**
1181
+ * Common resolved data for organization-based permissions
1182
+ *
1183
+ * Pre-built pattern for multi-organization systems where users can
1184
+ * belong to multiple organizations with different roles/permissions.
1185
+ */
1186
+ interface OrganizationResolvedData extends ResolvedData {
1187
+ /**
1188
+ * List of organization IDs the user belongs to
1189
+ */
1190
+ organizationIds: (string | number)[];
1191
+ /**
1192
+ * Map of organization ID to user's permissions in that org
1193
+ */
1194
+ orgPermissions: Map<string | number, Set<string>>;
1195
+ /**
1196
+ * Map of organization ID to user's roles in that org
1197
+ */
1198
+ orgRoles: Map<string | number, string[]>;
1199
+ /**
1200
+ * Check if user is owner of an organization
1201
+ * @param orgId - Organization ID
1202
+ */
1203
+ isOrgOwner(orgId: string | number): boolean;
1204
+ /**
1205
+ * Check if user has a specific permission in an organization
1206
+ * @param orgId - Organization ID
1207
+ * @param permission - Permission to check
1208
+ */
1209
+ hasOrgPermission(orgId: string | number, permission: string): boolean;
1210
+ /**
1211
+ * Check if user has a specific role in an organization
1212
+ * @param orgId - Organization ID
1213
+ * @param role - Role to check
1214
+ */
1215
+ hasOrgRole(orgId: string | number, role: string): boolean;
1216
+ }
1217
+ /**
1218
+ * Common resolved data for tenant-based systems
1219
+ */
1220
+ interface TenantResolvedData extends ResolvedData {
1221
+ /**
1222
+ * Current tenant ID (resolved from user context)
1223
+ */
1224
+ tenantId: string | number;
1225
+ /**
1226
+ * Tenant-specific settings/restrictions
1227
+ */
1228
+ tenantSettings?: Record<string, unknown>;
1229
+ /**
1230
+ * Tenant-specific feature flags
1231
+ */
1232
+ tenantFeatures?: Set<string>;
1233
+ }
1234
+ /**
1235
+ * Common resolved data for hierarchical permissions
1236
+ *
1237
+ * For systems with resource hierarchies (e.g., team -> project -> task)
1238
+ */
1239
+ interface HierarchyResolvedData extends ResolvedData {
1240
+ /**
1241
+ * Resources the user has direct access to
1242
+ */
1243
+ directAccess: Set<string>;
1244
+ /**
1245
+ * Resources the user has inherited access to (through hierarchy)
1246
+ */
1247
+ inheritedAccess: Set<string>;
1248
+ /**
1249
+ * Check if user can access a resource (direct or inherited)
1250
+ * @param resourceId - Resource ID
1251
+ */
1252
+ canAccess(resourceId: string): boolean;
1253
+ /**
1254
+ * Get the access level for a resource
1255
+ * @param resourceId - Resource ID
1256
+ * @returns Access level or null if no access
1257
+ */
1258
+ getAccessLevel(resourceId: string): string | null;
1259
+ }
1260
+ /**
1261
+ * Combined resolved data type for common use cases
1262
+ */
1263
+ type CommonResolvedData = OrganizationResolvedData & TenantResolvedData;
1264
+
1265
+ /**
1266
+ * Context Resolver Manager
1267
+ *
1268
+ * Orchestrates the resolution of context data from multiple resolvers,
1269
+ * handling caching, dependencies, and parallel execution.
1270
+ *
1271
+ * @module @kysera/rls/resolvers/manager
1272
+ */
1273
+
1274
+ /**
1275
+ * Manages context resolvers and orchestrates context resolution
1276
+ *
1277
+ * The ResolverManager is responsible for:
1278
+ * - Registering and organizing resolvers
1279
+ * - Resolving context data in the correct order (respecting dependencies)
1280
+ * - Caching resolved data
1281
+ * - Handling resolver failures
1282
+ *
1283
+ * @example
1284
+ * ```typescript
1285
+ * const manager = new ResolverManager({
1286
+ * cacheProvider: new RedisCacheProvider(redis),
1287
+ * defaultCacheTtl: 300,
1288
+ * parallelResolution: true
1289
+ * });
1290
+ *
1291
+ * // Register resolvers
1292
+ * manager.register(orgPermissionResolver);
1293
+ * manager.register(tenantSettingsResolver);
1294
+ *
1295
+ * // Resolve context
1296
+ * const enhancedCtx = await manager.resolve({
1297
+ * auth: { userId: '123', roles: ['user'] },
1298
+ * timestamp: new Date()
1299
+ * });
1300
+ *
1301
+ * // Use in RLS
1302
+ * await rlsContext.runAsync(enhancedCtx, async () => {
1303
+ * // Policies can access resolved data synchronously
1304
+ * });
1305
+ * ```
1306
+ */
1307
+ declare class ResolverManager<TResolved extends ResolvedData = ResolvedData> {
1308
+ private resolvers;
1309
+ private cacheProvider;
1310
+ private defaultCacheTtl;
1311
+ private parallelResolution;
1312
+ private resolverTimeout;
1313
+ private logger;
1314
+ constructor(options?: ResolverManagerOptions);
1315
+ /**
1316
+ * Register a context resolver
1317
+ *
1318
+ * @param resolver - Resolver to register
1319
+ * @throws RLSError if resolver with same name already exists
1320
+ */
1321
+ register<T extends ResolvedData>(resolver: ContextResolver<T>): void;
1322
+ /**
1323
+ * Unregister a context resolver
1324
+ *
1325
+ * @param name - Name of resolver to unregister
1326
+ * @returns true if resolver was removed, false if it didn't exist
1327
+ */
1328
+ unregister(name: string): boolean;
1329
+ /**
1330
+ * Check if a resolver is registered
1331
+ *
1332
+ * @param name - Resolver name
1333
+ */
1334
+ hasResolver(name: string): boolean;
1335
+ /**
1336
+ * Get all registered resolver names
1337
+ */
1338
+ getResolverNames(): string[];
1339
+ /**
1340
+ * Resolve context data using all registered resolvers
1341
+ *
1342
+ * @param baseContext - Base context to resolve
1343
+ * @returns Enhanced context with resolved data
1344
+ *
1345
+ * @example
1346
+ * ```typescript
1347
+ * const baseCtx = {
1348
+ * auth: { userId: '123', roles: ['user'], tenantId: 'acme' },
1349
+ * timestamp: new Date()
1350
+ * };
1351
+ *
1352
+ * const enhancedCtx = await manager.resolve(baseCtx);
1353
+ * // enhancedCtx.auth.resolved contains all resolved data
1354
+ * ```
1355
+ */
1356
+ resolve(baseContext: BaseResolverContext): Promise<EnhancedRLSContext<unknown, TResolved>>;
1357
+ /**
1358
+ * Resolve a single resolver (useful for partial updates)
1359
+ *
1360
+ * @param name - Resolver name
1361
+ * @param baseContext - Base context
1362
+ * @returns Resolved data from the specific resolver
1363
+ */
1364
+ resolveOne<T extends ResolvedData>(name: string, baseContext: BaseResolverContext): Promise<T | null>;
1365
+ /**
1366
+ * Invalidate cached data for a user
1367
+ *
1368
+ * @param userId - User ID whose cache should be invalidated
1369
+ * @param resolverName - Optional specific resolver to invalidate
1370
+ */
1371
+ invalidateCache(userId: string | number, resolverName?: string): Promise<void>;
1372
+ /**
1373
+ * Clear all cached data
1374
+ */
1375
+ clearCache(): Promise<void>;
1376
+ /**
1377
+ * Get resolvers in dependency order (topological sort)
1378
+ */
1379
+ private getResolverOrder;
1380
+ /**
1381
+ * Resolve resolvers sequentially
1382
+ */
1383
+ private resolveSequential;
1384
+ /**
1385
+ * Resolve resolvers in parallel (respecting dependencies)
1386
+ */
1387
+ private resolveParallel;
1388
+ /**
1389
+ * Resolve a single resolver with caching
1390
+ */
1391
+ private resolveWithCache;
1392
+ /**
1393
+ * Execute a promise with timeout
1394
+ */
1395
+ private withTimeout;
1396
+ /**
1397
+ * Merge resolved data from multiple resolvers
1398
+ */
1399
+ private mergeResolvedData;
1400
+ }
1401
+ /**
1402
+ * Create a context resolver manager with common defaults
1403
+ *
1404
+ * @param options - Manager options
1405
+ * @returns Configured ResolverManager
1406
+ */
1407
+ declare function createResolverManager<TResolved extends ResolvedData = ResolvedData>(options?: ResolverManagerOptions): ResolverManager<TResolved>;
1408
+ /**
1409
+ * Helper to create a context resolver
1410
+ *
1411
+ * @param config - Resolver configuration
1412
+ * @returns ContextResolver instance
1413
+ *
1414
+ * @example
1415
+ * ```typescript
1416
+ * const resolver = createResolver({
1417
+ * name: 'org-permissions',
1418
+ * resolve: async (base) => {
1419
+ * const orgs = await getEmployeeOrganizations(base.auth.userId);
1420
+ * return {
1421
+ * resolvedAt: new Date(),
1422
+ * organizationIds: orgs.map(o => o.id)
1423
+ * };
1424
+ * },
1425
+ * cacheKey: (base) => `rls:org:${base.auth.userId}`,
1426
+ * cacheTtl: 300
1427
+ * });
1428
+ * ```
1429
+ */
1430
+ declare function createResolver<TResolved extends ResolvedData>(config: ContextResolver<TResolved>): ContextResolver<TResolved>;
1431
+
1432
+ /**
1433
+ * ReBAC (Relationship-Based Access Control) Types
1434
+ *
1435
+ * Provides type definitions for relationship-based filtering in RLS policies.
1436
+ * ReBAC allows policies to be defined based on relationships between entities
1437
+ * in the database, enabling complex access control patterns like
1438
+ * "show products if user is employee of product's shop's organization".
1439
+ *
1440
+ * @module @kysera/rls/rebac/types
1441
+ */
1442
+
1443
+ /**
1444
+ * A single step in a relationship path
1445
+ *
1446
+ * Defines how to join from one table to another.
1447
+ *
1448
+ * @example
1449
+ * ```typescript
1450
+ * // Simple join: products.shop_id -> shops.id
1451
+ * const step: RelationshipStep = {
1452
+ * from: 'products',
1453
+ * to: 'shops',
1454
+ * fromColumn: 'shop_id',
1455
+ * toColumn: 'id'
1456
+ * };
1457
+ * ```
1458
+ */
1459
+ interface RelationshipStep {
1460
+ /**
1461
+ * Source table name
1462
+ */
1463
+ from: string;
1464
+ /**
1465
+ * Target table name
1466
+ */
1467
+ to: string;
1468
+ /**
1469
+ * Column in source table for the join
1470
+ * @default 'id' on target table side, '{to}_id' on source side
1471
+ */
1472
+ fromColumn?: string;
1473
+ /**
1474
+ * Column in target table for the join
1475
+ * @default 'id'
1476
+ */
1477
+ toColumn?: string;
1478
+ /**
1479
+ * Optional alias for the target table in the join
1480
+ * Useful when joining the same table multiple times
1481
+ */
1482
+ alias?: string;
1483
+ /**
1484
+ * Join type
1485
+ * @default 'inner'
1486
+ */
1487
+ joinType?: 'inner' | 'left' | 'right';
1488
+ /**
1489
+ * Additional conditions for this join step
1490
+ * @example { 'shops.deleted_at': null, 'shops.status': 'active' }
1491
+ */
1492
+ additionalConditions?: Record<string, unknown>;
1493
+ }
1494
+ /**
1495
+ * Complete relationship path definition
1496
+ *
1497
+ * Defines a chain of relationships from a source table to a target.
1498
+ *
1499
+ * @example
1500
+ * ```typescript
1501
+ * // Path: products -> shops -> organizations -> employees
1502
+ * const path: RelationshipPath = {
1503
+ * name: 'orgEmployee',
1504
+ * steps: [
1505
+ * { from: 'products', to: 'shops', fromColumn: 'shop_id' },
1506
+ * { from: 'shops', to: 'organizations', fromColumn: 'organization_id' },
1507
+ * { from: 'organizations', to: 'employees', toColumn: 'organization_id' }
1508
+ * ]
1509
+ * };
1510
+ * ```
1511
+ */
1512
+ interface RelationshipPath {
1513
+ /**
1514
+ * Unique name for this relationship path
1515
+ * Used in policy definitions to reference this path
1516
+ */
1517
+ name: string;
1518
+ /**
1519
+ * Steps in the relationship chain
1520
+ */
1521
+ steps: RelationshipStep[];
1522
+ /**
1523
+ * Optional description for documentation
1524
+ */
1525
+ description?: string;
1526
+ }
1527
+ /**
1528
+ * Condition to apply at the end of a relationship path
1529
+ *
1530
+ * @typeParam TCtx - Policy evaluation context type
1531
+ */
1532
+ type RelationshipCondition<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> = ((ctx: TCtx) => Record<string, unknown>) | Record<string, unknown>;
1533
+ /**
1534
+ * ReBAC policy definition
1535
+ *
1536
+ * Extends standard policy definition with relationship-based filtering.
1537
+ */
1538
+ interface ReBAcPolicyDefinition<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> extends Omit<PolicyDefinition, 'condition'> {
1539
+ /**
1540
+ * Name of the relationship path to use (defined in relationships config)
1541
+ */
1542
+ relationshipPath: string;
1543
+ /**
1544
+ * Conditions to apply at the end of the relationship
1545
+ * These conditions filter the final table in the relationship chain.
1546
+ *
1547
+ * @example
1548
+ * ```typescript
1549
+ * // Filter employees table at end of relationship
1550
+ * endCondition: ctx => ({
1551
+ * user_id: ctx.auth.userId,
1552
+ * status: 'active'
1553
+ * })
1554
+ * ```
1555
+ */
1556
+ endCondition: RelationshipCondition<TCtx>;
1557
+ /**
1558
+ * Whether this is a permissive or restrictive policy
1559
+ * - 'allow': Row is accessible if relationship exists
1560
+ * - 'deny': Row is NOT accessible if relationship exists
1561
+ * @default 'allow'
1562
+ */
1563
+ policyType?: 'allow' | 'deny';
1564
+ }
1565
+ /**
1566
+ * ReBAC configuration for a single table
1567
+ */
1568
+ interface TableReBAcConfig {
1569
+ /**
1570
+ * Relationship paths available for this table
1571
+ */
1572
+ relationships: RelationshipPath[];
1573
+ /**
1574
+ * ReBAC policies for this table
1575
+ */
1576
+ policies: ReBAcPolicyDefinition[];
1577
+ }
1578
+ /**
1579
+ * Complete ReBAC schema for all tables
1580
+ *
1581
+ * @typeParam DB - Database schema type
1582
+ */
1583
+ type ReBAcSchema<DB> = {
1584
+ [K in keyof DB]?: TableReBAcConfig;
1585
+ };
1586
+ /**
1587
+ * Compiled relationship path ready for query generation
1588
+ */
1589
+ interface CompiledRelationshipPath {
1590
+ /**
1591
+ * Path name
1592
+ */
1593
+ name: string;
1594
+ /**
1595
+ * Compiled join steps with defaults filled in
1596
+ */
1597
+ steps: Required<RelationshipStep>[];
1598
+ /**
1599
+ * Source table (first table in the chain)
1600
+ */
1601
+ sourceTable: string;
1602
+ /**
1603
+ * Target table (final table in the chain)
1604
+ */
1605
+ targetTable: string;
1606
+ }
1607
+ /**
1608
+ * Compiled ReBAC policy ready for evaluation
1609
+ */
1610
+ interface CompiledReBAcPolicy<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> {
1611
+ /**
1612
+ * Policy name
1613
+ */
1614
+ name: string;
1615
+ /**
1616
+ * Policy type (allow/deny)
1617
+ */
1618
+ type: 'allow' | 'deny';
1619
+ /**
1620
+ * Operations this policy applies to
1621
+ */
1622
+ operations: Set<string>;
1623
+ /**
1624
+ * Compiled relationship path
1625
+ */
1626
+ relationshipPath: CompiledRelationshipPath;
1627
+ /**
1628
+ * Function to get end conditions
1629
+ */
1630
+ getEndConditions: (ctx: TCtx) => Record<string, unknown>;
1631
+ /**
1632
+ * Priority for policy evaluation
1633
+ */
1634
+ priority: number;
1635
+ }
1636
+ /**
1637
+ * Generated EXISTS subquery for ReBAC filtering
1638
+ */
1639
+ interface ReBAcSubquery {
1640
+ /**
1641
+ * SQL for the EXISTS subquery
1642
+ */
1643
+ sql: string;
1644
+ /**
1645
+ * Parameter values for the subquery
1646
+ */
1647
+ parameters: unknown[];
1648
+ /**
1649
+ * Whether this is an allow (EXISTS) or deny (NOT EXISTS) check
1650
+ */
1651
+ isNegated: boolean;
1652
+ }
1653
+ /**
1654
+ * Options for ReBAC query generation
1655
+ */
1656
+ interface ReBAcQueryOptions {
1657
+ /**
1658
+ * Table alias for the main query table
1659
+ * @default table name
1660
+ */
1661
+ mainTableAlias?: string;
1662
+ /**
1663
+ * Whether to use qualified column names
1664
+ * @default true
1665
+ */
1666
+ qualifyColumns?: boolean;
1667
+ /**
1668
+ * Database dialect for query generation
1669
+ * @default 'postgres'
1670
+ */
1671
+ dialect?: 'postgres' | 'mysql' | 'sqlite';
1672
+ }
1673
+ /**
1674
+ * Common relationship pattern: Resource belongs to organization via owner
1675
+ *
1676
+ * @param resourceTable - Table containing the resource
1677
+ * @param organizationColumn - Column linking to organization
1678
+ *
1679
+ * @example
1680
+ * ```typescript
1681
+ * const path = orgMembershipPath('products', 'organization_id');
1682
+ * // Creates path: products -> organizations -> employees
1683
+ * ```
1684
+ */
1685
+ declare function orgMembershipPath(resourceTable: string, organizationColumn?: string): RelationshipPath;
1686
+ /**
1687
+ * Common relationship pattern: Resource belongs to shop's organization
1688
+ *
1689
+ * @param resourceTable - Table containing the resource
1690
+ * @param shopColumn - Column linking to shop
1691
+ *
1692
+ * @example
1693
+ * ```typescript
1694
+ * const path = shopOrgMembershipPath('products', 'shop_id');
1695
+ * // Creates path: products -> shops -> organizations -> employees
1696
+ * ```
1697
+ */
1698
+ declare function shopOrgMembershipPath(resourceTable: string, shopColumn?: string): RelationshipPath;
1699
+ /**
1700
+ * Common relationship pattern: Hierarchical team access
1701
+ *
1702
+ * @param resourceTable - Table containing the resource
1703
+ * @param teamColumn - Column linking to team
1704
+ *
1705
+ * @example
1706
+ * ```typescript
1707
+ * const path = teamHierarchyPath('tasks', 'team_id');
1708
+ * // Creates path: tasks -> teams -> team_members
1709
+ * ```
1710
+ */
1711
+ declare function teamHierarchyPath(resourceTable: string, teamColumn?: string): RelationshipPath;
1712
+
1713
+ /**
1714
+ * ReBAC Policy Registry
1715
+ *
1716
+ * Manages relationship definitions and ReBAC policies for RLS.
1717
+ *
1718
+ * @module @kysera/rls/rebac/registry
1719
+ */
1720
+
1721
+ /**
1722
+ * ReBAC Registry
1723
+ *
1724
+ * Manages relationship paths and ReBAC policies across tables.
1725
+ *
1726
+ * @example
1727
+ * ```typescript
1728
+ * const registry = new ReBAcRegistry();
1729
+ *
1730
+ * // Register relationship paths and policies
1731
+ * registry.loadSchema({
1732
+ * products: {
1733
+ * relationships: [
1734
+ * shopOrgMembershipPath('products', 'shop_id')
1735
+ * ],
1736
+ * policies: [
1737
+ * {
1738
+ * type: 'filter',
1739
+ * operation: 'read',
1740
+ * relationshipPath: 'products_shop_org_membership',
1741
+ * endCondition: ctx => ({
1742
+ * user_id: ctx.auth.userId,
1743
+ * status: 'active'
1744
+ * })
1745
+ * }
1746
+ * ]
1747
+ * }
1748
+ * });
1749
+ *
1750
+ * // Get policies for a table
1751
+ * const policies = registry.getPolicies('products', 'read');
1752
+ * ```
1753
+ */
1754
+ declare class ReBAcRegistry<DB = unknown> {
1755
+ private tables;
1756
+ private globalRelationships;
1757
+ private logger;
1758
+ constructor(schema?: ReBAcSchema<DB>, options?: {
1759
+ logger?: KyseraLogger;
1760
+ });
1761
+ /**
1762
+ * Load ReBAC schema
1763
+ */
1764
+ loadSchema(schema: ReBAcSchema<DB>): void;
1765
+ /**
1766
+ * Register ReBAC configuration for a single table
1767
+ */
1768
+ registerTable(table: string, config: TableReBAcConfig): void;
1769
+ /**
1770
+ * Register a global relationship path (available to all tables)
1771
+ */
1772
+ registerRelationship(path: RelationshipPath): void;
1773
+ /**
1774
+ * Get ReBAC policies for a table and operation
1775
+ */
1776
+ getPolicies(table: string, operation: Operation): CompiledReBAcPolicy[];
1777
+ /**
1778
+ * Get a specific relationship path
1779
+ */
1780
+ getRelationship(name: string, table?: string): CompiledRelationshipPath | undefined;
1781
+ /**
1782
+ * Check if table has ReBAC configuration
1783
+ */
1784
+ hasTable(table: string): boolean;
1785
+ /**
1786
+ * Get all registered table names
1787
+ */
1788
+ getTables(): string[];
1789
+ /**
1790
+ * Clear all registrations
1791
+ */
1792
+ clear(): void;
1793
+ /**
1794
+ * Compile a relationship path definition
1795
+ */
1796
+ private compileRelationshipPath;
1797
+ /**
1798
+ * Compile a ReBAC policy definition
1799
+ */
1800
+ private compilePolicy;
1801
+ }
1802
+ /**
1803
+ * Create a ReBAC registry
1804
+ */
1805
+ declare function createReBAcRegistry<DB = unknown>(schema?: ReBAcSchema<DB>, options?: {
1806
+ logger?: KyseraLogger;
1807
+ }): ReBAcRegistry<DB>;
1808
+
1809
+ /**
1810
+ * ReBAC Query Transformer
1811
+ *
1812
+ * Transforms queries to apply relationship-based access control policies.
1813
+ * Generates EXISTS subqueries that filter rows based on relationship chains.
1814
+ *
1815
+ * @module @kysera/rls/rebac/transformer
1816
+ */
1817
+
1818
+ /**
1819
+ * ReBAC query transformer
1820
+ *
1821
+ * Applies relationship-based access control to SELECT queries by generating
1822
+ * EXISTS subqueries that follow relationship paths.
1823
+ *
1824
+ * @example
1825
+ * ```typescript
1826
+ * const transformer = new ReBAcTransformer(registry);
1827
+ *
1828
+ * // Transform query
1829
+ * let query = db.selectFrom('products').selectAll();
1830
+ * query = transformer.transform(query, 'products', 'read');
1831
+ *
1832
+ * // Generated SQL includes EXISTS subquery:
1833
+ * // SELECT * FROM products p
1834
+ * // WHERE EXISTS (
1835
+ * // SELECT 1 FROM shops s
1836
+ * // JOIN organizations o ON s.organization_id = o.id
1837
+ * // JOIN employees e ON e.organization_id = o.id
1838
+ * // WHERE s.id = p.shop_id
1839
+ * // AND e.user_id = $1
1840
+ * // AND e.status = 'active'
1841
+ * // )
1842
+ * ```
1843
+ */
1844
+ declare class ReBAcTransformer<DB = unknown> {
1845
+ private registry;
1846
+ private options;
1847
+ constructor(registry: ReBAcRegistry<DB>, options?: ReBAcQueryOptions);
1848
+ /**
1849
+ * Transform a SELECT query by applying ReBAC policies
1850
+ *
1851
+ * @param qb - Query builder to transform
1852
+ * @param table - Table being queried
1853
+ * @param operation - Operation being performed
1854
+ * @returns Transformed query builder
1855
+ */
1856
+ transform<TB extends keyof DB & string, O>(qb: SelectQueryBuilder<DB, TB, O>, table: string, operation?: Operation): SelectQueryBuilder<DB, TB, O>;
1857
+ /**
1858
+ * Generate EXISTS condition SQL for a policy
1859
+ *
1860
+ * This method can be used to get the raw SQL for debugging or manual query building.
1861
+ *
1862
+ * @param policy - ReBAC policy to generate SQL for
1863
+ * @param ctx - RLS context
1864
+ * @param mainTable - Main query table
1865
+ * @param mainTableAlias - Alias for main table
1866
+ * @returns SQL string and parameters
1867
+ */
1868
+ generateExistsSql(policy: CompiledReBAcPolicy, ctx: RLSContext, mainTable: string, mainTableAlias?: string): {
1869
+ sql: string;
1870
+ params: unknown[];
1871
+ };
1872
+ /**
1873
+ * Apply a single ReBAC policy to a query
1874
+ *
1875
+ * NOTE: Uses type casting for dynamic SQL because Kysely's type system
1876
+ * requires compile-time known types, but ReBAC policies work with
1877
+ * runtime-generated EXISTS clauses.
1878
+ */
1879
+ private applyPolicy;
1880
+ /**
1881
+ * Create evaluation context for policy conditions
1882
+ */
1883
+ private createEvalContext;
1884
+ /**
1885
+ * Quote an identifier for the target dialect
1886
+ */
1887
+ private quote;
1888
+ /**
1889
+ * Generate parameter placeholder for the target dialect
1890
+ */
1891
+ private param;
1892
+ }
1893
+ /**
1894
+ * Create a ReBAC allow policy
1895
+ *
1896
+ * Rows are accessible if the relationship EXISTS with the given end conditions.
1897
+ *
1898
+ * @param operation - Operation(s) this policy applies to
1899
+ * @param relationshipPath - Name of the relationship path to use
1900
+ * @param endCondition - Conditions to apply at the end of the relationship
1901
+ * @param options - Additional policy options
1902
+ *
1903
+ * @example
1904
+ * ```typescript
1905
+ * // Allow read if user is employee of product's shop's organization
1906
+ * allowRelation('read', 'products_shop_org_membership', ctx => ({
1907
+ * user_id: ctx.auth.userId,
1908
+ * status: 'active'
1909
+ * }))
1910
+ * ```
1911
+ */
1912
+ declare function allowRelation(operation: Operation | Operation[], relationshipPath: string, endCondition: ((ctx: PolicyEvaluationContext) => Record<string, unknown>) | Record<string, unknown>, options?: {
1913
+ name?: string;
1914
+ priority?: number;
1915
+ }): ReBAcPolicyDefinition;
1916
+ /**
1917
+ * Create a ReBAC deny policy
1918
+ *
1919
+ * Rows are NOT accessible if the relationship EXISTS with the given conditions.
1920
+ *
1921
+ * @param operation - Operation(s) this policy applies to
1922
+ * @param relationshipPath - Name of the relationship path to use
1923
+ * @param endCondition - Conditions to apply at the end of the relationship
1924
+ * @param options - Additional policy options
1925
+ *
1926
+ * @example
1927
+ * ```typescript
1928
+ * // Deny access if user is blocked in the organization
1929
+ * denyRelation('all', 'products_shop_org_membership', ctx => ({
1930
+ * user_id: ctx.auth.userId,
1931
+ * status: 'blocked'
1932
+ * }))
1933
+ * ```
1934
+ */
1935
+ declare function denyRelation(operation: Operation | Operation[], relationshipPath: string, endCondition: ((ctx: PolicyEvaluationContext) => Record<string, unknown>) | Record<string, unknown>, options?: {
1936
+ name?: string;
1937
+ priority?: number;
1938
+ }): ReBAcPolicyDefinition;
1939
+ /**
1940
+ * Create a ReBAC transformer
1941
+ */
1942
+ declare function createReBAcTransformer<DB = unknown>(registry: ReBAcRegistry<DB>, options?: ReBAcQueryOptions): ReBAcTransformer<DB>;
1943
+
1944
+ /**
1945
+ * Field-Level Access Control Types
1946
+ *
1947
+ * Provides type definitions for controlling access to individual columns
1948
+ * based on context. This allows hiding sensitive fields from unauthorized users.
1949
+ *
1950
+ * @module @kysera/rls/field-access/types
1951
+ */
1952
+
1953
+ /**
1954
+ * Operations that can be controlled at field level
1955
+ */
1956
+ type FieldOperation = 'read' | 'write';
1957
+ /**
1958
+ * Field access condition function
1959
+ *
1960
+ * Returns true if the field is accessible, false otherwise.
1961
+ *
1962
+ * @typeParam TCtx - Policy evaluation context type
1963
+ */
1964
+ type FieldAccessCondition<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> = (ctx: TCtx) => boolean | Promise<boolean>;
1965
+ /**
1966
+ * Configuration for a single field's access control
1967
+ *
1968
+ * @example
1969
+ * ```typescript
1970
+ * const emailConfig: FieldAccessConfig = {
1971
+ * read: ctx => ctx.auth.userId === ctx.row.id || ctx.auth.roles.includes('admin'),
1972
+ * write: ctx => ctx.auth.userId === ctx.row.id
1973
+ * };
1974
+ * ```
1975
+ */
1976
+ interface FieldAccessConfig<TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> {
1977
+ /**
1978
+ * Condition for read access
1979
+ * If undefined, uses table default
1980
+ */
1981
+ read?: FieldAccessCondition<TCtx>;
1982
+ /**
1983
+ * Condition for write access
1984
+ * If undefined, uses table default
1985
+ */
1986
+ write?: FieldAccessCondition<TCtx>;
1987
+ /**
1988
+ * Value to use when field is not readable
1989
+ * @default null
1990
+ */
1991
+ maskedValue?: unknown;
1992
+ /**
1993
+ * Whether to completely omit the field when not readable
1994
+ * @default false (uses maskedValue instead)
1995
+ */
1996
+ omitWhenHidden?: boolean;
1997
+ }
1998
+ /**
1999
+ * Table field access configuration
2000
+ *
2001
+ * @typeParam TRow - Type of the database row
2002
+ * @typeParam TCtx - Policy evaluation context type
2003
+ *
2004
+ * @example
2005
+ * ```typescript
2006
+ * const usersFieldAccess: TableFieldAccessConfig<User> = {
2007
+ * default: 'allow',
2008
+ * fields: {
2009
+ * email: {
2010
+ * read: ctx => ctx.auth.userId === ctx.row.id || ctx.auth.roles.includes('admin')
2011
+ * },
2012
+ * password_hash: {
2013
+ * read: () => false,
2014
+ * write: () => false
2015
+ * },
2016
+ * mfa_totp_secret: {
2017
+ * read: ctx => ctx.auth.userId === ctx.row.id,
2018
+ * omitWhenHidden: true
2019
+ * }
2020
+ * }
2021
+ * };
2022
+ * ```
2023
+ */
2024
+ interface TableFieldAccessConfig<TRow = unknown, TCtx extends PolicyEvaluationContext = PolicyEvaluationContext> {
2025
+ /**
2026
+ * Default access policy for fields not explicitly configured
2027
+ * - 'allow': All fields are accessible by default
2028
+ * - 'deny': Only explicitly allowed fields are accessible
2029
+ * @default 'allow'
2030
+ */
2031
+ default?: 'allow' | 'deny';
2032
+ /**
2033
+ * Field-specific access configurations
2034
+ */
2035
+ fields: {
2036
+ [K in keyof TRow]?: FieldAccessConfig<TCtx>;
2037
+ };
2038
+ /**
2039
+ * Roles that bypass field access control
2040
+ */
2041
+ skipFor?: string[];
2042
+ }
2043
+ /**
2044
+ * Complete field access schema for all tables
2045
+ *
2046
+ * @typeParam DB - Database schema type
2047
+ */
2048
+ type FieldAccessSchema<DB> = {
2049
+ [K in keyof DB]?: TableFieldAccessConfig<DB[K]>;
2050
+ };
2051
+ /**
2052
+ * Compiled field access configuration ready for evaluation
2053
+ */
2054
+ interface CompiledFieldAccess {
2055
+ /**
2056
+ * Field name
2057
+ */
2058
+ field: string;
2059
+ /**
2060
+ * Compiled read condition
2061
+ * Returns true if field is readable
2062
+ */
2063
+ canRead: (ctx: PolicyEvaluationContext) => boolean | Promise<boolean>;
2064
+ /**
2065
+ * Compiled write condition
2066
+ * Returns true if field is writable
2067
+ */
2068
+ canWrite: (ctx: PolicyEvaluationContext) => boolean | Promise<boolean>;
2069
+ /**
2070
+ * Value to use when field is masked
2071
+ */
2072
+ maskedValue: unknown;
2073
+ /**
2074
+ * Whether to omit the field entirely when hidden
2075
+ */
2076
+ omitWhenHidden: boolean;
2077
+ }
2078
+ /**
2079
+ * Compiled table field access configuration
2080
+ */
2081
+ interface CompiledTableFieldAccess {
2082
+ /**
2083
+ * Table name
2084
+ */
2085
+ table: string;
2086
+ /**
2087
+ * Default access policy
2088
+ */
2089
+ defaultAccess: 'allow' | 'deny';
2090
+ /**
2091
+ * Roles that bypass field access
2092
+ */
2093
+ skipFor: string[];
2094
+ /**
2095
+ * Field-specific configurations
2096
+ */
2097
+ fields: Map<string, CompiledFieldAccess>;
2098
+ }
2099
+ /**
2100
+ * Result of field access evaluation
2101
+ */
2102
+ interface FieldAccessResult {
2103
+ /**
2104
+ * Whether the field is accessible
2105
+ */
2106
+ accessible: boolean;
2107
+ /**
2108
+ * If not accessible, the reason
2109
+ */
2110
+ reason?: string;
2111
+ /**
2112
+ * Value to use (original or masked)
2113
+ */
2114
+ value: unknown;
2115
+ /**
2116
+ * Whether the field should be omitted entirely
2117
+ */
2118
+ omit: boolean;
2119
+ }
2120
+ /**
2121
+ * Result of applying field access to a row
2122
+ */
2123
+ interface MaskedRow<T = Record<string, unknown>> {
2124
+ /**
2125
+ * The row with field access applied
2126
+ */
2127
+ data: Partial<T>;
2128
+ /**
2129
+ * Fields that were masked
2130
+ */
2131
+ maskedFields: string[];
2132
+ /**
2133
+ * Fields that were omitted
2134
+ */
2135
+ omittedFields: string[];
2136
+ }
2137
+ /**
2138
+ * Options for field access processing
2139
+ */
2140
+ interface FieldAccessOptions {
2141
+ /**
2142
+ * Whether to throw an error when accessing a denied field
2143
+ * @default false (returns masked value instead)
2144
+ */
2145
+ throwOnDenied?: boolean;
2146
+ /**
2147
+ * Whether to include metadata about masked fields in the result
2148
+ * @default false
2149
+ */
2150
+ includeMetadata?: boolean;
2151
+ /**
2152
+ * Fields to explicitly include (whitelist)
2153
+ * If specified, only these fields are processed
2154
+ */
2155
+ includeFields?: string[];
2156
+ /**
2157
+ * Fields to explicitly exclude (blacklist)
2158
+ * These fields are never included regardless of access
2159
+ */
2160
+ excludeFields?: string[];
2161
+ }
2162
+ /**
2163
+ * Always deny access to a field
2164
+ *
2165
+ * @example
2166
+ * ```typescript
2167
+ * const config = {
2168
+ * fields: {
2169
+ * password_hash: neverAccessible(),
2170
+ * api_secret: neverAccessible()
2171
+ * }
2172
+ * };
2173
+ * ```
2174
+ */
2175
+ declare function neverAccessible(): FieldAccessConfig;
2176
+ /**
2177
+ * Only the resource owner can access this field
2178
+ *
2179
+ * @param ownerField - Field name containing the owner ID
2180
+ *
2181
+ * @example
2182
+ * ```typescript
2183
+ * const config = {
2184
+ * fields: {
2185
+ * email: ownerOnly('user_id'),
2186
+ * phone: ownerOnly('user_id')
2187
+ * }
2188
+ * };
2189
+ * ```
2190
+ */
2191
+ declare function ownerOnly(ownerField?: string): FieldAccessConfig;
2192
+ /**
2193
+ * Owner or users with specific roles can access this field
2194
+ *
2195
+ * @param roles - Roles that can access besides owner
2196
+ * @param ownerField - Field name containing the owner ID
2197
+ *
2198
+ * @example
2199
+ * ```typescript
2200
+ * const config = {
2201
+ * fields: {
2202
+ * email: ownerOrRoles(['admin', 'support'], 'user_id'),
2203
+ * address: ownerOrRoles(['admin'], 'user_id')
2204
+ * }
2205
+ * };
2206
+ * ```
2207
+ */
2208
+ declare function ownerOrRoles(roles: string[], ownerField?: string): FieldAccessConfig;
2209
+ /**
2210
+ * Only users with specific roles can access this field
2211
+ *
2212
+ * @param roles - Roles that can access
2213
+ *
2214
+ * @example
2215
+ * ```typescript
2216
+ * const config = {
2217
+ * fields: {
2218
+ * internal_notes: rolesOnly(['admin', 'moderator']),
2219
+ * audit_log: rolesOnly(['admin'])
2220
+ * }
2221
+ * };
2222
+ * ```
2223
+ */
2224
+ declare function rolesOnly(roles: string[]): FieldAccessConfig;
2225
+ /**
2226
+ * Field is read-only (no write access)
2227
+ *
2228
+ * @param readCondition - Optional condition for read access
2229
+ *
2230
+ * @example
2231
+ * ```typescript
2232
+ * const config = {
2233
+ * fields: {
2234
+ * created_at: readOnly(),
2235
+ * version: readOnly()
2236
+ * }
2237
+ * };
2238
+ * ```
2239
+ */
2240
+ declare function readOnly(readCondition?: FieldAccessCondition): FieldAccessConfig;
2241
+ /**
2242
+ * Field has public read access but restricted write
2243
+ *
2244
+ * @param writeCondition - Condition for write access
2245
+ *
2246
+ * @example
2247
+ * ```typescript
2248
+ * const config = {
2249
+ * fields: {
2250
+ * display_name: publicReadRestrictedWrite(ctx => ctx.auth.userId === ctx.row.id),
2251
+ * bio: publicReadRestrictedWrite(ctx => ctx.auth.userId === ctx.row.id)
2252
+ * }
2253
+ * };
2254
+ * ```
2255
+ */
2256
+ declare function publicReadRestrictedWrite(writeCondition: FieldAccessCondition): FieldAccessConfig;
2257
+ /**
2258
+ * Mask field value with custom masking function
2259
+ *
2260
+ * @param maskFn - Function to mask the value
2261
+ * @param readCondition - Condition for full read access
2262
+ *
2263
+ * @example
2264
+ * ```typescript
2265
+ * const config = {
2266
+ * fields: {
2267
+ * email: maskedField(
2268
+ * value => value.replace(/(.{2}).*@/, '$1***@'),
2269
+ * ctx => ctx.auth.userId === ctx.row.id
2270
+ * ),
2271
+ * phone: maskedField(
2272
+ * value => value.replace(/\d(?=\d{4})/g, '*'),
2273
+ * ctx => ctx.auth.userId === ctx.row.id
2274
+ * )
2275
+ * }
2276
+ * };
2277
+ * ```
2278
+ */
2279
+ declare function maskedField(maskFn: (value: unknown) => unknown, readCondition: FieldAccessCondition): FieldAccessConfig & {
2280
+ maskFn: (value: unknown) => unknown;
2281
+ };
2282
+
2283
+ /**
2284
+ * Field Access Registry
2285
+ *
2286
+ * Manages field-level access control configurations across tables.
2287
+ *
2288
+ * @module @kysera/rls/field-access/registry
2289
+ */
2290
+
2291
+ /**
2292
+ * Field Access Registry
2293
+ *
2294
+ * Manages field-level access control configurations for all tables.
2295
+ *
2296
+ * @example
2297
+ * ```typescript
2298
+ * const registry = new FieldAccessRegistry();
2299
+ *
2300
+ * registry.loadSchema<Database>({
2301
+ * users: {
2302
+ * default: 'allow',
2303
+ * fields: {
2304
+ * email: ownerOrRoles(['admin'], 'id'),
2305
+ * password_hash: neverAccessible(),
2306
+ * mfa_secret: ownerOnly('id')
2307
+ * }
2308
+ * }
2309
+ * });
2310
+ *
2311
+ * // Check if field is accessible
2312
+ * const canRead = await registry.canReadField('users', 'email', evalCtx);
2313
+ * ```
2314
+ */
2315
+ declare class FieldAccessRegistry<DB = unknown> {
2316
+ private tables;
2317
+ private logger;
2318
+ constructor(schema?: FieldAccessSchema<DB>, options?: {
2319
+ logger?: KyseraLogger;
2320
+ });
2321
+ /**
2322
+ * Load field access schema
2323
+ */
2324
+ loadSchema(schema: FieldAccessSchema<DB>): void;
2325
+ /**
2326
+ * Register field access configuration for a table
2327
+ */
2328
+ registerTable(table: string, config: TableFieldAccessConfig): void;
2329
+ /**
2330
+ * Check if a field is readable in the current context
2331
+ *
2332
+ * @param table - Table name
2333
+ * @param field - Field name
2334
+ * @param ctx - Evaluation context
2335
+ * @returns True if field is readable
2336
+ */
2337
+ canReadField(table: string, field: string, ctx: PolicyEvaluationContext): Promise<boolean>;
2338
+ /**
2339
+ * Check if a field is writable in the current context
2340
+ *
2341
+ * @param table - Table name
2342
+ * @param field - Field name
2343
+ * @param ctx - Evaluation context
2344
+ * @returns True if field is writable
2345
+ */
2346
+ canWriteField(table: string, field: string, ctx: PolicyEvaluationContext): Promise<boolean>;
2347
+ /**
2348
+ * Get field configuration
2349
+ *
2350
+ * @param table - Table name
2351
+ * @param field - Field name
2352
+ * @returns Compiled field access config or undefined
2353
+ */
2354
+ getFieldConfig(table: string, field: string): CompiledFieldAccess | undefined;
2355
+ /**
2356
+ * Get table configuration
2357
+ *
2358
+ * @param table - Table name
2359
+ * @returns Compiled table field access config or undefined
2360
+ */
2361
+ getTableConfig(table: string): CompiledTableFieldAccess | undefined;
2362
+ /**
2363
+ * Check if table has field access configuration
2364
+ */
2365
+ hasTable(table: string): boolean;
2366
+ /**
2367
+ * Get all registered table names
2368
+ */
2369
+ getTables(): string[];
2370
+ /**
2371
+ * Get all fields with explicit configuration for a table
2372
+ *
2373
+ * @param table - Table name
2374
+ * @returns Array of field names
2375
+ */
2376
+ getConfiguredFields(table: string): string[];
2377
+ /**
2378
+ * Clear all configurations
2379
+ */
2380
+ clear(): void;
2381
+ /**
2382
+ * Compile a field access configuration
2383
+ */
2384
+ private compileFieldConfig;
2385
+ }
2386
+ /**
2387
+ * Create a field access registry
2388
+ */
2389
+ declare function createFieldAccessRegistry<DB = unknown>(schema?: FieldAccessSchema<DB>, options?: {
2390
+ logger?: KyseraLogger;
2391
+ }): FieldAccessRegistry<DB>;
2392
+
2393
+ /**
2394
+ * Field Access Processor
2395
+ *
2396
+ * Applies field-level access control to database rows and mutation data.
2397
+ *
2398
+ * @module @kysera/rls/field-access/processor
2399
+ */
2400
+
2401
+ /**
2402
+ * Field Access Processor
2403
+ *
2404
+ * Applies field-level access control rules to rows and mutation data.
2405
+ *
2406
+ * @example
2407
+ * ```typescript
2408
+ * const processor = new FieldAccessProcessor(registry);
2409
+ *
2410
+ * // Mask fields in a row
2411
+ * const result = await processor.maskRow('users', user, {
2412
+ * includeMetadata: true
2413
+ * });
2414
+ *
2415
+ * console.log(result.data); // Row with masked fields
2416
+ * console.log(result.maskedFields); // ['email', 'phone']
2417
+ * console.log(result.omittedFields); // ['mfa_secret']
2418
+ *
2419
+ * // Validate write access
2420
+ * await processor.validateWrite('users', { email: 'new@example.com' });
2421
+ * ```
2422
+ */
2423
+ declare class FieldAccessProcessor<DB = unknown> {
2424
+ private registry;
2425
+ private defaultMaskValue;
2426
+ constructor(registry: FieldAccessRegistry<DB>, defaultMaskValue?: unknown);
2427
+ /**
2428
+ * Apply field access control to a single row
2429
+ *
2430
+ * @param table - Table name
2431
+ * @param row - Row data
2432
+ * @param options - Processing options
2433
+ * @returns Masked row with metadata
2434
+ */
2435
+ maskRow<T extends Record<string, unknown>>(table: string, row: T, options?: FieldAccessOptions): Promise<MaskedRow<T>>;
2436
+ /**
2437
+ * Apply field access control to multiple rows
2438
+ *
2439
+ * @param table - Table name
2440
+ * @param rows - Array of rows
2441
+ * @param options - Processing options
2442
+ * @returns Array of masked rows
2443
+ */
2444
+ maskRows<T extends Record<string, unknown>>(table: string, rows: T[], options?: FieldAccessOptions): Promise<MaskedRow<T>[]>;
2445
+ /**
2446
+ * Validate that all fields in mutation data are writable
2447
+ *
2448
+ * @param table - Table name
2449
+ * @param data - Mutation data
2450
+ * @param existingRow - Existing row (for update operations)
2451
+ * @throws RLSPolicyViolation if any field is not writable
2452
+ */
2453
+ validateWrite(table: string, data: Record<string, unknown>, existingRow?: Record<string, unknown>): Promise<void>;
2454
+ /**
2455
+ * Filter mutation data to only include writable fields
2456
+ *
2457
+ * @param table - Table name
2458
+ * @param data - Mutation data
2459
+ * @param existingRow - Existing row (for update operations)
2460
+ * @returns Filtered data with only writable fields
2461
+ */
2462
+ filterWritableFields(table: string, data: Record<string, unknown>, existingRow?: Record<string, unknown>): Promise<{
2463
+ data: Record<string, unknown>;
2464
+ removedFields: string[];
2465
+ }>;
2466
+ /**
2467
+ * Get list of readable fields for a table
2468
+ *
2469
+ * @param table - Table name
2470
+ * @param row - Row data (for context-dependent fields)
2471
+ * @returns Array of readable field names
2472
+ */
2473
+ getReadableFields(table: string, row: Record<string, unknown>): Promise<string[]>;
2474
+ /**
2475
+ * Get list of writable fields for a table
2476
+ *
2477
+ * @param table - Table name
2478
+ * @param row - Existing row data (for context-dependent fields)
2479
+ * @returns Array of writable field names
2480
+ */
2481
+ getWritableFields(table: string, row: Record<string, unknown>): Promise<string[]>;
2482
+ /**
2483
+ * Get current RLS context
2484
+ */
2485
+ private getContext;
2486
+ /**
2487
+ * Create evaluation context
2488
+ */
2489
+ private createEvalContext;
2490
+ /**
2491
+ * Evaluate field access for a specific field
2492
+ */
2493
+ private evaluateFieldAccess;
2494
+ }
2495
+ /**
2496
+ * Create a field access processor
2497
+ */
2498
+ declare function createFieldAccessProcessor<DB = unknown>(registry: FieldAccessRegistry<DB>, defaultMaskValue?: unknown): FieldAccessProcessor<DB>;
2499
+
2500
+ /**
2501
+ * Policy Composition Types
2502
+ *
2503
+ * Provides types for creating reusable, composable RLS policies.
2504
+ *
2505
+ * @module @kysera/rls/composition/types
2506
+ */
2507
+
2508
+ /**
2509
+ * A named, reusable policy template
2510
+ *
2511
+ * Can be composed with other policies and applied to multiple tables.
2512
+ *
2513
+ * @example
2514
+ * ```typescript
2515
+ * const tenantIsolation = definePolicy({
2516
+ * name: 'tenantIsolation',
2517
+ * type: 'filter',
2518
+ * operation: 'read',
2519
+ * filter: ctx => ({ tenant_id: ctx.auth.tenantId }),
2520
+ * priority: 1000
2521
+ * });
2522
+ * ```
2523
+ */
2524
+ interface ReusablePolicy {
2525
+ /**
2526
+ * Unique name for this policy
2527
+ */
2528
+ name: string;
2529
+ /**
2530
+ * Description for documentation
2531
+ */
2532
+ description?: string;
2533
+ /**
2534
+ * Policy definitions (can include multiple policies)
2535
+ */
2536
+ policies: PolicyDefinition[];
2537
+ /**
2538
+ * Tags for categorization
2539
+ */
2540
+ tags?: string[];
2541
+ }
2542
+ /**
2543
+ * Configuration for a reusable policy template
2544
+ */
2545
+ interface ReusablePolicyConfig {
2546
+ /**
2547
+ * Policy name
2548
+ */
2549
+ name: string;
2550
+ /**
2551
+ * Description
2552
+ */
2553
+ description?: string;
2554
+ /**
2555
+ * Tags for categorization
2556
+ */
2557
+ tags?: string[];
2558
+ }
2559
+ /**
2560
+ * Extended table RLS configuration with policy composition support
2561
+ */
2562
+ interface ComposableTableConfig {
2563
+ /**
2564
+ * Reusable policies to extend from
2565
+ * Policies are applied in order (first = lowest priority)
2566
+ */
2567
+ extends?: ReusablePolicy[];
2568
+ /**
2569
+ * Additional table-specific policies
2570
+ */
2571
+ policies?: PolicyDefinition[];
2572
+ /**
2573
+ * Whether to allow access by default when no policies match
2574
+ * @default true
2575
+ */
2576
+ defaultDeny?: boolean;
2577
+ /**
2578
+ * Roles that bypass RLS
2579
+ */
2580
+ skipFor?: string[];
2581
+ }
2582
+ /**
2583
+ * Complete schema with composition support
2584
+ *
2585
+ * @typeParam DB - Database schema type
2586
+ */
2587
+ type ComposableRLSSchema<DB> = {
2588
+ [K in keyof DB]?: ComposableTableConfig;
2589
+ };
2590
+ /**
2591
+ * Base policy that can be extended
2592
+ */
2593
+ interface BasePolicyDefinition {
2594
+ /**
2595
+ * Unique identifier for this base policy
2596
+ */
2597
+ id: string;
2598
+ /**
2599
+ * Human-readable name
2600
+ */
2601
+ name: string;
2602
+ /**
2603
+ * Description
2604
+ */
2605
+ description?: string;
2606
+ /**
2607
+ * Policies included in this base
2608
+ */
2609
+ policies: PolicyDefinition[];
2610
+ /**
2611
+ * Other base policies this extends
2612
+ */
2613
+ extends?: string[];
2614
+ /**
2615
+ * Priority offset applied to all policies
2616
+ * @default 0
2617
+ */
2618
+ priorityOffset?: number;
2619
+ }
2620
+ /**
2621
+ * Policy inheritance chain resolution
2622
+ */
2623
+ interface ResolvedInheritance {
2624
+ /**
2625
+ * Final merged policies
2626
+ */
2627
+ policies: PolicyDefinition[];
2628
+ /**
2629
+ * Chain of base policies used
2630
+ */
2631
+ inheritanceChain: string[];
2632
+ /**
2633
+ * Any conflicts detected
2634
+ */
2635
+ conflicts: {
2636
+ policy: string;
2637
+ reason: string;
2638
+ }[];
2639
+ }
2640
+ /**
2641
+ * Multi-tenancy policy configuration
2642
+ */
2643
+ interface TenantIsolationConfig {
2644
+ /**
2645
+ * Column name for tenant ID
2646
+ * @default 'tenant_id'
2647
+ */
2648
+ tenantColumn?: string;
2649
+ /**
2650
+ * Operations to apply tenant isolation to
2651
+ * @default ['read', 'create', 'update', 'delete']
2652
+ */
2653
+ operations?: Operation[];
2654
+ /**
2655
+ * Whether to validate tenant on create/update
2656
+ * @default true
2657
+ */
2658
+ validateOnMutation?: boolean;
2659
+ }
2660
+ /**
2661
+ * Ownership policy configuration
2662
+ */
2663
+ interface OwnershipConfig {
2664
+ /**
2665
+ * Column name for owner ID
2666
+ * @default 'owner_id' or 'user_id'
2667
+ */
2668
+ ownerColumn?: string;
2669
+ /**
2670
+ * Operations owners can perform
2671
+ * @default ['read', 'update', 'delete']
2672
+ */
2673
+ ownerOperations?: Operation[];
2674
+ /**
2675
+ * Whether owners can delete
2676
+ * @default true
2677
+ */
2678
+ canDelete?: boolean;
2679
+ }
2680
+ /**
2681
+ * Soft delete policy configuration
2682
+ */
2683
+ interface SoftDeleteConfig {
2684
+ /**
2685
+ * Column name for soft delete flag
2686
+ * @default 'deleted_at'
2687
+ */
2688
+ deletedColumn?: string;
2689
+ /**
2690
+ * Whether to filter soft-deleted rows on read
2691
+ * @default true
2692
+ */
2693
+ filterOnRead?: boolean;
2694
+ /**
2695
+ * Whether to prevent hard deletes
2696
+ * @default true
2697
+ */
2698
+ preventHardDelete?: boolean;
2699
+ }
2700
+ /**
2701
+ * Status-based access configuration
2702
+ */
2703
+ interface StatusAccessConfig {
2704
+ /**
2705
+ * Column name for status
2706
+ * @default 'status'
2707
+ */
2708
+ statusColumn?: string;
2709
+ /**
2710
+ * Statuses that are publicly readable
2711
+ */
2712
+ publicStatuses?: string[];
2713
+ /**
2714
+ * Statuses that can be updated
2715
+ */
2716
+ editableStatuses?: string[];
2717
+ /**
2718
+ * Statuses that can be deleted
2719
+ */
2720
+ deletableStatuses?: string[];
2721
+ }
2722
+
2723
+ /**
2724
+ * Policy Composition Builder
2725
+ *
2726
+ * Factory functions for creating reusable, composable RLS policies.
2727
+ *
2728
+ * @module @kysera/rls/composition/builder
2729
+ */
2730
+
2731
+ /**
2732
+ * Create a reusable policy template
2733
+ *
2734
+ * @param config - Policy configuration
2735
+ * @param policies - Array of policy definitions
2736
+ * @returns Reusable policy template
2737
+ *
2738
+ * @example
2739
+ * ```typescript
2740
+ * const tenantPolicy = definePolicy(
2741
+ * {
2742
+ * name: 'tenantIsolation',
2743
+ * description: 'Filter by tenant_id',
2744
+ * tags: ['multi-tenant']
2745
+ * },
2746
+ * [
2747
+ * filter('read', ctx => ({ tenant_id: ctx.auth.tenantId }), {
2748
+ * priority: 1000,
2749
+ * name: 'tenant-filter'
2750
+ * }),
2751
+ * validate('create', ctx => ctx.data?.tenant_id === ctx.auth.tenantId, {
2752
+ * name: 'tenant-validate'
2753
+ * })
2754
+ * ]
2755
+ * );
2756
+ * ```
2757
+ */
2758
+ declare function definePolicy(config: ReusablePolicyConfig, policies: PolicyDefinition[]): ReusablePolicy;
2759
+ /**
2760
+ * Create a filter-only policy
2761
+ *
2762
+ * @param name - Policy name
2763
+ * @param filterFn - Filter condition
2764
+ * @param options - Additional options
2765
+ * @returns Reusable filter policy
2766
+ */
2767
+ declare function defineFilterPolicy(name: string, filterFn: (ctx: PolicyEvaluationContext) => Record<string, unknown>, options?: {
2768
+ priority?: number;
2769
+ }): ReusablePolicy;
2770
+ /**
2771
+ * Create an allow-based policy
2772
+ *
2773
+ * @param name - Policy name
2774
+ * @param operation - Operations to allow
2775
+ * @param condition - Allow condition
2776
+ * @param options - Additional options
2777
+ * @returns Reusable allow policy
2778
+ */
2779
+ declare function defineAllowPolicy(name: string, operation: Operation | Operation[], condition: (ctx: PolicyEvaluationContext) => boolean | Promise<boolean>, options?: {
2780
+ priority?: number;
2781
+ }): ReusablePolicy;
2782
+ /**
2783
+ * Create a deny-based policy
2784
+ *
2785
+ * @param name - Policy name
2786
+ * @param operation - Operations to deny
2787
+ * @param condition - Deny condition (optional - if not provided, always denies)
2788
+ * @param options - Additional options
2789
+ * @returns Reusable deny policy
2790
+ */
2791
+ declare function defineDenyPolicy(name: string, operation: Operation | Operation[], condition?: (ctx: PolicyEvaluationContext) => boolean | Promise<boolean>, options?: {
2792
+ priority?: number;
2793
+ }): ReusablePolicy;
2794
+ /**
2795
+ * Create a validation policy
2796
+ *
2797
+ * @param name - Policy name
2798
+ * @param operation - Operations to validate
2799
+ * @param condition - Validation condition
2800
+ * @param options - Additional options
2801
+ * @returns Reusable validate policy
2802
+ */
2803
+ declare function defineValidatePolicy(name: string, operation: 'create' | 'update' | 'all', condition: (ctx: PolicyEvaluationContext) => boolean | Promise<boolean>, options?: {
2804
+ priority?: number;
2805
+ }): ReusablePolicy;
2806
+ /**
2807
+ * Create a combined policy with multiple types
2808
+ *
2809
+ * @param name - Policy name
2810
+ * @param config - Policy configurations
2811
+ * @returns Reusable combined policy
2812
+ */
2813
+ declare function defineCombinedPolicy(name: string, config: {
2814
+ filter?: (ctx: PolicyEvaluationContext) => Record<string, unknown>;
2815
+ allow?: Record<string, (ctx: PolicyEvaluationContext) => boolean | Promise<boolean>>;
2816
+ deny?: Record<string, (ctx: PolicyEvaluationContext) => boolean | Promise<boolean>>;
2817
+ validate?: {
2818
+ create?: (ctx: PolicyEvaluationContext) => boolean | Promise<boolean>;
2819
+ update?: (ctx: PolicyEvaluationContext) => boolean | Promise<boolean>;
2820
+ };
2821
+ }): ReusablePolicy;
2822
+ /**
2823
+ * Create a tenant isolation policy
2824
+ *
2825
+ * Automatically filters by tenant_id and validates mutations.
2826
+ *
2827
+ * @param config - Tenant isolation configuration
2828
+ * @returns Reusable tenant isolation policy
2829
+ */
2830
+ declare function createTenantIsolationPolicy(config?: TenantIsolationConfig): ReusablePolicy;
2831
+ /**
2832
+ * Create an ownership policy
2833
+ *
2834
+ * Allows owners to read/update/delete their own resources.
2835
+ *
2836
+ * @param config - Ownership configuration
2837
+ * @returns Reusable ownership policy
2838
+ */
2839
+ declare function createOwnershipPolicy(config?: OwnershipConfig): ReusablePolicy;
2840
+ /**
2841
+ * Create a soft delete policy
2842
+ *
2843
+ * Filters out soft-deleted rows and optionally prevents hard deletes.
2844
+ *
2845
+ * @param config - Soft delete configuration
2846
+ * @returns Reusable soft delete policy
2847
+ */
2848
+ declare function createSoftDeletePolicy(config?: SoftDeleteConfig): ReusablePolicy;
2849
+ /**
2850
+ * Create a status-based access policy
2851
+ *
2852
+ * Controls access based on resource status.
2853
+ *
2854
+ * @param config - Status access configuration
2855
+ * @returns Reusable status policy
2856
+ */
2857
+ declare function createStatusAccessPolicy(config: StatusAccessConfig): ReusablePolicy;
2858
+ /**
2859
+ * Create an admin bypass policy
2860
+ *
2861
+ * Allows admin roles to perform all operations.
2862
+ *
2863
+ * @param roles - Roles that have admin access
2864
+ * @returns Reusable admin policy
2865
+ */
2866
+ declare function createAdminPolicy(roles: string[]): ReusablePolicy;
2867
+ /**
2868
+ * Compose multiple reusable policies into one
2869
+ *
2870
+ * @param name - Name for the composed policy
2871
+ * @param policies - Policies to compose
2872
+ * @returns Composed policy
2873
+ */
2874
+ declare function composePolicies(name: string, policies: ReusablePolicy[]): ReusablePolicy;
2875
+ /**
2876
+ * Extend a reusable policy with additional policies
2877
+ *
2878
+ * @param base - Base policy to extend
2879
+ * @param additional - Additional policies to add
2880
+ * @returns Extended policy
2881
+ */
2882
+ declare function extendPolicy(base: ReusablePolicy, additional: PolicyDefinition[]): ReusablePolicy;
2883
+ /**
2884
+ * Override policies from a base with new conditions
2885
+ *
2886
+ * @param base - Base policy
2887
+ * @param overrides - Policy name to new policy mapping
2888
+ * @returns Policy with overrides applied
2889
+ */
2890
+ declare function overridePolicy(base: ReusablePolicy, overrides: Record<string, PolicyDefinition>): ReusablePolicy;
2891
+
2892
+ /**
2893
+ * Audit Trail Types
2894
+ *
2895
+ * Provides type definitions for auditing RLS policy decisions.
2896
+ *
2897
+ * @module @kysera/rls/audit/types
2898
+ */
2899
+
2900
+ /**
2901
+ * RLS policy decision result
2902
+ */
2903
+ type AuditDecision = 'allow' | 'deny' | 'filter';
2904
+ /**
2905
+ * RLS audit event
2906
+ *
2907
+ * Represents a single policy evaluation event for audit logging.
2908
+ *
2909
+ * @example
2910
+ * ```typescript
2911
+ * const event: RLSAuditEvent = {
2912
+ * timestamp: new Date(),
2913
+ * userId: '123',
2914
+ * operation: 'update',
2915
+ * table: 'posts',
2916
+ * policyName: 'ownership-allow',
2917
+ * decision: 'allow',
2918
+ * context: { rowId: '456', tenantId: 'acme' }
2919
+ * };
2920
+ * ```
2921
+ */
2922
+ interface RLSAuditEvent {
2923
+ /**
2924
+ * Timestamp of the event
2925
+ */
2926
+ timestamp: Date;
2927
+ /**
2928
+ * User ID who performed the action
2929
+ */
2930
+ userId: string | number;
2931
+ /**
2932
+ * Tenant ID (if multi-tenant)
2933
+ */
2934
+ tenantId?: string | number;
2935
+ /**
2936
+ * Database operation
2937
+ */
2938
+ operation: Operation;
2939
+ /**
2940
+ * Table name
2941
+ */
2942
+ table: string;
2943
+ /**
2944
+ * Name of the policy that made the decision
2945
+ */
2946
+ policyName?: string;
2947
+ /**
2948
+ * Decision result
2949
+ */
2950
+ decision: AuditDecision;
2951
+ /**
2952
+ * Reason for the decision (especially for denials)
2953
+ */
2954
+ reason?: string;
2955
+ /**
2956
+ * Additional context about the event
2957
+ */
2958
+ context?: Record<string, unknown>;
2959
+ /**
2960
+ * Row ID(s) affected
2961
+ */
2962
+ rowIds?: (string | number)[];
2963
+ /**
2964
+ * Hash of the query (for grouping similar queries)
2965
+ */
2966
+ queryHash?: string;
2967
+ /**
2968
+ * Request ID for tracing
2969
+ */
2970
+ requestId?: string;
2971
+ /**
2972
+ * IP address of the requester
2973
+ */
2974
+ ipAddress?: string;
2975
+ /**
2976
+ * User agent string
2977
+ */
2978
+ userAgent?: string;
2979
+ /**
2980
+ * Duration of policy evaluation in milliseconds
2981
+ */
2982
+ durationMs?: number;
2983
+ /**
2984
+ * Whether this event was filtered from logging
2985
+ * (set by filtering rules but still available for debugging)
2986
+ */
2987
+ filtered?: boolean;
2988
+ }
2989
+ /**
2990
+ * Adapter for persisting audit events
2991
+ *
2992
+ * Implement this interface to store audit events in your preferred backend.
2993
+ *
2994
+ * @example
2995
+ * ```typescript
2996
+ * class DatabaseAuditAdapter implements RLSAuditAdapter {
2997
+ * constructor(private db: Kysely<AuditDB>) {}
2998
+ *
2999
+ * async log(event: RLSAuditEvent): Promise<void> {
3000
+ * await this.db.insertInto('rls_audit_log')
3001
+ * .values({
3002
+ * user_id: event.userId,
3003
+ * operation: event.operation,
3004
+ * table_name: event.table,
3005
+ * decision: event.decision,
3006
+ * context: JSON.stringify(event.context),
3007
+ * created_at: event.timestamp
3008
+ * })
3009
+ * .execute();
3010
+ * }
3011
+ *
3012
+ * async logBatch(events: RLSAuditEvent[]): Promise<void> {
3013
+ * await this.db.insertInto('rls_audit_log')
3014
+ * .values(events.map(e => ({
3015
+ * user_id: e.userId,
3016
+ * operation: e.operation,
3017
+ * table_name: e.table,
3018
+ * decision: e.decision,
3019
+ * context: JSON.stringify(e.context),
3020
+ * created_at: e.timestamp
3021
+ * })))
3022
+ * .execute();
3023
+ * }
3024
+ * }
3025
+ * ```
3026
+ */
3027
+ interface RLSAuditAdapter {
3028
+ /**
3029
+ * Log a single audit event
3030
+ *
3031
+ * @param event - Event to log
3032
+ */
3033
+ log(event: RLSAuditEvent): Promise<void>;
3034
+ /**
3035
+ * Log multiple audit events (for batch processing)
3036
+ *
3037
+ * @param events - Events to log
3038
+ */
3039
+ logBatch?(events: RLSAuditEvent[]): Promise<void>;
3040
+ /**
3041
+ * Flush any buffered events
3042
+ */
3043
+ flush?(): Promise<void>;
3044
+ /**
3045
+ * Close the adapter and release resources
3046
+ */
3047
+ close?(): Promise<void>;
3048
+ }
3049
+ /**
3050
+ * Configuration for table-specific audit settings
3051
+ */
3052
+ interface TableAuditConfig {
3053
+ /**
3054
+ * Whether audit is enabled for this table
3055
+ * @default true (if audit is globally enabled)
3056
+ */
3057
+ enabled?: boolean;
3058
+ /**
3059
+ * Log allowed decisions
3060
+ * @default false
3061
+ */
3062
+ logAllowed?: boolean;
3063
+ /**
3064
+ * Log denied decisions
3065
+ * @default true
3066
+ */
3067
+ logDenied?: boolean;
3068
+ /**
3069
+ * Log filter applications
3070
+ * @default false
3071
+ */
3072
+ logFilters?: boolean;
3073
+ /**
3074
+ * Context fields to include in audit logs
3075
+ * If empty, includes all available context
3076
+ */
3077
+ includeContext?: string[];
3078
+ /**
3079
+ * Context fields to exclude from audit logs
3080
+ */
3081
+ excludeContext?: string[];
3082
+ /**
3083
+ * Whether to include row data in audit logs
3084
+ * @default false (for privacy)
3085
+ */
3086
+ includeRowData?: boolean;
3087
+ /**
3088
+ * Whether to include mutation data in audit logs
3089
+ * @default false (for privacy)
3090
+ */
3091
+ includeMutationData?: boolean;
3092
+ /**
3093
+ * Custom filter function to determine if an event should be logged
3094
+ */
3095
+ filter?: (event: RLSAuditEvent) => boolean;
3096
+ }
3097
+ /**
3098
+ * Global audit configuration
3099
+ */
3100
+ interface AuditConfig {
3101
+ /**
3102
+ * Audit adapter for persisting events
3103
+ */
3104
+ adapter: RLSAuditAdapter;
3105
+ /**
3106
+ * Whether audit is enabled globally
3107
+ * @default true
3108
+ */
3109
+ enabled?: boolean;
3110
+ /**
3111
+ * Default settings for all tables
3112
+ */
3113
+ defaults?: Omit<TableAuditConfig, 'enabled'>;
3114
+ /**
3115
+ * Table-specific audit configurations
3116
+ */
3117
+ tables?: Record<string, TableAuditConfig>;
3118
+ /**
3119
+ * Buffer size for batch logging
3120
+ * Events are batched until this size is reached
3121
+ * @default 100
3122
+ */
3123
+ bufferSize?: number;
3124
+ /**
3125
+ * Maximum time to buffer events before flushing (ms)
3126
+ * @default 5000 (5 seconds)
3127
+ */
3128
+ flushInterval?: number;
3129
+ /**
3130
+ * Whether to log asynchronously (fire-and-forget)
3131
+ * @default true (for performance)
3132
+ */
3133
+ async?: boolean;
3134
+ /**
3135
+ * Error handler for audit failures
3136
+ */
3137
+ onError?: (error: Error, events: RLSAuditEvent[]) => void;
3138
+ /**
3139
+ * Sample rate for audit logging (0.0 to 1.0)
3140
+ * Use for high-traffic systems to reduce log volume
3141
+ * @default 1.0 (log all)
3142
+ */
3143
+ sampleRate?: number;
3144
+ }
3145
+ /**
3146
+ * Query parameters for retrieving audit events
3147
+ */
3148
+ interface AuditQueryParams {
3149
+ /**
3150
+ * Filter by user ID
3151
+ */
3152
+ userId?: string | number;
3153
+ /**
3154
+ * Filter by tenant ID
3155
+ */
3156
+ tenantId?: string | number;
3157
+ /**
3158
+ * Filter by table name
3159
+ */
3160
+ table?: string;
3161
+ /**
3162
+ * Filter by operation
3163
+ */
3164
+ operation?: Operation;
3165
+ /**
3166
+ * Filter by decision
3167
+ */
3168
+ decision?: AuditDecision;
3169
+ /**
3170
+ * Start timestamp (inclusive)
3171
+ */
3172
+ startTime?: Date;
3173
+ /**
3174
+ * End timestamp (exclusive)
3175
+ */
3176
+ endTime?: Date;
3177
+ /**
3178
+ * Filter by request ID
3179
+ */
3180
+ requestId?: string;
3181
+ /**
3182
+ * Maximum results to return
3183
+ */
3184
+ limit?: number;
3185
+ /**
3186
+ * Offset for pagination
3187
+ */
3188
+ offset?: number;
3189
+ }
3190
+ /**
3191
+ * Aggregated audit statistics
3192
+ */
3193
+ interface AuditStats {
3194
+ /**
3195
+ * Total number of events
3196
+ */
3197
+ totalEvents: number;
3198
+ /**
3199
+ * Events by decision type
3200
+ */
3201
+ byDecision: Record<AuditDecision, number>;
3202
+ /**
3203
+ * Events by operation
3204
+ */
3205
+ byOperation: Record<Operation, number>;
3206
+ /**
3207
+ * Events by table
3208
+ */
3209
+ byTable: Record<string, number>;
3210
+ /**
3211
+ * Top denied users
3212
+ */
3213
+ topDeniedUsers?: {
3214
+ userId: string | number;
3215
+ count: number;
3216
+ }[];
3217
+ /**
3218
+ * Time range of stats
3219
+ */
3220
+ timeRange: {
3221
+ start: Date;
3222
+ end: Date;
3223
+ };
3224
+ }
3225
+ /**
3226
+ * Simple console-based audit adapter for development/testing
3227
+ *
3228
+ * @example
3229
+ * ```typescript
3230
+ * const adapter = new ConsoleAuditAdapter({
3231
+ * format: 'json',
3232
+ * colors: true
3233
+ * });
3234
+ * ```
3235
+ */
3236
+ interface ConsoleAuditAdapterOptions {
3237
+ /**
3238
+ * Output format
3239
+ * @default 'text'
3240
+ */
3241
+ format?: 'text' | 'json';
3242
+ /**
3243
+ * Use colors in output (for text format)
3244
+ * @default true
3245
+ */
3246
+ colors?: boolean;
3247
+ /**
3248
+ * Include timestamp in output
3249
+ * @default true
3250
+ */
3251
+ includeTimestamp?: boolean;
3252
+ }
3253
+ /**
3254
+ * Console audit adapter implementation
3255
+ */
3256
+ declare class ConsoleAuditAdapter implements RLSAuditAdapter {
3257
+ private options;
3258
+ constructor(options?: ConsoleAuditAdapterOptions);
3259
+ log(event: RLSAuditEvent): Promise<void>;
3260
+ logBatch(events: RLSAuditEvent[]): Promise<void>;
3261
+ private getPrefix;
3262
+ }
3263
+ /**
3264
+ * In-memory audit adapter for testing
3265
+ *
3266
+ * Stores events in memory for later retrieval and assertion.
3267
+ */
3268
+ declare class InMemoryAuditAdapter implements RLSAuditAdapter {
3269
+ private events;
3270
+ private maxSize;
3271
+ constructor(maxSize?: number);
3272
+ log(event: RLSAuditEvent): Promise<void>;
3273
+ logBatch(events: RLSAuditEvent[]): Promise<void>;
3274
+ /**
3275
+ * Get all logged events
3276
+ */
3277
+ getEvents(): RLSAuditEvent[];
3278
+ /**
3279
+ * Query events
3280
+ */
3281
+ query(params: AuditQueryParams): RLSAuditEvent[];
3282
+ /**
3283
+ * Get statistics
3284
+ */
3285
+ getStats(params?: Pick<AuditQueryParams, 'startTime' | 'endTime'>): AuditStats;
3286
+ /**
3287
+ * Clear all events
3288
+ */
3289
+ clear(): void;
3290
+ /**
3291
+ * Get event count
3292
+ */
3293
+ get size(): number;
3294
+ }
3295
+
3296
+ /**
3297
+ * Audit Logger
3298
+ *
3299
+ * Manages audit event logging with buffering and filtering.
3300
+ *
3301
+ * @module @kysera/rls/audit/logger
3302
+ */
3303
+
3304
+ /**
3305
+ * Audit Logger
3306
+ *
3307
+ * Manages RLS audit event logging with buffering, filtering, and sampling.
3308
+ *
3309
+ * @example
3310
+ * ```typescript
3311
+ * const logger = new AuditLogger({
3312
+ * adapter: new DatabaseAuditAdapter(db),
3313
+ * bufferSize: 50,
3314
+ * flushInterval: 5000,
3315
+ * defaults: {
3316
+ * logAllowed: false,
3317
+ * logDenied: true,
3318
+ * logFilters: false
3319
+ * },
3320
+ * tables: {
3321
+ * sensitive_data: {
3322
+ * logAllowed: true,
3323
+ * includeContext: ['requestId', 'ipAddress']
3324
+ * }
3325
+ * }
3326
+ * });
3327
+ *
3328
+ * // Log an event
3329
+ * await logger.logDecision('update', 'posts', 'allow', 'ownership-allow');
3330
+ *
3331
+ * // Ensure all events are flushed
3332
+ * await logger.flush();
3333
+ * ```
3334
+ */
3335
+ declare class AuditLogger {
3336
+ private adapter;
3337
+ private config;
3338
+ private buffer;
3339
+ private flushTimer;
3340
+ private isShuttingDown;
3341
+ constructor(config: AuditConfig);
3342
+ /**
3343
+ * Log a policy decision
3344
+ *
3345
+ * @param operation - Database operation
3346
+ * @param table - Table name
3347
+ * @param decision - Decision result
3348
+ * @param policyName - Name of the policy
3349
+ * @param options - Additional options
3350
+ */
3351
+ logDecision(operation: Operation, table: string, decision: AuditDecision, policyName?: string, options?: {
3352
+ reason?: string;
3353
+ rowIds?: (string | number)[];
3354
+ queryHash?: string;
3355
+ durationMs?: number;
3356
+ context?: Record<string, unknown>;
3357
+ }): Promise<void>;
3358
+ /**
3359
+ * Log an allow decision
3360
+ */
3361
+ logAllow(operation: Operation, table: string, policyName?: string, options?: {
3362
+ reason?: string;
3363
+ rowIds?: (string | number)[];
3364
+ context?: Record<string, unknown>;
3365
+ }): Promise<void>;
3366
+ /**
3367
+ * Log a deny decision
3368
+ */
3369
+ logDeny(operation: Operation, table: string, policyName?: string, options?: {
3370
+ reason?: string;
3371
+ rowIds?: (string | number)[];
3372
+ context?: Record<string, unknown>;
3373
+ }): Promise<void>;
3374
+ /**
3375
+ * Log a filter application
3376
+ */
3377
+ logFilter(table: string, policyName?: string, options?: {
3378
+ context?: Record<string, unknown>;
3379
+ }): Promise<void>;
3380
+ /**
3381
+ * Flush buffered events
3382
+ */
3383
+ flush(): Promise<void>;
3384
+ /**
3385
+ * Close the logger
3386
+ */
3387
+ close(): Promise<void>;
3388
+ /**
3389
+ * Get buffer size
3390
+ */
3391
+ get bufferSize(): number;
3392
+ /**
3393
+ * Check if logger is enabled
3394
+ */
3395
+ get enabled(): boolean;
3396
+ /**
3397
+ * Enable or disable logging
3398
+ */
3399
+ setEnabled(enabled: boolean): void;
3400
+ /**
3401
+ * Get table-specific config with defaults
3402
+ */
3403
+ private getTableConfig;
3404
+ /**
3405
+ * Check if decision should be logged
3406
+ */
3407
+ private shouldLog;
3408
+ /**
3409
+ * Build audit event
3410
+ */
3411
+ private buildEvent;
3412
+ /**
3413
+ * Build context object with filtering
3414
+ */
3415
+ private buildContext;
3416
+ /**
3417
+ * Log event to buffer or directly
3418
+ */
3419
+ private logEvent;
3420
+ /**
3421
+ * Start the flush timer
3422
+ */
3423
+ private startFlushTimer;
3424
+ }
3425
+ /**
3426
+ * Create an audit logger
3427
+ */
3428
+ declare function createAuditLogger(config: AuditConfig): AuditLogger;
3429
+
3430
+ /**
3431
+ * Policy Testing Utilities
3432
+ *
3433
+ * Provides tools for unit testing RLS policies without a database.
3434
+ *
3435
+ * @module @kysera/rls/testing
3436
+ */
3437
+
3438
+ /**
3439
+ * Result of policy evaluation
3440
+ */
3441
+ interface PolicyEvaluationResult {
3442
+ /**
3443
+ * Whether the operation is allowed
3444
+ */
3445
+ allowed: boolean;
3446
+ /**
3447
+ * Name of the policy that made the decision
3448
+ */
3449
+ policyName?: string;
3450
+ /**
3451
+ * Type of decision
3452
+ */
3453
+ decisionType: 'allow' | 'deny' | 'default';
3454
+ /**
3455
+ * Reason for the decision
3456
+ */
3457
+ reason?: string;
3458
+ /**
3459
+ * All policies that were evaluated
3460
+ */
3461
+ evaluatedPolicies: {
3462
+ name: string;
3463
+ type: 'allow' | 'deny' | 'validate';
3464
+ result: boolean;
3465
+ }[];
3466
+ }
3467
+ /**
3468
+ * Result of filter evaluation
3469
+ */
3470
+ interface FilterEvaluationResult {
3471
+ /**
3472
+ * Generated filter conditions
3473
+ */
3474
+ conditions: Record<string, unknown>;
3475
+ /**
3476
+ * Names of all filters applied
3477
+ */
3478
+ appliedFilters: string[];
3479
+ }
3480
+ /**
3481
+ * Test context for policy evaluation
3482
+ */
3483
+ interface TestContext<TRow = Record<string, unknown>> {
3484
+ /**
3485
+ * Auth context
3486
+ */
3487
+ auth: RLSAuthContext;
3488
+ /**
3489
+ * Row data (for read/update/delete operations)
3490
+ */
3491
+ row?: TRow;
3492
+ /**
3493
+ * Mutation data (for create/update operations)
3494
+ */
3495
+ data?: Record<string, unknown>;
3496
+ /**
3497
+ * Additional metadata
3498
+ */
3499
+ meta?: Record<string, unknown>;
3500
+ }
3501
+ /**
3502
+ * Policy Tester
3503
+ *
3504
+ * Test RLS policies without a database connection.
3505
+ *
3506
+ * @example
3507
+ * ```typescript
3508
+ * const tester = createPolicyTester(rlsSchema);
3509
+ *
3510
+ * describe('Post RLS Policies', () => {
3511
+ * it('should allow owner to update their post', async () => {
3512
+ * const result = await tester.evaluate('posts', 'update', {
3513
+ * auth: { userId: 'user-1', roles: ['user'] },
3514
+ * row: { id: 'post-1', author_id: 'user-1', status: 'draft' }
3515
+ * });
3516
+ *
3517
+ * expect(result.allowed).toBe(true);
3518
+ * });
3519
+ *
3520
+ * it('should deny non-owner update', async () => {
3521
+ * const result = await tester.evaluate('posts', 'update', {
3522
+ * auth: { userId: 'user-2', roles: ['user'] },
3523
+ * row: { id: 'post-1', author_id: 'user-1', status: 'draft' }
3524
+ * });
3525
+ *
3526
+ * expect(result.allowed).toBe(false);
3527
+ * expect(result.reason).toContain('not owner');
3528
+ * });
3529
+ *
3530
+ * it('should apply filters correctly', async () => {
3531
+ * const filters = await tester.getFilters('posts', 'read', {
3532
+ * auth: { userId: 'user-1', tenantId: 'tenant-1', roles: [] }
3533
+ * });
3534
+ *
3535
+ * expect(filters.conditions).toEqual({
3536
+ * tenant_id: 'tenant-1',
3537
+ * deleted_at: null
3538
+ * });
3539
+ * });
3540
+ * });
3541
+ * ```
3542
+ */
3543
+ declare class PolicyTester<DB = unknown> {
3544
+ private registry;
3545
+ constructor(schema: RLSSchema<DB>);
3546
+ /**
3547
+ * Evaluate policies for an operation
3548
+ *
3549
+ * @param table - Table name
3550
+ * @param operation - Operation to test
3551
+ * @param context - Test context
3552
+ * @returns Evaluation result
3553
+ */
3554
+ evaluate(table: string, operation: Operation, context: TestContext): Promise<PolicyEvaluationResult>;
3555
+ /**
3556
+ * Get filter conditions for read operations
3557
+ *
3558
+ * @param table - Table name
3559
+ * @param operation - Must be 'read'
3560
+ * @param context - Test context
3561
+ * @returns Filter conditions
3562
+ */
3563
+ getFilters(table: string, _operation: 'read', context: Pick<TestContext, 'auth' | 'meta'>): FilterEvaluationResult;
3564
+ /**
3565
+ * Test if a specific policy allows the operation
3566
+ *
3567
+ * @param table - Table name
3568
+ * @param policyName - Name of the policy to test
3569
+ * @param context - Test context
3570
+ * @returns True if policy allows
3571
+ */
3572
+ testPolicy(table: string, policyName: string, context: TestContext): Promise<{
3573
+ found: boolean;
3574
+ result?: boolean;
3575
+ }>;
3576
+ /**
3577
+ * List all policies for a table
3578
+ */
3579
+ listPolicies(table: string): {
3580
+ allows: string[];
3581
+ denies: string[];
3582
+ filters: string[];
3583
+ validates: string[];
3584
+ };
3585
+ /**
3586
+ * Get all registered tables
3587
+ */
3588
+ getTables(): string[];
3589
+ /**
3590
+ * Evaluate a single policy
3591
+ */
3592
+ private evaluatePolicy;
3593
+ }
3594
+ /**
3595
+ * Create a policy tester
3596
+ *
3597
+ * @param schema - RLS schema to test
3598
+ * @returns PolicyTester instance
3599
+ */
3600
+ declare function createPolicyTester<DB = unknown>(schema: RLSSchema<DB>): PolicyTester<DB>;
3601
+ /**
3602
+ * Create a test auth context
3603
+ *
3604
+ * @param overrides - Values to override
3605
+ * @returns RLSAuthContext for testing
3606
+ */
3607
+ declare function createTestAuthContext(overrides: Partial<RLSAuthContext> & {
3608
+ userId: string | number;
3609
+ }): RLSAuthContext;
3610
+ /**
3611
+ * Create a test row
3612
+ *
3613
+ * @param data - Row data
3614
+ * @returns Row object
3615
+ */
3616
+ declare function createTestRow<T extends Record<string, unknown>>(data: T): T;
3617
+ /**
3618
+ * Assertion helpers for policy testing
3619
+ */
3620
+ declare const policyAssertions: {
3621
+ /**
3622
+ * Assert that the result is allowed
3623
+ */
3624
+ assertAllowed(result: PolicyEvaluationResult, message?: string): void;
3625
+ /**
3626
+ * Assert that the result is denied
3627
+ */
3628
+ assertDenied(result: PolicyEvaluationResult, message?: string): void;
3629
+ /**
3630
+ * Assert that a specific policy made the decision
3631
+ */
3632
+ assertPolicyUsed(result: PolicyEvaluationResult, policyName: string, message?: string): void;
3633
+ /**
3634
+ * Assert that filters include expected conditions
3635
+ */
3636
+ assertFiltersInclude(result: FilterEvaluationResult, expected: Record<string, unknown>, message?: string): void;
3637
+ };
3638
+
3639
+ export { type AuditConfig, type AuditDecision, AuditLogger, type AuditQueryParams, type AuditStats, type BasePolicyDefinition, type BaseResolverContext, type CommonResolvedData, type CompiledFieldAccess, CompiledFilterPolicy, CompiledPolicy, type CompiledReBAcPolicy, type CompiledRelationshipPath, type CompiledTableFieldAccess, type ComposableRLSSchema, type ComposableTableConfig, type CompositeResolvedData, ConditionalPolicyDefinition, ConsoleAuditAdapter, type ConsoleAuditAdapterOptions, type ContextResolver, type CreateRLSContextOptions, type EnhancedRLSAuthContext, type EnhancedRLSContext, type FieldAccessCondition, type FieldAccessConfig, type FieldAccessOptions, FieldAccessProcessor, FieldAccessRegistry, type FieldAccessResult, type FieldAccessSchema, type FieldOperation, FilterCondition, type FilterEvaluationResult, type HierarchyResolvedData, InMemoryAuditAdapter, InMemoryCacheProvider, type MaskedRow, Operation, type OrganizationResolvedData, type OwnershipConfig, PolicyActivationCondition, PolicyCondition, PolicyDefinition, PolicyEvaluationContext, type PolicyEvaluationResult, PolicyHints, type PolicyOptions, PolicyRegistry, PolicyTester, type RLSAuditAdapter, type RLSAuditEvent, RLSAuthContext, RLSContext, RLSContextError, RLSContextValidationError, RLSError, type RLSErrorCode, RLSErrorCodes, type RLSPluginOptions, RLSPluginOptionsSchema, RLSPolicyEvaluationError, RLSPolicyViolation, RLSRequestContext, RLSSchema, RLSSchemaError, type ReBAcPolicyDefinition, type ReBAcQueryOptions, ReBAcRegistry, type ReBAcSchema, type ReBAcSubquery, ReBAcTransformer, type RelationshipCondition, type RelationshipPath, type RelationshipStep, type ResolvedData, type ResolvedInheritance, type ResolverCacheProvider, ResolverManager, type ResolverManagerOptions, type ReusablePolicy, type ReusablePolicyConfig, type SoftDeleteConfig, type StatusAccessConfig, type TableAuditConfig, type TableFieldAccessConfig, TableRLSConfig, type TableReBAcConfig, type TenantIsolationConfig, type TenantResolvedData, type TestContext, allow, allowRelation, composePolicies, createAdminPolicy, createAuditLogger, createEvaluationContext, createFieldAccessProcessor, createFieldAccessRegistry, createOwnershipPolicy, createPolicyTester, createRLSContext, createReBAcRegistry, createReBAcTransformer, createResolver, createResolverManager, createSoftDeletePolicy, createStatusAccessPolicy, createTenantIsolationPolicy, createTestAuthContext, createTestRow, deepMerge, defineAllowPolicy, defineCombinedPolicy, defineDenyPolicy, defineFilterPolicy, definePolicy, defineRLSSchema, defineValidatePolicy, deny, denyRelation, extendPolicy, filter, hashString, isAsyncFunction, maskedField, mergeRLSSchemas, neverAccessible, normalizeOperations, orgMembershipPath, overridePolicy, ownerOnly, ownerOrRoles, policyAssertions, publicReadRestrictedWrite, readOnly, rlsContext, rlsPlugin, rolesOnly, safeEvaluate, shopOrgMembershipPath, teamHierarchyPath, validate, whenCondition, whenEnvironment, whenFeature, whenTimeRange, withRLSContext, withRLSContextAsync };