@kysera/dal
Advanced tools
+16
-3
| import { Kysely, Transaction } from 'kysely'; | ||
| import { KyseraExecutor, KyseraTransaction } from '@kysera/executor'; | ||
| export { AnyKyseraExecutor, ExecutorConfig, KyseraExecutor, KyseraExecutorMarker, KyseraTransaction, Plugin, PluginValidationDetails, PluginValidationError, PluginValidationErrorType, QueryBuilderContext } from '@kysera/executor'; | ||
| import { KyseraLogger } from '@kysera/core'; | ||
| import { DatabaseError, KyseraLogger } from '@kysera/core'; | ||
@@ -102,5 +102,9 @@ /** | ||
| */ | ||
| /** | ||
| * Error thrown when a query that requires a transaction is called outside of a transaction context. | ||
| * | ||
| * Extends DatabaseError from @kysera/core for consistent error hierarchy | ||
| * with `.code` and `.toJSON()` support. | ||
| * | ||
| * @example | ||
@@ -121,3 +125,3 @@ * ```typescript | ||
| */ | ||
| declare class TransactionRequiredError extends Error { | ||
| declare class TransactionRequiredError extends DatabaseError { | ||
| /** | ||
@@ -371,2 +375,11 @@ * Create a new TransactionRequiredError | ||
| declare function isInTransaction<DB>(ctx: DbContext<DB>): boolean; | ||
| /** | ||
| * Normalize input to DbContext. | ||
| * Accepts either a DbContext (returned as-is) or a database/executor instance | ||
| * (wrapped in a new context). | ||
| * | ||
| * @param ctxOrDb - Database context or database instance | ||
| * @returns Database context | ||
| */ | ||
| declare function toContext<DB>(ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>): DbContext<DB>; | ||
@@ -665,2 +678,2 @@ /** | ||
| export { type CreateContextOptions, DB_CONTEXT_SYMBOL, type DbContext, IN_TRANSACTION_SYMBOL, type InferArgs, type InferDB, type InferResult, type ParallelResult, type QueryFunction, SAVEPOINT_COUNTER_SYMBOL, type TransactionOptions, type TransactionOptionsWithLogger, TransactionRequiredError, chain, compose, conditional, createContext, createQuery, createSchemaContext, createTransactionalQuery, isDbContext, isInTransaction, mapResult, parallel, withContext, withTransaction }; | ||
| export { type CreateContextOptions, DB_CONTEXT_SYMBOL, type DbContext, IN_TRANSACTION_SYMBOL, type InferArgs, type InferDB, type InferResult, type ParallelResult, type QueryFunction, SAVEPOINT_COUNTER_SYMBOL, type TransactionOptions, type TransactionOptionsWithLogger, TransactionRequiredError, chain, compose, conditional, createContext, createQuery, createSchemaContext, createTransactionalQuery, isDbContext, isInTransaction, mapResult, parallel, toContext, withContext, withTransaction }; |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import {isKyseraExecutor,wrapTransaction,getPlugins}from'@kysera/executor';export{PluginValidationError}from'@kysera/executor';import {sql}from'kysely';import {silentLogger,detectDialect}from'@kysera/core';var m=Symbol.for("kysera.DbContext"),g=Symbol.for("kysera.InTransaction"),p=Symbol.for("kysera.SavepointCounter");function T(t){return typeof t=="object"&&t!==null&&m in t}var b=class t extends Error{constructor(e="Query requires a transaction. Use withTransaction() to execute this query."){super(e),this.name="TransactionRequiredError",Error.captureStackTrace&&Error.captureStackTrace(this,t);}};function h(t){return d(t)||F(t)?true:"isTransaction"in t&&t.isTransaction}function F(t){if(!("__kysera"in t&&t.__kysera))return false;let e=t;return e.__rawDb?e.__rawDb.isTransaction===true:false}function l(t,e){let n=typeof e=="boolean"?{isTransaction:e}:e??{},{isTransaction:r,schema:o}=n,a=r??h(t),s=o?t.withSchema(o):t,y={[m]:true,db:s,isTransaction:a};return o!==void 0?{...y,schema:o}:y}function Q(t,e){return l(t,{schema:e})}function d(t){return t[g]===true}function O(t){return t[g]=true,t}function S(t){let e=t,r=(e[p]??0)+1;if(!Number.isInteger(r)||r<1||r>1e6)throw new Error("Invalid savepoint counter: expected positive integer, got "+String(r));return e[p]=r,r}async function I(t,e,n={}){let{logger:r=silentLogger,rollbackErrorMode:o="log-only",onRollbackError:a}=n,s=T(t)?t.db:t;if(d(s)){let B=S(s),i="kysera_sp_"+String(B),D=detectDialect(s);try{D==="mssql"?await sql`SAVE TRANSACTION ${sql.id(i)}`.execute(s):await sql`SAVEPOINT ${sql.id(i)}`.execute(s);let x=T(t)?t.schema:void 0,u={[m]:!0,db:s,isTransaction:!0},C=x!==void 0?{...u,schema:x}:u,w=await e(C);return D!=="mssql"&&await sql`RELEASE SAVEPOINT ${sql.id(i)}`.execute(s),w}catch(x){try{D==="mssql"?await sql`ROLLBACK TRANSACTION ${sql.id(i)}`.execute(s):await sql`ROLLBACK TO SAVEPOINT ${sql.id(i)}`.execute(s);}catch(u){switch(o){case "throw":throw u;case "callback":a&&await a(x,u);break;case "log-only":default:r.error("Savepoint rollback failed for "+i+":",u);break}}throw x}}let y=s.transaction();return n.isolationLevel&&y.setIsolationLevel(n.isolationLevel),await y.execute(async B=>{let i=isKyseraExecutor(s)?wrapTransaction(B,getPlugins(s)):B;O(i),i[p]=0;let D=T(t)?t.schema:void 0,x=D?i.withSchema(D):i,u={[m]:true,db:x,isTransaction:true},C=D!==void 0?{...u,schema:D}:u;return await e(C)})}async function _(t,e){let n=l(t);return await e(n)}function N(t){return t.isTransaction}function A(t){return (e,...n)=>{let r=T(e)?e:l(e);return t(r,...n)}}function L(t){return A(async(e,...n)=>{if(!e.isTransaction)throw new b;return await t(e,...n)})}function f(t){return T(t)?t:l(t)}function v(t,e){return async(n,...r)=>{let o=f(n),a=await t(o,...r);return await e(o,a)}}function q(t,...e){return async(n,...r)=>{let o=f(n),a=await t(o,...r);for(let s of e)a=await s(o,a);return a}}function M(t){return async(e,...n)=>{let r=f(e),o=Object.entries(t),a=await Promise.all(o.map(async([s,y])=>{let B=await y(r,...n);return [s,B]}));return Object.fromEntries(a)}}function V(t,e,n){return async(r,...o)=>{let a=f(r);return await t(a,...o)?await e(a,...o):n}}function Y(t,e){return async(n,...r)=>{let o=f(n);return (await t(o,...r)).map(e)}}export{m as DB_CONTEXT_SYMBOL,g as IN_TRANSACTION_SYMBOL,p as SAVEPOINT_COUNTER_SYMBOL,b as TransactionRequiredError,q as chain,v as compose,V as conditional,l as createContext,A as createQuery,Q as createSchemaContext,L as createTransactionalQuery,T as isDbContext,N as isInTransaction,Y as mapResult,M as parallel,_ as withContext,I as withTransaction};//# sourceMappingURL=index.js.map | ||
| import {isKyseraExecutor,wrapTransaction,getPlugins}from'@kysera/executor';export{PluginValidationError}from'@kysera/executor';import {DatabaseError,ErrorCodes,silentLogger,detectDialect}from'@kysera/core';import {sql}from'kysely';var B=Symbol.for("kysera.DbContext"),f=Symbol.for("kysera.InTransaction"),p=Symbol.for("kysera.SavepointCounter");function m(t){return typeof t=="object"&&t!==null&&B in t}var b=class extends DatabaseError{constructor(e="Query requires a transaction. Use withTransaction() to execute this query."){super(e,ErrorCodes.DB_TRANSACTION_FAILED),this.name="TransactionRequiredError";}};function O(t){return g(t)||Q(t)?true:"isTransaction"in t&&t.isTransaction}function Q(t){if(!("__kysera"in t&&t.__kysera))return false;let e=t;return e.__rawDb?e.__rawDb.isTransaction===true:false}function C(t,e){let n=typeof e=="boolean"?{isTransaction:e}:e??{},{isTransaction:r,schema:o}=n,a=r??O(t),s=o?t.withSchema(o):t,y={[B]:true,db:s,isTransaction:a};return o!==void 0?{...y,schema:o}:y}function S(t,e){return C(t,{schema:e})}function g(t){return t[f]===true}function I(t){return t[f]=true,t}function _(t){let e=t,r=(e[p]??0)+1;if(!Number.isInteger(r)||r<1||r>1e6)throw new Error("Invalid savepoint counter: expected positive integer, got "+String(r));return e[p]=r,r}async function N(t,e,n={}){let{logger:r=silentLogger,rollbackErrorMode:o="log-only",onRollbackError:a}=n,s=m(t)?t.db:t;if(g(s)){let l=_(s),i="kysera_sp_"+String(l),D=detectDialect(s);try{D==="mssql"?await sql`SAVE TRANSACTION ${sql.id(i)}`.execute(s):await sql`SAVEPOINT ${sql.id(i)}`.execute(s);let x=m(t)?t.schema:void 0,u={[B]:!0,db:s,isTransaction:!0},d=x!==void 0?{...u,schema:x}:u,w=await e(d);return D!=="mssql"&&await sql`RELEASE SAVEPOINT ${sql.id(i)}`.execute(s),w}catch(x){try{D==="mssql"?await sql`ROLLBACK TRANSACTION ${sql.id(i)}`.execute(s):await sql`ROLLBACK TO SAVEPOINT ${sql.id(i)}`.execute(s);}catch(u){switch(o){case "throw":throw u;case "callback":a&&await a(x,u);break;case "log-only":default:r.error("Savepoint rollback failed for "+i+":",u);break}}throw x}}let y=s.transaction();return n.isolationLevel&&y.setIsolationLevel(n.isolationLevel),await y.execute(async l=>{let i=isKyseraExecutor(s)?wrapTransaction(l,getPlugins(s)):l;I(i),i[p]=0;let D=m(t)?t.schema:void 0,x=D?i.withSchema(D):i,u={[B]:true,db:x,isTransaction:true},d=D!==void 0?{...u,schema:D}:u;return await e(d)})}async function L(t,e){let n=C(t);return await e(n)}function v(t){return t.isTransaction}function T(t){return m(t)?t:C(t)}function A(t){return (e,...n)=>{let r=T(e);return t(r,...n)}}function q(t){return A(async(e,...n)=>{if(!e.isTransaction)throw new b;return await t(e,...n)})}function M(t,e){return async(n,...r)=>{let o=T(n),a=await t(o,...r);return await e(o,a)}}function V(t,...e){return async(n,...r)=>{let o=T(n),a=await t(o,...r);for(let s of e)a=await s(o,a);return a}}function Y(t){return async(e,...n)=>{let r=T(e),o=Object.entries(t),a=await Promise.all(o.map(async([s,y])=>{let l=await y(r,...n);return [s,l]}));return Object.fromEntries(a)}}function j(t,e,n){return async(r,...o)=>{let a=T(r);return await t(a,...o)?await e(a,...o):n}}function W(t,e){return async(n,...r)=>{let o=T(n);return (await t(o,...r)).map(e)}}export{B as DB_CONTEXT_SYMBOL,f as IN_TRANSACTION_SYMBOL,p as SAVEPOINT_COUNTER_SYMBOL,b as TransactionRequiredError,V as chain,M as compose,j as conditional,C as createContext,A as createQuery,S as createSchemaContext,q as createTransactionalQuery,m as isDbContext,v as isInTransaction,W as mapResult,Y as parallel,T as toContext,L as withContext,N as withTransaction};//# sourceMappingURL=index.js.map | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/types.ts","../src/errors.ts","../src/context.ts","../src/query.ts","../src/compose.ts"],"names":["DB_CONTEXT_SYMBOL","IN_TRANSACTION_SYMBOL","SAVEPOINT_COUNTER_SYMBOL","isDbContext","obj","TransactionRequiredError","_TransactionRequiredError","message","detectTransaction","db","hasInTransactionMarker","isKyseraTransaction","executor","createContext","options","opts","isTransactionOverride","schema","inTransaction","dbWithSchema","baseContext","createSchemaContext","markAsInTransaction","incrementSavepointCounter","nextId","withTransaction","fn","logger","silentLogger","rollbackErrorMode","onRollbackError","actualDb","savepointId","savepointName","dialect","detectDialect","sql","parentSchema","baseCtx","ctx","result","error","rollbackError","transactionBuilder","trx","wrappedTrx","isKyseraExecutor","wrapTransaction","getPlugins","baseTrxCtx","withContext","isInTransaction","createQuery","queryFn","dbOrCtx","args","createTransactionalQuery","toContext","ctxOrDb","compose","first","second","firstResult","chain","query","transforms","transform","parallel","queries","entries","results","key","conditional","condition","fallback","mapResult","mapper"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Core types for Functional DAL.\n *\n * @module @kysera/dal\n */\n\nimport type { Kysely, Transaction } from 'kysely'\nimport type { KyseraExecutor, KyseraTransaction } from '@kysera/executor'\n\n/**\n * Symbol used to reliably identify DbContext objects.\n * Using Symbol.for() ensures the same symbol is used across different module instances.\n */\nexport const DB_CONTEXT_SYMBOL: unique symbol = Symbol.for('kysera.DbContext')\n\n/**\n * Symbol used to mark that we are inside a transaction (for nested transaction detection).\n */\nexport const IN_TRANSACTION_SYMBOL: unique symbol = Symbol.for('kysera.InTransaction')\n\n/**\n * Symbol used to track savepoint nesting depth for unique savepoint names.\n */\nexport const SAVEPOINT_COUNTER_SYMBOL: unique symbol = Symbol.for('kysera.SavepointCounter')\n\n/**\n * Database context for query functions.\n *\n * Supports both raw Kysely instances and plugin-aware KyseraExecutor.\n * When using KyseraExecutor, all queries automatically have plugins applied.\n *\n * @typeParam DB - Database schema type, defaults to Record<string, unknown>\n */\nexport interface DbContext<DB = Record<string, unknown>> {\n /** Marker symbol for reliable type detection */\n readonly [DB_CONTEXT_SYMBOL]: true\n /** Database or transaction instance (raw or plugin-aware) */\n readonly db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>\n /** Whether the context is within a transaction */\n readonly isTransaction: boolean\n /**\n * Current PostgreSQL schema context.\n * When set, queries use this schema instead of the default.\n * undefined means using the default schema (typically 'public').\n *\n * @example\n * ```typescript\n * const ctx = createSchemaContext(executor, 'tenant_123')\n * // All queries through ctx.db will use the 'tenant_123' schema\n * ```\n */\n readonly schema?: string\n}\n\n/**\n * Options for transaction execution.\n */\nexport interface TransactionOptions {\n /**\n * Isolation level for the transaction.\n * Note: Kysely's setIsolationLevel must be called on the transaction builder.\n */\n isolationLevel?: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable'\n}\n\n/**\n * Query function signature.\n *\n * A query function accepts database context or any database instance and arguments,\n * returning a Promise with the result.\n *\n * Supports:\n * - `DbContext<DB>` - Explicit context (inside `withTransaction`)\n * - `Kysely<DB>` - Raw Kysely instance\n * - `KyseraExecutor<DB>` - Plugin-aware executor (recommended)\n *\n * @typeParam DB - Database schema type\n * @typeParam TArgs - Tuple of argument types\n * @typeParam TResult - Return type\n */\nexport type QueryFunction<DB, TArgs extends readonly unknown[], TResult> = (\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>,\n ...args: TArgs\n) => Promise<TResult>\n\n/**\n * Infer result type from a query function.\n */\nexport type InferResult<T> =\n T extends QueryFunction<Record<string, unknown>, readonly unknown[], infer R> ? R : never\n\n/**\n * Infer arguments type from a query function.\n */\nexport type InferArgs<T> =\n T extends QueryFunction<Record<string, unknown>, infer A, unknown> ? A : never\n\n/**\n * Infer database type from a query function.\n */\nexport type InferDB<T> = T extends QueryFunction<infer DB, readonly unknown[], unknown> ? DB : never\n\n/**\n * Type guard to check if a value is a DbContext.\n * Uses the DB_CONTEXT_SYMBOL for reliable detection.\n */\nexport function isDbContext<DB>(obj: unknown): obj is DbContext<DB> {\n return typeof obj === 'object' && obj !== null && DB_CONTEXT_SYMBOL in obj\n}\n","/**\n * Custom error classes for @kysera/dal\n *\n * @module @kysera/dal\n */\n\n/**\n * Error thrown when a query that requires a transaction is called outside of a transaction context.\n *\n * @example\n * ```typescript\n * import { createTransactionalQuery, withTransaction } from '@kysera/dal';\n *\n * const transferFunds = createTransactionalQuery(async (ctx, from, to, amount) => {\n * // ... transfer logic\n * });\n *\n * // This throws TransactionRequiredError:\n * await transferFunds(db, 1, 2, 100);\n *\n * // This works:\n * await withTransaction(db, (ctx) => transferFunds(ctx, 1, 2, 100));\n * ```\n */\nexport class TransactionRequiredError extends Error {\n /**\n * Create a new TransactionRequiredError\n *\n * @param message - Error message\n */\n constructor(\n message = 'Query requires a transaction. Use withTransaction() to execute this query.'\n ) {\n super(message)\n this.name = 'TransactionRequiredError'\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, TransactionRequiredError)\n }\n }\n}\n","/**\n * Database context and transaction utilities.\n *\n * @module @kysera/dal\n */\n\nimport type { Kysely, Transaction } from 'kysely'\nimport { sql } from 'kysely'\nimport type { KyseraExecutor, KyseraTransaction } from '@kysera/executor'\nimport { isKyseraExecutor, getPlugins, wrapTransaction } from '@kysera/executor'\nimport { silentLogger, type KyseraLogger, detectDialect } from '@kysera/core'\nimport type { DbContext, TransactionOptions } from './types.js'\nimport {\n DB_CONTEXT_SYMBOL,\n IN_TRANSACTION_SYMBOL,\n SAVEPOINT_COUNTER_SYMBOL,\n isDbContext\n} from './types.js'\n\n/**\n * Detect if a database instance is currently in a transaction.\n *\n * Uses a unified detection mechanism that checks:\n * 1. Internal IN_TRANSACTION_SYMBOL marker (set by withTransaction)\n * 2. KyseraTransaction marker (__kysera property with __rawDb.isTransaction)\n * 3. Kysely's isTransaction property (for raw Transaction instances)\n *\n * The internal marker takes precedence to ensure reliable detection\n * across nested transactions and savepoints.\n *\n * @internal\n */\nfunction detectTransaction<DB>(\n db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>\n): boolean {\n // Check internal marker first (most reliable, set by withTransaction)\n if (hasInTransactionMarker(db)) {\n return true\n }\n\n // Check for KyseraTransaction marker (wrapped transaction with plugins)\n // This ensures we don't assume Kysely internals on wrapped executors\n if (isKyseraTransaction(db)) {\n return true\n }\n\n // Fallback to Kysely's isTransaction property (for raw Transaction instances)\n // Only safe when not a KyseraExecutor (already checked above)\n return 'isTransaction' in db && db.isTransaction\n}\n\n/**\n * Check if a database instance is a KyseraTransaction.\n * KyseraTransaction has __kysera marker and wraps a Transaction instance.\n *\n * @internal\n */\nfunction isKyseraTransaction<DB>(\n db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>\n): db is KyseraTransaction<DB> {\n // Check for KyseraExecutor marker - runtime check for unknown inputs\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!('__kysera' in db && db.__kysera)) {\n return false\n }\n\n // Type assertion is safe here because we just checked __kysera exists\n const executor = db\n\n // Check if __rawDb exists (it might be missing on malformed executors)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!executor.__rawDb) {\n return false\n }\n\n // Check if the raw database is a transaction\n const rawDb = executor.__rawDb as unknown as { isTransaction?: boolean }\n return rawDb.isTransaction === true\n}\n\n/**\n * Options for creating a database context.\n */\nexport interface CreateContextOptions {\n /**\n * Override transaction detection.\n * If not provided, transaction status is auto-detected.\n */\n isTransaction?: boolean\n /**\n * PostgreSQL schema to use for all queries in this context.\n * When set, the db instance is wrapped with withSchema().\n *\n * @example 'auth', 'admin', 'tenant_123'\n */\n schema?: string\n}\n\n/**\n * Create a database context from any database instance.\n *\n * Supports raw Kysely instances and plugin-aware KyseraExecutor.\n * When using KyseraExecutor, plugins are automatically available in context.\n *\n * @param db - Kysely, KyseraExecutor, or transaction instance\n * @param options - Context options (isTransaction override, schema)\n * @returns Database context\n *\n * @example Basic usage\n * ```typescript\n * import { createContext } from \"@kysera/dal\";\n * import { createExecutor } from \"@kysera/executor\";\n *\n * const executor = await createExecutor(db, [softDeletePlugin()]);\n * const ctx = createContext(executor);\n * const user = await findUserById(ctx, 1); // soft-delete filter applied\n * ```\n *\n * @example With schema\n * ```typescript\n * const ctx = createContext(executor, { schema: 'auth' });\n * // All queries use the 'auth' schema\n * ```\n */\nexport function createContext<DB>(\n db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>,\n options?: CreateContextOptions | boolean\n): DbContext<DB> {\n // Handle backward compatibility: options can be just isTransaction boolean\n const opts: CreateContextOptions =\n typeof options === 'boolean' ? { isTransaction: options } : (options ?? {})\n\n const { isTransaction: isTransactionOverride, schema } = opts\n\n // If explicitly provided, use that value; otherwise use unified detection\n const inTransaction = isTransactionOverride ?? detectTransaction(db)\n\n // Apply schema if provided\n const dbWithSchema = schema ? db.withSchema(schema) : db\n\n // Use conditional to satisfy exactOptionalPropertyTypes\n const baseContext = {\n [DB_CONTEXT_SYMBOL]: true as const,\n db: dbWithSchema as Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>,\n isTransaction: inTransaction\n }\n\n return schema !== undefined\n ? { ...baseContext, schema }\n : baseContext\n}\n\n/**\n * Create a database context with a specific PostgreSQL schema.\n *\n * Convenience function for creating schema-scoped contexts.\n * Useful for multi-tenant applications using schema-per-tenant pattern.\n *\n * @param db - Kysely or KyseraExecutor instance\n * @param schema - PostgreSQL schema name\n * @returns Database context scoped to the specified schema\n *\n * @example Multi-tenant usage\n * ```typescript\n * import { createSchemaContext } from \"@kysera/dal\";\n *\n * // In middleware or request handler\n * const tenantSchema = `tenant_${request.tenantId}`;\n * const ctx = createSchemaContext(executor, tenantSchema);\n *\n * // All queries are scoped to tenant's schema\n * const users = await getUsers(ctx);\n * ```\n *\n * @example Domain separation\n * ```typescript\n * const authCtx = createSchemaContext(executor, 'auth');\n * const adminCtx = createSchemaContext(executor, 'admin');\n *\n * const user = await getUser(authCtx, userId);\n * const settings = await getSettings(adminCtx);\n * ```\n */\nexport function createSchemaContext<DB>(\n db: Kysely<DB> | KyseraExecutor<DB>,\n schema: string\n): DbContext<DB> {\n return createContext(db, { schema })\n}\n\n/**\n * Check if a database instance has the in-transaction marker.\n * @internal\n */\nfunction hasInTransactionMarker<DB>(\n db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>\n): boolean {\n return (db as unknown as Record<symbol, boolean>)[IN_TRANSACTION_SYMBOL] === true\n}\n\n/**\n * Mark a database instance as being in a transaction.\n * @internal\n */\nfunction markAsInTransaction<T>(obj: T): T {\n ;(obj as unknown as Record<symbol, boolean>)[IN_TRANSACTION_SYMBOL] = true\n return obj\n}\n\n/**\n * Increment and return the savepoint counter for a transaction.\n * Validates the counter to prevent SQL injection through malformed savepoint names.\n * @internal\n */\nfunction incrementSavepointCounter<DB>(\n db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>\n): number {\n const obj = db as unknown as Record<symbol, number>\n const current = obj[SAVEPOINT_COUNTER_SYMBOL] ?? 0\n const nextId = current + 1\n\n // CRITICAL: Explicit validation to prevent SQL injection\n // Savepoint ID must be a positive integer to ensure safe identifier construction\n if (!Number.isInteger(nextId) || nextId < 1 || nextId > 1_000_000) {\n throw new Error('Invalid savepoint counter: expected positive integer, got ' + String(nextId))\n }\n\n obj[SAVEPOINT_COUNTER_SYMBOL] = nextId\n return nextId\n}\n\n/**\n * Extended transaction options with logger support and rollback error handling.\n */\nexport interface TransactionOptionsWithLogger extends TransactionOptions {\n /**\n * Logger for transaction operations.\n * Defaults to silentLogger (no-op).\n */\n logger?: KyseraLogger\n /**\n * How to handle savepoint rollback errors in nested transactions.\n *\n * - 'log-only' (default): Log rollback errors but don't throw (original error is more important)\n * - 'throw': Throw rollback error instead of original error (useful for debugging)\n * - 'callback': Call onRollbackError callback with both errors (for custom handling)\n *\n * @default 'log-only'\n */\n rollbackErrorMode?: 'log-only' | 'throw' | 'callback'\n /**\n * Callback invoked when savepoint rollback fails (only used with rollbackErrorMode: 'callback').\n *\n * Receives both the original error that triggered the rollback and the rollback error.\n * This allows custom error handling logic (e.g., logging to external service, alerting, etc.)\n *\n * @param originalError - The error that caused the savepoint to rollback\n * @param rollbackError - The error that occurred during rollback\n */\n onRollbackError?: (originalError: unknown, rollbackError: unknown) => void | Promise<void>\n}\n\n/**\n * Execute a function within a transaction.\n *\n * If the database is a KyseraExecutor, plugins are automatically propagated\n * to the transaction context. Otherwise, creates a standard Kysely transaction.\n *\n * **Nested Transaction Support with Savepoints:**\n * When called within an existing transaction, this function automatically creates\n * a savepoint instead of a new transaction. If the nested operation throws an error,\n * only the savepoint is rolled back, leaving the parent transaction intact.\n *\n * @param db - Database instance (Kysely or KyseraExecutor) or DbContext\n * @param fn - Function to execute within transaction\n * @param options - Transaction options (isolation level, logger)\n * @returns Result of the function\n *\n * @example Basic usage\n * ```typescript\n * import { withTransaction } from \"@kysera/dal\";\n *\n * const result = await withTransaction(db, async (ctx) => {\n * const user = await createUser(ctx, userData);\n * const profile = await createProfile(ctx, { userId: user.id, ...profileData });\n * return { user, profile };\n * });\n * ```\n *\n * @example With KyseraExecutor (plugins propagated)\n * ```typescript\n * import { createExecutor } from \"@kysera/executor\";\n * import { withTransaction } from \"@kysera/dal\";\n *\n * const executor = await createExecutor(db, [softDeletePlugin()]);\n *\n * const result = await withTransaction(executor, async (ctx) => {\n * // All queries in transaction have soft-delete filter applied\n * const users = await getUsers(ctx);\n * return users;\n * });\n * ```\n *\n * @example Nested transactions with savepoints (automatic rollback on error)\n * ```typescript\n * await withTransaction(db, async (ctx) => {\n * const user = await createUser(ctx, userData);\n *\n * try {\n * // This nested call creates a SAVEPOINT\n * await withTransaction(ctx.db, async (nestedCtx) => {\n * await createProfile(nestedCtx, profileData);\n * throw new Error(\"Profile validation failed\");\n * });\n * } catch (error) {\n * // Profile creation rolled back to savepoint\n * // User creation still exists in transaction\n * }\n *\n * // User creation will be committed\n * });\n * ```\n *\n * @example With isolation level (top-level only)\n * ```typescript\n * await withTransaction(db, async (ctx) => {\n * // Serializable isolation for strict consistency\n * return await criticalOperation(ctx);\n * }, { isolationLevel: \"serializable\" });\n * ```\n *\n * @example With custom logger\n * ```typescript\n * import { consoleLogger } from \"@kysera/core\";\n *\n * await withTransaction(db, async (ctx) => {\n * return await operation(ctx);\n * }, { logger: consoleLogger });\n * ```\n *\n * @example With rollback error handling\n * ```typescript\n * // Throw rollback error instead of original error (debugging)\n * await withTransaction(db, async (ctx) => {\n * // ...\n * }, { rollbackErrorMode: 'throw' });\n *\n * // Custom error handling callback\n * await withTransaction(db, async (ctx) => {\n * // ...\n * }, {\n * rollbackErrorMode: 'callback',\n * onRollbackError: async (originalError, rollbackError) => {\n * await logToMonitoring({\n * type: 'savepoint_rollback_failure',\n * originalError,\n * rollbackError\n * });\n * }\n * });\n * ```\n */\n// eslint-disable-next-line complexity\nexport async function withTransaction<DB, T>(\n db: Kysely<DB> | KyseraExecutor<DB> | DbContext<DB>,\n fn: (ctx: DbContext<DB>) => Promise<T>,\n options: TransactionOptionsWithLogger = {}\n): Promise<T> {\n const { logger = silentLogger, rollbackErrorMode = 'log-only', onRollbackError } = options\n\n // Handle DbContext input - extract the db instance\n const actualDb: Kysely<DB> | KyseraExecutor<DB> = isDbContext<DB>(db)\n ? (db.db as Kysely<DB> | KyseraExecutor<DB>)\n : db\n\n // Check if we are already inside a transaction (nested call)\n if (hasInTransactionMarker(actualDb)) {\n // Use savepoint for nested transaction\n const savepointId = incrementSavepointCounter(actualDb)\n // CRITICAL: Strict validation ensures savepointId is a positive integer (1-1000000)\n // This prevents SQL injection through savepoint name construction\n const savepointName = 'kysera_sp_' + String(savepointId)\n\n // Detect dialect for savepoint syntax\n const dialect = detectDialect(actualDb)\n\n try {\n // Create savepoint - use dialect-specific syntax\n // PostgreSQL/MySQL/SQLite: SAVEPOINT name\n // MSSQL: SAVE TRANSACTION name\n if (dialect === 'mssql') {\n await sql`SAVE TRANSACTION ${sql.id(savepointName)}`.execute(actualDb as Transaction<DB>)\n } else {\n await sql`SAVEPOINT ${sql.id(savepointName)}`.execute(actualDb as Transaction<DB>)\n }\n\n // Create context with same db (already in transaction)\n // Preserve schema from parent context if available\n const parentSchema = isDbContext<DB>(db) ? db.schema : undefined\n const baseCtx = {\n [DB_CONTEXT_SYMBOL]: true as const,\n db: actualDb,\n isTransaction: true as const\n }\n const ctx: DbContext<DB> = parentSchema !== undefined\n ? { ...baseCtx, schema: parentSchema }\n : baseCtx\n\n // Execute function\n const result = await fn(ctx)\n\n // Release savepoint on success\n // PostgreSQL/MySQL/SQLite: RELEASE SAVEPOINT name\n // MSSQL: Does not support RELEASE, savepoint is automatically released on commit\n if (dialect !== 'mssql') {\n await sql`RELEASE SAVEPOINT ${sql.id(savepointName)}`.execute(actualDb as Transaction<DB>)\n }\n\n return result\n } catch (error) {\n // Rollback to savepoint on error\n try {\n // PostgreSQL/MySQL/SQLite: ROLLBACK TO SAVEPOINT name\n // MSSQL: ROLLBACK TRANSACTION name\n if (dialect === 'mssql') {\n await sql`ROLLBACK TRANSACTION ${sql.id(savepointName)}`.execute(\n actualDb as Transaction<DB>\n )\n } else {\n await sql`ROLLBACK TO SAVEPOINT ${sql.id(savepointName)}`.execute(\n actualDb as Transaction<DB>\n )\n }\n } catch (rollbackError) {\n // Handle rollback errors based on configured mode\n switch (rollbackErrorMode) {\n case 'throw':\n // Throw rollback error instead of original error (useful for debugging)\n throw rollbackError\n case 'callback':\n // Call custom callback with both errors\n // eslint-disable-next-line max-depth\n if (onRollbackError) {\n await onRollbackError(error, rollbackError)\n }\n break\n case 'log-only':\n default:\n // Log rollback failure (default behavior)\n // Original error is more important, so we do not throw here\n logger.error('Savepoint rollback failed for ' + savepointName + ':', rollbackError)\n break\n }\n }\n throw error\n }\n }\n\n // Create transaction builder\n const transactionBuilder = actualDb.transaction()\n\n // Apply isolation level if specified (only valid for top-level transaction)\n if (options.isolationLevel) {\n transactionBuilder.setIsolationLevel(options.isolationLevel)\n }\n\n return await transactionBuilder.execute(async (trx: Transaction<DB>) => {\n // Wrap transaction with plugins if using KyseraExecutor\n const wrappedTrx = isKyseraExecutor(actualDb) ? wrapTransaction(trx, getPlugins(actualDb)) : trx\n\n // Mark the transaction as being in a transaction (for nested detection)\n markAsInTransaction(wrappedTrx)\n\n // Initialize savepoint counter\n ;(wrappedTrx as unknown as Record<symbol, number>)[SAVEPOINT_COUNTER_SYMBOL] = 0\n\n // Preserve schema from parent context if available\n const parentSchema = isDbContext<DB>(db) ? db.schema : undefined\n const dbWithSchema = parentSchema ? wrappedTrx.withSchema(parentSchema) : wrappedTrx\n const baseTrxCtx = {\n [DB_CONTEXT_SYMBOL]: true as const,\n db: dbWithSchema,\n isTransaction: true as const\n }\n const ctx: DbContext<DB> = parentSchema !== undefined\n ? { ...baseTrxCtx, schema: parentSchema }\n : baseTrxCtx\n\n return await fn(ctx)\n })\n}\n\n/**\n * Execute a function with a database context.\n *\n * Creates a context without a transaction.\n * Supports both Kysely and KyseraExecutor instances.\n *\n * @param db - Database instance (Kysely or KyseraExecutor)\n * @param fn - Function to execute\n * @returns Result of the function\n *\n * @example\n * ```typescript\n * import { withContext } from \"@kysera/dal\";\n *\n * const users = await withContext(db, async (ctx) => {\n * return getAllUsers(ctx);\n * });\n * ```\n */\nexport async function withContext<DB, T>(\n db: Kysely<DB> | KyseraExecutor<DB>,\n fn: (ctx: DbContext<DB>) => Promise<T>\n): Promise<T> {\n const ctx = createContext(db)\n return await fn(ctx)\n}\n\n/**\n * Check if context is within a transaction.\n *\n * @param ctx - Database context\n * @returns True if within a transaction\n */\nexport function isInTransaction<DB>(ctx: DbContext<DB>): boolean {\n return ctx.isTransaction\n}\n","/**\n * Query function creation utilities.\n *\n * @module @kysera/dal\n */\n\nimport type { Kysely } from 'kysely'\nimport type { KyseraExecutor } from '@kysera/executor'\nimport type { DbContext, QueryFunction } from './types.js'\nimport { isDbContext } from './types.js'\nimport { createContext } from './context.js'\nimport { TransactionRequiredError } from './errors.js'\n\n/**\n * Create a typed query function.\n *\n * Query functions are the core building blocks of Functional DAL.\n * They receive a database context and arguments, and return a Promise.\n *\n * The result type is automatically inferred from the query.\n * Supports raw Kysely instances and plugin-aware KyseraExecutor.\n *\n * @param queryFn - Query implementation function\n * @returns Callable query function\n *\n * @example Basic query\n * ```typescript\n * import { createQuery } from '@kysera/dal';\n *\n * const getUserById = createQuery(\n * (ctx, id: number) =>\n * ctx.db\n * .selectFrom('users')\n * .select(['id', 'email', 'name'])\n * .where('id', '=', id)\n * .executeTakeFirst()\n * );\n *\n * // Usage with raw Kysely\n * const user = await getUserById(db, 1);\n * // Type: { id: number; email: string; name: string } | undefined\n * ```\n *\n * @example With KyseraExecutor (plugins applied)\n * ```typescript\n * import { createQuery } from '@kysera/dal';\n * import { createExecutor } from '@kysera/executor';\n * import { softDeletePlugin } from '@kysera/soft-delete';\n *\n * const executor = await createExecutor(db, [softDeletePlugin()]);\n *\n * const getUsers = createQuery((ctx) =>\n * ctx.db.selectFrom('users').selectAll().execute()\n * );\n *\n * // Soft-delete filter automatically applied\n * const users = await getUsers(executor);\n * ```\n *\n * @example With transaction\n * ```typescript\n * import { createQuery, withTransaction } from '@kysera/dal';\n *\n * const result = await withTransaction(db, async (ctx) => {\n * return getUserById(ctx, 1);\n * });\n * ```\n */\nexport function createQuery<DB, TArgs extends readonly unknown[], TResult>(\n queryFn: (ctx: DbContext<DB>, ...args: TArgs) => Promise<TResult>\n): QueryFunction<DB, TArgs, TResult> {\n return (\n dbOrCtx: Kysely<DB> | KyseraExecutor<DB> | DbContext<DB>,\n ...args: TArgs\n ): Promise<TResult> => {\n // Use Symbol-based detection for reliable context identification\n const ctx: DbContext<DB> = isDbContext<DB>(dbOrCtx) ? dbOrCtx : createContext(dbOrCtx)\n\n return queryFn(ctx, ...args)\n }\n}\n\n/**\n * Create a query function that requires a transaction.\n *\n * Throws a TransactionRequiredError if called outside a transaction context.\n *\n * @param queryFn - Query implementation function\n * @returns Query function that requires transaction\n *\n * @example\n * ```typescript\n * import { createTransactionalQuery, withTransaction } from '@kysera/dal';\n *\n * const transferFunds = createTransactionalQuery(\n * async (ctx, fromId: number, toId: number, amount: number) => {\n * await ctx.db\n * .updateTable('accounts')\n * .set((eb) => ({ balance: eb('balance', '-', amount) }))\n * .where('id', '=', fromId)\n * .execute();\n *\n * await ctx.db\n * .updateTable('accounts')\n * .set((eb) => ({ balance: eb('balance', '+', amount) }))\n * .where('id', '=', toId)\n * .execute();\n *\n * return { success: true };\n * }\n * );\n *\n * // This will work\n * await withTransaction(db, (ctx) => transferFunds(ctx, 1, 2, 100));\n *\n * // This will throw TransactionRequiredError\n * await transferFunds(db, 1, 2, 100);\n * ```\n */\nexport function createTransactionalQuery<DB, TArgs extends readonly unknown[], TResult>(\n queryFn: (ctx: DbContext<DB>, ...args: TArgs) => Promise<TResult>\n): QueryFunction<DB, TArgs, TResult> {\n return createQuery(async (ctx: DbContext<DB>, ...args: TArgs) => {\n if (!ctx.isTransaction) {\n throw new TransactionRequiredError()\n }\n return await queryFn(ctx, ...args)\n })\n}\n","/**\n * Query composition utilities.\n *\n * @module @kysera/dal\n */\n\nimport type { Kysely } from 'kysely'\nimport type { KyseraExecutor } from '@kysera/executor'\nimport type { DbContext, QueryFunction } from './types.js'\nimport { isDbContext } from './types.js'\nimport { createContext } from './context.js'\n\n/**\n * Normalize input to DbContext.\n * Uses Symbol-based detection for reliable context identification.\n * @internal\n */\nfunction toContext<DB>(ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>): DbContext<DB> {\n if (isDbContext<DB>(ctxOrDb)) {\n return ctxOrDb\n }\n return createContext(ctxOrDb)\n}\n\n/**\n * Compose two query functions sequentially.\n *\n * The result of the first query is passed to the second.\n *\n * @param first - First query function\n * @param second - Function that receives context and first result\n * @returns Composed query function\n *\n * @example\n * ```typescript\n * import { createQuery, compose } from '@kysera/dal';\n *\n * const getUserById = createQuery((ctx, id: number) =>\n * ctx.db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirstOrThrow()\n * );\n *\n * const getPostsByUserId = createQuery((ctx, userId: number) =>\n * ctx.db.selectFrom('posts').selectAll().where('user_id', '=', userId).execute()\n * );\n *\n * const getUserWithPosts = compose(\n * getUserById,\n * async (ctx, user) => ({\n * ...user,\n * posts: await getPostsByUserId(ctx, user.id),\n * })\n * );\n *\n * const result = await getUserWithPosts(db, 1);\n * // { id: 1, name: '...', posts: [...] }\n * ```\n */\nexport function compose<DB, TArgs extends readonly unknown[], TFirst, TResult>(\n first: QueryFunction<DB, TArgs, TFirst>,\n second: (ctx: DbContext<DB>, result: TFirst) => Promise<TResult>\n): QueryFunction<DB, TArgs, TResult> {\n return async (\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>,\n ...args: TArgs\n ): Promise<TResult> => {\n const ctx = toContext(ctxOrDb)\n const firstResult = await first(ctx, ...args)\n return await second(ctx, firstResult)\n }\n}\n\n/**\n * Chain multiple operations on a query result.\n *\n * Supports up to 8 type-safe transforms. For more than 8 transforms,\n * the return type falls back to `unknown` (use type assertion if needed).\n *\n * @param query - Initial query function\n * @param transforms - Array of transform functions (up to 8 with full type safety)\n * @returns Chained query function\n *\n * @example Basic usage (2 transforms)\n * ```typescript\n * import { createQuery, chain } from '@kysera/dal';\n *\n * const getUser = createQuery((ctx, id: number) =>\n * ctx.db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirstOrThrow()\n * );\n *\n * const getUserFull = chain(\n * getUser,\n * async (ctx, user) => ({ ...user, posts: await getPosts(ctx, user.id) }),\n * async (ctx, data) => ({ ...data, followers: await getFollowers(ctx, data.id) })\n * );\n * // Type: QueryFunction<DB, [number], { ...user, posts: Post[], followers: User[] }>\n * ```\n *\n * @example Maximum type-safe transforms (8)\n * ```typescript\n * const complexQuery = chain(\n * getUser,\n * async (ctx, user) => ({ ...user, posts: await getPosts(ctx, user.id) }),\n * async (ctx, data) => ({ ...data, comments: await getComments(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, likes: await getLikes(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, shares: await getShares(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, followers: await getFollowers(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, following: await getFollowing(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, stats: await getStats(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, metadata: await getMetadata(ctx, data.id) })\n * );\n * // Type is still inferred correctly!\n * ```\n *\n * @example More than 8 transforms (fallback to unknown)\n * ```typescript\n * const veryComplexQuery = chain(\n * getUser,\n * t1, t2, t3, t4, t5, t6, t7, t8, t9 // 9 transforms\n * );\n * // Type: QueryFunction<DB, [number], unknown>\n * // Use type assertion: const result = await veryComplexQuery(db, 1) as MyType\n * ```\n */\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>\n): QueryFunction<DB, TArgs, T2>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>\n): QueryFunction<DB, TArgs, T3>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3, T4>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>,\n t3: (ctx: DbContext<DB>, result: T3) => Promise<T4>\n): QueryFunction<DB, TArgs, T4>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3, T4, T5>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>,\n t3: (ctx: DbContext<DB>, result: T3) => Promise<T4>,\n t4: (ctx: DbContext<DB>, result: T4) => Promise<T5>\n): QueryFunction<DB, TArgs, T5>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3, T4, T5, T6>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>,\n t3: (ctx: DbContext<DB>, result: T3) => Promise<T4>,\n t4: (ctx: DbContext<DB>, result: T4) => Promise<T5>,\n t5: (ctx: DbContext<DB>, result: T5) => Promise<T6>\n): QueryFunction<DB, TArgs, T6>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3, T4, T5, T6, T7>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>,\n t3: (ctx: DbContext<DB>, result: T3) => Promise<T4>,\n t4: (ctx: DbContext<DB>, result: T4) => Promise<T5>,\n t5: (ctx: DbContext<DB>, result: T5) => Promise<T6>,\n t6: (ctx: DbContext<DB>, result: T6) => Promise<T7>\n): QueryFunction<DB, TArgs, T7>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3, T4, T5, T6, T7, T8>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>,\n t3: (ctx: DbContext<DB>, result: T3) => Promise<T4>,\n t4: (ctx: DbContext<DB>, result: T4) => Promise<T5>,\n t5: (ctx: DbContext<DB>, result: T5) => Promise<T6>,\n t6: (ctx: DbContext<DB>, result: T6) => Promise<T7>,\n t7: (ctx: DbContext<DB>, result: T7) => Promise<T8>\n): QueryFunction<DB, TArgs, T8>\nexport function chain<DB, TArgs extends readonly unknown[]>(\n query: QueryFunction<DB, TArgs, unknown>,\n ...transforms: ((ctx: DbContext<DB>, result: unknown) => Promise<unknown>)[]\n): QueryFunction<DB, TArgs, unknown> {\n return async (\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>,\n ...args: TArgs\n ): Promise<unknown> => {\n const ctx = toContext(ctxOrDb)\n let result = await query(ctx, ...args)\n for (const transform of transforms) {\n result = await transform(ctx, result)\n }\n return result\n }\n}\n\n/**\n * Result type for parallel query execution.\n *\n * Uses mapped types to infer the result type of each query function,\n * maintaining full type safety across all database schemas.\n */\nexport type ParallelResult<\n DB,\n TArgs extends readonly unknown[],\n T extends Record<string, QueryFunction<DB, TArgs, unknown>>\n> = {\n [K in keyof T]: T[K] extends QueryFunction<DB, TArgs, infer R> ? R : never\n}\n\n/**\n * Execute multiple queries in parallel.\n *\n * All queries receive the same arguments and are executed concurrently.\n *\n * @param queries - Object of query functions\n * @returns Query function that returns object with all results\n *\n * @example\n * ```typescript\n * import { createQuery, parallel } from '@kysera/dal';\n *\n * const getUserById = createQuery((ctx, id: number) =>\n * ctx.db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirst()\n * );\n *\n * const getUserStats = createQuery((ctx, id: number) =>\n * ctx.db.selectFrom('user_stats').selectAll().where('user_id', '=', id).executeTakeFirst()\n * );\n *\n * const getNotifications = createQuery((ctx, id: number) =>\n * ctx.db.selectFrom('notifications').selectAll().where('user_id', '=', id).execute()\n * );\n *\n * const getDashboardData = parallel({\n * user: getUserById,\n * stats: getUserStats,\n * notifications: getNotifications,\n * });\n *\n * const dashboard = await getDashboardData(db, userId);\n * // { user: {...}, stats: {...}, notifications: [...] }\n * ```\n */\nexport function parallel<\n DB,\n TArgs extends readonly unknown[],\n T extends Record<string, QueryFunction<DB, TArgs, unknown>>\n>(\n queries: T\n): QueryFunction<\n DB,\n TArgs,\n { [K in keyof T]: T[K] extends QueryFunction<DB, TArgs, infer R> ? R : never }\n> {\n return async (ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>, ...args: TArgs) => {\n const ctx = toContext(ctxOrDb)\n const entries = Object.entries(queries)\n const results = await Promise.all(\n entries.map(async ([key, query]) => {\n const result = await query(ctx, ...args)\n return [key, result] as const\n })\n )\n\n return Object.fromEntries(results) as {\n [K in keyof T]: T[K] extends QueryFunction<DB, TArgs, infer R> ? R : never\n }\n }\n}\n\n/**\n * Execute a query conditionally.\n *\n * @param condition - Condition function\n * @param query - Query to execute if condition is true\n * @param fallback - Optional fallback value if condition is false\n * @returns Conditional query function\n *\n * @example\n * ```typescript\n * import { createQuery, conditional } from '@kysera/dal';\n *\n * const getPremiumFeatures = createQuery((ctx, userId: number) =>\n * ctx.db.selectFrom('premium_features').selectAll().where('user_id', '=', userId).execute()\n * );\n *\n * const getFeatures = conditional(\n * (ctx, userId: number, isPremium: boolean) => isPremium,\n * getPremiumFeatures,\n * [] // Return empty array for non-premium users\n * );\n * ```\n */\nexport function conditional<DB, TArgs extends readonly unknown[], TResult, TFallback = undefined>(\n condition: (ctx: DbContext<DB>, ...args: TArgs) => boolean | Promise<boolean>,\n query: QueryFunction<DB, TArgs, TResult>,\n fallback?: TFallback\n): QueryFunction<DB, TArgs, TResult | TFallback> {\n return async (\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>,\n ...args: TArgs\n ): Promise<TResult | TFallback> => {\n const ctx = toContext(ctxOrDb)\n const shouldExecute = await condition(ctx, ...args)\n if (shouldExecute) {\n return await query(ctx, ...args)\n }\n return fallback as TFallback\n }\n}\n\n/**\n * Map over query results.\n *\n * @param query - Query that returns an array\n * @param mapper - Function to apply to each element\n * @returns Query with mapped results\n *\n * @example\n * ```typescript\n * import { createQuery, mapResult } from '@kysera/dal';\n *\n * const getUsers = createQuery((ctx) =>\n * ctx.db.selectFrom('users').selectAll().execute()\n * );\n *\n * const getUserNames = mapResult(getUsers, (user) => user.name);\n *\n * const names = await getUserNames(db); // string[]\n * ```\n */\nexport function mapResult<DB, TArgs extends readonly unknown[], TItem, TResult>(\n query: QueryFunction<DB, TArgs, TItem[]>,\n mapper: (item: TItem, index: number) => TResult\n): QueryFunction<DB, TArgs, TResult[]> {\n return async (\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>,\n ...args: TArgs\n ): Promise<TResult[]> => {\n const ctx = toContext(ctxOrDb)\n const items = await query(ctx, ...args)\n return items.map(mapper)\n }\n}\n"]} | ||
| {"version":3,"sources":["../src/types.ts","../src/errors.ts","../src/context.ts","../src/query.ts","../src/compose.ts"],"names":["DB_CONTEXT_SYMBOL","IN_TRANSACTION_SYMBOL","SAVEPOINT_COUNTER_SYMBOL","isDbContext","obj","TransactionRequiredError","DatabaseError","message","ErrorCodes","detectTransaction","db","hasInTransactionMarker","isKyseraTransaction","executor","createContext","options","opts","isTransactionOverride","schema","inTransaction","dbWithSchema","baseContext","createSchemaContext","markAsInTransaction","incrementSavepointCounter","nextId","withTransaction","fn","logger","silentLogger","rollbackErrorMode","onRollbackError","actualDb","savepointId","savepointName","dialect","detectDialect","sql","parentSchema","baseCtx","ctx","result","error","rollbackError","transactionBuilder","trx","wrappedTrx","isKyseraExecutor","wrapTransaction","getPlugins","baseTrxCtx","withContext","isInTransaction","toContext","ctxOrDb","createQuery","queryFn","dbOrCtx","args","createTransactionalQuery","compose","first","second","firstResult","chain","query","transforms","transform","parallel","queries","entries","results","key","conditional","condition","fallback","mapResult","mapper"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Core types for Functional DAL.\n *\n * @module @kysera/dal\n */\n\nimport type { Kysely, Transaction } from 'kysely'\nimport type { KyseraExecutor, KyseraTransaction } from '@kysera/executor'\n\n/**\n * Symbol used to reliably identify DbContext objects.\n * Using Symbol.for() ensures the same symbol is used across different module instances.\n */\nexport const DB_CONTEXT_SYMBOL: unique symbol = Symbol.for('kysera.DbContext')\n\n/**\n * Symbol used to mark that we are inside a transaction (for nested transaction detection).\n */\nexport const IN_TRANSACTION_SYMBOL: unique symbol = Symbol.for('kysera.InTransaction')\n\n/**\n * Symbol used to track savepoint nesting depth for unique savepoint names.\n */\nexport const SAVEPOINT_COUNTER_SYMBOL: unique symbol = Symbol.for('kysera.SavepointCounter')\n\n/**\n * Database context for query functions.\n *\n * Supports both raw Kysely instances and plugin-aware KyseraExecutor.\n * When using KyseraExecutor, all queries automatically have plugins applied.\n *\n * @typeParam DB - Database schema type, defaults to Record<string, unknown>\n */\nexport interface DbContext<DB = Record<string, unknown>> {\n /** Marker symbol for reliable type detection */\n readonly [DB_CONTEXT_SYMBOL]: true\n /** Database or transaction instance (raw or plugin-aware) */\n readonly db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>\n /** Whether the context is within a transaction */\n readonly isTransaction: boolean\n /**\n * Current PostgreSQL schema context.\n * When set, queries use this schema instead of the default.\n * undefined means using the default schema (typically 'public').\n *\n * @example\n * ```typescript\n * const ctx = createSchemaContext(executor, 'tenant_123')\n * // All queries through ctx.db will use the 'tenant_123' schema\n * ```\n */\n readonly schema?: string\n}\n\n/**\n * Options for transaction execution.\n */\nexport interface TransactionOptions {\n /**\n * Isolation level for the transaction.\n * Note: Kysely's setIsolationLevel must be called on the transaction builder.\n */\n isolationLevel?: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable'\n}\n\n/**\n * Query function signature.\n *\n * A query function accepts database context or any database instance and arguments,\n * returning a Promise with the result.\n *\n * Supports:\n * - `DbContext<DB>` - Explicit context (inside `withTransaction`)\n * - `Kysely<DB>` - Raw Kysely instance\n * - `KyseraExecutor<DB>` - Plugin-aware executor (recommended)\n *\n * @typeParam DB - Database schema type\n * @typeParam TArgs - Tuple of argument types\n * @typeParam TResult - Return type\n */\nexport type QueryFunction<DB, TArgs extends readonly unknown[], TResult> = (\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>,\n ...args: TArgs\n) => Promise<TResult>\n\n/**\n * Infer result type from a query function.\n */\nexport type InferResult<T> =\n T extends QueryFunction<Record<string, unknown>, readonly unknown[], infer R> ? R : never\n\n/**\n * Infer arguments type from a query function.\n */\nexport type InferArgs<T> =\n T extends QueryFunction<Record<string, unknown>, infer A, unknown> ? A : never\n\n/**\n * Infer database type from a query function.\n */\nexport type InferDB<T> = T extends QueryFunction<infer DB, readonly unknown[], unknown> ? DB : never\n\n/**\n * Type guard to check if a value is a DbContext.\n * Uses the DB_CONTEXT_SYMBOL for reliable detection.\n */\nexport function isDbContext<DB>(obj: unknown): obj is DbContext<DB> {\n return typeof obj === 'object' && obj !== null && DB_CONTEXT_SYMBOL in obj\n}\n","/**\n * Custom error classes for @kysera/dal\n *\n * @module @kysera/dal\n */\n\nimport { DatabaseError, ErrorCodes } from '@kysera/core'\n\n/**\n * Error thrown when a query that requires a transaction is called outside of a transaction context.\n *\n * Extends DatabaseError from @kysera/core for consistent error hierarchy\n * with `.code` and `.toJSON()` support.\n *\n * @example\n * ```typescript\n * import { createTransactionalQuery, withTransaction } from '@kysera/dal';\n *\n * const transferFunds = createTransactionalQuery(async (ctx, from, to, amount) => {\n * // ... transfer logic\n * });\n *\n * // This throws TransactionRequiredError:\n * await transferFunds(db, 1, 2, 100);\n *\n * // This works:\n * await withTransaction(db, (ctx) => transferFunds(ctx, 1, 2, 100));\n * ```\n */\nexport class TransactionRequiredError extends DatabaseError {\n /**\n * Create a new TransactionRequiredError\n *\n * @param message - Error message\n */\n constructor(\n message = 'Query requires a transaction. Use withTransaction() to execute this query.'\n ) {\n super(message, ErrorCodes.DB_TRANSACTION_FAILED)\n this.name = 'TransactionRequiredError'\n }\n}\n","/**\n * Database context and transaction utilities.\n *\n * @module @kysera/dal\n */\n\nimport type { Kysely, Transaction } from 'kysely'\nimport { sql } from 'kysely'\nimport type { KyseraExecutor, KyseraTransaction } from '@kysera/executor'\nimport { isKyseraExecutor, getPlugins, wrapTransaction } from '@kysera/executor'\nimport { silentLogger, type KyseraLogger, detectDialect } from '@kysera/core'\nimport type { DbContext, TransactionOptions } from './types.js'\nimport {\n DB_CONTEXT_SYMBOL,\n IN_TRANSACTION_SYMBOL,\n SAVEPOINT_COUNTER_SYMBOL,\n isDbContext\n} from './types.js'\n\n/**\n * Detect if a database instance is currently in a transaction.\n *\n * Uses a unified detection mechanism that checks:\n * 1. Internal IN_TRANSACTION_SYMBOL marker (set by withTransaction)\n * 2. KyseraTransaction marker (__kysera property with __rawDb.isTransaction)\n * 3. Kysely's isTransaction property (for raw Transaction instances)\n *\n * The internal marker takes precedence to ensure reliable detection\n * across nested transactions and savepoints.\n *\n * @internal\n */\nfunction detectTransaction<DB>(\n db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>\n): boolean {\n // Check internal marker first (most reliable, set by withTransaction)\n if (hasInTransactionMarker(db)) {\n return true\n }\n\n // Check for KyseraTransaction marker (wrapped transaction with plugins)\n // This ensures we don't assume Kysely internals on wrapped executors\n if (isKyseraTransaction(db)) {\n return true\n }\n\n // Fallback to Kysely's isTransaction property (for raw Transaction instances)\n // Only safe when not a KyseraExecutor (already checked above)\n return 'isTransaction' in db && db.isTransaction\n}\n\n/**\n * Check if a database instance is a KyseraTransaction.\n * KyseraTransaction has __kysera marker and wraps a Transaction instance.\n *\n * @internal\n */\nfunction isKyseraTransaction<DB>(\n db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>\n): db is KyseraTransaction<DB> {\n // Check for KyseraExecutor marker - runtime check for unknown inputs\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!('__kysera' in db && db.__kysera)) {\n return false\n }\n\n // Type assertion is safe here because we just checked __kysera exists\n const executor = db\n\n // Check if __rawDb exists (it might be missing on malformed executors)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!executor.__rawDb) {\n return false\n }\n\n // Check if the raw database is a transaction\n const rawDb = executor.__rawDb as unknown as { isTransaction?: boolean }\n return rawDb.isTransaction === true\n}\n\n/**\n * Options for creating a database context.\n */\nexport interface CreateContextOptions {\n /**\n * Override transaction detection.\n * If not provided, transaction status is auto-detected.\n */\n isTransaction?: boolean\n /**\n * PostgreSQL schema to use for all queries in this context.\n * When set, the db instance is wrapped with withSchema().\n *\n * @example 'auth', 'admin', 'tenant_123'\n */\n schema?: string\n}\n\n/**\n * Create a database context from any database instance.\n *\n * Supports raw Kysely instances and plugin-aware KyseraExecutor.\n * When using KyseraExecutor, plugins are automatically available in context.\n *\n * @param db - Kysely, KyseraExecutor, or transaction instance\n * @param options - Context options (isTransaction override, schema)\n * @returns Database context\n *\n * @example Basic usage\n * ```typescript\n * import { createContext } from \"@kysera/dal\";\n * import { createExecutor } from \"@kysera/executor\";\n *\n * const executor = await createExecutor(db, [softDeletePlugin()]);\n * const ctx = createContext(executor);\n * const user = await findUserById(ctx, 1); // soft-delete filter applied\n * ```\n *\n * @example With schema\n * ```typescript\n * const ctx = createContext(executor, { schema: 'auth' });\n * // All queries use the 'auth' schema\n * ```\n */\nexport function createContext<DB>(\n db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>,\n options?: CreateContextOptions | boolean\n): DbContext<DB> {\n // Handle backward compatibility: options can be just isTransaction boolean\n const opts: CreateContextOptions =\n typeof options === 'boolean' ? { isTransaction: options } : (options ?? {})\n\n const { isTransaction: isTransactionOverride, schema } = opts\n\n // If explicitly provided, use that value; otherwise use unified detection\n const inTransaction = isTransactionOverride ?? detectTransaction(db)\n\n // Apply schema if provided\n const dbWithSchema = schema ? db.withSchema(schema) : db\n\n // Use conditional to satisfy exactOptionalPropertyTypes\n const baseContext = {\n [DB_CONTEXT_SYMBOL]: true as const,\n db: dbWithSchema as Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>,\n isTransaction: inTransaction\n }\n\n return schema !== undefined\n ? { ...baseContext, schema }\n : baseContext\n}\n\n/**\n * Create a database context with a specific PostgreSQL schema.\n *\n * Convenience function for creating schema-scoped contexts.\n * Useful for multi-tenant applications using schema-per-tenant pattern.\n *\n * @param db - Kysely or KyseraExecutor instance\n * @param schema - PostgreSQL schema name\n * @returns Database context scoped to the specified schema\n *\n * @example Multi-tenant usage\n * ```typescript\n * import { createSchemaContext } from \"@kysera/dal\";\n *\n * // In middleware or request handler\n * const tenantSchema = `tenant_${request.tenantId}`;\n * const ctx = createSchemaContext(executor, tenantSchema);\n *\n * // All queries are scoped to tenant's schema\n * const users = await getUsers(ctx);\n * ```\n *\n * @example Domain separation\n * ```typescript\n * const authCtx = createSchemaContext(executor, 'auth');\n * const adminCtx = createSchemaContext(executor, 'admin');\n *\n * const user = await getUser(authCtx, userId);\n * const settings = await getSettings(adminCtx);\n * ```\n */\nexport function createSchemaContext<DB>(\n db: Kysely<DB> | KyseraExecutor<DB>,\n schema: string\n): DbContext<DB> {\n return createContext(db, { schema })\n}\n\n/**\n * Check if a database instance has the in-transaction marker.\n * @internal\n */\nfunction hasInTransactionMarker<DB>(\n db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>\n): boolean {\n return (db as unknown as Record<symbol, boolean>)[IN_TRANSACTION_SYMBOL] === true\n}\n\n/**\n * Mark a database instance as being in a transaction.\n * @internal\n */\nfunction markAsInTransaction<T>(obj: T): T {\n ;(obj as unknown as Record<symbol, boolean>)[IN_TRANSACTION_SYMBOL] = true\n return obj\n}\n\n/**\n * Increment and return the savepoint counter for a transaction.\n * Validates the counter to prevent SQL injection through malformed savepoint names.\n * @internal\n */\nfunction incrementSavepointCounter<DB>(\n db: Kysely<DB> | Transaction<DB> | KyseraExecutor<DB> | KyseraTransaction<DB>\n): number {\n const obj = db as unknown as Record<symbol, number>\n const current = obj[SAVEPOINT_COUNTER_SYMBOL] ?? 0\n const nextId = current + 1\n\n // CRITICAL: Explicit validation to prevent SQL injection\n // Savepoint ID must be a positive integer to ensure safe identifier construction\n if (!Number.isInteger(nextId) || nextId < 1 || nextId > 1_000_000) {\n throw new Error('Invalid savepoint counter: expected positive integer, got ' + String(nextId))\n }\n\n obj[SAVEPOINT_COUNTER_SYMBOL] = nextId\n return nextId\n}\n\n/**\n * Extended transaction options with logger support and rollback error handling.\n */\nexport interface TransactionOptionsWithLogger extends TransactionOptions {\n /**\n * Logger for transaction operations.\n * Defaults to silentLogger (no-op).\n */\n logger?: KyseraLogger\n /**\n * How to handle savepoint rollback errors in nested transactions.\n *\n * - 'log-only' (default): Log rollback errors but don't throw (original error is more important)\n * - 'throw': Throw rollback error instead of original error (useful for debugging)\n * - 'callback': Call onRollbackError callback with both errors (for custom handling)\n *\n * @default 'log-only'\n */\n rollbackErrorMode?: 'log-only' | 'throw' | 'callback'\n /**\n * Callback invoked when savepoint rollback fails (only used with rollbackErrorMode: 'callback').\n *\n * Receives both the original error that triggered the rollback and the rollback error.\n * This allows custom error handling logic (e.g., logging to external service, alerting, etc.)\n *\n * @param originalError - The error that caused the savepoint to rollback\n * @param rollbackError - The error that occurred during rollback\n */\n onRollbackError?: (originalError: unknown, rollbackError: unknown) => void | Promise<void>\n}\n\n/**\n * Execute a function within a transaction.\n *\n * If the database is a KyseraExecutor, plugins are automatically propagated\n * to the transaction context. Otherwise, creates a standard Kysely transaction.\n *\n * **Nested Transaction Support with Savepoints:**\n * When called within an existing transaction, this function automatically creates\n * a savepoint instead of a new transaction. If the nested operation throws an error,\n * only the savepoint is rolled back, leaving the parent transaction intact.\n *\n * @param db - Database instance (Kysely or KyseraExecutor) or DbContext\n * @param fn - Function to execute within transaction\n * @param options - Transaction options (isolation level, logger)\n * @returns Result of the function\n *\n * @example Basic usage\n * ```typescript\n * import { withTransaction } from \"@kysera/dal\";\n *\n * const result = await withTransaction(db, async (ctx) => {\n * const user = await createUser(ctx, userData);\n * const profile = await createProfile(ctx, { userId: user.id, ...profileData });\n * return { user, profile };\n * });\n * ```\n *\n * @example With KyseraExecutor (plugins propagated)\n * ```typescript\n * import { createExecutor } from \"@kysera/executor\";\n * import { withTransaction } from \"@kysera/dal\";\n *\n * const executor = await createExecutor(db, [softDeletePlugin()]);\n *\n * const result = await withTransaction(executor, async (ctx) => {\n * // All queries in transaction have soft-delete filter applied\n * const users = await getUsers(ctx);\n * return users;\n * });\n * ```\n *\n * @example Nested transactions with savepoints (automatic rollback on error)\n * ```typescript\n * await withTransaction(db, async (ctx) => {\n * const user = await createUser(ctx, userData);\n *\n * try {\n * // This nested call creates a SAVEPOINT\n * await withTransaction(ctx.db, async (nestedCtx) => {\n * await createProfile(nestedCtx, profileData);\n * throw new Error(\"Profile validation failed\");\n * });\n * } catch (error) {\n * // Profile creation rolled back to savepoint\n * // User creation still exists in transaction\n * }\n *\n * // User creation will be committed\n * });\n * ```\n *\n * @example With isolation level (top-level only)\n * ```typescript\n * await withTransaction(db, async (ctx) => {\n * // Serializable isolation for strict consistency\n * return await criticalOperation(ctx);\n * }, { isolationLevel: \"serializable\" });\n * ```\n *\n * @example With custom logger\n * ```typescript\n * import { consoleLogger } from \"@kysera/core\";\n *\n * await withTransaction(db, async (ctx) => {\n * return await operation(ctx);\n * }, { logger: consoleLogger });\n * ```\n *\n * @example With rollback error handling\n * ```typescript\n * // Throw rollback error instead of original error (debugging)\n * await withTransaction(db, async (ctx) => {\n * // ...\n * }, { rollbackErrorMode: 'throw' });\n *\n * // Custom error handling callback\n * await withTransaction(db, async (ctx) => {\n * // ...\n * }, {\n * rollbackErrorMode: 'callback',\n * onRollbackError: async (originalError, rollbackError) => {\n * await logToMonitoring({\n * type: 'savepoint_rollback_failure',\n * originalError,\n * rollbackError\n * });\n * }\n * });\n * ```\n */\n// eslint-disable-next-line complexity\nexport async function withTransaction<DB, T>(\n db: Kysely<DB> | KyseraExecutor<DB> | DbContext<DB>,\n fn: (ctx: DbContext<DB>) => Promise<T>,\n options: TransactionOptionsWithLogger = {}\n): Promise<T> {\n const { logger = silentLogger, rollbackErrorMode = 'log-only', onRollbackError } = options\n\n // Handle DbContext input - extract the db instance\n const actualDb: Kysely<DB> | KyseraExecutor<DB> = isDbContext<DB>(db)\n ? (db.db as Kysely<DB> | KyseraExecutor<DB>)\n : db\n\n // Check if we are already inside a transaction (nested call)\n if (hasInTransactionMarker(actualDb)) {\n // Use savepoint for nested transaction\n const savepointId = incrementSavepointCounter(actualDb)\n // CRITICAL: Strict validation ensures savepointId is a positive integer (1-1000000)\n // This prevents SQL injection through savepoint name construction\n const savepointName = 'kysera_sp_' + String(savepointId)\n\n // Detect dialect for savepoint syntax\n const dialect = detectDialect(actualDb)\n\n try {\n // Create savepoint - use dialect-specific syntax\n // PostgreSQL/MySQL/SQLite: SAVEPOINT name\n // MSSQL: SAVE TRANSACTION name\n if (dialect === 'mssql') {\n await sql`SAVE TRANSACTION ${sql.id(savepointName)}`.execute(actualDb as Transaction<DB>)\n } else {\n await sql`SAVEPOINT ${sql.id(savepointName)}`.execute(actualDb as Transaction<DB>)\n }\n\n // Create context with same db (already in transaction)\n // Preserve schema from parent context if available\n const parentSchema = isDbContext<DB>(db) ? db.schema : undefined\n const baseCtx = {\n [DB_CONTEXT_SYMBOL]: true as const,\n db: actualDb,\n isTransaction: true as const\n }\n const ctx: DbContext<DB> = parentSchema !== undefined\n ? { ...baseCtx, schema: parentSchema }\n : baseCtx\n\n // Execute function\n const result = await fn(ctx)\n\n // Release savepoint on success\n // PostgreSQL/MySQL/SQLite: RELEASE SAVEPOINT name\n // MSSQL: Does not support RELEASE, savepoint is automatically released on commit\n if (dialect !== 'mssql') {\n await sql`RELEASE SAVEPOINT ${sql.id(savepointName)}`.execute(actualDb as Transaction<DB>)\n }\n\n return result\n } catch (error) {\n // Rollback to savepoint on error\n try {\n // PostgreSQL/MySQL/SQLite: ROLLBACK TO SAVEPOINT name\n // MSSQL: ROLLBACK TRANSACTION name\n if (dialect === 'mssql') {\n await sql`ROLLBACK TRANSACTION ${sql.id(savepointName)}`.execute(\n actualDb as Transaction<DB>\n )\n } else {\n await sql`ROLLBACK TO SAVEPOINT ${sql.id(savepointName)}`.execute(\n actualDb as Transaction<DB>\n )\n }\n } catch (rollbackError) {\n // Handle rollback errors based on configured mode\n switch (rollbackErrorMode) {\n case 'throw':\n // Throw rollback error instead of original error (useful for debugging)\n throw rollbackError\n case 'callback':\n // Call custom callback with both errors\n // eslint-disable-next-line max-depth\n if (onRollbackError) {\n await onRollbackError(error, rollbackError)\n }\n break\n case 'log-only':\n default:\n // Log rollback failure (default behavior)\n // Original error is more important, so we do not throw here\n logger.error('Savepoint rollback failed for ' + savepointName + ':', rollbackError)\n break\n }\n }\n throw error\n }\n }\n\n // Create transaction builder\n const transactionBuilder = actualDb.transaction()\n\n // Apply isolation level if specified (only valid for top-level transaction)\n if (options.isolationLevel) {\n transactionBuilder.setIsolationLevel(options.isolationLevel)\n }\n\n return await transactionBuilder.execute(async (trx: Transaction<DB>) => {\n // Wrap transaction with plugins if using KyseraExecutor\n const wrappedTrx = isKyseraExecutor(actualDb) ? wrapTransaction(trx, getPlugins(actualDb)) : trx\n\n // Mark the transaction as being in a transaction (for nested detection)\n markAsInTransaction(wrappedTrx)\n\n // Initialize savepoint counter\n ;(wrappedTrx as unknown as Record<symbol, number>)[SAVEPOINT_COUNTER_SYMBOL] = 0\n\n // Preserve schema from parent context if available\n const parentSchema = isDbContext<DB>(db) ? db.schema : undefined\n const dbWithSchema = parentSchema ? wrappedTrx.withSchema(parentSchema) : wrappedTrx\n const baseTrxCtx = {\n [DB_CONTEXT_SYMBOL]: true as const,\n db: dbWithSchema,\n isTransaction: true as const\n }\n const ctx: DbContext<DB> = parentSchema !== undefined\n ? { ...baseTrxCtx, schema: parentSchema }\n : baseTrxCtx\n\n return await fn(ctx)\n })\n}\n\n/**\n * Execute a function with a database context.\n *\n * Creates a context without a transaction.\n * Supports both Kysely and KyseraExecutor instances.\n *\n * @param db - Database instance (Kysely or KyseraExecutor)\n * @param fn - Function to execute\n * @returns Result of the function\n *\n * @example\n * ```typescript\n * import { withContext } from \"@kysera/dal\";\n *\n * const users = await withContext(db, async (ctx) => {\n * return getAllUsers(ctx);\n * });\n * ```\n */\nexport async function withContext<DB, T>(\n db: Kysely<DB> | KyseraExecutor<DB>,\n fn: (ctx: DbContext<DB>) => Promise<T>\n): Promise<T> {\n const ctx = createContext(db)\n return await fn(ctx)\n}\n\n/**\n * Check if context is within a transaction.\n *\n * @param ctx - Database context\n * @returns True if within a transaction\n */\nexport function isInTransaction<DB>(ctx: DbContext<DB>): boolean {\n return ctx.isTransaction\n}\n\n/**\n * Normalize input to DbContext.\n * Accepts either a DbContext (returned as-is) or a database/executor instance\n * (wrapped in a new context).\n *\n * @param ctxOrDb - Database context or database instance\n * @returns Database context\n */\nexport function toContext<DB>(\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>\n): DbContext<DB> {\n return isDbContext<DB>(ctxOrDb) ? ctxOrDb : createContext(ctxOrDb)\n}\n","/**\n * Query function creation utilities.\n *\n * @module @kysera/dal\n */\n\nimport type { Kysely } from 'kysely'\nimport type { KyseraExecutor } from '@kysera/executor'\nimport type { DbContext, QueryFunction } from './types.js'\nimport { toContext } from './context.js'\nimport { TransactionRequiredError } from './errors.js'\n\n/**\n * Create a typed query function.\n *\n * Query functions are the core building blocks of Functional DAL.\n * They receive a database context and arguments, and return a Promise.\n *\n * The result type is automatically inferred from the query.\n * Supports raw Kysely instances and plugin-aware KyseraExecutor.\n *\n * @param queryFn - Query implementation function\n * @returns Callable query function\n *\n * @example Basic query\n * ```typescript\n * import { createQuery } from '@kysera/dal';\n *\n * const getUserById = createQuery(\n * (ctx, id: number) =>\n * ctx.db\n * .selectFrom('users')\n * .select(['id', 'email', 'name'])\n * .where('id', '=', id)\n * .executeTakeFirst()\n * );\n *\n * // Usage with raw Kysely\n * const user = await getUserById(db, 1);\n * // Type: { id: number; email: string; name: string } | undefined\n * ```\n *\n * @example With KyseraExecutor (plugins applied)\n * ```typescript\n * import { createQuery } from '@kysera/dal';\n * import { createExecutor } from '@kysera/executor';\n * import { softDeletePlugin } from '@kysera/soft-delete';\n *\n * const executor = await createExecutor(db, [softDeletePlugin()]);\n *\n * const getUsers = createQuery((ctx) =>\n * ctx.db.selectFrom('users').selectAll().execute()\n * );\n *\n * // Soft-delete filter automatically applied\n * const users = await getUsers(executor);\n * ```\n *\n * @example With transaction\n * ```typescript\n * import { createQuery, withTransaction } from '@kysera/dal';\n *\n * const result = await withTransaction(db, async (ctx) => {\n * return getUserById(ctx, 1);\n * });\n * ```\n */\nexport function createQuery<DB, TArgs extends readonly unknown[], TResult>(\n queryFn: (ctx: DbContext<DB>, ...args: TArgs) => Promise<TResult>\n): QueryFunction<DB, TArgs, TResult> {\n return (\n dbOrCtx: Kysely<DB> | KyseraExecutor<DB> | DbContext<DB>,\n ...args: TArgs\n ): Promise<TResult> => {\n const ctx: DbContext<DB> = toContext(dbOrCtx)\n\n return queryFn(ctx, ...args)\n }\n}\n\n/**\n * Create a query function that requires a transaction.\n *\n * Throws a TransactionRequiredError if called outside a transaction context.\n *\n * @param queryFn - Query implementation function\n * @returns Query function that requires transaction\n *\n * @example\n * ```typescript\n * import { createTransactionalQuery, withTransaction } from '@kysera/dal';\n *\n * const transferFunds = createTransactionalQuery(\n * async (ctx, fromId: number, toId: number, amount: number) => {\n * await ctx.db\n * .updateTable('accounts')\n * .set((eb) => ({ balance: eb('balance', '-', amount) }))\n * .where('id', '=', fromId)\n * .execute();\n *\n * await ctx.db\n * .updateTable('accounts')\n * .set((eb) => ({ balance: eb('balance', '+', amount) }))\n * .where('id', '=', toId)\n * .execute();\n *\n * return { success: true };\n * }\n * );\n *\n * // This will work\n * await withTransaction(db, (ctx) => transferFunds(ctx, 1, 2, 100));\n *\n * // This will throw TransactionRequiredError\n * await transferFunds(db, 1, 2, 100);\n * ```\n */\nexport function createTransactionalQuery<DB, TArgs extends readonly unknown[], TResult>(\n queryFn: (ctx: DbContext<DB>, ...args: TArgs) => Promise<TResult>\n): QueryFunction<DB, TArgs, TResult> {\n return createQuery(async (ctx: DbContext<DB>, ...args: TArgs) => {\n if (!ctx.isTransaction) {\n throw new TransactionRequiredError()\n }\n return await queryFn(ctx, ...args)\n })\n}\n","/**\n * Query composition utilities.\n *\n * @module @kysera/dal\n */\n\nimport type { Kysely } from 'kysely'\nimport type { KyseraExecutor } from '@kysera/executor'\nimport type { DbContext, QueryFunction } from './types.js'\nimport { toContext } from './context.js'\n\n/**\n * Compose two query functions sequentially.\n *\n * The result of the first query is passed to the second.\n *\n * @param first - First query function\n * @param second - Function that receives context and first result\n * @returns Composed query function\n *\n * @example\n * ```typescript\n * import { createQuery, compose } from '@kysera/dal';\n *\n * const getUserById = createQuery((ctx, id: number) =>\n * ctx.db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirstOrThrow()\n * );\n *\n * const getPostsByUserId = createQuery((ctx, userId: number) =>\n * ctx.db.selectFrom('posts').selectAll().where('user_id', '=', userId).execute()\n * );\n *\n * const getUserWithPosts = compose(\n * getUserById,\n * async (ctx, user) => ({\n * ...user,\n * posts: await getPostsByUserId(ctx, user.id),\n * })\n * );\n *\n * const result = await getUserWithPosts(db, 1);\n * // { id: 1, name: '...', posts: [...] }\n * ```\n */\nexport function compose<DB, TArgs extends readonly unknown[], TFirst, TResult>(\n first: QueryFunction<DB, TArgs, TFirst>,\n second: (ctx: DbContext<DB>, result: TFirst) => Promise<TResult>\n): QueryFunction<DB, TArgs, TResult> {\n return async (\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>,\n ...args: TArgs\n ): Promise<TResult> => {\n const ctx = toContext(ctxOrDb)\n const firstResult = await first(ctx, ...args)\n return await second(ctx, firstResult)\n }\n}\n\n/**\n * Chain multiple operations on a query result.\n *\n * Supports up to 8 type-safe transforms. For more than 8 transforms,\n * the return type falls back to `unknown` (use type assertion if needed).\n *\n * @param query - Initial query function\n * @param transforms - Array of transform functions (up to 8 with full type safety)\n * @returns Chained query function\n *\n * @example Basic usage (2 transforms)\n * ```typescript\n * import { createQuery, chain } from '@kysera/dal';\n *\n * const getUser = createQuery((ctx, id: number) =>\n * ctx.db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirstOrThrow()\n * );\n *\n * const getUserFull = chain(\n * getUser,\n * async (ctx, user) => ({ ...user, posts: await getPosts(ctx, user.id) }),\n * async (ctx, data) => ({ ...data, followers: await getFollowers(ctx, data.id) })\n * );\n * // Type: QueryFunction<DB, [number], { ...user, posts: Post[], followers: User[] }>\n * ```\n *\n * @example Maximum type-safe transforms (8)\n * ```typescript\n * const complexQuery = chain(\n * getUser,\n * async (ctx, user) => ({ ...user, posts: await getPosts(ctx, user.id) }),\n * async (ctx, data) => ({ ...data, comments: await getComments(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, likes: await getLikes(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, shares: await getShares(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, followers: await getFollowers(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, following: await getFollowing(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, stats: await getStats(ctx, data.id) }),\n * async (ctx, data) => ({ ...data, metadata: await getMetadata(ctx, data.id) })\n * );\n * // Type is still inferred correctly!\n * ```\n *\n * @example More than 8 transforms (fallback to unknown)\n * ```typescript\n * const veryComplexQuery = chain(\n * getUser,\n * t1, t2, t3, t4, t5, t6, t7, t8, t9 // 9 transforms\n * );\n * // Type: QueryFunction<DB, [number], unknown>\n * // Use type assertion: const result = await veryComplexQuery(db, 1) as MyType\n * ```\n */\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>\n): QueryFunction<DB, TArgs, T2>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>\n): QueryFunction<DB, TArgs, T3>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3, T4>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>,\n t3: (ctx: DbContext<DB>, result: T3) => Promise<T4>\n): QueryFunction<DB, TArgs, T4>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3, T4, T5>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>,\n t3: (ctx: DbContext<DB>, result: T3) => Promise<T4>,\n t4: (ctx: DbContext<DB>, result: T4) => Promise<T5>\n): QueryFunction<DB, TArgs, T5>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3, T4, T5, T6>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>,\n t3: (ctx: DbContext<DB>, result: T3) => Promise<T4>,\n t4: (ctx: DbContext<DB>, result: T4) => Promise<T5>,\n t5: (ctx: DbContext<DB>, result: T5) => Promise<T6>\n): QueryFunction<DB, TArgs, T6>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3, T4, T5, T6, T7>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>,\n t3: (ctx: DbContext<DB>, result: T3) => Promise<T4>,\n t4: (ctx: DbContext<DB>, result: T4) => Promise<T5>,\n t5: (ctx: DbContext<DB>, result: T5) => Promise<T6>,\n t6: (ctx: DbContext<DB>, result: T6) => Promise<T7>\n): QueryFunction<DB, TArgs, T7>\nexport function chain<DB, TArgs extends readonly unknown[], T1, T2, T3, T4, T5, T6, T7, T8>(\n query: QueryFunction<DB, TArgs, T1>,\n t1: (ctx: DbContext<DB>, result: T1) => Promise<T2>,\n t2: (ctx: DbContext<DB>, result: T2) => Promise<T3>,\n t3: (ctx: DbContext<DB>, result: T3) => Promise<T4>,\n t4: (ctx: DbContext<DB>, result: T4) => Promise<T5>,\n t5: (ctx: DbContext<DB>, result: T5) => Promise<T6>,\n t6: (ctx: DbContext<DB>, result: T6) => Promise<T7>,\n t7: (ctx: DbContext<DB>, result: T7) => Promise<T8>\n): QueryFunction<DB, TArgs, T8>\nexport function chain<DB, TArgs extends readonly unknown[]>(\n query: QueryFunction<DB, TArgs, unknown>,\n ...transforms: ((ctx: DbContext<DB>, result: unknown) => Promise<unknown>)[]\n): QueryFunction<DB, TArgs, unknown> {\n return async (\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>,\n ...args: TArgs\n ): Promise<unknown> => {\n const ctx = toContext(ctxOrDb)\n let result = await query(ctx, ...args)\n for (const transform of transforms) {\n result = await transform(ctx, result)\n }\n return result\n }\n}\n\n/**\n * Result type for parallel query execution.\n *\n * Uses mapped types to infer the result type of each query function,\n * maintaining full type safety across all database schemas.\n */\nexport type ParallelResult<\n DB,\n TArgs extends readonly unknown[],\n T extends Record<string, QueryFunction<DB, TArgs, unknown>>\n> = {\n [K in keyof T]: T[K] extends QueryFunction<DB, TArgs, infer R> ? R : never\n}\n\n/**\n * Execute multiple queries in parallel.\n *\n * All queries receive the same arguments and are executed concurrently.\n *\n * @param queries - Object of query functions\n * @returns Query function that returns object with all results\n *\n * @example\n * ```typescript\n * import { createQuery, parallel } from '@kysera/dal';\n *\n * const getUserById = createQuery((ctx, id: number) =>\n * ctx.db.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirst()\n * );\n *\n * const getUserStats = createQuery((ctx, id: number) =>\n * ctx.db.selectFrom('user_stats').selectAll().where('user_id', '=', id).executeTakeFirst()\n * );\n *\n * const getNotifications = createQuery((ctx, id: number) =>\n * ctx.db.selectFrom('notifications').selectAll().where('user_id', '=', id).execute()\n * );\n *\n * const getDashboardData = parallel({\n * user: getUserById,\n * stats: getUserStats,\n * notifications: getNotifications,\n * });\n *\n * const dashboard = await getDashboardData(db, userId);\n * // { user: {...}, stats: {...}, notifications: [...] }\n * ```\n */\nexport function parallel<\n DB,\n TArgs extends readonly unknown[],\n T extends Record<string, QueryFunction<DB, TArgs, unknown>>\n>(\n queries: T\n): QueryFunction<\n DB,\n TArgs,\n { [K in keyof T]: T[K] extends QueryFunction<DB, TArgs, infer R> ? R : never }\n> {\n return async (ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>, ...args: TArgs) => {\n const ctx = toContext(ctxOrDb)\n const entries = Object.entries(queries)\n const results = await Promise.all(\n entries.map(async ([key, query]) => {\n const result = await query(ctx, ...args)\n return [key, result] as const\n })\n )\n\n return Object.fromEntries(results) as {\n [K in keyof T]: T[K] extends QueryFunction<DB, TArgs, infer R> ? R : never\n }\n }\n}\n\n/**\n * Execute a query conditionally.\n *\n * @param condition - Condition function\n * @param query - Query to execute if condition is true\n * @param fallback - Optional fallback value if condition is false\n * @returns Conditional query function\n *\n * @example\n * ```typescript\n * import { createQuery, conditional } from '@kysera/dal';\n *\n * const getPremiumFeatures = createQuery((ctx, userId: number) =>\n * ctx.db.selectFrom('premium_features').selectAll().where('user_id', '=', userId).execute()\n * );\n *\n * const getFeatures = conditional(\n * (ctx, userId: number, isPremium: boolean) => isPremium,\n * getPremiumFeatures,\n * [] // Return empty array for non-premium users\n * );\n * ```\n */\nexport function conditional<DB, TArgs extends readonly unknown[], TResult, TFallback = undefined>(\n condition: (ctx: DbContext<DB>, ...args: TArgs) => boolean | Promise<boolean>,\n query: QueryFunction<DB, TArgs, TResult>,\n fallback?: TFallback\n): QueryFunction<DB, TArgs, TResult | TFallback> {\n return async (\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>,\n ...args: TArgs\n ): Promise<TResult | TFallback> => {\n const ctx = toContext(ctxOrDb)\n const shouldExecute = await condition(ctx, ...args)\n if (shouldExecute) {\n return await query(ctx, ...args)\n }\n return fallback as TFallback\n }\n}\n\n/**\n * Map over query results.\n *\n * @param query - Query that returns an array\n * @param mapper - Function to apply to each element\n * @returns Query with mapped results\n *\n * @example\n * ```typescript\n * import { createQuery, mapResult } from '@kysera/dal';\n *\n * const getUsers = createQuery((ctx) =>\n * ctx.db.selectFrom('users').selectAll().execute()\n * );\n *\n * const getUserNames = mapResult(getUsers, (user) => user.name);\n *\n * const names = await getUserNames(db); // string[]\n * ```\n */\nexport function mapResult<DB, TArgs extends readonly unknown[], TItem, TResult>(\n query: QueryFunction<DB, TArgs, TItem[]>,\n mapper: (item: TItem, index: number) => TResult\n): QueryFunction<DB, TArgs, TResult[]> {\n return async (\n ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>,\n ...args: TArgs\n ): Promise<TResult[]> => {\n const ctx = toContext(ctxOrDb)\n const items = await query(ctx, ...args)\n return items.map(mapper)\n }\n}\n"]} |
+10
-10
| { | ||
| "name": "@kysera/dal", | ||
| "version": "0.8.7", | ||
| "version": "0.8.8", | ||
| "description": "Functional Data Access Layer for Kysely - query functions, context passing, composition, plugin integration", | ||
@@ -19,19 +19,19 @@ "type": "module", | ||
| "dependencies": { | ||
| "@kysera/executor": "0.8.7", | ||
| "@kysera/core": "0.8.7" | ||
| "@kysera/core": "0.8.8", | ||
| "@kysera/executor": "0.8.8" | ||
| }, | ||
| "peerDependencies": { | ||
| "kysely": ">=0.28.9" | ||
| "kysely": ">=0.28.14" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/better-sqlite3": "^7.6.13", | ||
| "@types/node": "^25.5.0", | ||
| "@types/pg": "^8.18.0", | ||
| "@vitest/coverage-v8": "^4.1.0", | ||
| "@types/node": "^25.5.2", | ||
| "@types/pg": "^8.20.0", | ||
| "@vitest/coverage-v8": "^4.1.3", | ||
| "better-sqlite3": "^12.8.0", | ||
| "kysely": "^0.28.12", | ||
| "kysely": "^0.28.15", | ||
| "pg": "^8.20.0", | ||
| "tsup": "^8.5.1", | ||
| "typescript": "^5.9.3", | ||
| "vitest": "^4.1.0" | ||
| "typescript": "^6.0.2", | ||
| "vitest": "^4.1.3" | ||
| }, | ||
@@ -38,0 +38,0 @@ "keywords": [ |
+1
-14
@@ -10,18 +10,5 @@ /** | ||
| import type { DbContext, QueryFunction } from './types.js' | ||
| import { isDbContext } from './types.js' | ||
| import { createContext } from './context.js' | ||
| import { toContext } from './context.js' | ||
| /** | ||
| * Normalize input to DbContext. | ||
| * Uses Symbol-based detection for reliable context identification. | ||
| * @internal | ||
| */ | ||
| function toContext<DB>(ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB>): DbContext<DB> { | ||
| if (isDbContext<DB>(ctxOrDb)) { | ||
| return ctxOrDb | ||
| } | ||
| return createContext(ctxOrDb) | ||
| } | ||
| /** | ||
| * Compose two query functions sequentially. | ||
@@ -28,0 +15,0 @@ * |
+14
-0
@@ -529,1 +529,15 @@ /** | ||
| } | ||
| /** | ||
| * Normalize input to DbContext. | ||
| * Accepts either a DbContext (returned as-is) or a database/executor instance | ||
| * (wrapped in a new context). | ||
| * | ||
| * @param ctxOrDb - Database context or database instance | ||
| * @returns Database context | ||
| */ | ||
| export function toContext<DB>( | ||
| ctxOrDb: DbContext<DB> | Kysely<DB> | KyseraExecutor<DB> | ||
| ): DbContext<DB> { | ||
| return isDbContext<DB>(ctxOrDb) ? ctxOrDb : createContext(ctxOrDb) | ||
| } |
+7
-7
@@ -7,5 +7,10 @@ /** | ||
| import { DatabaseError, ErrorCodes } from '@kysera/core' | ||
| /** | ||
| * Error thrown when a query that requires a transaction is called outside of a transaction context. | ||
| * | ||
| * Extends DatabaseError from @kysera/core for consistent error hierarchy | ||
| * with `.code` and `.toJSON()` support. | ||
| * | ||
| * @example | ||
@@ -26,3 +31,3 @@ * ```typescript | ||
| */ | ||
| export class TransactionRequiredError extends Error { | ||
| export class TransactionRequiredError extends DatabaseError { | ||
| /** | ||
@@ -36,10 +41,5 @@ * Create a new TransactionRequiredError | ||
| ) { | ||
| super(message) | ||
| super(message, ErrorCodes.DB_TRANSACTION_FAILED) | ||
| this.name = 'TransactionRequiredError' | ||
| // Maintains proper stack trace for where our error was thrown (only available on V8) | ||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
| if (Error.captureStackTrace) { | ||
| Error.captureStackTrace(this, TransactionRequiredError) | ||
| } | ||
| } | ||
| } |
+1
-0
@@ -132,2 +132,3 @@ /** | ||
| createSchemaContext, | ||
| toContext, | ||
| withTransaction, | ||
@@ -134,0 +135,0 @@ withContext, |
+2
-4
@@ -10,4 +10,3 @@ /** | ||
| import type { DbContext, QueryFunction } from './types.js' | ||
| import { isDbContext } from './types.js' | ||
| import { createContext } from './context.js' | ||
| import { toContext } from './context.js' | ||
| import { TransactionRequiredError } from './errors.js' | ||
@@ -77,4 +76,3 @@ | ||
| ): Promise<TResult> => { | ||
| // Use Symbol-based detection for reliable context identification | ||
| const ctx: DbContext<DB> = isDbContext<DB>(dbOrCtx) ? dbOrCtx : createContext(dbOrCtx) | ||
| const ctx: DbContext<DB> = toContext(dbOrCtx) | ||
@@ -81,0 +79,0 @@ return queryFn(ctx, ...args) |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
147643
0.08%1900
0.58%+ Added
+ Added
- Removed
- Removed
Updated
Updated