Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@kysera/executor

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@kysera/executor - npm Package Compare versions

Comparing version
0.8.4
to
0.8.5
+23
-14
dist/index.d.ts

@@ -407,9 +407,12 @@ import { Kysely, Transaction } from 'kysely';

* @example
* // Multi-tenant with dynamic schema resolution
* // Multi-tenant with withSchema()
* const executor = await createExecutor(db, [
* schemaPlugin({
* resolveSchema: (ctx) => ctx.metadata.tenantSchema as string,
* allowedSchemas: ['tenant_a', 'tenant_b', 'tenant_c']
* defaultSchema: 'public',
* allowedSchemas: ['public', 'tenant_a', 'tenant_b', 'tenant_c']
* })
* ])
* // Use withSchema() to set tenant schema per-request
* const tenantDb = executor.withSchema(`tenant_${tenantId}`)
* const users = await tenantDb.selectFrom('users').selectAll().execute()
*/

@@ -432,15 +435,18 @@

*
* @param context - Query builder context with operation, table, and metadata
* The resolver receives `context.schema` which is set when `withSchema()` is called.
* This allows you to add validation or transformation logic on top of withSchema().
*
* @param context - Query builder context with operation, table, schema, and metadata
* @returns Schema name or undefined to use default
*
* @example
* // Resolve schema from metadata
* resolveSchema: (ctx) => ctx.metadata.tenantSchema as string
* // Use schema set by withSchema() with fallback
* resolveSchema: (ctx) => ctx.schema ?? 'public'
*
* @example
* // Schema based on table name
* // Schema based on table name (auto-routing)
* resolveSchema: (ctx) => {
* if (ctx.table.startsWith('auth_')) return 'auth'
* if (ctx.table.startsWith('admin_')) return 'admin'
* return undefined // use default
* return ctx.schema // use withSchema() value or default
* }

@@ -505,13 +511,16 @@ */

* @example
* // Multi-tenant application
* // Multi-tenant application with schema validation
* const executor = await createExecutor(db, [
* schemaPlugin({
* defaultSchema: 'public',
* resolveSchema: (ctx) => {
* const tenantId = ctx.metadata.tenantId as string
* return tenantId ? `tenant_${tenantId}` : undefined
* },
* allowedSchemas: ['public', 'tenant_a', 'tenant_b']
* allowedSchemas: ['public', 'tenant_a', 'tenant_b', 'tenant_c'],
* strictValidation: true // throws if schema not in whitelist
* })
* ])
*
* // Per-request: use withSchema() to set tenant context
* app.use((req, res, next) => {
* req.db = executor.withSchema(`tenant_${req.tenantId}`)
* next()
* })
*/

