UNPKG

forge-sql-orm

Version:

Drizzle ORM integration for Atlassian @forge/sql. Provides a custom driver, schema migration, two levels of caching (local and global via @forge/kvs), optimistic locking, and query analysis.

1,186 lines (1,097 loc) 37.8 kB
import { MySqlRawQueryResult, MySqlRemoteDatabase, MySqlRemotePreparedQueryHKT, MySqlRemoteQueryResultHKT, } from "drizzle-orm/mysql-proxy"; import { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types"; import { applyFromDriverTransform, ForgeSqlOrmOptions, mapSelectFieldsWithAlias } from "../../.."; import { MySqlSelectBase, MySqlSelectBuilder } from "drizzle-orm/mysql-core"; import { MySqlTable } from "drizzle-orm/mysql-core/table"; import { MySqlDeleteBase, MySqlInsertBuilder, MySqlUpdateBuilder, } from "drizzle-orm/mysql-core/query-builders"; import { clearCache, getFromCache, setCacheResult } from "../../../utils/cacheUtils"; import { cacheApplicationContext, evictLocalCacheQuery, getQueryLocalCacheQuery, saveQueryLocalCacheQuery, saveTableIfInsideCacheContext, } from "../../../utils/cacheContextUtils"; import { isSQLWrapper, SQLWrapper } from "drizzle-orm/sql/sql"; import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session"; import { getTableColumns, Query, SQL } from "drizzle-orm"; import { MySqlDialect } from "drizzle-orm/mysql-core/dialect"; import { GetSelectTableName, GetSelectTableSelection, SelectResultField, } from "drizzle-orm/query-builders/select.types"; // ============================================================================ // TYPES AND INTERFACES // ============================================================================ /** * Base interface for query builders that can be executed */ interface QueryBuilder { execute: (...args: any[]) => Promise<any>; then?: (onfulfilled?: any, onrejected?: any) => Promise<any>; toSQL?: () => any; } /** * Error codes that should not trigger cache clearing */ const NON_CACHE_CLEARING_ERROR_CODES = ["VALIDATION_ERROR", "CONSTRAINT_ERROR"] as const; /** * Error codes that should trigger cache clearing */ const CACHE_CLEARING_ERROR_CODES = ["DEADLOCK", "LOCK_WAIT_TIMEOUT", "CONNECTION_ERROR"] as const; /** * Error message patterns that should not trigger cache clearing */ const NON_CACHE_CLEARING_PATTERNS = [/validation/i, /constraint/i] as const; /** * Error message patterns that should trigger cache clearing */ const CACHE_CLEARING_PATTERNS = [/timeout/i, /connection/i] as const; // ============================================================================ // CACHE MANAGEMENT UTILITIES // ============================================================================ /** * Determines whether cache should be cleared based on the error type. * Only clears cache for errors that might indicate data consistency issues. * * @param error - The error that occurred during query execution * @returns true if cache should be cleared, false otherwise */ function shouldClearCacheOnError(error: any): boolean { // Don't clear cache for client-side errors (validation, etc.) if (error?.code && NON_CACHE_CLEARING_ERROR_CODES.includes(error.code)) { return false; } if ( error?.message && NON_CACHE_CLEARING_PATTERNS.some((pattern) => pattern.test(error.message)) ) { return false; } // Clear cache for database-level errors that might affect data consistency if (error?.code && CACHE_CLEARING_ERROR_CODES.includes(error.code)) { return true; } if (error?.message && CACHE_CLEARING_PATTERNS.some((pattern) => pattern.test(error.message))) { return true; } // For unknown errors, be conservative and clear cache return true; } // ============================================================================ // EXPORTED TYPES // ============================================================================ /** * Type for select queries with field aliasing */ export type SelectAliasedType = <TSelection extends SelectedFields>( fields: TSelection, ) => MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>; /** * Type for select distinct queries with field aliasing */ export type SelectAliasedDistinctType = <TSelection extends SelectedFields>( fields: TSelection, ) => MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>; /** * Type for select queries with field aliasing and caching */ export type SelectAliasedCacheableType = <TSelection extends SelectedFields>( fields: TSelection, cacheTtl?: number, ) => MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>; /** * Type for select distinct queries with field aliasing and caching */ export type SelectAliasedDistinctCacheableType = <TSelection extends SelectedFields>( fields: TSelection, cacheTtl?: number, ) => MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>; /** * Type for select queries from table with field aliasing */ export type SelectAllFromAliasedType = <T extends MySqlTable>( table: T, ) => MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField<GetSelectTableSelection<T>[Key]>; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField<GetSelectTableSelection<T>[Key]>; }[K]; }[], any >; /** * Type for select distinct queries from table with field aliasing */ export type SelectAllDistinctFromAliasedType = <T extends MySqlTable>( table: T, ) => MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField<GetSelectTableSelection<T>[Key]>; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField<GetSelectTableSelection<T>[Key]>; }[K]; }[], any >; /** * Type for select queries from table with field aliasing and caching */ export type SelectAllFromCacheableAliasedType = <T extends MySqlTable>( table: T, cacheTtl?: number, ) => MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField<GetSelectTableSelection<T>[Key]>; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField<GetSelectTableSelection<T>[Key]>; }[K]; }[], any >; /** * Type for select distinct queries from table with field aliasing and caching */ export type SelectAllDistinctFromCacheableAliasedType = <T extends MySqlTable>( table: T, cacheTtl?: number, ) => MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField<GetSelectTableSelection<T>[Key]>; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField<GetSelectTableSelection<T>[Key]>; }[K]; }[], any >; /** * Type for executing raw SQL queries with local cache */ export type ExecuteQuery = <T>( query: SQLWrapper | string, ) => Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>>; /** * Type for executing raw SQL queries with local and global cache */ export type ExecuteQueryCacheable = <T>( query: SQLWrapper | string, cacheTtl?: number, ) => Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>>; /** * Type for insert operations with cache eviction */ export type InsertAndEvictCacheType = <TTable extends MySqlTable>( table: TTable, ) => MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT>; /** * Type for update operations with cache eviction */ export type UpdateAndEvictCacheType = <TTable extends MySqlTable>( table: TTable, ) => MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT>; /** * Type for delete operations with cache eviction */ export type DeleteAndEvictCacheType = <TTable extends MySqlTable>( table: TTable, ) => MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT>; /** * Handles successful query execution with cache management. * * @param rows - Query result rows * @param onfulfilled - Success callback * @param table - The table being modified * @param options - ForgeSQL ORM options * @param isCached - Whether to clear cache immediately * @returns Promise with result */ async function handleSuccessfulExecution( rows: unknown[], onfulfilled: any, table: MySqlTable, options: ForgeSqlOrmOptions, isCached: boolean, ): Promise<any> { try { await evictLocalCacheQuery(table, options); await saveTableIfInsideCacheContext(table); if (isCached && !cacheApplicationContext.getStore()) { await clearCache(table, options); } const result = onfulfilled ? onfulfilled(rows) : rows; return result; } catch (error) { // Only clear cache for certain types of errors if (shouldClearCacheOnError(error)) { await evictLocalCacheQuery(table, options); if (isCached) { await clearCache(table, options).catch((e) => { // eslint-disable-next-line no-console console.warn("Ignore cache clear errors", e); }); } else { await saveTableIfInsideCacheContext(table); } } throw error; } } /** * Handles function calls on the wrapped builder. * * @param value - The function to call * @param target - The target object * @param args - Function arguments * @param table - The table being modified * @param options - ForgeSQL ORM options * @param isCached - Whether to clear cache immediately * @returns Function result or wrapped builder */ function handleFunctionCall( value: Function, target: any, args: any[], table: MySqlTable, options: ForgeSqlOrmOptions, isCached: boolean, ): any { const result = value.apply(target, args); if (typeof result === "object" && result !== null && "execute" in result) { return wrapCacheEvictBuilder(result as QueryBuilder, table, options, isCached); } return result; } /** * Wraps a query builder with cache eviction functionality. * Automatically clears cache after successful execution of insert/update/delete operations. * * @param rawBuilder - The original query builder to wrap * @param table - The table being modified (used for cache eviction) * @param options - ForgeSQL ORM options including cache configuration * @param isCached - Whether to clear cache immediately * @returns Wrapped query builder with cache eviction */ const wrapCacheEvictBuilder = <TTable extends MySqlTable>( rawBuilder: QueryBuilder, table: TTable, options: ForgeSqlOrmOptions, isCached: boolean, ): QueryBuilder => { return new Proxy(rawBuilder, { get(target, prop, receiver) { if (prop === "then") { return (onfulfilled?: any, onrejected?: any) => target .execute() .then( (rows: unknown[]) => handleSuccessfulExecution(rows, onfulfilled, table, options, isCached), onrejected, ); } const value = Reflect.get(target, prop, receiver); if (typeof value === "function") { return (...args: any[]) => handleFunctionCall(value, target, args, table, options, isCached); } return value; }, }); }; /** * Creates an insert query builder that automatically evicts cache after execution. * * @param db - The database instance * @param table - The table to insert into * @param options - ForgeSQL ORM options * @returns Insert query builder with cache eviction */ function insertAndEvictCacheBuilder<TTable extends MySqlTable>( db: MySqlRemoteDatabase<any>, table: TTable, options: ForgeSqlOrmOptions, isCached: boolean, ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> { const builder = db.insert(table); return wrapCacheEvictBuilder( builder as unknown as QueryBuilder, table, options, isCached, ) as unknown as MySqlInsertBuilder< TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT >; } /** * Creates an update query builder that automatically evicts cache after execution. * * @param db - The database instance * @param table - The table to update * @param options - ForgeSQL ORM options * @returns Update query builder with cache eviction */ function updateAndEvictCacheBuilder<TTable extends MySqlTable>( db: MySqlRemoteDatabase<any>, table: TTable, options: ForgeSqlOrmOptions, isCached: boolean, ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> { const builder = db.update(table); return wrapCacheEvictBuilder( builder as unknown as QueryBuilder, table, options, isCached, ) as unknown as MySqlUpdateBuilder< TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT >; } /** * Creates a delete query builder that automatically evicts cache after execution. * * @param db - The database instance * @param table - The table to delete from * @param options - ForgeSQL ORM options * @returns Delete query builder with cache eviction */ function deleteAndEvictCacheBuilder<TTable extends MySqlTable>( db: MySqlRemoteDatabase<any>, table: TTable, options: ForgeSqlOrmOptions, isCached: boolean, ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> { const builder = db.delete(table); return wrapCacheEvictBuilder( builder as unknown as QueryBuilder, table, options, isCached, ) as unknown as MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT>; } /** * Handles cached query execution with proper error handling. * * @param target - The query target * @param options - ForgeSQL ORM options * @param cacheTtl - Cache TTL * @param selections - Field selections * @param aliasMap - Field alias mapping * @param onfulfilled - Success callback * @param onrejected - Error callback * @returns Promise with cached result */ async function handleCachedQuery( target: any, options: ForgeSqlOrmOptions, cacheTtl: number, selections: any, aliasMap: any, onfulfilled?: any, onrejected?: any, ): Promise<any> { try { const localCached = await getQueryLocalCacheQuery(target, options); if (localCached) { return onfulfilled ? onfulfilled(localCached) : localCached; } const cacheResult = await getFromCache(target, options); if (cacheResult) { return onfulfilled ? onfulfilled(cacheResult) : cacheResult; } const rows = await target.execute(); const transformed = applyFromDriverTransform(rows, selections, aliasMap); await saveQueryLocalCacheQuery(target, transformed, options); await setCacheResult(target, options, transformed, cacheTtl).catch((cacheError) => { // Log cache error but don't fail the query // eslint-disable-next-line no-console console.warn("Cache set error:", cacheError); }); return onfulfilled ? onfulfilled(transformed) : transformed; } catch (error) { if (onrejected) { return onrejected(error); } throw error; } } /** * Handles non-cached query execution. * * @param target - The query target * @param options - ForgeSQL ORM options * @param selections - Field selections * @param aliasMap - Field alias mapping * @param onfulfilled - Success callback * @param onrejected - Error callback * @returns Promise with transformed result */ async function handleNonCachedQuery( target: any, options: any, selections: any, aliasMap: any, onfulfilled?: any, onrejected?: any, ): Promise<any> { try { const localCached = await getQueryLocalCacheQuery(target, options); if (localCached) { return onfulfilled ? onfulfilled(localCached) : localCached; } const rows = await target.execute(); const transformed = applyFromDriverTransform(rows, selections, aliasMap); await saveQueryLocalCacheQuery(target, transformed, options); return onfulfilled ? onfulfilled(transformed) : transformed; } catch (error) { if (onrejected) { return onrejected(error); } throw error; } } /** * Creates a select query builder with field aliasing and optional caching support. * * @param db - The database instance * @param fields - The fields to select with aliases * @param selectFn - Function to create the base select query * @param useCache - Whether to enable caching for this query * @param options - ForgeSQL ORM options * @param cacheTtl - Optional cache TTL override * @returns Select query builder with aliasing and optional caching */ /** * Creates a catch handler for Promise-like objects */ function createCatchHandler(receiver: any): (onrejected: any) => Promise<any> { return (onrejected: any) => receiver.then(undefined, onrejected); } /** * Creates a finally handler for Promise-like objects */ function createFinallyHandler(receiver: any): (onfinally: any) => Promise<any> { return (onfinally: any) => { const handleFinally = (value: any) => Promise.resolve(value).finally(onfinally); const handleReject = (reason: any) => Promise.reject(reason).finally(onfinally); return receiver.then(handleFinally, handleReject); }; } /** * Creates a then handler for cached queries */ function createCachedThenHandler( target: any, options: ForgeSqlOrmOptions, cacheTtl: number | undefined, selections: any, aliasMap: any, ): (onfulfilled?: any, onrejected?: any) => Promise<any> { return (onfulfilled?: any, onrejected?: any) => { const ttl = cacheTtl ?? options.cacheTTL ?? 120; return handleCachedQuery(target, options, ttl, selections, aliasMap, onfulfilled, onrejected); }; } /** * Creates a then handler for non-cached queries */ function createNonCachedThenHandler( target: any, options: ForgeSqlOrmOptions, selections: any, aliasMap: any, ): (onfulfilled?: any, onrejected?: any) => Promise<any> { return (onfulfilled?: any, onrejected?: any) => { return handleNonCachedQuery(target, options, selections, aliasMap, onfulfilled, onrejected); }; } /** * Creates an execute handler that transforms results */ function createExecuteHandler( target: any, selections: any, aliasMap: any, ): (...args: any[]) => Promise<any> { return async (...args: any[]) => { const rows = await target.execute(...args); return applyFromDriverTransform(rows, selections, aliasMap); }; } /** * Creates a function call handler that wraps results */ function createFunctionCallHandler( value: Function, target: any, wrapBuilder: (rawBuilder: any) => any, ): (...args: any[]) => any { return (...args: any[]) => { const result = value.apply(target, args); if (typeof result === "object" && result !== null && "execute" in result) { return wrapBuilder(result); } return result; }; } /** * Creates a select query builder with field aliasing and optional caching support. * * @param db - The database instance * @param fields - The fields to select with aliases * @param selectFn - Function to create the base select query * @param useCache - Whether to enable caching for this query * @param options - ForgeSQL ORM options * @param cacheTtl - Optional cache TTL override * @returns Select query builder with aliasing and optional caching */ function createAliasedSelectBuilder<TSelection extends SelectedFields>( db: MySqlRemoteDatabase<any>, fields: TSelection, selectFn: (selections: any) => MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>, useCache: boolean, options: ForgeSqlOrmOptions, cacheTtl?: number, ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> { const { selections, aliasMap } = mapSelectFieldsWithAlias(fields); const builder = selectFn(selections); const wrapBuilder = (rawBuilder: any): any => { return new Proxy(rawBuilder, { get(target, prop, receiver) { if (prop === "execute") { return createExecuteHandler(target, selections, aliasMap); } if (prop === "then") { return useCache ? createCachedThenHandler(target, options, cacheTtl, selections, aliasMap) : createNonCachedThenHandler(target, options, selections, aliasMap); } if (prop === "catch") { return createCatchHandler(receiver); } if (prop === "finally") { return createFinallyHandler(receiver); } const value = Reflect.get(target, prop, receiver); if (typeof value === "function") { return createFunctionCallHandler(value, target, wrapBuilder); } return value; }, }); }; return wrapBuilder(builder); } // ============================================================================ // CONFIGURATION AND CONSTANTS // ============================================================================ /** * Default options for ForgeSQL ORM */ const DEFAULT_OPTIONS: ForgeSqlOrmOptions = { logRawSqlQuery: false, disableOptimisticLocking: false, cacheTTL: 120, cacheWrapTable: true, cacheEntityQueryName: "sql", cacheEntityExpirationName: "expiration", cacheEntityDataName: "data", } as const; // ============================================================================ // QUERY BUILDER FACTORIES // ============================================================================ /** * Creates a raw SQL query executor with caching support */ function createRawQueryExecutor( db: MySqlRemoteDatabase<any>, options: ForgeSqlOrmOptions, useGlobalCache: boolean = false, ) { return async function <T extends { [column: string]: any }>( query: SQLWrapper | string, cacheTtl?: number, ): Promise<MySqlRawQueryResult> { let sql: Query; if (isSQLWrapper(query)) { const dialect = (db as unknown as { dialect: MySqlDialect }).dialect; sql = dialect.sqlToQuery(query as SQL); } else { sql = { sql: query, params: [], }; } // Check local cache first const localCacheResult = await getQueryLocalCacheQuery(sql, options); if (localCacheResult) { return localCacheResult as MySqlRawQueryResult; } // Check global cache if enabled if (useGlobalCache) { const cacheResult = await getFromCache({ toSQL: () => sql }, options); if (cacheResult) { return cacheResult as MySqlRawQueryResult; } } // Execute query const results = await db.execute<T>(query); // Save to local cache await saveQueryLocalCacheQuery(sql, results, options); // Save to global cache if enabled if (useGlobalCache) { await setCacheResult( { toSQL: () => sql }, options, results, cacheTtl ?? options.cacheTTL ?? 120, ); } return results; }; } // ============================================================================ // MAIN PATCH FUNCTION // ============================================================================ /** * Patches a Drizzle database instance with additional methods for aliased selects and cache management. * * This function extends the database instance with: * - selectAliased: Select with field aliasing support * - selectAliasedDistinct: Select distinct with field aliasing support * - selectAliasedCacheable: Select with field aliasing and caching * - selectAliasedDistinctCacheable: Select distinct with field aliasing and caching * - insertAndEvictCache: Insert operations that automatically evict cache * - updateAndEvictCache: Update operations that automatically evict cache * - deleteAndEvictCache: Delete operations that automatically evict cache * * @param db - The Drizzle database instance to patch * @param options - Optional ForgeSQL ORM configuration * @returns The patched database instance with additional methods */ export function patchDbWithSelectAliased( db: MySqlRemoteDatabase<any>, options?: ForgeSqlOrmOptions, ): MySqlRemoteDatabase<any> & { selectAliased: SelectAliasedType; selectAliasedDistinct: SelectAliasedDistinctType; selectAliasedCacheable: SelectAliasedCacheableType; selectAliasedDistinctCacheable: SelectAliasedDistinctCacheableType; insertWithCacheContext: InsertAndEvictCacheType; insertAndEvictCache: InsertAndEvictCacheType; updateAndEvictCache: UpdateAndEvictCacheType; updateWithCacheContext: UpdateAndEvictCacheType; deleteAndEvictCache: DeleteAndEvictCacheType; deleteWithCacheContext: DeleteAndEvictCacheType; } { const newOptions = { ...DEFAULT_OPTIONS, ...options }; // ============================================================================ // SELECT METHODS WITH FIELD ALIASING // ============================================================================ // Select aliased without cache db.selectAliased = function <TSelection extends SelectedFields>(fields: TSelection) { return createAliasedSelectBuilder( db, fields, (selections) => db.select(selections), false, newOptions, ); }; // Select aliased with cache db.selectAliasedCacheable = function <TSelection extends SelectedFields>( fields: TSelection, cacheTtl?: number, ) { return createAliasedSelectBuilder( db, fields, (selections) => db.select(selections), true, newOptions, cacheTtl, ); }; // Select aliased distinct without cache db.selectAliasedDistinct = function <TSelection extends SelectedFields>(fields: TSelection) { return createAliasedSelectBuilder( db, fields, (selections) => db.selectDistinct(selections), false, newOptions, ); }; // Select aliased distinct with cache db.selectAliasedDistinctCacheable = function <TSelection extends SelectedFields>( fields: TSelection, cacheTtl?: number, ) { return createAliasedSelectBuilder( db, fields, (selections) => db.selectDistinct(selections), true, newOptions, cacheTtl, ); }; // ============================================================================ // TABLE-BASED SELECT METHODS // ============================================================================ /** * Creates a select query builder for all columns from a table with field aliasing support. * This is a convenience method that automatically selects all columns from the specified table. * * @param table - The table to select from * @returns Select query builder with all table columns and field aliasing support * @example * ```typescript * const users = await db.selectFrom(userTable).where(eq(userTable.id, 1)); * ``` */ db.selectFrom = function <T extends MySqlTable>( table: T, ): MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }[K]; }[], any > { return db.selectAliased(getTableColumns(table)).from(table) as unknown as MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }[K]; }[], any >; }; /** * Creates a select query builder for all columns from a table with field aliasing and caching support. * This is a convenience method that automatically selects all columns from the specified table with caching enabled. * * @param table - The table to select from * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL) * @returns Select query builder with all table columns, field aliasing, and caching support * @example * ```typescript * const users = await db.selectFromCacheable(userTable, 300).where(eq(userTable.id, 1)); * ``` */ db.selectFromCacheable = function <T extends MySqlTable>( table: T, cacheTtl?: number, ): MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }[K]; }[], any > { return db .selectAliasedCacheable(getTableColumns(table), cacheTtl) .from(table) as unknown as MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }[K]; }[], any >; }; /** * Creates a select distinct query builder for all columns from a table with field aliasing support. * This is a convenience method that automatically selects all distinct columns from the specified table. * * @param table - The table to select from * @returns Select distinct query builder with all table columns and field aliasing support * @example * ```typescript * const uniqueUsers = await db.selectDistinctFrom(userTable).where(eq(userTable.status, 'active')); * ``` */ db.selectDistinctFrom = function <T extends MySqlTable>( table: T, ): MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }[K]; }[], any > { return db .selectAliasedDistinct(getTableColumns(table)) .from(table) as unknown as MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }[K]; }[], any >; }; /** * Creates a select distinct query builder for all columns from a table with field aliasing and caching support. * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled. * * @param table - The table to select from * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL) * @returns Select distinct query builder with all table columns, field aliasing, and caching support * @example * ```typescript * const uniqueUsers = await db.selectDistinctFromCacheable(userTable, 300).where(eq(userTable.status, 'active')); * ``` */ db.selectDistinctFromCacheable = function <T extends MySqlTable>( table: T, cacheTtl?: number, ): MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }[K]; }[], any > { return db .selectAliasedDistinctCacheable(getTableColumns(table), cacheTtl) .from(table) as unknown as MySqlSelectBase< GetSelectTableName<T>, GetSelectTableSelection<T>, "single", MySqlRemotePreparedQueryHKT, GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {}, false, never, { [K in keyof { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }]: { [Key in keyof GetSelectTableSelection<T>]: SelectResultField< GetSelectTableSelection<T>[Key] >; }[K]; }[], any >; }; // ============================================================================ // CACHE-AWARE MODIFY OPERATIONS // ============================================================================ // Insert with cache context support (participates in cache clearing when used within cache context) db.insertWithCacheContext = function <TTable extends MySqlTable>(table: TTable) { return insertAndEvictCacheBuilder(db, table, newOptions, false); }; // Insert with cache eviction db.insertAndEvictCache = function <TTable extends MySqlTable>(table: TTable) { return insertAndEvictCacheBuilder(db, table, newOptions, true); }; // Update with cache context support (participates in cache clearing when used within cache context) db.updateWithCacheContext = function <TTable extends MySqlTable>(table: TTable) { return updateAndEvictCacheBuilder(db, table, newOptions, false); }; // Update with cache eviction db.updateAndEvictCache = function <TTable extends MySqlTable>(table: TTable) { return updateAndEvictCacheBuilder(db, table, newOptions, true); }; // Delete with cache context support (participates in cache clearing when used within cache context) db.deleteWithCacheContext = function <TTable extends MySqlTable>(table: TTable) { return deleteAndEvictCacheBuilder(db, table, newOptions, false); }; // Delete with cache eviction db.deleteAndEvictCache = function <TTable extends MySqlTable>(table: TTable) { return deleteAndEvictCacheBuilder(db, table, newOptions, true); }; // ============================================================================ // RAW SQL QUERY EXECUTORS // ============================================================================ /** * Executes a raw SQL query with local cache support. * This method provides local caching for raw SQL queries within the current invocation context. * Results are cached locally and will be returned from cache on subsequent identical queries. * * @param query - The SQL query to execute (SQLWrapper or string) * @returns Promise with query results * @example * ```typescript * // Using SQLWrapper * const result = await db.executeQuery(sql`SELECT * FROM users WHERE id = ${userId}`); * * // Using string * const result = await db.executeQuery("SELECT * FROM users WHERE status = 'active'"); * ``` */ db.executeQuery = createRawQueryExecutor(db, newOptions, false); /** * Executes a raw SQL query with both local and global cache support. * This method provides comprehensive caching for raw SQL queries: * - Local cache: Within the current invocation context * - Global cache: Cross-invocation caching using @forge/kvs * * @param query - The SQL query to execute (SQLWrapper or string) * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL) * @returns Promise with query results * @example * ```typescript * // Using SQLWrapper with custom TTL * const result = await db.executeQueryCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300); * * // Using string with default TTL * const result = await db.executeQueryCacheable("SELECT * FROM users WHERE status = 'active'"); * ``` */ db.executeQueryCacheable = createRawQueryExecutor(db, newOptions, true); return db; }