@kysera/rls 0.7.2 → 0.7.4

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.
@@ -1,5 +1,5 @@
1
1
  import { Kysely } from 'kysely';
2
- import { R as RLSSchema } from '../types-6eCXh_Jd.js';
2
+ import { R as RLSSchema } from '../types-Dowjd6zG.js';
3
3
 
4
4
  /**
5
5
  * Options for PostgreSQL RLS generation
@@ -6,11 +6,7 @@ var PostgresRLSGenerator = class {
6
6
  * Generate all PostgreSQL RLS statements from schema
7
7
  */
8
8
  generateStatements(schema, options = {}) {
9
- const {
10
- force = true,
11
- schemaName = "public",
12
- policyPrefix = "rls"
13
- } = options;
9
+ const { force = true, schemaName = "public", policyPrefix = "rls" } = options;
14
10
  const statements = [];
15
11
  for (const [table, config] of Object.entries(schema)) {
16
12
  if (!config) continue;
@@ -41,10 +37,7 @@ var PostgresRLSGenerator = class {
41
37
  if (!policy.using && !policy.withCheck) {
42
38
  return null;
43
39
  }
44
- const parts = [
45
- `CREATE POLICY "${name}"`,
46
- `ON ${table}`
47
- ];
40
+ const parts = [`CREATE POLICY "${name}"`, `ON ${table}`];
48
41
  if (policy.type === "deny") {
49
42
  parts.push("AS RESTRICTIVE");
50
43
  } else {
@@ -192,11 +185,7 @@ var RLSMigrationGenerator = class {
192
185
  * Generate migration file content
193
186
  */
194
187
  generateMigration(schema, options = {}) {
195
- const {
196
- name = "rls_policies",
197
- includeContextFunctions = true,
198
- ...generatorOptions
199
- } = options;
188
+ const { name = "rls_policies", includeContextFunctions = true, ...generatorOptions } = options;
200
189
  const upStatements = this.generator.generateStatements(schema, generatorOptions);
201
190
  const downStatements = this.generator.generateDropStatements(schema, generatorOptions);
202
191
  const contextFunctions = includeContextFunctions ? this.generator.generateContextFunctions() : "";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/native/postgres.ts","../../src/native/migration.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Kysely } from 'kysely';\nimport { sql } from 'kysely';\nimport type { RLSSchema, TableRLSConfig, PolicyDefinition, Operation } from '../policy/types.js';\n\n/**\n * Options for PostgreSQL RLS generation\n */\nexport interface PostgresRLSOptions {\n /** Force RLS on table owners */\n force?: boolean;\n /** Schema name (default: public) */\n schemaName?: string;\n /** Prefix for generated policy names */\n policyPrefix?: string;\n}\n\n/**\n * PostgreSQL RLS Generator\n * Generates native PostgreSQL RLS statements from Kysera RLS schema\n */\nexport class PostgresRLSGenerator {\n /**\n * Generate all PostgreSQL RLS statements from schema\n */\n generateStatements<DB>(\n schema: RLSSchema<DB>,\n options: PostgresRLSOptions = {}\n ): string[] {\n const {\n force = true,\n schemaName = 'public',\n policyPrefix = 'rls',\n } = options;\n\n const statements: string[] = [];\n\n for (const [table, config] of Object.entries(schema)) {\n if (!config) continue;\n\n const qualifiedTable = `${schemaName}.${table}`;\n const tableConfig = config as TableRLSConfig;\n\n // Enable RLS on table\n statements.push(`ALTER TABLE ${qualifiedTable} ENABLE ROW LEVEL SECURITY;`);\n\n if (force) {\n statements.push(`ALTER TABLE ${qualifiedTable} FORCE ROW LEVEL SECURITY;`);\n }\n\n // Generate policies\n let policyIndex = 0;\n for (const policy of tableConfig.policies) {\n const policyName = policy.name ?? `${policyPrefix}_${table}_${policy.type}_${policyIndex++}`;\n const policySQL = this.generatePolicy(qualifiedTable, policyName, policy);\n if (policySQL) {\n statements.push(policySQL);\n }\n }\n }\n\n return statements;\n }\n\n /**\n * Generate a single policy statement\n */\n private generatePolicy(\n table: string,\n name: string,\n policy: PolicyDefinition\n ): string | null {\n // Skip filter policies (they're ORM-only)\n if (policy.type === 'filter' || policy.type === 'validate') {\n return null;\n }\n\n // Need USING or WITH CHECK clause for native RLS\n if (!policy.using && !policy.withCheck) {\n return null;\n }\n\n const parts: string[] = [\n `CREATE POLICY \"${name}\"`,\n `ON ${table}`,\n ];\n\n // Policy type\n if (policy.type === 'deny') {\n parts.push('AS RESTRICTIVE');\n } else {\n parts.push('AS PERMISSIVE');\n }\n\n // Target role\n parts.push(`TO ${policy.role ?? 'public'}`);\n\n // Operation\n parts.push(`FOR ${this.mapOperation(policy.operation)}`);\n\n // USING clause\n if (policy.using) {\n parts.push(`USING (${policy.using})`);\n }\n\n // WITH CHECK clause\n if (policy.withCheck) {\n parts.push(`WITH CHECK (${policy.withCheck})`);\n }\n\n return parts.join('\\n ') + ';';\n }\n\n /**\n * Map Kysera operation to PostgreSQL operation\n */\n private mapOperation(operation: Operation | Operation[]): string {\n if (Array.isArray(operation)) {\n if (operation.length === 0) {\n return 'ALL';\n }\n if (operation.length === 4 || operation.includes('all')) {\n return 'ALL';\n }\n // PostgreSQL doesn't support multiple operations in one policy\n // Return first operation\n return this.mapSingleOperation(operation[0]!);\n }\n return this.mapSingleOperation(operation);\n }\n\n /**\n * Map single operation\n */\n private mapSingleOperation(op: Operation): string {\n switch (op) {\n case 'read': return 'SELECT';\n case 'create': return 'INSERT';\n case 'update': return 'UPDATE';\n case 'delete': return 'DELETE';\n case 'all': return 'ALL';\n default: return 'ALL';\n }\n }\n\n /**\n * Generate context-setting functions for PostgreSQL\n * These functions should be STABLE for optimal performance\n */\n generateContextFunctions(): string {\n return `\n-- RLS Context Functions (STABLE for query planner optimization)\n-- These functions read session variables set by the application\n\nCREATE OR REPLACE FUNCTION rls_current_user_id()\nRETURNS text\nLANGUAGE SQL STABLE\nAS $$ SELECT current_setting('app.user_id', true) $$;\n\nCREATE OR REPLACE FUNCTION rls_current_tenant_id()\nRETURNS uuid\nLANGUAGE SQL STABLE\nAS $$ SELECT NULLIF(current_setting('app.tenant_id', true), '')::uuid $$;\n\nCREATE OR REPLACE FUNCTION rls_current_roles()\nRETURNS text[]\nLANGUAGE SQL STABLE\nAS $$ SELECT string_to_array(COALESCE(current_setting('app.roles', true), ''), ',') $$;\n\nCREATE OR REPLACE FUNCTION rls_has_role(role_name text)\nRETURNS boolean\nLANGUAGE SQL STABLE\nAS $$ SELECT role_name = ANY(rls_current_roles()) $$;\n\nCREATE OR REPLACE FUNCTION rls_current_permissions()\nRETURNS text[]\nLANGUAGE SQL STABLE\nAS $$ SELECT string_to_array(COALESCE(current_setting('app.permissions', true), ''), ',') $$;\n\nCREATE OR REPLACE FUNCTION rls_has_permission(permission_name text)\nRETURNS boolean\nLANGUAGE SQL STABLE\nAS $$ SELECT permission_name = ANY(rls_current_permissions()) $$;\n\nCREATE OR REPLACE FUNCTION rls_is_system()\nRETURNS boolean\nLANGUAGE SQL STABLE\nAS $$ SELECT COALESCE(current_setting('app.is_system', true), 'false')::boolean $$;\n`;\n }\n\n /**\n * Generate DROP statements for cleaning up\n */\n generateDropStatements<DB>(\n schema: RLSSchema<DB>,\n options: PostgresRLSOptions = {}\n ): string[] {\n const { schemaName = 'public', policyPrefix = 'rls' } = options;\n const statements: string[] = [];\n\n for (const table of Object.keys(schema)) {\n const qualifiedTable = `${schemaName}.${table}`;\n\n // Drop all policies with prefix\n statements.push(\n `DO $$ BEGIN\n EXECUTE (\n SELECT string_agg('DROP POLICY IF EXISTS ' || quote_ident(policyname) || ' ON ${qualifiedTable};', E'\\\\n')\n FROM pg_policies\n WHERE tablename = '${table}'\n AND schemaname = '${schemaName}'\n AND policyname LIKE '${policyPrefix}_%'\n );\nEND $$;`\n );\n\n // Disable RLS\n statements.push(`ALTER TABLE ${qualifiedTable} DISABLE ROW LEVEL SECURITY;`);\n }\n\n return statements;\n }\n}\n\n/**\n * Sync RLS context to PostgreSQL session settings\n * Call this at the start of each request/transaction\n */\nexport async function syncContextToPostgres<DB>(\n db: Kysely<DB>,\n context: {\n userId: string | number;\n tenantId?: string | number;\n roles?: string[];\n permissions?: string[];\n isSystem?: boolean;\n }\n): Promise<void> {\n const { userId, tenantId, roles, permissions, isSystem } = context;\n\n await sql`\n SELECT\n set_config('app.user_id', ${String(userId)}, true),\n set_config('app.tenant_id', ${tenantId ? String(tenantId) : ''}, true),\n set_config('app.roles', ${(roles ?? []).join(',')}, true),\n set_config('app.permissions', ${(permissions ?? []).join(',')}, true),\n set_config('app.is_system', ${isSystem ? 'true' : 'false'}, true)\n `.execute(db);\n}\n\n/**\n * Clear RLS context from PostgreSQL session\n */\nexport async function clearPostgresContext<DB>(db: Kysely<DB>): Promise<void> {\n await sql`\n SELECT\n set_config('app.user_id', '', true),\n set_config('app.tenant_id', '', true),\n set_config('app.roles', '', true),\n set_config('app.permissions', '', true),\n set_config('app.is_system', 'false', true)\n `.execute(db);\n}\n","import type { RLSSchema } from '../policy/types.js';\nimport { PostgresRLSGenerator, type PostgresRLSOptions } from './postgres.js';\n\n/**\n * Options for migration generation\n */\nexport interface MigrationOptions extends PostgresRLSOptions {\n /** Migration name */\n name?: string;\n /** Include context functions in migration */\n includeContextFunctions?: boolean;\n}\n\n/**\n * RLS Migration Generator\n * Generates Kysely migration files for RLS policies\n */\nexport class RLSMigrationGenerator {\n private generator = new PostgresRLSGenerator();\n\n /**\n * Generate migration file content\n */\n generateMigration<DB>(\n schema: RLSSchema<DB>,\n options: MigrationOptions = {}\n ): string {\n const {\n name = 'rls_policies',\n includeContextFunctions = true,\n ...generatorOptions\n } = options;\n\n const upStatements = this.generator.generateStatements(schema, generatorOptions);\n const downStatements = this.generator.generateDropStatements(schema, generatorOptions);\n\n const contextFunctions = includeContextFunctions\n ? this.generator.generateContextFunctions()\n : '';\n\n return `import { Kysely, sql } from 'kysely';\n\n/**\n * Migration: ${name}\n * Generated by @kysera/rls\n *\n * This migration sets up Row-Level Security policies for the database.\n */\n\nexport async function up(db: Kysely<any>): Promise<void> {\n${includeContextFunctions ? ` // Create RLS context functions\n await sql.raw(\\`${this.escapeTemplate(contextFunctions)}\\`).execute(db);\n\n` : ''} // Enable RLS and create policies\n${upStatements.map(s => ` await sql.raw(\\`${this.escapeTemplate(s)}\\`).execute(db);`).join('\\n')}\n}\n\nexport async function down(db: Kysely<any>): Promise<void> {\n${downStatements.map(s => ` await sql.raw(\\`${this.escapeTemplate(s)}\\`).execute(db);`).join('\\n')}\n${includeContextFunctions ? `\n // Drop RLS context functions\n await sql.raw(\\`\n DROP FUNCTION IF EXISTS rls_current_user_id();\n DROP FUNCTION IF EXISTS rls_current_tenant_id();\n DROP FUNCTION IF EXISTS rls_current_roles();\n DROP FUNCTION IF EXISTS rls_has_role(text);\n DROP FUNCTION IF EXISTS rls_current_permissions();\n DROP FUNCTION IF EXISTS rls_has_permission(text);\n DROP FUNCTION IF EXISTS rls_is_system();\n \\`).execute(db);` : ''}\n}\n`;\n }\n\n /**\n * Escape template literal for embedding in string\n */\n private escapeTemplate(str: string): string {\n return str.replace(/`/g, '\\\\`').replace(/\\$/g, '\\\\$');\n }\n\n /**\n * Generate migration filename with timestamp\n */\n generateFilename(name: string = 'rls_policies'): string {\n const timestamp = new Date().toISOString()\n .replace(/[-:]/g, '')\n .replace('T', '_')\n .replace(/\\..+/, '');\n return `${timestamp}_${name}.ts`;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/native/postgres.ts","../../src/native/migration.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Kysely } from 'kysely'\nimport { sql } from 'kysely'\nimport type { RLSSchema, TableRLSConfig, PolicyDefinition, Operation } from '../policy/types.js'\n\n/**\n * Options for PostgreSQL RLS generation\n */\nexport interface PostgresRLSOptions {\n /** Force RLS on table owners */\n force?: boolean\n /** Schema name (default: public) */\n schemaName?: string\n /** Prefix for generated policy names */\n policyPrefix?: string\n}\n\n/**\n * PostgreSQL RLS Generator\n * Generates native PostgreSQL RLS statements from Kysera RLS schema\n */\nexport class PostgresRLSGenerator {\n /**\n * Generate all PostgreSQL RLS statements from schema\n */\n generateStatements<DB>(schema: RLSSchema<DB>, options: PostgresRLSOptions = {}): string[] {\n const { force = true, schemaName = 'public', policyPrefix = 'rls' } = options\n\n const statements: string[] = []\n\n for (const [table, config] of Object.entries(schema)) {\n if (!config) continue\n\n const qualifiedTable = `${schemaName}.${table}`\n const tableConfig = config as TableRLSConfig\n\n // Enable RLS on table\n statements.push(`ALTER TABLE ${qualifiedTable} ENABLE ROW LEVEL SECURITY;`)\n\n if (force) {\n statements.push(`ALTER TABLE ${qualifiedTable} FORCE ROW LEVEL SECURITY;`)\n }\n\n // Generate policies\n let policyIndex = 0\n for (const policy of tableConfig.policies) {\n const policyName = policy.name ?? `${policyPrefix}_${table}_${policy.type}_${policyIndex++}`\n const policySQL = this.generatePolicy(qualifiedTable, policyName, policy)\n if (policySQL) {\n statements.push(policySQL)\n }\n }\n }\n\n return statements\n }\n\n /**\n * Generate a single policy statement\n */\n private generatePolicy(table: string, name: string, policy: PolicyDefinition): string | null {\n // Skip filter policies (they're ORM-only)\n if (policy.type === 'filter' || policy.type === 'validate') {\n return null\n }\n\n // Need USING or WITH CHECK clause for native RLS\n if (!policy.using && !policy.withCheck) {\n return null\n }\n\n const parts: string[] = [`CREATE POLICY \"${name}\"`, `ON ${table}`]\n\n // Policy type\n if (policy.type === 'deny') {\n parts.push('AS RESTRICTIVE')\n } else {\n parts.push('AS PERMISSIVE')\n }\n\n // Target role\n parts.push(`TO ${policy.role ?? 'public'}`)\n\n // Operation\n parts.push(`FOR ${this.mapOperation(policy.operation)}`)\n\n // USING clause\n if (policy.using) {\n parts.push(`USING (${policy.using})`)\n }\n\n // WITH CHECK clause\n if (policy.withCheck) {\n parts.push(`WITH CHECK (${policy.withCheck})`)\n }\n\n return parts.join('\\n ') + ';'\n }\n\n /**\n * Map Kysera operation to PostgreSQL operation\n */\n private mapOperation(operation: Operation | Operation[]): string {\n if (Array.isArray(operation)) {\n if (operation.length === 0) {\n return 'ALL'\n }\n if (operation.length === 4 || operation.includes('all')) {\n return 'ALL'\n }\n // PostgreSQL doesn't support multiple operations in one policy\n // Return first operation\n return this.mapSingleOperation(operation[0]!)\n }\n return this.mapSingleOperation(operation)\n }\n\n /**\n * Map single operation\n */\n private mapSingleOperation(op: Operation): string {\n switch (op) {\n case 'read':\n return 'SELECT'\n case 'create':\n return 'INSERT'\n case 'update':\n return 'UPDATE'\n case 'delete':\n return 'DELETE'\n case 'all':\n return 'ALL'\n default:\n return 'ALL'\n }\n }\n\n /**\n * Generate context-setting functions for PostgreSQL\n * These functions should be STABLE for optimal performance\n */\n generateContextFunctions(): string {\n return `\n-- RLS Context Functions (STABLE for query planner optimization)\n-- These functions read session variables set by the application\n\nCREATE OR REPLACE FUNCTION rls_current_user_id()\nRETURNS text\nLANGUAGE SQL STABLE\nAS $$ SELECT current_setting('app.user_id', true) $$;\n\nCREATE OR REPLACE FUNCTION rls_current_tenant_id()\nRETURNS uuid\nLANGUAGE SQL STABLE\nAS $$ SELECT NULLIF(current_setting('app.tenant_id', true), '')::uuid $$;\n\nCREATE OR REPLACE FUNCTION rls_current_roles()\nRETURNS text[]\nLANGUAGE SQL STABLE\nAS $$ SELECT string_to_array(COALESCE(current_setting('app.roles', true), ''), ',') $$;\n\nCREATE OR REPLACE FUNCTION rls_has_role(role_name text)\nRETURNS boolean\nLANGUAGE SQL STABLE\nAS $$ SELECT role_name = ANY(rls_current_roles()) $$;\n\nCREATE OR REPLACE FUNCTION rls_current_permissions()\nRETURNS text[]\nLANGUAGE SQL STABLE\nAS $$ SELECT string_to_array(COALESCE(current_setting('app.permissions', true), ''), ',') $$;\n\nCREATE OR REPLACE FUNCTION rls_has_permission(permission_name text)\nRETURNS boolean\nLANGUAGE SQL STABLE\nAS $$ SELECT permission_name = ANY(rls_current_permissions()) $$;\n\nCREATE OR REPLACE FUNCTION rls_is_system()\nRETURNS boolean\nLANGUAGE SQL STABLE\nAS $$ SELECT COALESCE(current_setting('app.is_system', true), 'false')::boolean $$;\n`\n }\n\n /**\n * Generate DROP statements for cleaning up\n */\n generateDropStatements<DB>(schema: RLSSchema<DB>, options: PostgresRLSOptions = {}): string[] {\n const { schemaName = 'public', policyPrefix = 'rls' } = options\n const statements: string[] = []\n\n for (const table of Object.keys(schema)) {\n const qualifiedTable = `${schemaName}.${table}`\n\n // Drop all policies with prefix\n statements.push(\n `DO $$ BEGIN\n EXECUTE (\n SELECT string_agg('DROP POLICY IF EXISTS ' || quote_ident(policyname) || ' ON ${qualifiedTable};', E'\\\\n')\n FROM pg_policies\n WHERE tablename = '${table}'\n AND schemaname = '${schemaName}'\n AND policyname LIKE '${policyPrefix}_%'\n );\nEND $$;`\n )\n\n // Disable RLS\n statements.push(`ALTER TABLE ${qualifiedTable} DISABLE ROW LEVEL SECURITY;`)\n }\n\n return statements\n }\n}\n\n/**\n * Sync RLS context to PostgreSQL session settings\n * Call this at the start of each request/transaction\n */\nexport async function syncContextToPostgres<DB>(\n db: Kysely<DB>,\n context: {\n userId: string | number\n tenantId?: string | number\n roles?: string[]\n permissions?: string[]\n isSystem?: boolean\n }\n): Promise<void> {\n const { userId, tenantId, roles, permissions, isSystem } = context\n\n await sql`\n SELECT\n set_config('app.user_id', ${String(userId)}, true),\n set_config('app.tenant_id', ${tenantId ? String(tenantId) : ''}, true),\n set_config('app.roles', ${(roles ?? []).join(',')}, true),\n set_config('app.permissions', ${(permissions ?? []).join(',')}, true),\n set_config('app.is_system', ${isSystem ? 'true' : 'false'}, true)\n `.execute(db)\n}\n\n/**\n * Clear RLS context from PostgreSQL session\n */\nexport async function clearPostgresContext<DB>(db: Kysely<DB>): Promise<void> {\n await sql`\n SELECT\n set_config('app.user_id', '', true),\n set_config('app.tenant_id', '', true),\n set_config('app.roles', '', true),\n set_config('app.permissions', '', true),\n set_config('app.is_system', 'false', true)\n `.execute(db)\n}\n","import type { RLSSchema } from '../policy/types.js'\nimport { PostgresRLSGenerator, type PostgresRLSOptions } from './postgres.js'\n\n/**\n * Options for migration generation\n */\nexport interface MigrationOptions extends PostgresRLSOptions {\n /** Migration name */\n name?: string\n /** Include context functions in migration */\n includeContextFunctions?: boolean\n}\n\n/**\n * RLS Migration Generator\n * Generates Kysely migration files for RLS policies\n */\nexport class RLSMigrationGenerator {\n private generator = new PostgresRLSGenerator()\n\n /**\n * Generate migration file content\n */\n generateMigration<DB>(schema: RLSSchema<DB>, options: MigrationOptions = {}): string {\n const { name = 'rls_policies', includeContextFunctions = true, ...generatorOptions } = options\n\n const upStatements = this.generator.generateStatements(schema, generatorOptions)\n const downStatements = this.generator.generateDropStatements(schema, generatorOptions)\n\n const contextFunctions = includeContextFunctions\n ? this.generator.generateContextFunctions()\n : ''\n\n return `import { Kysely, sql } from 'kysely';\n\n/**\n * Migration: ${name}\n * Generated by @kysera/rls\n *\n * This migration sets up Row-Level Security policies for the database.\n */\n\nexport async function up(db: Kysely<any>): Promise<void> {\n${\n includeContextFunctions\n ? ` // Create RLS context functions\n await sql.raw(\\`${this.escapeTemplate(contextFunctions)}\\`).execute(db);\n\n`\n : ''\n} // Enable RLS and create policies\n${upStatements.map(s => ` await sql.raw(\\`${this.escapeTemplate(s)}\\`).execute(db);`).join('\\n')}\n}\n\nexport async function down(db: Kysely<any>): Promise<void> {\n${downStatements.map(s => ` await sql.raw(\\`${this.escapeTemplate(s)}\\`).execute(db);`).join('\\n')}\n${\n includeContextFunctions\n ? `\n // Drop RLS context functions\n await sql.raw(\\`\n DROP FUNCTION IF EXISTS rls_current_user_id();\n DROP FUNCTION IF EXISTS rls_current_tenant_id();\n DROP FUNCTION IF EXISTS rls_current_roles();\n DROP FUNCTION IF EXISTS rls_has_role(text);\n DROP FUNCTION IF EXISTS rls_current_permissions();\n DROP FUNCTION IF EXISTS rls_has_permission(text);\n DROP FUNCTION IF EXISTS rls_is_system();\n \\`).execute(db);`\n : ''\n}\n}\n`\n }\n\n /**\n * Escape template literal for embedding in string\n */\n private escapeTemplate(str: string): string {\n return str.replace(/`/g, '\\\\`').replace(/\\$/g, '\\\\$')\n }\n\n /**\n * Generate migration filename with timestamp\n */\n generateFilename(name = 'rls_policies'): string {\n const timestamp = new Date()\n .toISOString()\n .replace(/[-:]/g, '')\n .replace('T', '_')\n .replace(/\\..+/, '')\n return `${timestamp}_${name}.ts`\n }\n}\n"]}
@@ -199,7 +199,7 @@ interface RLSContext<TUser = unknown, TMeta = unknown> {
199
199
  * @typeParam TAuth - Custom user type for auth context
200
200
  * @typeParam TRow - Type of the database row being evaluated
201
201
  * @typeParam TData - Type of the data being inserted/updated
202
- * @typeParam TDB - Database schema type for Kysely
202
+ * @typeParam DB - Database schema type for Kysely
203
203
  *
204
204
  * @example
205
205
  * ```typescript
@@ -217,7 +217,7 @@ interface RLSContext<TUser = unknown, TMeta = unknown> {
217
217
  * };
218
218
  * ```
219
219
  */
220
- interface PolicyEvaluationContext<TAuth = unknown, TRow = unknown, TData = unknown, TDB = unknown> {
220
+ interface PolicyEvaluationContext<TAuth = unknown, TRow = unknown, TData = unknown, DB = unknown> {
221
221
  /**
222
222
  * Authentication context
223
223
  * Contains user identity and authorization information
@@ -245,7 +245,7 @@ interface PolicyEvaluationContext<TAuth = unknown, TRow = unknown, TData = unkno
245
245
  * Available for policies that need to perform additional queries
246
246
  * Use sparingly as it can impact performance
247
247
  */
248
- db?: Kysely<TDB>;
248
+ db?: Kysely<DB>;
249
249
  /**
250
250
  * Custom metadata (optional)
251
251
  * Can contain any additional context needed for policy evaluation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kysera/rls",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Row-Level Security plugin for Kysely - declarative policies, query transformation, native RLS support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,26 +20,35 @@
20
20
  "src"
21
21
  ],
22
22
  "peerDependencies": {
23
- "kysely": ">=0.28.8"
23
+ "kysely": ">=0.28.8",
24
+ "@kysera/executor": "0.7.4",
25
+ "@kysera/repository": "0.7.4"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "@kysera/executor": {
29
+ "optional": true
30
+ },
31
+ "@kysera/repository": {
32
+ "optional": true
33
+ }
24
34
  },
25
35
  "dependencies": {
26
- "zod": "^4.1.13",
27
- "@kysera/core": "0.7.2",
28
- "@kysera/repository": "0.7.2",
29
- "@kysera/executor": "0.7.2"
36
+ "@kysera/core": "0.7.4"
30
37
  },
31
38
  "devDependencies": {
32
39
  "@types/better-sqlite3": "^7.6.13",
33
- "@types/node": "^24.10.1",
34
- "@types/pg": "^8.15.6",
35
- "@vitest/coverage-v8": "^4.0.15",
40
+ "@types/node": "^25.0.3",
41
+ "@types/pg": "^8.16.0",
42
+ "@vitest/coverage-v8": "^4.0.16",
36
43
  "better-sqlite3": "^12.5.0",
37
44
  "kysely": "^0.28.9",
38
- "mysql2": "^3.15.2",
45
+ "mysql2": "^3.16.0",
39
46
  "pg": "^8.16.3",
40
47
  "tsup": "^8.5.1",
41
48
  "typescript": "^5.9.3",
42
- "vitest": "^4.0.15"
49
+ "vitest": "^4.0.16",
50
+ "@kysera/executor": "0.7.4",
51
+ "@kysera/repository": "0.7.4"
43
52
  },
44
53
  "keywords": [
45
54
  "kysely",
@@ -1,9 +1,9 @@
1
- export { rlsStorage } from './storage.js';
1
+ export { rlsStorage } from './storage.js'
2
2
  export {
3
3
  rlsContext,
4
4
  createRLSContext,
5
5
  withRLSContext,
6
6
  withRLSContextAsync,
7
- type CreateRLSContextOptions,
8
- } from './manager.js';
9
- export type { RLSContext, RLSAuthContext, RLSRequestContext } from './types.js';
7
+ type CreateRLSContextOptions
8
+ } from './manager.js'
9
+ export type { RLSContext, RLSAuthContext, RLSRequestContext } from './types.js'
@@ -1,14 +1,14 @@
1
- import { rlsStorage } from './storage.js';
2
- import type { RLSContext, RLSAuthContext, RLSRequestContext } from './types.js';
3
- import { RLSContextError, RLSContextValidationError } from '../errors.js';
1
+ import { rlsStorage } from './storage.js'
2
+ import type { RLSContext, RLSAuthContext, RLSRequestContext } from './types.js'
3
+ import { RLSContextError, RLSContextValidationError } from '../errors.js'
4
4
 
5
5
  /**
6
6
  * Options for creating RLS context
7
7
  */
8
8
  export interface CreateRLSContextOptions<TUser = unknown, TMeta = unknown> {
9
- auth: RLSAuthContext<TUser>;
10
- request?: Partial<RLSRequestContext>;
11
- meta?: TMeta;
9
+ auth: RLSAuthContext<TUser>
10
+ request?: Partial<RLSRequestContext>
11
+ meta?: TMeta
12
12
  }
13
13
 
14
14
  /**
@@ -17,28 +17,28 @@ export interface CreateRLSContextOptions<TUser = unknown, TMeta = unknown> {
17
17
  export function createRLSContext<TUser = unknown, TMeta = unknown>(
18
18
  options: CreateRLSContextOptions<TUser, TMeta>
19
19
  ): RLSContext<TUser, TMeta> {
20
- validateAuthContext(options.auth);
20
+ validateAuthContext(options.auth)
21
21
 
22
22
  const context: RLSContext<TUser, TMeta> = {
23
23
  auth: {
24
24
  ...options.auth,
25
- isSystem: options.auth.isSystem ?? false, // Default to false if not provided
25
+ isSystem: options.auth.isSystem ?? false // Default to false if not provided
26
26
  },
27
- timestamp: new Date(),
28
- };
27
+ timestamp: new Date()
28
+ }
29
29
 
30
30
  if (options.request) {
31
31
  context.request = {
32
32
  ...options.request,
33
- timestamp: options.request.timestamp ?? new Date(),
34
- } as RLSRequestContext;
33
+ timestamp: options.request.timestamp ?? new Date()
34
+ } as RLSRequestContext
35
35
  }
36
36
 
37
37
  if (options.meta !== undefined) {
38
- context.meta = options.meta;
38
+ context.meta = options.meta
39
39
  }
40
40
 
41
- return context;
41
+ return context
42
42
  }
43
43
 
44
44
  /**
@@ -46,11 +46,11 @@ export function createRLSContext<TUser = unknown, TMeta = unknown>(
46
46
  */
47
47
  function validateAuthContext(auth: RLSAuthContext): void {
48
48
  if (auth.userId === undefined || auth.userId === null) {
49
- throw new RLSContextValidationError('userId is required in auth context', 'userId');
49
+ throw new RLSContextValidationError('userId is required in auth context', 'userId')
50
50
  }
51
51
 
52
52
  if (!Array.isArray(auth.roles)) {
53
- throw new RLSContextValidationError('roles must be an array', 'roles');
53
+ throw new RLSContextValidationError('roles must be an array', 'roles')
54
54
  }
55
55
  }
56
56
 
@@ -63,14 +63,14 @@ class RLSContextManager {
63
63
  * Run a synchronous function within an RLS context
64
64
  */
65
65
  run<T>(context: RLSContext, fn: () => T): T {
66
- return rlsStorage.run(context, fn);
66
+ return rlsStorage.run(context, fn)
67
67
  }
68
68
 
69
69
  /**
70
70
  * Run an async function within an RLS context
71
71
  */
72
72
  async runAsync<T>(context: RLSContext, fn: () => Promise<T>): Promise<T> {
73
- return rlsStorage.run(context, fn);
73
+ return await rlsStorage.run(context, fn)
74
74
  }
75
75
 
76
76
  /**
@@ -78,25 +78,25 @@ class RLSContextManager {
78
78
  * @throws RLSContextError if no context is set
79
79
  */
80
80
  getContext(): RLSContext {
81
- const ctx = rlsStorage.getStore();
81
+ const ctx = rlsStorage.getStore()
82
82
  if (!ctx) {
83
- throw new RLSContextError();
83
+ throw new RLSContextError()
84
84
  }
85
- return ctx;
85
+ return ctx
86
86
  }
87
87
 
88
88
  /**
89
89
  * Get current RLS context or null if not set
90
90
  */
91
91
  getContextOrNull(): RLSContext | null {
92
- return rlsStorage.getStore() ?? null;
92
+ return rlsStorage.getStore() ?? null
93
93
  }
94
94
 
95
95
  /**
96
96
  * Check if running within RLS context
97
97
  */
98
98
  hasContext(): boolean {
99
- return rlsStorage.getStore() !== undefined;
99
+ return rlsStorage.getStore() !== undefined
100
100
  }
101
101
 
102
102
  /**
@@ -104,7 +104,7 @@ class RLSContextManager {
104
104
  * @throws RLSContextError if no context is set
105
105
  */
106
106
  getAuth(): RLSAuthContext {
107
- return this.getContext().auth;
107
+ return this.getContext().auth
108
108
  }
109
109
 
110
110
  /**
@@ -112,7 +112,7 @@ class RLSContextManager {
112
112
  * @throws RLSContextError if no context is set
113
113
  */
114
114
  getUserId(): string | number {
115
- return this.getAuth().userId;
115
+ return this.getAuth().userId
116
116
  }
117
117
 
118
118
  /**
@@ -120,76 +120,76 @@ class RLSContextManager {
120
120
  * @throws RLSContextError if no context is set
121
121
  */
122
122
  getTenantId(): string | number | undefined {
123
- return this.getAuth().tenantId;
123
+ return this.getAuth().tenantId
124
124
  }
125
125
 
126
126
  /**
127
127
  * Check if current user has a specific role
128
128
  */
129
129
  hasRole(role: string): boolean {
130
- const ctx = this.getContextOrNull();
131
- return ctx?.auth.roles.includes(role) ?? false;
130
+ const ctx = this.getContextOrNull()
131
+ return ctx?.auth.roles.includes(role) ?? false
132
132
  }
133
133
 
134
134
  /**
135
135
  * Check if current user has a specific permission
136
136
  */
137
137
  hasPermission(permission: string): boolean {
138
- const ctx = this.getContextOrNull();
139
- return ctx?.auth.permissions?.includes(permission) ?? false;
138
+ const ctx = this.getContextOrNull()
139
+ return ctx?.auth.permissions?.includes(permission) ?? false
140
140
  }
141
141
 
142
142
  /**
143
143
  * Check if current context is a system context (bypasses RLS)
144
144
  */
145
145
  isSystem(): boolean {
146
- const ctx = this.getContextOrNull();
147
- return ctx?.auth.isSystem ?? false;
146
+ const ctx = this.getContextOrNull()
147
+ return ctx?.auth.isSystem ?? false
148
148
  }
149
149
 
150
150
  /**
151
151
  * Create a system context for operations that should bypass RLS
152
152
  */
153
153
  asSystem<T>(fn: () => T): T {
154
- const currentCtx = this.getContextOrNull();
154
+ const currentCtx = this.getContextOrNull()
155
155
  if (!currentCtx) {
156
- throw new RLSContextError('Cannot create system context without existing context');
156
+ throw new RLSContextError('Cannot create system context without existing context')
157
157
  }
158
158
 
159
159
  const systemCtx: RLSContext = {
160
160
  ...currentCtx,
161
- auth: { ...currentCtx.auth, isSystem: true },
162
- };
161
+ auth: { ...currentCtx.auth, isSystem: true }
162
+ }
163
163
 
164
- return this.run(systemCtx, fn);
164
+ return this.run(systemCtx, fn)
165
165
  }
166
166
 
167
167
  /**
168
168
  * Create a system context for async operations
169
169
  */
170
170
  async asSystemAsync<T>(fn: () => Promise<T>): Promise<T> {
171
- const currentCtx = this.getContextOrNull();
171
+ const currentCtx = this.getContextOrNull()
172
172
  if (!currentCtx) {
173
- throw new RLSContextError('Cannot create system context without existing context');
173
+ throw new RLSContextError('Cannot create system context without existing context')
174
174
  }
175
175
 
176
176
  const systemCtx: RLSContext = {
177
177
  ...currentCtx,
178
- auth: { ...currentCtx.auth, isSystem: true },
179
- };
178
+ auth: { ...currentCtx.auth, isSystem: true }
179
+ }
180
180
 
181
- return this.runAsync(systemCtx, fn);
181
+ return await this.runAsync(systemCtx, fn)
182
182
  }
183
183
  }
184
184
 
185
185
  // Export singleton instance
186
- export const rlsContext = new RLSContextManager();
186
+ export const rlsContext = new RLSContextManager()
187
187
 
188
188
  /**
189
189
  * Convenience function to run code within RLS context
190
190
  */
191
191
  export function withRLSContext<T>(context: RLSContext, fn: () => T): T {
192
- return rlsContext.run(context, fn);
192
+ return rlsContext.run(context, fn)
193
193
  }
194
194
 
195
195
  /**
@@ -199,5 +199,5 @@ export async function withRLSContextAsync<T>(
199
199
  context: RLSContext,
200
200
  fn: () => Promise<T>
201
201
  ): Promise<T> {
202
- return rlsContext.runAsync(context, fn);
202
+ return await rlsContext.runAsync(context, fn)
203
203
  }
@@ -1,8 +1,8 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
- import type { RLSContext } from './types.js';
1
+ import { AsyncLocalStorage } from 'node:async_hooks'
2
+ import type { RLSContext } from './types.js'
3
3
 
4
4
  /**
5
5
  * AsyncLocalStorage instance for RLS context
6
6
  * Provides automatic context propagation across async boundaries
7
7
  */
8
- export const rlsStorage = new AsyncLocalStorage<RLSContext>();
8
+ export const rlsStorage = new AsyncLocalStorage<RLSContext>()
@@ -1,5 +1 @@
1
- export type {
2
- RLSContext,
3
- RLSAuthContext,
4
- RLSRequestContext,
5
- } from '../policy/types.js';
1
+ export type { RLSContext, RLSAuthContext, RLSRequestContext } from '../policy/types.js'