@@ -518,0 +527,0 @@ declare function schemaPlugin(options?: SchemaPluginOptions): Plugin;

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/types.ts","../src/executor.ts","../src/plugins/schema-plugin.ts"],"names":["isRepositoryLike","obj","INTERCEPTED_METHODS","INTERCEPTED_METHODS_SET","METHOD_TO_OPERATION","PluginValidationError","message","type","details","validatePlugins","plugins","names","plugin","dep","conflict","detectCircularDependencies","map","p","visited","stack","inStack","path","frame","start","cycle","depPlugin","resolvePluginOrder","inDegree","dependents","result","available","insertSorted","arr","priority","left","right","mid","midPriority","a","b","pA","pB","current","deps","newDegree","createInterceptedMethod","db","method","interceptors","currentSchema","operation","table","originalMethod","qb","context","MARKER_PROPS","MAX_CACHE_SIZE","UNDEFINED_SENTINEL","LRUCache","maxSize","key","value","wrappedValue","firstKey","createProxy","allPlugins","methodCache","interceptedCache","cachedTransactionWrapper","schemaProxyCache","handler","target","prop","receiver","intercepted","schema","cachedSchemaProxy","schemaDb","newProxy","withWrapper","name","fn","wrappedFn","innerDb","withRecursiveWrapper","trx","wrappedTrx","bound","createExecutor","config","enabled","sorted","error","createExecutorSync","isKyseraExecutor","getPlugins","executor","wrapTransaction","applyPlugins","getRawDb","destroyExecutor","i","SchemaValidationError","allowedSchemas","schemaPlugin","options","defaultSchema","resolveSchema","validateSchema","strictValidation","allowedSet","_db","allowedList","getResolvedSchema"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @kysera/executor - Unified Execution Layer Types\n * @module @kysera/executor\n */\n\nimport type { Kysely, Transaction } from 'kysely'\n\n/**\n * Query builder context passed to plugin interceptors\n */\nexport interface QueryBuilderContext {\n /** Type of operation */\n readonly operation: 'select' | 'insert' | 'update' | 'delete' | 'replace' | 'merge'\n /** Table name */\n readonly table: string\n /**\n * Current schema context (if withSchema was called).\n * undefined means default schema is being used.\n */\n readonly schema?: string\n /** Additional metadata */\n readonly metadata: Record<string, unknown>\n}\n\n/**\n * Plugin interface - unified for both Repository and DAL patterns\n *\n * Plugins can:\n * - Intercept queries before execution (interceptQuery)\n * - Extend repositories with additional methods (extendRepository)\n * - Initialize resources on startup (onInit)\n * - Cleanup resources on shutdown (onDestroy)\n */\nexport interface Plugin {\n /** Unique plugin name */\n readonly name: string\n /** Plugin version */\n readonly version: string\n /** Plugin dependencies (must be loaded first) */\n readonly dependencies?: readonly string[]\n /** Higher priority = runs first (default: 0) */\n readonly priority?: number\n /** Plugins that conflict with this one */\n readonly conflictsWith?: readonly string[]\n\n /**\n * Lifecycle: Called once when plugin is initialized\n * @param db - Kysely database instance (not the executor wrapper)\n */\n onInit?<DB>(db: Kysely<DB>): Promise<void> | void\n\n /**\n * Lifecycle: Called when executor is being destroyed\n * Use for cleanup, closing connections, clearing timers, etc.\n */\n onDestroy?(): Promise<void> | void\n\n /**\n * Query interception: Modify query builder before execution\n * Works in both Repository and DAL patterns via @kysera/executor\n *\n * @typeParam QB - Query builder type (intentionally unconstrained)\n *\n * **Type Safety Note:**\n * QB is intentionally unconstrained (not constrained to any base type) because:\n *\n * 1. **Kysely's query builders lack a shared interface**: SelectQueryBuilder,\n * InsertQueryBuilder, UpdateQueryBuilder, DeleteQueryBuilder, and MergeQueryBuilder\n * don't share a common base interface that includes query modification methods\n * like where(), and(), etc.\n *\n * 2. **Each builder has unique generic parameters**: Even if they implemented\n * Compilable<unknown>, their type parameters differ (DB, TB, O, UT, etc.),\n * making it impossible to express a shared constraint.\n *\n * 3. **Type inference requirement**: The QB type must be preserved exactly as-is\n * through the plugin chain. Any constraint would break this preservation.\n *\n * **Plugin Implementation Pattern:**\n * Plugins must handle type safety internally by:\n * 1. Checking the operation type from context.operation\n * 2. Casting to the appropriate specific builder type\n * 3. Using type assertions (documented in plugin code)\n *\n * @example\n * ```typescript\n * interceptQuery<QB>(qb: QB, context: QueryBuilderContext): QB {\n * if (context.operation === 'select') {\n * // Cast to SelectQueryBuilder for type-safe WHERE clause\n * type GenericSelect = SelectQueryBuilder<Record<string, unknown>, string, Record<string, unknown>>;\n * return (qb as unknown as GenericSelect)\n * .where('deleted_at', 'is', null) as QB;\n * }\n * return qb;\n * }\n * ```\n */\n interceptQuery?<QB>(qb: QB, context: QueryBuilderContext): QB\n\n /**\n * Repository extensions: Add methods to repositories (Repository pattern only)\n */\n extendRepository?<T extends object>(repo: T): T\n}\n\n/**\n * Marker interface for KyseraExecutor\n */\nexport interface KyseraExecutorMarker<DB = unknown> {\n readonly __kysera: true\n readonly __plugins: readonly Plugin[]\n /** Raw Kysely instance bypassing plugin interceptors (for internal plugin use) */\n readonly __rawDb: Kysely<DB>\n /**\n * Current schema context (if withSchema was called).\n * undefined means default schema is being used.\n */\n readonly __schema?: string\n}\n\n/**\n * Plugin-aware Kysely wrapper type\n * Extends Kysely with plugin interception capabilities\n */\nexport type KyseraExecutor<DB> = Kysely<DB> & KyseraExecutorMarker<DB>\n\n/**\n * Plugin-aware Transaction wrapper type\n */\nexport type KyseraTransaction<DB> = Transaction<DB> & KyseraExecutorMarker<DB>\n\n/**\n * Union type for any Kysera executor (database or transaction)\n */\nexport type AnyKyseraExecutor<DB> = KyseraExecutor<DB> | KyseraTransaction<DB>\n\n/**\n * Configuration for executor creation\n */\nexport interface ExecutorConfig {\n /** Enable/disable plugin interception at runtime */\n readonly enabled?: boolean\n}\n\n/**\n * Plugin validation error details\n */\nexport interface PluginValidationDetails {\n readonly pluginName: string\n readonly missingDependency?: string\n readonly conflictingPlugin?: string\n readonly cycle?: readonly string[]\n}\n\n/**\n * Plugin validation error types\n */\nexport type PluginValidationErrorType =\n | 'DUPLICATE_NAME'\n | 'MISSING_DEPENDENCY'\n | 'CONFLICT'\n | 'CIRCULAR_DEPENDENCY'\n | 'INITIALIZATION_FAILED'\n\n/**\n * Base repository interface for plugin extensions.\n * Plugins should use this interface to type-check repository objects.\n *\n * This interface represents the minimum contract that a repository-like object\n * must fulfill to be extended by plugins. It's designed to work with both\n * the @kysera/repository pattern and custom repository implementations.\n *\n * @template DB - The database schema type (defaults to unknown for flexibility)\n *\n * @example\n * ```typescript\n * import type { BaseRepositoryLike } from '@kysera/executor'\n *\n * // In a plugin's extendRepository method:\n * extendRepository<T extends object>(repo: T): T {\n * if (!isRepositoryLike(repo)) {\n * return repo // Not a repository, skip extension\n * }\n *\n * // Now we can safely access repo.tableName and repo.executor\n * const { tableName, executor } = repo\n * // ... extend the repository\n * }\n * ```\n */\nexport interface BaseRepositoryLike<DB = unknown> {\n /** The name of the database table this repository manages */\n readonly tableName: string\n /** The Kysely executor (database or transaction) */\n readonly executor: Kysely<DB>\n /** Find a record by its primary key */\n findById?: (id: unknown) => Promise<unknown>\n /** Find all records in the table */\n findAll?: () => Promise<unknown[]>\n /** Create a new record */\n create?: (data: unknown) => Promise<unknown>\n /** Update an existing record by primary key */\n update?: (id: unknown, data: unknown) => Promise<unknown>\n /** Delete a record by primary key (returns deleted record or boolean) */\n delete?: (id: unknown) => Promise<unknown>\n}\n\n/**\n * Type guard to check if an object is a repository-like object.\n *\n * This function checks for the minimum required properties of a repository:\n * - `tableName`: A string identifying the database table\n * - `executor`: A Kysely instance for database operations\n *\n * @param obj - The object to check\n * @returns True if the object is repository-like, false otherwise\n *\n * @example\n * ```typescript\n * import { isRepositoryLike } from '@kysera/executor'\n *\n * function processRepo(maybeRepo: unknown) {\n * if (isRepositoryLike(maybeRepo)) {\n * console.log(`Repository for table: ${maybeRepo.tableName}`)\n * }\n * }\n * ```\n */\nexport function isRepositoryLike<DB = unknown>(obj: unknown): obj is BaseRepositoryLike<DB> {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'tableName' in obj &&\n 'executor' in obj &&\n typeof (obj as Record<string, unknown>)['tableName'] === 'string'\n )\n}\n","/**\n * @kysera/executor - KyseraExecutor Implementation\n * @module @kysera/executor\n *\n * ## Architecture Notes\n *\n * ### Type System Constraints\n *\n * This implementation uses type assertions due to Kysely's complex type system.\n * All assertions are documented inline and verified safe through runtime behavior.\n *\n * **Type Assertion Categories:**\n *\n * 1. **Plugin interceptQuery** (Line 261)\n * - Issue: QB is constrained to `Compilable<unknown>` but query builders have\n * incompatible method signatures (where, and, etc.)\n * - Safety: Plugin authors must cast based on `context.operation` type\n * - Alternative: None - Kysely lacks a shared interface for query modification\n *\n * 2. **Transaction wrapping** (Lines 326, 332)\n * - Issue: Transaction<DB> extends Kysely<DB> but proxy requires Kysely type\n * - Safety: Structural compatibility verified - Transaction IS-A Kysely\n * - Alternative: None - TypeScript requires explicit cast despite structural typing\n *\n * 3. **Dynamic method access** (Line 245)\n * - Issue: Kysely<DB> lacks index signature for dynamic property access\n * - Safety: Method names validated against INTERCEPTED_METHODS constant\n * - Alternative: None - Cannot use mapped types with runtime method names\n *\n * 4. **Object.assign marker properties** (Lines 394, 427, 473, 488, 527)\n * - Issue: Object.assign returns intersection type (Kysely & Marker)\n * - Safety: Type assertion to union type (KyseraExecutor/KyseraTransaction)\n * - Alternative: Manual object spread (less performant, same type assertion needed)\n *\n * 5. **wrapTransaction cast chain** (Lines 539, 542)\n * - Issue: Transaction -> Kysely -> Proxy -> KyseraTransaction requires casts\n * - Safety: All types structurally compatible; verified in tests\n * - Alternative: None - TypeScript nominal types would solve this\n *\n * 6. **getRawDb executor check** (Line 588)\n * - Issue: Need to check if plain Kysely has __rawDb property\n * - Safety: Optional chaining handles both KyseraExecutor and plain Kysely\n * - Alternative: Type guard (more verbose, same runtime behavior)\n *\n * ### Transaction API Limitation\n *\n * The wrapped transaction only exposes `.execute()` method, not `.setIsolationLevel()`.\n * This is intentional: isolation level should be set before plugin interception.\n *\n * **Rationale:**\n * - Isolation level is a transaction-level concern, not a query-level concern\n * - Setting isolation level after plugin initialization could cause inconsistencies\n * - Keeps the wrapper API simple and focused on query interception\n *\n * **Escape Hatch:**\n * ```typescript\n * executor.__rawDb.transaction().setIsolationLevel('serializable').execute(...)\n * ```\n *\n * This design keeps the plugin system simple while allowing escape hatches.\n */\n\nimport type { Kysely, Transaction } from 'kysely'\nimport type {\n Plugin,\n KyseraExecutor,\n KyseraTransaction,\n QueryBuilderContext,\n ExecutorConfig,\n PluginValidationErrorType,\n PluginValidationDetails\n} from './types.js'\n\n/** Methods that accept table name and should be intercepted */\nexport const INTERCEPTED_METHODS = [\n 'selectFrom',\n 'insertInto',\n 'updateTable',\n 'deleteFrom',\n 'replaceInto', // MySQL REPLACE\n 'mergeInto' // SQL MERGE (Kysely 0.28.x)\n] as const\n\nexport type InterceptedMethod = (typeof INTERCEPTED_METHODS)[number]\n\n/** Pre-computed Set for O(1) lookup instead of Array.includes O(n) */\nconst INTERCEPTED_METHODS_SET = new Set<string>(INTERCEPTED_METHODS)\n\n/** Map method names to operation types */\nconst METHOD_TO_OPERATION: Record<InterceptedMethod, QueryBuilderContext['operation']> = {\n selectFrom: 'select',\n insertInto: 'insert',\n updateTable: 'update',\n deleteFrom: 'delete',\n replaceInto: 'replace',\n mergeInto: 'merge'\n}\n\n/**\n * Plugin validation error\n */\nexport class PluginValidationError extends Error {\n constructor(\n message: string,\n public readonly type: PluginValidationErrorType,\n public readonly details: PluginValidationDetails\n ) {\n super(message)\n this.name = 'PluginValidationError'\n }\n}\n\n/**\n * Validate plugins for conflicts, duplicates, and missing dependencies\n */\nexport function validatePlugins(plugins: readonly Plugin[]): void {\n const names = new Set<string>()\n\n for (const plugin of plugins) {\n if (names.has(plugin.name)) {\n throw new PluginValidationError(`Duplicate plugin: \"${plugin.name}\"`, 'DUPLICATE_NAME', {\n pluginName: plugin.name\n })\n }\n names.add(plugin.name)\n }\n\n for (const plugin of plugins) {\n if (plugin.dependencies) {\n for (const dep of plugin.dependencies) {\n if (!names.has(dep)) {\n throw new PluginValidationError(\n `Plugin \"${plugin.name}\" requires \"${dep}\" which is not registered`,\n 'MISSING_DEPENDENCY',\n { pluginName: plugin.name, missingDependency: dep }\n )\n }\n }\n }\n\n if (plugin.conflictsWith) {\n for (const conflict of plugin.conflictsWith) {\n if (names.has(conflict)) {\n throw new PluginValidationError(\n `Plugin \"${plugin.name}\" conflicts with \"${conflict}\"`,\n 'CONFLICT',\n { pluginName: plugin.name, conflictingPlugin: conflict }\n )\n }\n }\n }\n }\n\n detectCircularDependencies(plugins)\n}\n\n/**\n * Detect circular dependencies using iterative DFS\n * Prevents stack overflow with deep dependency chains\n */\nfunction detectCircularDependencies(plugins: readonly Plugin[]): void {\n const map = new Map(plugins.map(p => [p.name, p]))\n const visited = new Set<string>()\n\n for (const plugin of plugins) {\n if (visited.has(plugin.name)) continue\n\n // Iterative DFS using explicit stack\n const stack: { name: string; deps: readonly string[]; depIndex: number }[] = []\n const inStack = new Set<string>()\n const path: string[] = []\n\n stack.push({ name: plugin.name, deps: plugin.dependencies ?? [], depIndex: 0 })\n inStack.add(plugin.name)\n path.push(plugin.name)\n\n while (stack.length > 0) {\n const frame = stack[stack.length - 1]!\n\n if (frame.depIndex >= frame.deps.length) {\n // Done with this node, backtrack\n stack.pop()\n inStack.delete(frame.name)\n path.pop()\n visited.add(frame.name)\n continue\n }\n\n const dep = frame.deps[frame.depIndex]!\n frame.depIndex++\n\n if (inStack.has(dep)) {\n // Cycle detected\n const start = path.indexOf(dep)\n const cycle = [...path.slice(start), dep]\n throw new PluginValidationError(\n `Circular dependency: ${cycle.join(' -> ')}`,\n 'CIRCULAR_DEPENDENCY',\n { pluginName: frame.name, cycle }\n )\n }\n\n if (!visited.has(dep)) {\n const depPlugin = map.get(dep)\n if (depPlugin) {\n stack.push({ name: dep, deps: depPlugin.dependencies ?? [], depIndex: 0 })\n inStack.add(dep)\n path.push(dep)\n }\n }\n }\n }\n}\n\n/**\n * Resolve plugin execution order using topological sort with priority\n */\nexport function resolvePluginOrder(plugins: readonly Plugin[]): Plugin[] {\n if (plugins.length === 0) return []\n\n const map = new Map(plugins.map(p => [p.name, p]))\n const inDegree = new Map<string, number>()\n const dependents = new Map<string, Set<string>>()\n\n for (const plugin of plugins) {\n inDegree.set(plugin.name, 0)\n dependents.set(plugin.name, new Set())\n }\n\n for (const plugin of plugins) {\n if (plugin.dependencies) {\n for (const dep of plugin.dependencies) {\n inDegree.set(plugin.name, (inDegree.get(plugin.name) ?? 0) + 1)\n dependents.get(dep)?.add(plugin.name)\n }\n }\n }\n\n const result: Plugin[] = []\n const available = plugins.filter(p => (inDegree.get(p.name) ?? 0) === 0)\n\n // Helper to maintain sorted order efficiently (descending priority, then alphabetical)\n const insertSorted = (arr: Plugin[], plugin: Plugin): void => {\n const priority = plugin.priority ?? 0\n let left = 0\n let right = arr.length\n\n // Binary search for insertion point (O(log n))\n // We want descending priority (high to low), then alphabetical\n while (left < right) {\n const mid = (left + right) >>> 1\n const midPriority = arr[mid]!.priority ?? 0\n // If mid has higher priority, or same priority but earlier name, insert after mid\n if (midPriority > priority || (midPriority === priority && arr[mid]!.name < plugin.name)) {\n left = mid + 1\n } else {\n right = mid\n }\n }\n arr.splice(left, 0, plugin)\n }\n\n // Initial sort: descending priority (high to low), then alphabetical\n available.sort((a, b) => {\n const pA = a.priority ?? 0\n const pB = b.priority ?? 0\n return pA !== pB ? pB - pA : a.name.localeCompare(b.name)\n })\n\n while (available.length > 0) {\n // Take first element (highest priority): O(1) with shift\n const current = available.shift()\n // Safety: available.length > 0 check ensures current is defined\n if (!current) break\n result.push(current)\n\n const deps = dependents.get(current.name)\n if (deps) {\n for (const dep of deps) {\n const newDegree = (inDegree.get(dep) ?? 0) - 1\n inDegree.set(dep, newDegree)\n if (newDegree === 0) {\n const plugin = map.get(dep)\n // Insert maintaining sorted order: O(log n) search + O(n) splice\n // Overall complexity: O(n log n) instead of O(n²)\n if (plugin) insertSorted(available, plugin)\n }\n }\n }\n }\n\n return result\n}\n\n/**\n * Create intercepted method that applies plugins\n *\n * @param db - Kysely database instance\n * @param method - Method name being intercepted\n * @param interceptors - Plugins with interceptQuery methods\n * @param currentSchema - Optional schema context (from withSchema)\n */\nfunction createInterceptedMethod<DB>(\n db: Kysely<DB>,\n method: InterceptedMethod,\n interceptors: readonly Plugin[],\n currentSchema?: string\n): (table: string) => unknown {\n const operation = METHOD_TO_OPERATION[method]\n\n return (table: string) => {\n /**\n * TYPE ASSERTION #3: Dynamic method access\n *\n * Cast: Kysely<DB> -> Record<string, (t: string) => unknown>\n *\n * Why needed:\n * - Kysely<DB> interface doesn't have an index signature\n * - TypeScript doesn't allow db[method] for dynamic property access\n *\n * Why safe:\n * - Method name validated against INTERCEPTED_METHODS constant\n * - Runtime check throws if method doesn't exist\n * - All intercepted methods have signature: (table: string) => QueryBuilder\n */\n const originalMethod = (db as unknown as Record<string, (t: string) => unknown>)[method]\n if (!originalMethod) {\n throw new Error(`Method ${method} not found on Kysely instance`)\n }\n // Call with correct 'this' context\n let qb = originalMethod.call(db, table)\n\n // Apply interceptors with schema context\n // Use spread to conditionally include schema only when defined\n const context: QueryBuilderContext = currentSchema !== undefined\n ? { operation, table, schema: currentSchema, metadata: {} }\n : { operation, table, metadata: {} }\n\n for (const plugin of interceptors) {\n if (plugin.interceptQuery) {\n qb = plugin.interceptQuery(qb, context)\n }\n }\n\n return qb\n }\n}\n\n/** Marker properties Set for fast O(1) lookup */\nconst MARKER_PROPS = new Set<string | symbol>(['__kysera', '__plugins', '__rawDb'])\n\n/** Maximum size for LRU caches to prevent unbounded growth */\nconst MAX_CACHE_SIZE = 100\n\n/**\n * Sentinel value to distinguish \"cached undefined\" from \"not in cache\"\n * @internal\n */\nconst UNDEFINED_SENTINEL = Symbol('UNDEFINED_SENTINEL')\n\n/**\n * Wrapper type for cache values to handle undefined correctly\n * @internal\n */\ntype CacheValue<V> = V | typeof UNDEFINED_SENTINEL\n\n/**\n * Simple LRU cache implementation to prevent unbounded cache growth.\n *\n * Correctly handles undefined values using a sentinel pattern:\n * - get() returns undefined for both \"cached undefined\" and \"not in cache\"\n * - has() returns true only if key is actually in cache (even if value is undefined)\n *\n * @internal\n */\nclass LRUCache<K, V> {\n private cache: Map<K, CacheValue<V>>\n private readonly maxSize: number\n\n constructor(maxSize: number) {\n this.cache = new Map()\n this.maxSize = maxSize\n }\n\n get(key: K): V | undefined {\n const value = this.cache.get(key)\n if (value !== undefined) {\n // Move to end (most recently used)\n this.cache.delete(key)\n this.cache.set(key, value)\n // Unwrap sentinel value\n return value === UNDEFINED_SENTINEL ? undefined : value\n }\n return undefined\n }\n\n set(key: K, value: V): void {\n // Wrap undefined values with sentinel\n const wrappedValue: CacheValue<V> = value === undefined ? UNDEFINED_SENTINEL : value\n\n // Delete if exists to move to end\n if (this.cache.has(key)) {\n this.cache.delete(key)\n }\n this.cache.set(key, wrappedValue)\n\n // Evict oldest (first) entry if size exceeded\n if (this.cache.size > this.maxSize) {\n const firstKey = this.cache.keys().next().value\n if (firstKey !== undefined) {\n this.cache.delete(firstKey)\n }\n }\n }\n\n has(key: K): boolean {\n return this.cache.has(key)\n }\n}\n\n/**\n * Create plugin-aware executor using Proxy\n * Optimized with LRU caching and Set-based lookups\n *\n * @param db - Kysely database instance\n * @param interceptors - Plugins with interceptQuery methods\n * @param allPlugins - All registered plugins\n * @param currentSchema - Optional schema context (from withSchema)\n */\nfunction createProxy<DB>(\n db: Kysely<DB>,\n interceptors: readonly Plugin[],\n allPlugins: readonly Plugin[],\n currentSchema?: string\n): KyseraExecutor<DB> {\n // Cache for bound methods to avoid repeated .bind() allocations\n const methodCache = new Map<string | symbol, unknown>()\n\n // Cache intercepted methods to avoid repeated creation\n const interceptedCache = new Map<string, (table: string) => unknown>()\n\n // Cached transaction wrapper (created once, reused)\n let cachedTransactionWrapper:\n | (() => { execute: <T>(fn: (trx: Transaction<DB>) => Promise<T>) => Promise<T> })\n | null = null\n\n // LRU cache for withSchema to prevent unbounded growth (max 100 schemas)\n const schemaProxyCache = new LRUCache<string, KyseraExecutor<DB>>(MAX_CACHE_SIZE)\n\n const handler: ProxyHandler<Kysely<DB>> = {\n // Handle 'in' operator for type guards\n has(target, prop) {\n if (MARKER_PROPS.has(prop)) return true\n if (prop === '__schema') return true\n return Reflect.has(target, prop)\n },\n\n get(target, prop, receiver) {\n // Fast path: marker properties (O(1) Set lookup)\n if (prop === '__kysera') return true\n if (prop === '__plugins') return allPlugins\n if (prop === '__rawDb') return target\n if (prop === '__schema') return currentSchema\n\n // Fast path: check intercepted methods first (most common hot path)\n if (typeof prop === 'string' && INTERCEPTED_METHODS_SET.has(prop)) {\n let intercepted = interceptedCache.get(prop)\n if (!intercepted) {\n intercepted = createInterceptedMethod(target, prop as InterceptedMethod, interceptors, currentSchema)\n interceptedCache.set(prop, intercepted)\n }\n return intercepted\n }\n\n // Intercept withSchema to maintain plugin proxy and track schema\n if (prop === 'withSchema') {\n return (schema: string) => {\n const cachedSchemaProxy = schemaProxyCache.get(schema)\n if (cachedSchemaProxy) {\n return cachedSchemaProxy\n }\n const schemaDb = target.withSchema(schema)\n // Pass schema to new proxy so it's available in QueryBuilderContext\n const newProxy = createProxy(schemaDb, interceptors, allPlugins, schema)\n schemaProxyCache.set(schema, newProxy)\n return newProxy\n }\n }\n\n // Intercept with() for CTEs - cache the wrapper and also wrap the result\n if (prop === 'with') {\n if (!methodCache.has('with')) {\n const withWrapper = (name: string, fn: (db: Kysely<DB>) => unknown): unknown => {\n const wrappedFn = (innerDb: Kysely<DB>): unknown =>\n fn(createProxy(innerDb, interceptors, allPlugins))\n const originalMethod = Reflect.get(target, 'with') as (\n n: string,\n f: (db: Kysely<DB>) => unknown\n ) => Kysely<DB>\n const result = originalMethod.call(target, name, wrappedFn)\n return createProxy(result, interceptors, allPlugins)\n }\n methodCache.set('with', withWrapper)\n }\n return methodCache.get('with')\n }\n\n // Intercept withRecursive() for recursive CTEs - cache the wrapper and wrap result\n if (prop === 'withRecursive') {\n if (!methodCache.has('withRecursive')) {\n const withRecursiveWrapper = (name: string, fn: (db: Kysely<DB>) => unknown): unknown => {\n const wrappedFn = (innerDb: Kysely<DB>): unknown =>\n fn(createProxy(innerDb, interceptors, allPlugins))\n const originalMethod = Reflect.get(target, 'withRecursive') as (\n n: string,\n f: (db: Kysely<DB>) => unknown\n ) => Kysely<DB>\n const result = originalMethod.call(target, name, wrappedFn)\n return createProxy(result, interceptors, allPlugins)\n }\n methodCache.set('withRecursive', withRecursiveWrapper)\n }\n return methodCache.get('withRecursive')\n }\n\n // Cached transaction wrapper\n // NOTE: Transaction API limitation - only execute() method is wrapped\n // Methods like setIsolationLevel() are not available on the wrapper\n // This is intentional: isolation level should be set before plugin interception\n // For advanced use cases, use: executor.__rawDb.transaction().setIsolationLevel(...).execute(...)\n if (prop === 'transaction') {\n if (!cachedTransactionWrapper) {\n cachedTransactionWrapper = () => ({\n execute: async <T>(fn: (trx: Transaction<DB>) => Promise<T>): Promise<T> => {\n return await target.transaction().execute(async trx => {\n /**\n * TYPE ASSERTION #2a: Transaction to Kysely for proxy creation\n *\n * Cast: Transaction<DB> -> Kysely<DB>\n *\n * Why needed:\n * - createProxy expects Kysely<DB>, not Transaction<DB>\n * - TypeScript doesn't recognize structural compatibility automatically\n *\n * Why safe:\n * - Transaction<DB> extends Kysely<DB> (verified in Kysely types)\n * - All Kysely methods are available on Transaction\n * - createProxy only accesses Kysely methods\n */\n const wrappedTrx = createProxy(\n trx as unknown as Kysely<DB>,\n interceptors,\n allPlugins\n )\n /**\n * TYPE ASSERTION #2b: Wrapped proxy back to Transaction\n *\n * Cast: KyseraExecutor<DB> -> Transaction<DB>\n *\n * Why needed:\n * - User callback expects Transaction<DB>, not KyseraExecutor<DB>\n * - Proxy wraps a Transaction but returns KyseraExecutor type\n *\n * Why safe:\n * - Original trx is Transaction<DB>\n * - Proxy preserves all Transaction methods\n * - Only adds marker properties (__kysera, __plugins, __rawDb)\n */\n return await fn(wrappedTrx as unknown as Transaction<DB>)\n })\n }\n })\n }\n return cachedTransactionWrapper\n }\n\n // Check method cache for bound functions\n if (methodCache.has(prop)) {\n return methodCache.get(prop)\n }\n\n const value = Reflect.get(target, prop, receiver)\n\n // Cache bound methods to avoid repeated .bind() allocations\n if (typeof value === 'function') {\n const bound = value.bind(target)\n methodCache.set(prop, bound)\n return bound\n }\n\n return value\n }\n }\n\n return new Proxy(db, handler) as KyseraExecutor<DB>\n}\n\n/**\n * Create a plugin-aware executor\n *\n * Zero overhead if no plugins have interceptQuery\n *\n * @param db - Kysely database instance\n * @param plugins - Array of plugins to apply\n * @param config - Optional configuration\n * @returns Plugin-aware executor\n *\n * @example\n * ```typescript\n * import { createExecutor } from '@kysera/executor';\n * import { softDeletePlugin } from '@kysera/soft-delete';\n *\n * const executor = await createExecutor(db, [softDeletePlugin()]);\n *\n * // All queries now have soft-delete filter applied\n * const users = await executor.selectFrom('users').selectAll().execute();\n * ```\n */\nexport async function createExecutor<DB>(\n db: Kysely<DB>,\n plugins: readonly Plugin[] = [],\n config: ExecutorConfig = {}\n): Promise<KyseraExecutor<DB>> {\n const { enabled = true } = config\n\n // Fast path: no plugins or disabled\n if (plugins.length === 0 || !enabled) {\n /**\n * TYPE ASSERTION #4a: Object.assign result to KyseraExecutor\n *\n * Cast: Kysely<DB> & KyseraExecutorMarker<DB> -> KyseraExecutor<DB>\n *\n * Why needed:\n * - Object.assign returns intersection type (Kysely & Marker)\n * - KyseraExecutor is defined as: type KyseraExecutor<DB> = Kysely<DB> & KyseraExecutorMarker<DB>\n * - TypeScript treats intersection types different from type aliases\n *\n * Why safe:\n * - We're adding exactly the marker properties defined in KyseraExecutorMarker\n * - Runtime type is identical to KyseraExecutor type definition\n * - No structural difference between intersection and type alias at runtime\n */\n return Object.assign(db, {\n __kysera: true as const,\n __plugins: plugins,\n __rawDb: db\n }) as KyseraExecutor<DB>\n }\n\n // Validate and sort plugins\n validatePlugins(plugins)\n const sorted = resolvePluginOrder(plugins)\n\n // Initialize plugins with error handling\n for (const plugin of sorted) {\n try {\n await plugin.onInit?.(db)\n } catch (error) {\n throw new PluginValidationError(\n `Plugin \"${plugin.name}\" failed to initialize: ${error instanceof Error ? error.message : String(error)}`,\n 'INITIALIZATION_FAILED',\n { pluginName: plugin.name }\n )\n }\n }\n\n // Filter plugins with interceptQuery for performance\n const interceptors = sorted.filter(p => p.interceptQuery)\n\n // Fast path: no interceptors\n if (interceptors.length === 0) {\n /**\n * TYPE ASSERTION #4b: Object.assign result to KyseraExecutor (no interceptors)\n *\n * Same as #4a but with sorted plugins instead of input plugins.\n * This path is for plugins that have onInit but no interceptQuery.\n */\n return Object.assign(db, {\n __kysera: true as const,\n __plugins: sorted,\n __rawDb: db\n }) as KyseraExecutor<DB>\n }\n\n // Create proxy with interception\n return createProxy(db, interceptors, sorted)\n}\n\n/**\n * Creates executor synchronously WITHOUT calling plugin onInit hooks.\n *\n * @warning This function skips plugin initialization. Use createExecutor()\n * instead unless you are certain plugins don't need async initialization.\n *\n * Use cases where this is safe:\n * - Plugins without onInit hooks\n * - Plugins with synchronous-only initialization\n * - Testing scenarios where initialization is handled separately\n *\n * @param db - Kysely database instance\n * @param plugins - Array of plugins to apply\n * @param config - Optional configuration\n * @returns Plugin-aware executor (without onInit called)\n *\n * @example\n * ```typescript\n * // Use for simple plugins without async init:\n * const executor = createExecutorSync(db, [simplePlugin]);\n *\n * // WARNING: Plugin onInit hooks are NOT called!\n * // If your plugin requires initialization, use createExecutor() instead.\n * ```\n */\nexport function createExecutorSync<DB>(\n db: Kysely<DB>,\n plugins: readonly Plugin[] = [],\n config: ExecutorConfig = {}\n): KyseraExecutor<DB> {\n const { enabled = true } = config\n\n if (plugins.length === 0 || !enabled) {\n /**\n * TYPE ASSERTION #4c: Object.assign in createExecutorSync (no plugins/disabled)\n *\n * Same as #4a - see explanation there.\n */\n return Object.assign(db, {\n __kysera: true as const,\n __plugins: plugins,\n __rawDb: db\n }) as KyseraExecutor<DB>\n }\n\n validatePlugins(plugins)\n const sorted = resolvePluginOrder(plugins)\n const interceptors = sorted.filter(p => p.interceptQuery)\n\n if (interceptors.length === 0) {\n /**\n * TYPE ASSERTION #4d: Object.assign in createExecutorSync (no interceptors)\n *\n * Same as #4b - see explanation there.\n */\n return Object.assign(db, {\n __kysera: true as const,\n __plugins: sorted,\n __rawDb: db\n }) as KyseraExecutor<DB>\n }\n\n return createProxy(db, interceptors, sorted)\n}\n\n/**\n * Check if value is a KyseraExecutor\n */\nexport function isKyseraExecutor<DB>(\n value: Kysely<DB> | KyseraExecutor<DB>\n): value is KyseraExecutor<DB> {\n return '__kysera' in value && value.__kysera\n}\n\n/**\n * Get plugins from executor\n */\nexport function getPlugins<DB>(executor: KyseraExecutor<DB>): readonly Plugin[] {\n return executor.__plugins\n}\n\n/**\n * Wrap transaction with plugins\n */\nexport function wrapTransaction<DB>(\n trx: Transaction<DB>,\n plugins: readonly Plugin[]\n): KyseraTransaction<DB> {\n const interceptors = plugins.filter(p => p.interceptQuery)\n\n if (interceptors.length === 0) {\n /**\n * TYPE ASSERTION #4e: Object.assign for transaction wrapping (no interceptors)\n *\n * Cast: Transaction<DB> & KyseraExecutorMarker<DB> -> KyseraTransaction<DB>\n *\n * Similar to #4a but for Transaction type instead of Kysely type.\n * KyseraTransaction is defined as: type KyseraTransaction<DB> = Transaction<DB> & KyseraExecutorMarker<DB>\n */\n return Object.assign(trx, {\n __kysera: true as const,\n __plugins: plugins,\n __rawDb: trx\n }) as KyseraTransaction<DB>\n }\n\n /**\n * TYPE ASSERTION #5: wrapTransaction cast chain\n *\n * Double cast: Transaction<DB> -> Kysely<DB> -> KyseraExecutor<DB> -> KyseraTransaction<DB>\n *\n * Why needed:\n * - createProxy expects Kysely<DB> and returns KyseraExecutor<DB>\n * - We need to return KyseraTransaction<DB>\n * - TypeScript doesn't recognize that KyseraExecutor wrapping a Transaction is compatible with KyseraTransaction\n *\n * Why safe:\n * - Transaction<DB> extends Kysely<DB> (first cast is upcast)\n * - createProxy preserves all methods (adds marker properties only)\n * - KyseraTransaction<DB> = Transaction<DB> & Marker, KyseraExecutor<DB> = Kysely<DB> & Marker\n * - Since original is Transaction, wrapped result is structurally KyseraTransaction\n * - Verified in integration tests (packages/executor/test/executor.test.ts)\n */\n return createProxy(\n trx as unknown as Kysely<DB>,\n interceptors,\n plugins\n ) as unknown as KyseraTransaction<DB>\n}\n\n/**\n * Apply plugins to a query builder manually\n * Useful for complex queries that bypass normal interception\n */\nexport function applyPlugins<QB>(\n qb: QB,\n plugins: readonly Plugin[],\n context: QueryBuilderContext\n): QB {\n let result = qb\n for (const plugin of plugins) {\n if (plugin.interceptQuery) {\n result = plugin.interceptQuery(result, context)\n }\n }\n return result\n}\n\n/**\n * Get raw Kysely instance from executor, bypassing plugin interceptors.\n * Returns the executor itself if it's not a KyseraExecutor.\n *\n * Useful for plugins that need to:\n * - Perform internal queries without triggering interceptors\n * - Avoid double-filtering (e.g., soft-delete checking its own records)\n * - Access the underlying Kysely instance for advanced operations\n *\n * @param executor - Kysely or KyseraExecutor instance\n * @returns Raw Kysely instance without plugin interception\n *\n * @example\n * ```typescript\n * // Inside a plugin's extendRepository:\n * const rawDb = getRawDb(baseRepo.executor);\n * // This query bypasses all plugin interceptors\n * const result = await rawDb.selectFrom('users').selectAll().execute();\n * ```\n */\nexport function getRawDb<DB>(executor: Kysely<DB>): Kysely<DB> {\n /**\n * TYPE ASSERTION #6: getRawDb executor check\n *\n * Cast: Kysely<DB> -> KyseraExecutor<DB>\n *\n * Why needed:\n * - Need to check if executor has __rawDb property\n * - Plain Kysely<DB> doesn't have __rawDb, only KyseraExecutor<DB> does\n * - TypeScript doesn't allow property access without type assertion\n *\n * Why safe:\n * - Optional chaining (??) handles both cases gracefully:\n * - If KyseraExecutor: __rawDb exists and is returned\n * - If plain Kysely: __rawDb is undefined, executor is returned\n * - No runtime error possible - undefined ?? executor always succeeds\n * - Type guard alternative would be more verbose with same behavior\n */\n const kyseraExecutor = executor as unknown as KyseraExecutor<DB>\n return kyseraExecutor.__rawDb ?? executor\n}\n\n/**\n * Destroy executor and call onDestroy for all plugins\n *\n * @param executor - KyseraExecutor instance to destroy\n *\n * @example\n * ```typescript\n * const executor = await createExecutor(db, [myPlugin]);\n * // ... use executor ...\n * await destroyExecutor(executor); // Calls onDestroy on all plugins\n * await db.destroy(); // Then destroy underlying Kysely instance\n * ```\n */\nexport async function destroyExecutor<DB>(executor: KyseraExecutor<DB>): Promise<void> {\n const plugins = executor.__plugins\n\n // Call onDestroy in reverse order (cleanup in reverse of initialization)\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin?.onDestroy) {\n await plugin.onDestroy()\n }\n }\n}\n","/**\n * Schema Plugin - Unified schema management for Kysera\n *\n * This plugin provides centralized schema configuration and validation\n * for multi-tenant and modular database architectures.\n *\n * @example\n * // Basic usage with default schema\n * const executor = await createExecutor(db, [\n * schemaPlugin({ defaultSchema: 'auth' })\n * ])\n *\n * @example\n * // Multi-tenant with dynamic schema resolution\n * const executor = await createExecutor(db, [\n * schemaPlugin({\n * resolveSchema: (ctx) => ctx.metadata.tenantSchema as string,\n * allowedSchemas: ['tenant_a', 'tenant_b', 'tenant_c']\n * })\n * ])\n */\n\nimport type { Plugin, QueryBuilderContext } from '../types.js'\n\n/**\n * Configuration options for the Schema Plugin\n */\nexport interface SchemaPluginOptions {\n /**\n * Default schema for all queries.\n * Used when no schema is resolved dynamically.\n * @default 'public'\n */\n defaultSchema?: string\n\n /**\n * Dynamic schema resolver function.\n * Called for each query to determine the schema.\n * If returns undefined, defaultSchema is used.\n *\n * @param context - Query builder context with operation, table, and metadata\n * @returns Schema name or undefined to use default\n *\n * @example\n * // Resolve schema from metadata\n * resolveSchema: (ctx) => ctx.metadata.tenantSchema as string\n *\n * @example\n * // Schema based on table name\n * resolveSchema: (ctx) => {\n * if (ctx.table.startsWith('auth_')) return 'auth'\n * if (ctx.table.startsWith('admin_')) return 'admin'\n * return undefined // use default\n * }\n */\n resolveSchema?: (context: QueryBuilderContext) => string | undefined\n\n /**\n * Async schema validator.\n * Called during plugin initialization to validate the default schema.\n *\n * @param schema - Schema name to validate\n * @returns true if valid, false otherwise\n *\n * @example\n * validateSchema: async (schema) => {\n * return await adapter.schemaExists(db, schema)\n * }\n */\n validateSchema?: (schema: string) => boolean | Promise<boolean>\n\n /**\n * Whitelist of allowed schemas.\n * If set, only these schemas can be used.\n * Queries with other schemas will throw an error.\n *\n * @example\n * allowedSchemas: ['public', 'auth', 'admin']\n */\n allowedSchemas?: string[]\n\n /**\n * Whether to throw an error when schema validation fails.\n * If false, falls back to defaultSchema.\n * @default true\n */\n strictValidation?: boolean\n}\n\n/**\n * Error thrown when schema validation fails\n */\nexport class SchemaValidationError extends Error {\n constructor(\n message: string,\n public readonly schema: string,\n public readonly allowedSchemas?: string[]\n ) {\n super(message)\n this.name = 'SchemaValidationError'\n }\n}\n\n/**\n * Create a Schema Plugin for unified schema management.\n *\n * The plugin provides:\n * - Default schema configuration\n * - Dynamic schema resolution per query\n * - Schema whitelist validation\n * - Schema metadata in QueryBuilderContext\n *\n * @param options - Plugin configuration options\n * @returns Plugin instance\n *\n * @example\n * // Simple default schema\n * const executor = await createExecutor(db, [\n * schemaPlugin({ defaultSchema: 'app' })\n * ])\n *\n * @example\n * // Multi-tenant application\n * const executor = await createExecutor(db, [\n * schemaPlugin({\n * defaultSchema: 'public',\n * resolveSchema: (ctx) => {\n * const tenantId = ctx.metadata.tenantId as string\n * return tenantId ? `tenant_${tenantId}` : undefined\n * },\n * allowedSchemas: ['public', 'tenant_a', 'tenant_b']\n * })\n * ])\n */\nexport function schemaPlugin(options: SchemaPluginOptions = {}): Plugin {\n const {\n defaultSchema = 'public',\n resolveSchema,\n validateSchema,\n allowedSchemas,\n strictValidation = true\n } = options\n\n // Pre-compute allowed schemas set for O(1) lookup\n const allowedSet = allowedSchemas ? new Set(allowedSchemas) : null\n\n return {\n name: '@kysera/schema',\n version: '1.0.0',\n priority: 1000, // Run early to set schema context for other plugins\n\n async onInit(_db) {\n // Validate default schema during initialization\n if (validateSchema) {\n const isValid = await validateSchema(defaultSchema)\n if (!isValid) {\n throw new SchemaValidationError(\n `Invalid default schema: ${defaultSchema}`,\n defaultSchema\n )\n }\n }\n\n // Validate allowed schemas if whitelist is provided\n if (allowedSet && !allowedSet.has(defaultSchema)) {\n const allowedList = allowedSchemas ? allowedSchemas.join(', ') : ''\n throw new SchemaValidationError(\n `Default schema \"${defaultSchema}\" is not in allowed list: [${allowedList}]`,\n defaultSchema,\n allowedSchemas\n )\n }\n },\n\n interceptQuery<QB>(qb: QB, context: QueryBuilderContext): QB {\n // Resolve schema for this query\n let schema = resolveSchema?.(context) ?? context.schema ?? defaultSchema\n\n // Validate against whitelist\n if (allowedSet && !allowedSet.has(schema)) {\n if (strictValidation) {\n const allowedList = allowedSchemas ? allowedSchemas.join(', ') : ''\n throw new SchemaValidationError(\n `Schema \"${schema}\" is not in allowed list: [${allowedList}]`,\n schema,\n allowedSchemas\n )\n }\n // Fall back to default if not strict\n schema = defaultSchema\n }\n\n // Store resolved schema in metadata for other plugins\n context.metadata['__resolvedSchema'] = schema\n\n return qb\n }\n }\n}\n\n/**\n * Get the resolved schema from query context metadata.\n * Useful for plugins that need to access the schema set by schemaPlugin.\n *\n * @param context - Query builder context\n * @returns Resolved schema or undefined\n *\n * @example\n * // In another plugin's interceptQuery\n * interceptQuery(qb, context) {\n * const schema = getResolvedSchema(context)\n * if (schema === 'admin') {\n * // Apply admin-specific logic\n * }\n * return qb\n * }\n */\nexport function getResolvedSchema(context: QueryBuilderContext): string | undefined {\n return context.metadata['__resolvedSchema'] as string | undefined\n}\n"]}
{"version":3,"sources":["../src/types.ts","../src/executor.ts","../src/plugins/schema-plugin.ts"],"names":["isRepositoryLike","obj","INTERCEPTED_METHODS","INTERCEPTED_METHODS_SET","METHOD_TO_OPERATION","PluginValidationError","message","type","details","validatePlugins","plugins","names","plugin","dep","conflict","detectCircularDependencies","map","p","visited","stack","inStack","path","frame","start","cycle","depPlugin","resolvePluginOrder","inDegree","dependents","result","available","insertSorted","arr","priority","left","right","mid","midPriority","a","b","pA","pB","current","deps","newDegree","createInterceptedMethod","db","method","interceptors","currentSchema","operation","table","originalMethod","qb","context","MARKER_PROPS","MAX_CACHE_SIZE","UNDEFINED_SENTINEL","LRUCache","maxSize","key","value","wrappedValue","firstKey","createProxy","allPlugins","methodCache","interceptedCache","cachedTransactionWrapper","schemaProxyCache","handler","target","prop","receiver","intercepted","schema","cachedSchemaProxy","schemaDb","newProxy","withWrapper","name","fn","wrappedFn","innerDb","withRecursiveWrapper","trx","wrappedTrx","bound","createExecutor","config","enabled","sorted","error","createExecutorSync","isKyseraExecutor","getPlugins","executor","wrapTransaction","applyPlugins","getRawDb","destroyExecutor","i","SchemaValidationError","allowedSchemas","schemaPlugin","options","defaultSchema","resolveSchema","validateSchema","strictValidation","allowedSet","_db","allowedList","getResolvedSchema"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @kysera/executor - Unified Execution Layer Types\n * @module @kysera/executor\n */\n\nimport type { Kysely, Transaction } from 'kysely'\n\n/**\n * Query builder context passed to plugin interceptors\n */\nexport interface QueryBuilderContext {\n /** Type of operation */\n readonly operation: 'select' | 'insert' | 'update' | 'delete' | 'replace' | 'merge'\n /** Table name */\n readonly table: string\n /**\n * Current schema context (if withSchema was called).\n * undefined means default schema is being used.\n */\n readonly schema?: string\n /** Additional metadata */\n readonly metadata: Record<string, unknown>\n}\n\n/**\n * Plugin interface - unified for both Repository and DAL patterns\n *\n * Plugins can:\n * - Intercept queries before execution (interceptQuery)\n * - Extend repositories with additional methods (extendRepository)\n * - Initialize resources on startup (onInit)\n * - Cleanup resources on shutdown (onDestroy)\n */\nexport interface Plugin {\n /** Unique plugin name */\n readonly name: string\n /** Plugin version */\n readonly version: string\n /** Plugin dependencies (must be loaded first) */\n readonly dependencies?: readonly string[]\n /** Higher priority = runs first (default: 0) */\n readonly priority?: number\n /** Plugins that conflict with this one */\n readonly conflictsWith?: readonly string[]\n\n /**\n * Lifecycle: Called once when plugin is initialized\n * @param db - Kysely database instance (not the executor wrapper)\n */\n onInit?<DB>(db: Kysely<DB>): Promise<void> | void\n\n /**\n * Lifecycle: Called when executor is being destroyed\n * Use for cleanup, closing connections, clearing timers, etc.\n */\n onDestroy?(): Promise<void> | void\n\n /**\n * Query interception: Modify query builder before execution\n * Works in both Repository and DAL patterns via @kysera/executor\n *\n * @typeParam QB - Query builder type (intentionally unconstrained)\n *\n * **Type Safety Note:**\n * QB is intentionally unconstrained (not constrained to any base type) because:\n *\n * 1. **Kysely's query builders lack a shared interface**: SelectQueryBuilder,\n * InsertQueryBuilder, UpdateQueryBuilder, DeleteQueryBuilder, and MergeQueryBuilder\n * don't share a common base interface that includes query modification methods\n * like where(), and(), etc.\n *\n * 2. **Each builder has unique generic parameters**: Even if they implemented\n * Compilable<unknown>, their type parameters differ (DB, TB, O, UT, etc.),\n * making it impossible to express a shared constraint.\n *\n * 3. **Type inference requirement**: The QB type must be preserved exactly as-is\n * through the plugin chain. Any constraint would break this preservation.\n *\n * **Plugin Implementation Pattern:**\n * Plugins must handle type safety internally by:\n * 1. Checking the operation type from context.operation\n * 2. Casting to the appropriate specific builder type\n * 3. Using type assertions (documented in plugin code)\n *\n * @example\n * ```typescript\n * interceptQuery<QB>(qb: QB, context: QueryBuilderContext): QB {\n * if (context.operation === 'select') {\n * // Cast to SelectQueryBuilder for type-safe WHERE clause\n * type GenericSelect = SelectQueryBuilder<Record<string, unknown>, string, Record<string, unknown>>;\n * return (qb as unknown as GenericSelect)\n * .where('deleted_at', 'is', null) as QB;\n * }\n * return qb;\n * }\n * ```\n */\n interceptQuery?<QB>(qb: QB, context: QueryBuilderContext): QB\n\n /**\n * Repository extensions: Add methods to repositories (Repository pattern only)\n */\n extendRepository?<T extends object>(repo: T): T\n}\n\n/**\n * Marker interface for KyseraExecutor\n */\nexport interface KyseraExecutorMarker<DB = unknown> {\n readonly __kysera: true\n readonly __plugins: readonly Plugin[]\n /** Raw Kysely instance bypassing plugin interceptors (for internal plugin use) */\n readonly __rawDb: Kysely<DB>\n /**\n * Current schema context (if withSchema was called).\n * undefined means default schema is being used.\n */\n readonly __schema?: string\n}\n\n/**\n * Plugin-aware Kysely wrapper type\n * Extends Kysely with plugin interception capabilities\n */\nexport type KyseraExecutor<DB> = Kysely<DB> & KyseraExecutorMarker<DB>\n\n/**\n * Plugin-aware Transaction wrapper type\n */\nexport type KyseraTransaction<DB> = Transaction<DB> & KyseraExecutorMarker<DB>\n\n/**\n * Union type for any Kysera executor (database or transaction)\n */\nexport type AnyKyseraExecutor<DB> = KyseraExecutor<DB> | KyseraTransaction<DB>\n\n/**\n * Configuration for executor creation\n */\nexport interface ExecutorConfig {\n /** Enable/disable plugin interception at runtime */\n readonly enabled?: boolean\n}\n\n/**\n * Plugin validation error details\n */\nexport interface PluginValidationDetails {\n readonly pluginName: string\n readonly missingDependency?: string\n readonly conflictingPlugin?: string\n readonly cycle?: readonly string[]\n}\n\n/**\n * Plugin validation error types\n */\nexport type PluginValidationErrorType =\n | 'DUPLICATE_NAME'\n | 'MISSING_DEPENDENCY'\n | 'CONFLICT'\n | 'CIRCULAR_DEPENDENCY'\n | 'INITIALIZATION_FAILED'\n\n/**\n * Base repository interface for plugin extensions.\n * Plugins should use this interface to type-check repository objects.\n *\n * This interface represents the minimum contract that a repository-like object\n * must fulfill to be extended by plugins. It's designed to work with both\n * the @kysera/repository pattern and custom repository implementations.\n *\n * @template DB - The database schema type (defaults to unknown for flexibility)\n *\n * @example\n * ```typescript\n * import type { BaseRepositoryLike } from '@kysera/executor'\n *\n * // In a plugin's extendRepository method:\n * extendRepository<T extends object>(repo: T): T {\n * if (!isRepositoryLike(repo)) {\n * return repo // Not a repository, skip extension\n * }\n *\n * // Now we can safely access repo.tableName and repo.executor\n * const { tableName, executor } = repo\n * // ... extend the repository\n * }\n * ```\n */\nexport interface BaseRepositoryLike<DB = unknown> {\n /** The name of the database table this repository manages */\n readonly tableName: string\n /** The Kysely executor (database or transaction) */\n readonly executor: Kysely<DB>\n /** Find a record by its primary key */\n findById?: (id: unknown) => Promise<unknown>\n /** Find all records in the table */\n findAll?: () => Promise<unknown[]>\n /** Create a new record */\n create?: (data: unknown) => Promise<unknown>\n /** Update an existing record by primary key */\n update?: (id: unknown, data: unknown) => Promise<unknown>\n /** Delete a record by primary key (returns deleted record or boolean) */\n delete?: (id: unknown) => Promise<unknown>\n}\n\n/**\n * Type guard to check if an object is a repository-like object.\n *\n * This function checks for the minimum required properties of a repository:\n * - `tableName`: A string identifying the database table\n * - `executor`: A Kysely instance for database operations\n *\n * @param obj - The object to check\n * @returns True if the object is repository-like, false otherwise\n *\n * @example\n * ```typescript\n * import { isRepositoryLike } from '@kysera/executor'\n *\n * function processRepo(maybeRepo: unknown) {\n * if (isRepositoryLike(maybeRepo)) {\n * console.log(`Repository for table: ${maybeRepo.tableName}`)\n * }\n * }\n * ```\n */\nexport function isRepositoryLike<DB = unknown>(obj: unknown): obj is BaseRepositoryLike<DB> {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'tableName' in obj &&\n 'executor' in obj &&\n typeof (obj as Record<string, unknown>)['tableName'] === 'string'\n )\n}\n","/**\n * @kysera/executor - KyseraExecutor Implementation\n * @module @kysera/executor\n *\n * ## Architecture Notes\n *\n * ### Type System Constraints\n *\n * This implementation uses type assertions due to Kysely's complex type system.\n * All assertions are documented inline and verified safe through runtime behavior.\n *\n * **Type Assertion Categories:**\n *\n * 1. **Plugin interceptQuery** (Line 261)\n * - Issue: QB is constrained to `Compilable<unknown>` but query builders have\n * incompatible method signatures (where, and, etc.)\n * - Safety: Plugin authors must cast based on `context.operation` type\n * - Alternative: None - Kysely lacks a shared interface for query modification\n *\n * 2. **Transaction wrapping** (Lines 326, 332)\n * - Issue: Transaction<DB> extends Kysely<DB> but proxy requires Kysely type\n * - Safety: Structural compatibility verified - Transaction IS-A Kysely\n * - Alternative: None - TypeScript requires explicit cast despite structural typing\n *\n * 3. **Dynamic method access** (Line 245)\n * - Issue: Kysely<DB> lacks index signature for dynamic property access\n * - Safety: Method names validated against INTERCEPTED_METHODS constant\n * - Alternative: None - Cannot use mapped types with runtime method names\n *\n * 4. **Object.assign marker properties** (Lines 394, 427, 473, 488, 527)\n * - Issue: Object.assign returns intersection type (Kysely & Marker)\n * - Safety: Type assertion to union type (KyseraExecutor/KyseraTransaction)\n * - Alternative: Manual object spread (less performant, same type assertion needed)\n *\n * 5. **wrapTransaction cast chain** (Lines 539, 542)\n * - Issue: Transaction -> Kysely -> Proxy -> KyseraTransaction requires casts\n * - Safety: All types structurally compatible; verified in tests\n * - Alternative: None - TypeScript nominal types would solve this\n *\n * 6. **getRawDb executor check** (Line 588)\n * - Issue: Need to check if plain Kysely has __rawDb property\n * - Safety: Optional chaining handles both KyseraExecutor and plain Kysely\n * - Alternative: Type guard (more verbose, same runtime behavior)\n *\n * ### Transaction API Limitation\n *\n * The wrapped transaction only exposes `.execute()` method, not `.setIsolationLevel()`.\n * This is intentional: isolation level should be set before plugin interception.\n *\n * **Rationale:**\n * - Isolation level is a transaction-level concern, not a query-level concern\n * - Setting isolation level after plugin initialization could cause inconsistencies\n * - Keeps the wrapper API simple and focused on query interception\n *\n * **Escape Hatch:**\n * ```typescript\n * executor.__rawDb.transaction().setIsolationLevel('serializable').execute(...)\n * ```\n *\n * This design keeps the plugin system simple while allowing escape hatches.\n */\n\nimport type { Kysely, Transaction } from 'kysely'\nimport type {\n Plugin,\n KyseraExecutor,\n KyseraTransaction,\n QueryBuilderContext,\n ExecutorConfig,\n PluginValidationErrorType,\n PluginValidationDetails\n} from './types.js'\n\n/** Methods that accept table name and should be intercepted */\nexport const INTERCEPTED_METHODS = [\n 'selectFrom',\n 'insertInto',\n 'updateTable',\n 'deleteFrom',\n 'replaceInto', // MySQL REPLACE\n 'mergeInto' // SQL MERGE (Kysely 0.28.x)\n] as const\n\nexport type InterceptedMethod = (typeof INTERCEPTED_METHODS)[number]\n\n/** Pre-computed Set for O(1) lookup instead of Array.includes O(n) */\nconst INTERCEPTED_METHODS_SET = new Set<string>(INTERCEPTED_METHODS)\n\n/** Map method names to operation types */\nconst METHOD_TO_OPERATION: Record<InterceptedMethod, QueryBuilderContext['operation']> = {\n selectFrom: 'select',\n insertInto: 'insert',\n updateTable: 'update',\n deleteFrom: 'delete',\n replaceInto: 'replace',\n mergeInto: 'merge'\n}\n\n/**\n * Plugin validation error\n */\nexport class PluginValidationError extends Error {\n constructor(\n message: string,\n public readonly type: PluginValidationErrorType,\n public readonly details: PluginValidationDetails\n ) {\n super(message)\n this.name = 'PluginValidationError'\n }\n}\n\n/**\n * Validate plugins for conflicts, duplicates, and missing dependencies\n */\nexport function validatePlugins(plugins: readonly Plugin[]): void {\n const names = new Set<string>()\n\n for (const plugin of plugins) {\n if (names.has(plugin.name)) {\n throw new PluginValidationError(`Duplicate plugin: \"${plugin.name}\"`, 'DUPLICATE_NAME', {\n pluginName: plugin.name\n })\n }\n names.add(plugin.name)\n }\n\n for (const plugin of plugins) {\n if (plugin.dependencies) {\n for (const dep of plugin.dependencies) {\n if (!names.has(dep)) {\n throw new PluginValidationError(\n `Plugin \"${plugin.name}\" requires \"${dep}\" which is not registered`,\n 'MISSING_DEPENDENCY',\n { pluginName: plugin.name, missingDependency: dep }\n )\n }\n }\n }\n\n if (plugin.conflictsWith) {\n for (const conflict of plugin.conflictsWith) {\n if (names.has(conflict)) {\n throw new PluginValidationError(\n `Plugin \"${plugin.name}\" conflicts with \"${conflict}\"`,\n 'CONFLICT',\n { pluginName: plugin.name, conflictingPlugin: conflict }\n )\n }\n }\n }\n }\n\n detectCircularDependencies(plugins)\n}\n\n/**\n * Detect circular dependencies using iterative DFS\n * Prevents stack overflow with deep dependency chains\n */\nfunction detectCircularDependencies(plugins: readonly Plugin[]): void {\n const map = new Map(plugins.map(p => [p.name, p]))\n const visited = new Set<string>()\n\n for (const plugin of plugins) {\n if (visited.has(plugin.name)) continue\n\n // Iterative DFS using explicit stack\n const stack: { name: string; deps: readonly string[]; depIndex: number }[] = []\n const inStack = new Set<string>()\n const path: string[] = []\n\n stack.push({ name: plugin.name, deps: plugin.dependencies ?? [], depIndex: 0 })\n inStack.add(plugin.name)\n path.push(plugin.name)\n\n while (stack.length > 0) {\n const frame = stack[stack.length - 1]!\n\n if (frame.depIndex >= frame.deps.length) {\n // Done with this node, backtrack\n stack.pop()\n inStack.delete(frame.name)\n path.pop()\n visited.add(frame.name)\n continue\n }\n\n const dep = frame.deps[frame.depIndex]!\n frame.depIndex++\n\n if (inStack.has(dep)) {\n // Cycle detected\n const start = path.indexOf(dep)\n const cycle = [...path.slice(start), dep]\n throw new PluginValidationError(\n `Circular dependency: ${cycle.join(' -> ')}`,\n 'CIRCULAR_DEPENDENCY',\n { pluginName: frame.name, cycle }\n )\n }\n\n if (!visited.has(dep)) {\n const depPlugin = map.get(dep)\n if (depPlugin) {\n stack.push({ name: dep, deps: depPlugin.dependencies ?? [], depIndex: 0 })\n inStack.add(dep)\n path.push(dep)\n }\n }\n }\n }\n}\n\n/**\n * Resolve plugin execution order using topological sort with priority\n */\nexport function resolvePluginOrder(plugins: readonly Plugin[]): Plugin[] {\n if (plugins.length === 0) return []\n\n const map = new Map(plugins.map(p => [p.name, p]))\n const inDegree = new Map<string, number>()\n const dependents = new Map<string, Set<string>>()\n\n for (const plugin of plugins) {\n inDegree.set(plugin.name, 0)\n dependents.set(plugin.name, new Set())\n }\n\n for (const plugin of plugins) {\n if (plugin.dependencies) {\n for (const dep of plugin.dependencies) {\n inDegree.set(plugin.name, (inDegree.get(plugin.name) ?? 0) + 1)\n dependents.get(dep)?.add(plugin.name)\n }\n }\n }\n\n const result: Plugin[] = []\n const available = plugins.filter(p => (inDegree.get(p.name) ?? 0) === 0)\n\n // Helper to maintain sorted order efficiently (descending priority, then alphabetical)\n const insertSorted = (arr: Plugin[], plugin: Plugin): void => {\n const priority = plugin.priority ?? 0\n let left = 0\n let right = arr.length\n\n // Binary search for insertion point (O(log n))\n // We want descending priority (high to low), then alphabetical\n while (left < right) {\n const mid = (left + right) >>> 1\n const midPriority = arr[mid]!.priority ?? 0\n // If mid has higher priority, or same priority but earlier name, insert after mid\n if (midPriority > priority || (midPriority === priority && arr[mid]!.name < plugin.name)) {\n left = mid + 1\n } else {\n right = mid\n }\n }\n arr.splice(left, 0, plugin)\n }\n\n // Initial sort: descending priority (high to low), then alphabetical\n available.sort((a, b) => {\n const pA = a.priority ?? 0\n const pB = b.priority ?? 0\n return pA !== pB ? pB - pA : a.name.localeCompare(b.name)\n })\n\n while (available.length > 0) {\n // Take first element (highest priority): O(1) with shift\n const current = available.shift()\n // Safety: available.length > 0 check ensures current is defined\n if (!current) break\n result.push(current)\n\n const deps = dependents.get(current.name)\n if (deps) {\n for (const dep of deps) {\n const newDegree = (inDegree.get(dep) ?? 0) - 1\n inDegree.set(dep, newDegree)\n if (newDegree === 0) {\n const plugin = map.get(dep)\n // Insert maintaining sorted order: O(log n) search + O(n) splice\n // Overall complexity: O(n log n) instead of O(n²)\n if (plugin) insertSorted(available, plugin)\n }\n }\n }\n }\n\n return result\n}\n\n/**\n * Create intercepted method that applies plugins\n *\n * @param db - Kysely database instance\n * @param method - Method name being intercepted\n * @param interceptors - Plugins with interceptQuery methods\n * @param currentSchema - Optional schema context (from withSchema)\n */\nfunction createInterceptedMethod<DB>(\n db: Kysely<DB>,\n method: InterceptedMethod,\n interceptors: readonly Plugin[],\n currentSchema?: string\n): (table: string) => unknown {\n const operation = METHOD_TO_OPERATION[method]\n\n return (table: string) => {\n /**\n * TYPE ASSERTION #3: Dynamic method access\n *\n * Cast: Kysely<DB> -> Record<string, (t: string) => unknown>\n *\n * Why needed:\n * - Kysely<DB> interface doesn't have an index signature\n * - TypeScript doesn't allow db[method] for dynamic property access\n *\n * Why safe:\n * - Method name validated against INTERCEPTED_METHODS constant\n * - Runtime check throws if method doesn't exist\n * - All intercepted methods have signature: (table: string) => QueryBuilder\n */\n const originalMethod = (db as unknown as Record<string, (t: string) => unknown>)[method]\n if (!originalMethod) {\n throw new Error(`Method ${method} not found on Kysely instance`)\n }\n // Call with correct 'this' context\n let qb = originalMethod.call(db, table)\n\n // Apply interceptors with schema context\n // Use spread to conditionally include schema only when defined\n const context: QueryBuilderContext = currentSchema !== undefined\n ? { operation, table, schema: currentSchema, metadata: {} }\n : { operation, table, metadata: {} }\n\n for (const plugin of interceptors) {\n if (plugin.interceptQuery) {\n qb = plugin.interceptQuery(qb, context)\n }\n }\n\n return qb\n }\n}\n\n/** Marker properties Set for fast O(1) lookup */\nconst MARKER_PROPS = new Set<string | symbol>(['__kysera', '__plugins', '__rawDb'])\n\n/** Maximum size for LRU caches to prevent unbounded growth */\nconst MAX_CACHE_SIZE = 100\n\n/**\n * Sentinel value to distinguish \"cached undefined\" from \"not in cache\"\n * @internal\n */\nconst UNDEFINED_SENTINEL = Symbol('UNDEFINED_SENTINEL')\n\n/**\n * Wrapper type for cache values to handle undefined correctly\n * @internal\n */\ntype CacheValue<V> = V | typeof UNDEFINED_SENTINEL\n\n/**\n * Simple LRU cache implementation to prevent unbounded cache growth.\n *\n * Correctly handles undefined values using a sentinel pattern:\n * - get() returns undefined for both \"cached undefined\" and \"not in cache\"\n * - has() returns true only if key is actually in cache (even if value is undefined)\n *\n * @internal\n */\nclass LRUCache<K, V> {\n private cache: Map<K, CacheValue<V>>\n private readonly maxSize: number\n\n constructor(maxSize: number) {\n this.cache = new Map()\n this.maxSize = maxSize\n }\n\n get(key: K): V | undefined {\n const value = this.cache.get(key)\n if (value !== undefined) {\n // Move to end (most recently used)\n this.cache.delete(key)\n this.cache.set(key, value)\n // Unwrap sentinel value\n return value === UNDEFINED_SENTINEL ? undefined : value\n }\n return undefined\n }\n\n set(key: K, value: V): void {\n // Wrap undefined values with sentinel\n const wrappedValue: CacheValue<V> = value === undefined ? UNDEFINED_SENTINEL : value\n\n // Delete if exists to move to end\n if (this.cache.has(key)) {\n this.cache.delete(key)\n }\n this.cache.set(key, wrappedValue)\n\n // Evict oldest (first) entry if size exceeded\n if (this.cache.size > this.maxSize) {\n const firstKey = this.cache.keys().next().value\n if (firstKey !== undefined) {\n this.cache.delete(firstKey)\n }\n }\n }\n\n has(key: K): boolean {\n return this.cache.has(key)\n }\n}\n\n/**\n * Create plugin-aware executor using Proxy\n * Optimized with LRU caching and Set-based lookups\n *\n * @param db - Kysely database instance\n * @param interceptors - Plugins with interceptQuery methods\n * @param allPlugins - All registered plugins\n * @param currentSchema - Optional schema context (from withSchema)\n */\nfunction createProxy<DB>(\n db: Kysely<DB>,\n interceptors: readonly Plugin[],\n allPlugins: readonly Plugin[],\n currentSchema?: string\n): KyseraExecutor<DB> {\n // Cache for bound methods to avoid repeated .bind() allocations\n const methodCache = new Map<string | symbol, unknown>()\n\n // Cache intercepted methods to avoid repeated creation\n const interceptedCache = new Map<string, (table: string) => unknown>()\n\n // Cached transaction wrapper (created once, reused)\n let cachedTransactionWrapper:\n | (() => { execute: <T>(fn: (trx: Transaction<DB>) => Promise<T>) => Promise<T> })\n | null = null\n\n // LRU cache for withSchema to prevent unbounded growth (max 100 schemas)\n const schemaProxyCache = new LRUCache<string, KyseraExecutor<DB>>(MAX_CACHE_SIZE)\n\n const handler: ProxyHandler<Kysely<DB>> = {\n // Handle 'in' operator for type guards\n has(target, prop) {\n if (MARKER_PROPS.has(prop)) return true\n if (prop === '__schema') return true\n return Reflect.has(target, prop)\n },\n\n get(target, prop, receiver) {\n // Fast path: marker properties (O(1) Set lookup)\n if (prop === '__kysera') return true\n if (prop === '__plugins') return allPlugins\n if (prop === '__rawDb') return target\n if (prop === '__schema') return currentSchema\n\n // Fast path: check intercepted methods first (most common hot path)\n if (typeof prop === 'string' && INTERCEPTED_METHODS_SET.has(prop)) {\n let intercepted = interceptedCache.get(prop)\n if (!intercepted) {\n intercepted = createInterceptedMethod(target, prop as InterceptedMethod, interceptors, currentSchema)\n interceptedCache.set(prop, intercepted)\n }\n return intercepted\n }\n\n // Intercept withSchema to maintain plugin proxy and track schema\n if (prop === 'withSchema') {\n return (schema: string) => {\n const cachedSchemaProxy = schemaProxyCache.get(schema)\n if (cachedSchemaProxy) {\n return cachedSchemaProxy\n }\n const schemaDb = target.withSchema(schema)\n // Pass schema to new proxy so it's available in QueryBuilderContext\n const newProxy = createProxy(schemaDb, interceptors, allPlugins, schema)\n schemaProxyCache.set(schema, newProxy)\n return newProxy\n }\n }\n\n // Intercept with() for CTEs - cache the wrapper and also wrap the result\n if (prop === 'with') {\n if (!methodCache.has('with')) {\n const withWrapper = (name: string, fn: (db: Kysely<DB>) => unknown): unknown => {\n const wrappedFn = (innerDb: Kysely<DB>): unknown =>\n fn(createProxy(innerDb, interceptors, allPlugins))\n const originalMethod = Reflect.get(target, 'with') as (\n n: string,\n f: (db: Kysely<DB>) => unknown\n ) => Kysely<DB>\n const result = originalMethod.call(target, name, wrappedFn)\n return createProxy(result, interceptors, allPlugins)\n }\n methodCache.set('with', withWrapper)\n }\n return methodCache.get('with')\n }\n\n // Intercept withRecursive() for recursive CTEs - cache the wrapper and wrap result\n if (prop === 'withRecursive') {\n if (!methodCache.has('withRecursive')) {\n const withRecursiveWrapper = (name: string, fn: (db: Kysely<DB>) => unknown): unknown => {\n const wrappedFn = (innerDb: Kysely<DB>): unknown =>\n fn(createProxy(innerDb, interceptors, allPlugins))\n const originalMethod = Reflect.get(target, 'withRecursive') as (\n n: string,\n f: (db: Kysely<DB>) => unknown\n ) => Kysely<DB>\n const result = originalMethod.call(target, name, wrappedFn)\n return createProxy(result, interceptors, allPlugins)\n }\n methodCache.set('withRecursive', withRecursiveWrapper)\n }\n return methodCache.get('withRecursive')\n }\n\n // Cached transaction wrapper\n // NOTE: Transaction API limitation - only execute() method is wrapped\n // Methods like setIsolationLevel() are not available on the wrapper\n // This is intentional: isolation level should be set before plugin interception\n // For advanced use cases, use: executor.__rawDb.transaction().setIsolationLevel(...).execute(...)\n if (prop === 'transaction') {\n if (!cachedTransactionWrapper) {\n cachedTransactionWrapper = () => ({\n execute: async <T>(fn: (trx: Transaction<DB>) => Promise<T>): Promise<T> => {\n return await target.transaction().execute(async trx => {\n /**\n * TYPE ASSERTION #2a: Transaction to Kysely for proxy creation\n *\n * Cast: Transaction<DB> -> Kysely<DB>\n *\n * Why needed:\n * - createProxy expects Kysely<DB>, not Transaction<DB>\n * - TypeScript doesn't recognize structural compatibility automatically\n *\n * Why safe:\n * - Transaction<DB> extends Kysely<DB> (verified in Kysely types)\n * - All Kysely methods are available on Transaction\n * - createProxy only accesses Kysely methods\n */\n const wrappedTrx = createProxy(\n trx as unknown as Kysely<DB>,\n interceptors,\n allPlugins\n )\n /**\n * TYPE ASSERTION #2b: Wrapped proxy back to Transaction\n *\n * Cast: KyseraExecutor<DB> -> Transaction<DB>\n *\n * Why needed:\n * - User callback expects Transaction<DB>, not KyseraExecutor<DB>\n * - Proxy wraps a Transaction but returns KyseraExecutor type\n *\n * Why safe:\n * - Original trx is Transaction<DB>\n * - Proxy preserves all Transaction methods\n * - Only adds marker properties (__kysera, __plugins, __rawDb)\n */\n return await fn(wrappedTrx as unknown as Transaction<DB>)\n })\n }\n })\n }\n return cachedTransactionWrapper\n }\n\n // Check method cache for bound functions\n if (methodCache.has(prop)) {\n return methodCache.get(prop)\n }\n\n const value = Reflect.get(target, prop, receiver)\n\n // Cache bound methods to avoid repeated .bind() allocations\n if (typeof value === 'function') {\n const bound = value.bind(target)\n methodCache.set(prop, bound)\n return bound\n }\n\n return value\n }\n }\n\n return new Proxy(db, handler) as KyseraExecutor<DB>\n}\n\n/**\n * Create a plugin-aware executor\n *\n * Zero overhead if no plugins have interceptQuery\n *\n * @param db - Kysely database instance\n * @param plugins - Array of plugins to apply\n * @param config - Optional configuration\n * @returns Plugin-aware executor\n *\n * @example\n * ```typescript\n * import { createExecutor } from '@kysera/executor';\n * import { softDeletePlugin } from '@kysera/soft-delete';\n *\n * const executor = await createExecutor(db, [softDeletePlugin()]);\n *\n * // All queries now have soft-delete filter applied\n * const users = await executor.selectFrom('users').selectAll().execute();\n * ```\n */\nexport async function createExecutor<DB>(\n db: Kysely<DB>,\n plugins: readonly Plugin[] = [],\n config: ExecutorConfig = {}\n): Promise<KyseraExecutor<DB>> {\n const { enabled = true } = config\n\n // Fast path: no plugins or disabled\n if (plugins.length === 0 || !enabled) {\n /**\n * TYPE ASSERTION #4a: Object.assign result to KyseraExecutor\n *\n * Cast: Kysely<DB> & KyseraExecutorMarker<DB> -> KyseraExecutor<DB>\n *\n * Why needed:\n * - Object.assign returns intersection type (Kysely & Marker)\n * - KyseraExecutor is defined as: type KyseraExecutor<DB> = Kysely<DB> & KyseraExecutorMarker<DB>\n * - TypeScript treats intersection types different from type aliases\n *\n * Why safe:\n * - We're adding exactly the marker properties defined in KyseraExecutorMarker\n * - Runtime type is identical to KyseraExecutor type definition\n * - No structural difference between intersection and type alias at runtime\n */\n return Object.assign(db, {\n __kysera: true as const,\n __plugins: plugins,\n __rawDb: db\n }) as KyseraExecutor<DB>\n }\n\n // Validate and sort plugins\n validatePlugins(plugins)\n const sorted = resolvePluginOrder(plugins)\n\n // Initialize plugins with error handling\n for (const plugin of sorted) {\n try {\n await plugin.onInit?.(db)\n } catch (error) {\n throw new PluginValidationError(\n `Plugin \"${plugin.name}\" failed to initialize: ${error instanceof Error ? error.message : String(error)}`,\n 'INITIALIZATION_FAILED',\n { pluginName: plugin.name }\n )\n }\n }\n\n // Filter plugins with interceptQuery for performance\n const interceptors = sorted.filter(p => p.interceptQuery)\n\n // Fast path: no interceptors\n if (interceptors.length === 0) {\n /**\n * TYPE ASSERTION #4b: Object.assign result to KyseraExecutor (no interceptors)\n *\n * Same as #4a but with sorted plugins instead of input plugins.\n * This path is for plugins that have onInit but no interceptQuery.\n */\n return Object.assign(db, {\n __kysera: true as const,\n __plugins: sorted,\n __rawDb: db\n }) as KyseraExecutor<DB>\n }\n\n // Create proxy with interception\n return createProxy(db, interceptors, sorted)\n}\n\n/**\n * Creates executor synchronously WITHOUT calling plugin onInit hooks.\n *\n * @warning This function skips plugin initialization. Use createExecutor()\n * instead unless you are certain plugins don't need async initialization.\n *\n * Use cases where this is safe:\n * - Plugins without onInit hooks\n * - Plugins with synchronous-only initialization\n * - Testing scenarios where initialization is handled separately\n *\n * @param db - Kysely database instance\n * @param plugins - Array of plugins to apply\n * @param config - Optional configuration\n * @returns Plugin-aware executor (without onInit called)\n *\n * @example\n * ```typescript\n * // Use for simple plugins without async init:\n * const executor = createExecutorSync(db, [simplePlugin]);\n *\n * // WARNING: Plugin onInit hooks are NOT called!\n * // If your plugin requires initialization, use createExecutor() instead.\n * ```\n */\nexport function createExecutorSync<DB>(\n db: Kysely<DB>,\n plugins: readonly Plugin[] = [],\n config: ExecutorConfig = {}\n): KyseraExecutor<DB> {\n const { enabled = true } = config\n\n if (plugins.length === 0 || !enabled) {\n /**\n * TYPE ASSERTION #4c: Object.assign in createExecutorSync (no plugins/disabled)\n *\n * Same as #4a - see explanation there.\n */\n return Object.assign(db, {\n __kysera: true as const,\n __plugins: plugins,\n __rawDb: db\n }) as KyseraExecutor<DB>\n }\n\n validatePlugins(plugins)\n const sorted = resolvePluginOrder(plugins)\n const interceptors = sorted.filter(p => p.interceptQuery)\n\n if (interceptors.length === 0) {\n /**\n * TYPE ASSERTION #4d: Object.assign in createExecutorSync (no interceptors)\n *\n * Same as #4b - see explanation there.\n */\n return Object.assign(db, {\n __kysera: true as const,\n __plugins: sorted,\n __rawDb: db\n }) as KyseraExecutor<DB>\n }\n\n return createProxy(db, interceptors, sorted)\n}\n\n/**\n * Check if value is a KyseraExecutor\n */\nexport function isKyseraExecutor<DB>(\n value: Kysely<DB> | KyseraExecutor<DB>\n): value is KyseraExecutor<DB> {\n return '__kysera' in value && value.__kysera\n}\n\n/**\n * Get plugins from executor\n */\nexport function getPlugins<DB>(executor: KyseraExecutor<DB>): readonly Plugin[] {\n return executor.__plugins\n}\n\n/**\n * Wrap transaction with plugins\n */\nexport function wrapTransaction<DB>(\n trx: Transaction<DB>,\n plugins: readonly Plugin[]\n): KyseraTransaction<DB> {\n const interceptors = plugins.filter(p => p.interceptQuery)\n\n if (interceptors.length === 0) {\n /**\n * TYPE ASSERTION #4e: Object.assign for transaction wrapping (no interceptors)\n *\n * Cast: Transaction<DB> & KyseraExecutorMarker<DB> -> KyseraTransaction<DB>\n *\n * Similar to #4a but for Transaction type instead of Kysely type.\n * KyseraTransaction is defined as: type KyseraTransaction<DB> = Transaction<DB> & KyseraExecutorMarker<DB>\n */\n return Object.assign(trx, {\n __kysera: true as const,\n __plugins: plugins,\n __rawDb: trx\n }) as KyseraTransaction<DB>\n }\n\n /**\n * TYPE ASSERTION #5: wrapTransaction cast chain\n *\n * Double cast: Transaction<DB> -> Kysely<DB> -> KyseraExecutor<DB> -> KyseraTransaction<DB>\n *\n * Why needed:\n * - createProxy expects Kysely<DB> and returns KyseraExecutor<DB>\n * - We need to return KyseraTransaction<DB>\n * - TypeScript doesn't recognize that KyseraExecutor wrapping a Transaction is compatible with KyseraTransaction\n *\n * Why safe:\n * - Transaction<DB> extends Kysely<DB> (first cast is upcast)\n * - createProxy preserves all methods (adds marker properties only)\n * - KyseraTransaction<DB> = Transaction<DB> & Marker, KyseraExecutor<DB> = Kysely<DB> & Marker\n * - Since original is Transaction, wrapped result is structurally KyseraTransaction\n * - Verified in integration tests (packages/executor/test/executor.test.ts)\n */\n return createProxy(\n trx as unknown as Kysely<DB>,\n interceptors,\n plugins\n ) as unknown as KyseraTransaction<DB>\n}\n\n/**\n * Apply plugins to a query builder manually\n * Useful for complex queries that bypass normal interception\n */\nexport function applyPlugins<QB>(\n qb: QB,\n plugins: readonly Plugin[],\n context: QueryBuilderContext\n): QB {\n let result = qb\n for (const plugin of plugins) {\n if (plugin.interceptQuery) {\n result = plugin.interceptQuery(result, context)\n }\n }\n return result\n}\n\n/**\n * Get raw Kysely instance from executor, bypassing plugin interceptors.\n * Returns the executor itself if it's not a KyseraExecutor.\n *\n * Useful for plugins that need to:\n * - Perform internal queries without triggering interceptors\n * - Avoid double-filtering (e.g., soft-delete checking its own records)\n * - Access the underlying Kysely instance for advanced operations\n *\n * @param executor - Kysely or KyseraExecutor instance\n * @returns Raw Kysely instance without plugin interception\n *\n * @example\n * ```typescript\n * // Inside a plugin's extendRepository:\n * const rawDb = getRawDb(baseRepo.executor);\n * // This query bypasses all plugin interceptors\n * const result = await rawDb.selectFrom('users').selectAll().execute();\n * ```\n */\nexport function getRawDb<DB>(executor: Kysely<DB>): Kysely<DB> {\n /**\n * TYPE ASSERTION #6: getRawDb executor check\n *\n * Cast: Kysely<DB> -> KyseraExecutor<DB>\n *\n * Why needed:\n * - Need to check if executor has __rawDb property\n * - Plain Kysely<DB> doesn't have __rawDb, only KyseraExecutor<DB> does\n * - TypeScript doesn't allow property access without type assertion\n *\n * Why safe:\n * - Optional chaining (??) handles both cases gracefully:\n * - If KyseraExecutor: __rawDb exists and is returned\n * - If plain Kysely: __rawDb is undefined, executor is returned\n * - No runtime error possible - undefined ?? executor always succeeds\n * - Type guard alternative would be more verbose with same behavior\n */\n const kyseraExecutor = executor as unknown as KyseraExecutor<DB>\n return kyseraExecutor.__rawDb ?? executor\n}\n\n/**\n * Destroy executor and call onDestroy for all plugins\n *\n * @param executor - KyseraExecutor instance to destroy\n *\n * @example\n * ```typescript\n * const executor = await createExecutor(db, [myPlugin]);\n * // ... use executor ...\n * await destroyExecutor(executor); // Calls onDestroy on all plugins\n * await db.destroy(); // Then destroy underlying Kysely instance\n * ```\n */\nexport async function destroyExecutor<DB>(executor: KyseraExecutor<DB>): Promise<void> {\n const plugins = executor.__plugins\n\n // Call onDestroy in reverse order (cleanup in reverse of initialization)\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin?.onDestroy) {\n await plugin.onDestroy()\n }\n }\n}\n","/**\n * Schema Plugin - Unified schema management for Kysera\n *\n * This plugin provides centralized schema configuration and validation\n * for multi-tenant and modular database architectures.\n *\n * @example\n * // Basic usage with default schema\n * const executor = await createExecutor(db, [\n * schemaPlugin({ defaultSchema: 'auth' })\n * ])\n *\n * @example\n * // Multi-tenant with withSchema()\n * const executor = await createExecutor(db, [\n * schemaPlugin({\n * defaultSchema: 'public',\n * allowedSchemas: ['public', 'tenant_a', 'tenant_b', 'tenant_c']\n * })\n * ])\n * // Use withSchema() to set tenant schema per-request\n * const tenantDb = executor.withSchema(`tenant_${tenantId}`)\n * const users = await tenantDb.selectFrom('users').selectAll().execute()\n */\n\nimport type { Plugin, QueryBuilderContext } from '../types.js'\n\n/**\n * Configuration options for the Schema Plugin\n */\nexport interface SchemaPluginOptions {\n /**\n * Default schema for all queries.\n * Used when no schema is resolved dynamically.\n * @default 'public'\n */\n defaultSchema?: string\n\n /**\n * Dynamic schema resolver function.\n * Called for each query to determine the schema.\n * If returns undefined, defaultSchema is used.\n *\n * The resolver receives `context.schema` which is set when `withSchema()` is called.\n * This allows you to add validation or transformation logic on top of withSchema().\n *\n * @param context - Query builder context with operation, table, schema, and metadata\n * @returns Schema name or undefined to use default\n *\n * @example\n * // Use schema set by withSchema() with fallback\n * resolveSchema: (ctx) => ctx.schema ?? 'public'\n *\n * @example\n * // Schema based on table name (auto-routing)\n * resolveSchema: (ctx) => {\n * if (ctx.table.startsWith('auth_')) return 'auth'\n * if (ctx.table.startsWith('admin_')) return 'admin'\n * return ctx.schema // use withSchema() value or default\n * }\n */\n resolveSchema?: (context: QueryBuilderContext) => string | undefined\n\n /**\n * Async schema validator.\n * Called during plugin initialization to validate the default schema.\n *\n * @param schema - Schema name to validate\n * @returns true if valid, false otherwise\n *\n * @example\n * validateSchema: async (schema) => {\n * return await adapter.schemaExists(db, schema)\n * }\n */\n validateSchema?: (schema: string) => boolean | Promise<boolean>\n\n /**\n * Whitelist of allowed schemas.\n * If set, only these schemas can be used.\n * Queries with other schemas will throw an error.\n *\n * @example\n * allowedSchemas: ['public', 'auth', 'admin']\n */\n allowedSchemas?: string[]\n\n /**\n * Whether to throw an error when schema validation fails.\n * If false, falls back to defaultSchema.\n * @default true\n */\n strictValidation?: boolean\n}\n\n/**\n * Error thrown when schema validation fails\n */\nexport class SchemaValidationError extends Error {\n constructor(\n message: string,\n public readonly schema: string,\n public readonly allowedSchemas?: string[]\n ) {\n super(message)\n this.name = 'SchemaValidationError'\n }\n}\n\n/**\n * Create a Schema Plugin for unified schema management.\n *\n * The plugin provides:\n * - Default schema configuration\n * - Dynamic schema resolution per query\n * - Schema whitelist validation\n * - Schema metadata in QueryBuilderContext\n *\n * @param options - Plugin configuration options\n * @returns Plugin instance\n *\n * @example\n * // Simple default schema\n * const executor = await createExecutor(db, [\n * schemaPlugin({ defaultSchema: 'app' })\n * ])\n *\n * @example\n * // Multi-tenant application with schema validation\n * const executor = await createExecutor(db, [\n * schemaPlugin({\n * defaultSchema: 'public',\n * allowedSchemas: ['public', 'tenant_a', 'tenant_b', 'tenant_c'],\n * strictValidation: true // throws if schema not in whitelist\n * })\n * ])\n *\n * // Per-request: use withSchema() to set tenant context\n * app.use((req, res, next) => {\n * req.db = executor.withSchema(`tenant_${req.tenantId}`)\n * next()\n * })\n */\nexport function schemaPlugin(options: SchemaPluginOptions = {}): Plugin {\n const {\n defaultSchema = 'public',\n resolveSchema,\n validateSchema,\n allowedSchemas,\n strictValidation = true\n } = options\n\n // Pre-compute allowed schemas set for O(1) lookup\n const allowedSet = allowedSchemas ? new Set(allowedSchemas) : null\n\n return {\n name: '@kysera/schema',\n version: '1.0.0',\n priority: 1000, // Run early to set schema context for other plugins\n\n async onInit(_db) {\n // Validate default schema during initialization\n if (validateSchema) {\n const isValid = await validateSchema(defaultSchema)\n if (!isValid) {\n throw new SchemaValidationError(\n `Invalid default schema: ${defaultSchema}`,\n defaultSchema\n )\n }\n }\n\n // Validate allowed schemas if whitelist is provided\n if (allowedSet && !allowedSet.has(defaultSchema)) {\n const allowedList = allowedSchemas ? allowedSchemas.join(', ') : ''\n throw new SchemaValidationError(\n `Default schema \"${defaultSchema}\" is not in allowed list: [${allowedList}]`,\n defaultSchema,\n allowedSchemas\n )\n }\n },\n\n interceptQuery<QB>(qb: QB, context: QueryBuilderContext): QB {\n // Resolve schema for this query\n let schema = resolveSchema?.(context) ?? context.schema ?? defaultSchema\n\n // Validate against whitelist\n if (allowedSet && !allowedSet.has(schema)) {\n if (strictValidation) {\n const allowedList = allowedSchemas ? allowedSchemas.join(', ') : ''\n throw new SchemaValidationError(\n `Schema \"${schema}\" is not in allowed list: [${allowedList}]`,\n schema,\n allowedSchemas\n )\n }\n // Fall back to default if not strict\n schema = defaultSchema\n }\n\n // Store resolved schema in metadata for other plugins\n context.metadata['__resolvedSchema'] = schema\n\n return qb\n }\n }\n}\n\n/**\n * Get the resolved schema from query context metadata.\n * Useful for plugins that need to access the schema set by schemaPlugin.\n *\n * @param context - Query builder context\n * @returns Resolved schema or undefined\n *\n * @example\n * // In another plugin's interceptQuery\n * interceptQuery(qb, context) {\n * const schema = getResolvedSchema(context)\n * if (schema === 'admin') {\n * // Apply admin-specific logic\n * }\n * return qb\n * }\n */\nexport function getResolvedSchema(context: QueryBuilderContext): string | undefined {\n return context.metadata['__resolvedSchema'] as string | undefined\n}\n"]}
{
"name": "@kysera/executor",
"version": "0.8.4",
"version": "0.8.5",
"description": "Unified Execution Layer for Kysera - Plugin-aware Kysely wrapper",

@@ -5,0 +5,0 @@ "type": "module",

@@ -14,9 +14,12 @@ /**

* @example
* // Multi-tenant with dynamic schema resolution
* // Multi-tenant with withSchema()
* const executor = await createExecutor(db, [
* schemaPlugin({
* resolveSchema: (ctx) => ctx.metadata.tenantSchema as string,
* allowedSchemas: ['tenant_a', 'tenant_b', 'tenant_c']
* defaultSchema: 'public',
* allowedSchemas: ['public', 'tenant_a', 'tenant_b', 'tenant_c']
* })
* ])
* // Use withSchema() to set tenant schema per-request
* const tenantDb = executor.withSchema(`tenant_${tenantId}`)
* const users = await tenantDb.selectFrom('users').selectAll().execute()
*/

@@ -42,15 +45,18 @@

*
* @param context - Query builder context with operation, table, and metadata
* The resolver receives `context.schema` which is set when `withSchema()` is called.
* This allows you to add validation or transformation logic on top of withSchema().
*
* @param context - Query builder context with operation, table, schema, and metadata
* @returns Schema name or undefined to use default
*
* @example
* // Resolve schema from metadata
* resolveSchema: (ctx) => ctx.metadata.tenantSchema as string
* // Use schema set by withSchema() with fallback
* resolveSchema: (ctx) => ctx.schema ?? 'public'
*
* @example
* // Schema based on table name
* // Schema based on table name (auto-routing)
* resolveSchema: (ctx) => {
* if (ctx.table.startsWith('auth_')) return 'auth'
* if (ctx.table.startsWith('admin_')) return 'admin'
* return undefined // use default
* return ctx.schema // use withSchema() value or default
* }

@@ -125,13 +131,16 @@ */

* @example
* // Multi-tenant application
* // Multi-tenant application with schema validation
* const executor = await createExecutor(db, [
* schemaPlugin({
* defaultSchema: 'public',
* resolveSchema: (ctx) => {
* const tenantId = ctx.metadata.tenantId as string
* return tenantId ? `tenant_${tenantId}` : undefined
* },
* allowedSchemas: ['public', 'tenant_a', 'tenant_b']
* allowedSchemas: ['public', 'tenant_a', 'tenant_b', 'tenant_c'],
* strictValidation: true // throws if schema not in whitelist
* })
* ])
*
* // Per-request: use withSchema() to set tenant context
* app.use((req, res, next) => {
* req.db = executor.withSchema(`tenant_${req.tenantId}`)
* next()
* })
*/

@@ -138,0 +147,0 @@ export function schemaPlugin(options: SchemaPluginOptions = {}): Plugin {