@kysera/soft-delete 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -862,10 +862,6 @@ Test files:
862
862
 
863
863
  MIT
864
864
 
865
- ## Contributing
866
-
867
- See the main [Kysera repository](https://github.com/kysera-dev/kysera) for contribution guidelines.
868
-
869
865
  ## Links
870
866
 
871
867
  - [Documentation](https://kysera.dev)
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import {isRepositoryLike,getRawDb}from'@kysera/executor';import {sql}from'kysely';import {silentLogger,NotFoundError}from'@kysera/core';var w="__VERSION__",b=w.startsWith("__")?"0.0.0-dev":w;var S=(y={})=>{let{deletedAtColumn:l="deleted_at",includeDeleted:u=false,tables:i,primaryKeyColumn:n="id",logger:s=silentLogger}=y;return {name:"@kysera/soft-delete",version:b,priority:100,onInit(){},async onDestroy(){s.debug("Soft-delete plugin destroyed");},interceptQuery(a,t){return (!i||i.includes(t.table))&&t.operation==="select"&&!t.metadata.includeDeleted&&!u?(s.debug(`Filtering soft-deleted records from ${t.table}`),a.where(`${t.table}.${l}`,"is",null)):a},extendRepository(a){if(!isRepositoryLike(a))return a;let t=a;if(!(!i||i.includes(t.tableName)))return s.debug(`Table ${t.tableName} does not support soft delete, skipping extension`),a;s.debug(`Extending repository for table ${t.tableName} with soft delete methods`);let r=getRawDb(t.executor);return {...t,async findAll(){return u?await r.selectFrom(t.tableName).selectAll().execute():await r.selectFrom(t.tableName).selectAll().where(l,"is",null).execute()},async findById(e){return u?await r.selectFrom(t.tableName).selectAll().where(n,"=",e).executeTakeFirst()??null:await r.selectFrom(t.tableName).selectAll().where(n,"=",e).where(l,"is",null).executeTakeFirst()??null},async softDelete(e){s.info(`Soft deleting record ${e} from ${t.tableName}`),await r.updateTable(t.tableName).set({[l]:sql`CURRENT_TIMESTAMP`}).where(n,"=",e).execute();let o=await r.selectFrom(t.tableName).selectAll().where(n,"=",e).executeTakeFirst();if(!o)throw s.warn(`Record ${e} not found in ${t.tableName} for soft delete`),new NotFoundError("Record",{id:e});return o},async restore(e){s.info(`Restoring soft-deleted record ${e} from ${t.tableName}`),await r.updateTable(t.tableName).set({[l]:null}).where(n,"=",e).execute();let o=await r.selectFrom(t.tableName).selectAll().where(n,"=",e).executeTakeFirst();if(!o)throw s.warn(`Record ${e} not found in ${t.tableName} for restore`),new NotFoundError("Record",{id:e});return o},async hardDelete(e){s.info(`Hard deleting record ${e} from ${t.tableName}`),await r.deleteFrom(t.tableName).where(n,"=",e).execute();},async findWithDeleted(e){return await r.selectFrom(t.tableName).selectAll().where(n,"=",e).executeTakeFirst()??null},async findAllWithDeleted(){return await r.selectFrom(t.tableName).selectAll().execute()},async findDeleted(){return await r.selectFrom(t.tableName).selectAll().where(l,"is not",null).execute()},async softDeleteMany(e){if(e.length===0)return [];s.info(`Soft deleting ${e.length} records from ${t.tableName}`),await r.updateTable(t.tableName).set({[l]:sql`CURRENT_TIMESTAMP`}).where(n,"in",e).execute();let o=await r.selectFrom(t.tableName).selectAll().where(n,"in",e).execute();if(o.length!==e.length){let d=o.map(c=>c[n]),f=e.filter(c=>!d.includes(c));throw s.warn(`Some records not found for soft delete: ${f.join(", ")}`),new NotFoundError("Records",{ids:f})}return o},async restoreMany(e){return e.length===0?[]:(s.info(`Restoring ${e.length} soft-deleted records from ${t.tableName}`),await r.updateTable(t.tableName).set({[l]:null}).where(n,"in",e).execute(),await r.selectFrom(t.tableName).selectAll().where(n,"in",e).execute())},async hardDeleteMany(e){e.length!==0&&(s.info(`Hard deleting ${e.length} records from ${t.tableName}`),await r.deleteFrom(t.tableName).where(n,"in",e).execute());}}}}};export{S as softDeletePlugin};//# sourceMappingURL=index.js.map
1
+ import {isRepositoryLike,getRawDb}from'@kysera/executor';import {sql}from'kysely';import {silentLogger,NotFoundError}from'@kysera/core';var w="__VERSION__",b=w.startsWith("__")?"0.0.0-dev":w;var S=(y={})=>{let{deletedAtColumn:l="deleted_at",includeDeleted:u=false,tables:i,primaryKeyColumn:n="id",logger:s=silentLogger}=y;return {name:"@kysera/soft-delete",version:b,priority:100,onInit(){},onDestroy(){return s.debug("Soft-delete plugin destroyed"),Promise.resolve()},interceptQuery(a,t){return (!i||i.includes(t.table))&&t.operation==="select"&&!t.metadata.includeDeleted&&!u?(s.debug(`Filtering soft-deleted records from ${t.table}`),a.where(`${t.table}.${l}`,"is",null)):a},extendRepository(a){if(!isRepositoryLike(a))return a;let t=a;if(!(!i||i.includes(t.tableName)))return s.debug(`Table ${t.tableName} does not support soft delete, skipping extension`),a;s.debug(`Extending repository for table ${t.tableName} with soft delete methods`);let r=getRawDb(t.executor);return {...t,async findAll(){return u?await r.selectFrom(t.tableName).selectAll().execute():await r.selectFrom(t.tableName).selectAll().where(l,"is",null).execute()},async findById(e){return u?await r.selectFrom(t.tableName).selectAll().where(n,"=",e).executeTakeFirst()??null:await r.selectFrom(t.tableName).selectAll().where(n,"=",e).where(l,"is",null).executeTakeFirst()??null},async softDelete(e){s.info(`Soft deleting record ${e} from ${t.tableName}`),await r.updateTable(t.tableName).set({[l]:sql`CURRENT_TIMESTAMP`}).where(n,"=",e).execute();let o=await r.selectFrom(t.tableName).selectAll().where(n,"=",e).executeTakeFirst();if(!o)throw s.warn(`Record ${e} not found in ${t.tableName} for soft delete`),new NotFoundError("Record",{id:e});return o},async restore(e){s.info(`Restoring soft-deleted record ${e} from ${t.tableName}`),await r.updateTable(t.tableName).set({[l]:null}).where(n,"=",e).execute();let o=await r.selectFrom(t.tableName).selectAll().where(n,"=",e).executeTakeFirst();if(!o)throw s.warn(`Record ${e} not found in ${t.tableName} for restore`),new NotFoundError("Record",{id:e});return o},async hardDelete(e){s.info(`Hard deleting record ${e} from ${t.tableName}`),await r.deleteFrom(t.tableName).where(n,"=",e).execute();},async findWithDeleted(e){return await r.selectFrom(t.tableName).selectAll().where(n,"=",e).executeTakeFirst()??null},async findAllWithDeleted(){return await r.selectFrom(t.tableName).selectAll().execute()},async findDeleted(){return await r.selectFrom(t.tableName).selectAll().where(l,"is not",null).execute()},async softDeleteMany(e){if(e.length===0)return [];s.info(`Soft deleting ${e.length} records from ${t.tableName}`),await r.updateTable(t.tableName).set({[l]:sql`CURRENT_TIMESTAMP`}).where(n,"in",e).execute();let o=await r.selectFrom(t.tableName).selectAll().where(n,"in",e).execute();if(o.length!==e.length){let d=o.map(c=>c[n]),f=e.filter(c=>!d.includes(c));throw s.warn(`Some records not found for soft delete: ${f.join(", ")}`),new NotFoundError("Records",{ids:f})}return o},async restoreMany(e){return e.length===0?[]:(s.info(`Restoring ${e.length} soft-deleted records from ${t.tableName}`),await r.updateTable(t.tableName).set({[l]:null}).where(n,"in",e).execute(),await r.selectFrom(t.tableName).selectAll().where(n,"in",e).execute())},async hardDeleteMany(e){e.length!==0&&(s.info(`Hard deleting ${e.length} records from ${t.tableName}`),await r.deleteFrom(t.tableName).where(n,"in",e).execute());}}}}};export{S as softDeletePlugin};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts","../src/index.ts"],"names":["RAW_VERSION","VERSION","softDeletePlugin","options","deletedAtColumn","includeDeleted","tables","primaryKeyColumn","logger","silentLogger","qb","context","repo","isRepositoryLike","baseRepo","rawDb","getRawDb","id","sql","record","NotFoundError","ids","records","foundIds","r","missingIds"],"mappings":"wIAKA,IAAMA,CAAAA,CAAc,aAAA,CACPC,CAAAA,CAAUD,CAAAA,CAAY,WAAW,IAAI,CAAA,CAAI,WAAA,CAAcA,CAAAA,KCmMvDE,CAAAA,CAAmB,CAACC,CAAAA,CAA6B,KAAe,CAC3E,GAAM,CACJ,eAAA,CAAAC,CAAAA,CAAkB,aAClB,cAAA,CAAAC,CAAAA,CAAiB,KAAA,CACjB,MAAA,CAAAC,EACA,gBAAA,CAAAC,CAAAA,CAAmB,IAAA,CACnB,MAAA,CAAAC,EAASC,YACX,CAAA,CAAIN,CAAAA,CAEJ,OAAO,CACL,IAAA,CAAM,qBAAA,CACN,QAASF,CAAAA,CACT,QAAA,CAAU,IAKV,MAAA,EAAS,CAET,CAAA,CAKA,MAAM,WAAY,CAEhBO,CAAAA,CAAO,KAAA,CAAM,8BAA8B,EAC7C,CAAA,CAeA,cAAA,CAAmBE,CAAAA,CAAQC,CAAAA,CAAkC,CAK3D,OAAA,CAH2B,CAACL,GAAUA,CAAAA,CAAO,QAAA,CAASK,EAAQ,KAAK,CAAA,GAKjEA,CAAAA,CAAQ,SAAA,GAAc,UACtB,CAACA,CAAAA,CAAQ,QAAA,CAAS,cAAA,EAClB,CAACN,CAAAA,EAEDG,CAAAA,CAAO,KAAA,CAAM,CAAA,oCAAA,EAAuCG,EAAQ,KAAK,CAAA,CAAE,CAAA,CAO3DD,CAAAA,CAA4C,MAClD,CAAA,EAAGC,CAAAA,CAAQ,KAAK,CAAA,CAAA,EAAIP,CAAe,CAAA,CAAA,CACnC,IAAA,CACA,IACF,CAAA,EAOKM,CACT,CAAA,CAmBA,gBAAA,CAAmCE,CAAAA,CAAY,CAE7C,GAAI,CAACC,gBAAAA,CAAiBD,CAAI,CAAA,CACxB,OAAOA,EAIT,IAAME,CAAAA,CAAWF,CAAAA,CAMjB,GAAI,EAHuB,CAACN,CAAAA,EAAUA,CAAAA,CAAO,QAAA,CAASQ,EAAS,SAAS,CAAA,CAAA,CAItE,OAAAN,CAAAA,CAAO,MAAM,CAAA,MAAA,EAASM,CAAAA,CAAS,SAAS,CAAA,iDAAA,CAAmD,CAAA,CACpFF,EAGTJ,CAAAA,CAAO,KAAA,CAAM,CAAA,+BAAA,EAAkCM,CAAAA,CAAS,SAAS,CAAA,yBAAA,CAA2B,CAAA,CAG5F,IAAMC,CAAAA,CAAQC,SAASF,CAAAA,CAAS,QAAQ,CAAA,CAmMxC,OAjMqB,CACnB,GAAGA,CAAAA,CAUH,MAAM,OAAA,EAA8B,CAClC,OAAKT,CAAAA,CAUU,MAAMU,CAAAA,CAAM,UAAA,CAAWD,EAAS,SAAS,CAAA,CAAE,SAAA,EAAU,CAAE,SAAQ,CAR7D,MAAMC,CAAAA,CAClB,UAAA,CAAWD,EAAS,SAAS,CAAA,CAC7B,SAAA,EAAU,CACV,MAAMV,CAAAA,CAA0B,IAAA,CAAM,IAAI,CAAA,CAC1C,SAMP,CAAA,CAEA,MAAM,QAAA,CAASa,EAAuC,CACpD,OAAKZ,CAAAA,CAWU,MAAMU,EAClB,UAAA,CAAWD,CAAAA,CAAS,SAAS,CAAA,CAC7B,SAAA,GACA,KAAA,CAAMP,CAAAA,CAA2B,GAAA,CAAKU,CAAW,EACjD,gBAAA,EAAiB,EACH,IAAA,CAdA,MAAMF,EAClB,UAAA,CAAWD,CAAAA,CAAS,SAAS,CAAA,CAC7B,WAAU,CACV,KAAA,CAAMP,EAA2B,GAAA,CAAKU,CAAW,EACjD,KAAA,CAAMb,CAAAA,CAA0B,IAAA,CAAM,IAAI,EAC1C,gBAAA,EAAiB,EACH,IASrB,CAAA,CAEA,MAAM,UAAA,CAAWa,CAAAA,CAAuC,CACtDT,CAAAA,CAAO,KAAK,CAAA,qBAAA,EAAwBS,CAAE,SAASH,CAAAA,CAAS,SAAS,EAAE,CAAA,CAEnE,MAAMC,CAAAA,CACH,WAAA,CAAYD,EAAS,SAAS,CAAA,CAC9B,GAAA,CAAI,CAAE,CAACV,CAAe,EAAGc,GAAAA,CAAAA,iBAAAA,CAAuB,CAAU,EAC1D,KAAA,CAAMX,CAAAA,CAA2B,IAAKU,CAAW,CAAA,CACjD,SAAQ,CAGX,IAAME,CAAAA,CAAS,MAAMJ,EAClB,UAAA,CAAWD,CAAAA,CAAS,SAAS,CAAA,CAC7B,WAAU,CACV,KAAA,CAAMP,CAAAA,CAA2B,GAAA,CAAKU,CAAW,CAAA,CACjD,gBAAA,GAEH,GAAI,CAACE,EACH,MAAAX,CAAAA,CAAO,IAAA,CAAK,CAAA,OAAA,EAAUS,CAAE,CAAA,cAAA,EAAiBH,CAAAA,CAAS,SAAS,CAAA,gBAAA,CAAkB,EACvE,IAAIM,aAAAA,CAAc,QAAA,CAAU,CAAE,GAAAH,CAAG,CAAC,EAG1C,OAAOE,CACT,EAEA,MAAM,OAAA,CAAQF,CAAAA,CAAuC,CACnDT,EAAO,IAAA,CAAK,CAAA,8BAAA,EAAiCS,CAAE,CAAA,MAAA,EAASH,EAAS,SAAS,CAAA,CAAE,CAAA,CAC5E,MAAMC,EACH,WAAA,CAAYD,CAAAA,CAAS,SAAS,CAAA,CAC9B,GAAA,CAAI,CAAE,CAACV,CAAe,EAAG,IAAK,CAAU,CAAA,CACxC,KAAA,CAAMG,CAAAA,CAA2B,GAAA,CAAKU,CAAW,CAAA,CACjD,OAAA,EAAQ,CAGX,IAAME,EAAS,MAAMJ,CAAAA,CAClB,UAAA,CAAWD,CAAAA,CAAS,SAAS,CAAA,CAC7B,SAAA,EAAU,CACV,KAAA,CAAMP,EAA2B,GAAA,CAAKU,CAAW,CAAA,CACjD,gBAAA,GAEH,GAAI,CAACE,CAAAA,CACH,MAAAX,EAAO,IAAA,CAAK,CAAA,OAAA,EAAUS,CAAE,CAAA,cAAA,EAAiBH,CAAAA,CAAS,SAAS,CAAA,YAAA,CAAc,CAAA,CACnE,IAAIM,aAAAA,CAAc,SAAU,CAAE,EAAA,CAAAH,CAAG,CAAC,EAG1C,OAAOE,CACT,CAAA,CAEA,MAAM,WAAWF,CAAAA,CAAoC,CACnDT,EAAO,IAAA,CAAK,CAAA,qBAAA,EAAwBS,CAAE,CAAA,MAAA,EAASH,CAAAA,CAAS,SAAS,CAAA,CAAE,EACnE,MAAMC,CAAAA,CACH,UAAA,CAAWD,CAAAA,CAAS,SAAS,CAAA,CAC7B,KAAA,CAAMP,CAAAA,CAA2B,GAAA,CAAKU,CAAW,CAAA,CACjD,OAAA,GACL,CAAA,CAEA,MAAM,gBAAgBA,CAAAA,CAAuC,CAO3D,OALe,MAAMF,EAClB,UAAA,CAAWD,CAAAA,CAAS,SAAS,CAAA,CAC7B,WAAU,CACV,KAAA,CAAMP,CAAAA,CAA2B,GAAA,CAAKU,CAAW,CAAA,CACjD,gBAAA,IACc,IACnB,CAAA,CAEA,MAAM,kBAAA,EAAyC,CAG7C,OADe,MAAMF,EAAM,UAAA,CAAWD,CAAAA,CAAS,SAAS,CAAA,CAAE,WAAU,CAAE,OAAA,EAExE,CAAA,CAEA,MAAM,WAAA,EAAkC,CAOtC,OALe,MAAMC,CAAAA,CAClB,WAAWD,CAAAA,CAAS,SAAS,CAAA,CAC7B,SAAA,GACA,KAAA,CAAMV,CAAAA,CAA0B,QAAA,CAAU,IAAI,EAC9C,OAAA,EAEL,CAAA,CAEA,MAAM,eAAeiB,CAAAA,CAA8C,CACjE,GAAIA,CAAAA,CAAI,MAAA,GAAW,EACjB,OAAO,EAAC,CAGVb,CAAAA,CAAO,KAAK,CAAA,cAAA,EAAiBa,CAAAA,CAAI,MAAM,CAAA,cAAA,EAAiBP,EAAS,SAAS,CAAA,CAAE,CAAA,CAE5E,MAAMC,EACH,WAAA,CAAYD,CAAAA,CAAS,SAAS,CAAA,CAC9B,GAAA,CAAI,CAAE,CAACV,CAAe,EAAGc,GAAAA,CAAAA,iBAAAA,CAAuB,CAAU,CAAA,CAC1D,KAAA,CAAMX,CAAAA,CAA2B,IAAA,CAAMc,CAAY,CAAA,CACnD,OAAA,EAAQ,CAGX,IAAMC,EAAU,MAAMP,CAAAA,CACnB,UAAA,CAAWD,CAAAA,CAAS,SAAS,CAAA,CAC7B,SAAA,EAAU,CACV,KAAA,CAAMP,EAA2B,IAAA,CAAMc,CAAY,CAAA,CACnD,OAAA,GAEH,GAAIC,CAAAA,CAAQ,MAAA,GAAWD,CAAAA,CAAI,OAAQ,CACjC,IAAME,EAAWD,CAAAA,CAAQ,GAAA,CAAKE,GAA+BA,CAAAA,CAAEjB,CAAgB,CAAC,CAAA,CAC1EkB,EAAaJ,CAAAA,CAAI,MAAA,CAAOJ,CAAAA,EAAM,CAACM,EAAS,QAAA,CAASN,CAAE,CAAC,CAAA,CAC1D,MAAAT,CAAAA,CAAO,IAAA,CAAK,2CAA2CiB,CAAAA,CAAW,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA,CACxE,IAAIL,cAAc,SAAA,CAAW,CAAE,GAAA,CAAKK,CAAW,CAAC,CACxD,CAEA,OAAOH,CACT,EAEA,MAAM,WAAA,CAAYD,EAA8C,CAC9D,OAAIA,EAAI,MAAA,GAAW,CAAA,CACV,EAAC,EAGVb,EAAO,IAAA,CAAK,CAAA,UAAA,EAAaa,CAAAA,CAAI,MAAM,8BAA8BP,CAAAA,CAAS,SAAS,CAAA,CAAE,CAAA,CAErF,MAAMC,CAAAA,CACH,WAAA,CAAYD,EAAS,SAAS,CAAA,CAC9B,IAAI,CAAE,CAACV,CAAe,EAAG,IAAK,CAAU,CAAA,CACxC,KAAA,CAAMG,CAAAA,CAA2B,KAAMc,CAAY,CAAA,CACnD,OAAA,EAAQ,CAEK,MAAMN,CAAAA,CACnB,UAAA,CAAWD,EAAS,SAAS,CAAA,CAC7B,WAAU,CACV,KAAA,CAAMP,CAAAA,CAA2B,IAAA,CAAMc,CAAY,CAAA,CACnD,OAAA,EAAQ,CAGb,CAAA,CAEA,MAAM,cAAA,CAAeA,CAAAA,CAAyC,CACxDA,CAAAA,CAAI,SAAW,CAAA,GAInBb,CAAAA,CAAO,KAAK,CAAA,cAAA,EAAiBa,CAAAA,CAAI,MAAM,CAAA,cAAA,EAAiBP,CAAAA,CAAS,SAAS,CAAA,CAAE,EAE5E,MAAMC,CAAAA,CACH,UAAA,CAAWD,CAAAA,CAAS,SAAS,CAAA,CAC7B,KAAA,CAAMP,CAAAA,CAA2B,IAAA,CAAMc,CAAY,CAAA,CACnD,OAAA,IACL,CACF,CAGF,CACF,CACF","file":"index.js","sourcesContent":["/**\n * Package version - injected at build time by tsup\n * Falls back to development version if not replaced\n * @internal\n */\nconst RAW_VERSION = '__VERSION__'\nexport const VERSION = RAW_VERSION.startsWith('__') ? '0.0.0-dev' : RAW_VERSION\n","import type { Plugin, QueryBuilderContext, BaseRepositoryLike } from '@kysera/executor'\nimport { getRawDb, isRepositoryLike } from '@kysera/executor'\nimport type { SelectQueryBuilder } from 'kysely'\nimport { sql } from 'kysely'\nimport { NotFoundError, silentLogger } from '@kysera/core'\nimport type { KyseraLogger } from '@kysera/core'\nimport { VERSION } from './version.js'\n\n/**\n * Configuration options for the soft delete plugin.\n *\n * @example\n * ```typescript\n * const plugin = softDeletePlugin({\n * deletedAtColumn: 'deleted_at',\n * includeDeleted: false,\n * tables: ['users', 'posts'], // Only these tables support soft delete\n * primaryKeyColumn: 'id' // Default primary key column\n * })\n * ```\n */\nexport interface SoftDeleteOptions {\n /**\n * Column name for soft delete timestamp.\n *\n * @default 'deleted_at'\n */\n deletedAtColumn?: string\n\n /**\n * Include deleted records by default in queries.\n * When false, soft-deleted records are automatically filtered out.\n *\n * @default false\n */\n includeDeleted?: boolean\n\n /**\n * List of tables that support soft delete.\n * If not provided, all tables are assumed to support it.\n *\n * @example ['users', 'posts', 'comments']\n */\n tables?: string[]\n\n /**\n * Primary key column name used for identifying records.\n * Tables with different primary key names (uuid, user_id, etc.) can be configured.\n *\n * @default 'id'\n * @example 'uuid', 'user_id', 'post_id'\n */\n primaryKeyColumn?: string\n\n /**\n * Logger for plugin operations.\n * Uses KyseraLogger interface from @kysera/core.\n *\n * @default silentLogger (no output)\n */\n logger?: KyseraLogger\n}\n\n/**\n * Methods added to repositories by the soft delete plugin\n */\nexport interface SoftDeleteMethods<T> {\n softDelete(id: number | string): Promise<T>\n restore(id: number | string): Promise<T>\n hardDelete(id: number | string): Promise<void>\n findWithDeleted(id: number | string): Promise<T | null>\n findAllWithDeleted(): Promise<T[]>\n findDeleted(): Promise<T[]>\n softDeleteMany(ids: (number | string)[]): Promise<T[]>\n restoreMany(ids: (number | string)[]): Promise<T[]>\n hardDeleteMany(ids: (number | string)[]): Promise<void>\n}\n\n/**\n * Repository extended with soft delete methods.\n * Uses a generic base repository type for flexibility across different repository implementations.\n *\n * @typeParam Entity - The entity type managed by the repository\n * @typeParam BaseRepo - The base repository type to extend (defaults to Record<string, never>)\n *\n * @example\n * ```typescript\n * // Type-safe usage with base repository\n * type User = { id: number; name: string; deleted_at: Date | null };\n * type UserRepo = { findAll(): Promise<User[]>; findById(id: number): Promise<User> };\n * type ExtendedUserRepo = SoftDeleteRepository<User, UserRepo>;\n *\n * // Now includes both UserRepo methods and soft delete methods\n * const repo: ExtendedUserRepo = ...;\n * await repo.findAll(); // From UserRepo\n * await repo.softDelete(1); // From SoftDeleteMethods\n * ```\n */\nexport type SoftDeleteRepository<\n Entity,\n BaseRepo extends object = Record<string, never>\n> = BaseRepo & SoftDeleteMethods<Entity>\n\n/**\n * Internal repository interface for soft-delete operations.\n * Uses the unified BaseRepositoryLike interface from @kysera/executor.\n */\ntype SoftDeleteBaseRepository = BaseRepositoryLike<Record<string, unknown>>\n\n/**\n * Soft Delete Plugin for Kysera\n *\n * This plugin implements soft delete functionality using the Method Override pattern:\n * - Automatically filters out soft-deleted records from SELECT queries\n * - Adds softDelete(), restore(), and hardDelete() methods to repositories\n * - Provides findWithDeleted() and findDeleted() utility methods\n *\n * ## Usage\n *\n * ```typescript\n * import { softDeletePlugin } from '@kysera/soft-delete'\n * import { createORM } from '@kysera/repository'\n *\n * const orm = await createORM(db, [\n * softDeletePlugin({\n * deletedAtColumn: 'deleted_at',\n * tables: ['users', 'posts']\n * })\n * ])\n *\n * const userRepo = orm.createRepository(createUserRepository)\n *\n * // Soft delete a user (sets deleted_at)\n * await userRepo.softDelete(1)\n *\n * // Find all users (excludes soft-deleted)\n * await userRepo.findAll()\n *\n * // Find including deleted\n * await userRepo.findAllWithDeleted()\n *\n * // Restore a soft-deleted user\n * await userRepo.restore(1)\n *\n * // Permanently delete (real DELETE)\n * await userRepo.hardDelete(1)\n * ```\n *\n * ## Architecture Note\n *\n * This plugin uses Method Override, not full query interception:\n * - ✅ SELECT queries are automatically filtered\n * - ❌ DELETE queries are NOT automatically converted to soft deletes\n * - Use softDelete() method explicitly instead of delete()\n *\n * This design is intentional for simplicity and explicitness.\n *\n * ## Transaction Behavior\n *\n * **IMPORTANT**: Soft delete operations respect ACID properties and work correctly with transactions:\n *\n * - ✅ **Commits with transaction**: softDelete/restore operations use the same executor\n * as other repository operations, so they commit together\n * - ✅ **Rolls back with transaction**: If a transaction is rolled back, soft delete\n * operations are also rolled back\n * - ✅ **Atomic operations**: All soft delete operations (including bulk) are atomic\n *\n * ### Correct Transaction Usage\n *\n * ```typescript\n * // ✅ CORRECT: Soft delete is part of transaction\n * await db.transaction().execute(async (trx) => {\n * const repos = createRepositories(trx) // Use transaction executor\n * await repos.users.softDelete(1)\n * await repos.posts.softDeleteMany([1, 2, 3])\n * // If transaction rolls back, both operations roll back\n * })\n * ```\n *\n * ### Cascade Soft Delete Pattern\n *\n * For related entities, you need to manually implement cascade soft delete:\n *\n * ```typescript\n * // Cascade soft delete pattern\n * await db.transaction().execute(async (trx) => {\n * const repos = createRepositories(trx)\n * const userId = 123\n *\n * // First, soft delete child records\n * const userPosts = await repos.posts.findBy({ user_id: userId })\n * await repos.posts.softDeleteMany(userPosts.map(p => p.id))\n *\n * // Then, soft delete parent\n * await repos.users.softDelete(userId)\n * })\n * ```\n *\n * @param options - Configuration options for soft delete behavior\n * @returns Plugin instance that can be used with createORM\n */\nexport const softDeletePlugin = (options: SoftDeleteOptions = {}): Plugin => {\n const {\n deletedAtColumn = 'deleted_at',\n includeDeleted = false,\n tables,\n primaryKeyColumn = 'id',\n logger = silentLogger\n } = options\n\n return {\n name: '@kysera/soft-delete',\n version: VERSION,\n priority: 100, // Run first to filter soft-deleted records before other plugins\n\n /**\n * Lifecycle: No initialization needed for soft-delete plugin\n */\n onInit() {\n // No initialization required\n },\n\n /**\n * Lifecycle: Cleanup resources when executor is destroyed\n */\n async onDestroy() {\n // No cleanup required - soft-delete plugin has no persistent resources\n logger.debug('Soft-delete plugin destroyed')\n },\n\n /**\n * Intercept queries to automatically filter soft-deleted records.\n *\n * NOTE: This plugin uses the Method Override pattern, not full query interception.\n * - SELECT queries are automatically filtered to exclude soft-deleted records\n * - DELETE operations are NOT automatically converted to soft deletes\n * - Use the softDelete() method instead of delete() to perform soft deletes\n * - Use hardDelete() method to bypass soft delete and perform a real DELETE\n *\n * This approach is simpler and more explicit than full query interception.\n *\n * Works with both Repository and DAL patterns through the unified executor layer.\n */\n interceptQuery<QB>(qb: QB, context: QueryBuilderContext): QB {\n // Check if table supports soft delete\n const supportsSoftDelete = !tables || tables.includes(context.table)\n\n // Only filter SELECT queries when not explicitly including deleted\n if (\n supportsSoftDelete &&\n context.operation === 'select' &&\n !context.metadata['includeDeleted'] &&\n !includeDeleted\n ) {\n logger.debug(`Filtering soft-deleted records from ${context.table}`)\n // Add WHERE deleted_at IS NULL to the query builder\n type GenericSelectQueryBuilder = SelectQueryBuilder<\n Record<string, unknown>,\n string,\n Record<string, unknown>\n >\n return (qb as unknown as GenericSelectQueryBuilder).where(\n `${context.table}.${deletedAtColumn}` as never,\n 'is',\n null\n ) as QB\n }\n\n // Note: DELETE operations are NOT intercepted here\n // Use softDelete() method instead of delete() to perform soft deletes\n // This is by design - method override is simpler and more explicit\n\n return qb\n },\n\n /**\n * Extend repository with soft delete methods.\n *\n * Adds the following methods to repositories:\n * - softDelete(id): Marks record as deleted by setting deleted_at timestamp\n * - restore(id): Restores a soft-deleted record by setting deleted_at to null\n * - hardDelete(id): Permanently deletes a record (bypasses soft delete)\n * - findWithDeleted(id): Find a record including soft-deleted ones\n * - findAllWithDeleted(): Find all records including soft-deleted ones\n * - findDeleted(): Find only soft-deleted records\n * - softDeleteMany(ids): Soft delete multiple records (bulk operation)\n * - restoreMany(ids): Restore multiple soft-deleted records (bulk operation)\n * - hardDeleteMany(ids): Permanently delete multiple records (bulk operation)\n *\n * Also overrides findAll() and findById() to automatically filter out\n * soft-deleted records (unless includeDeleted option is set).\n */\n extendRepository<T extends object>(repo: T): T {\n // Use the shared type guard from @kysera/executor\n if (!isRepositoryLike(repo)) {\n return repo\n }\n\n // Type assertion is safe after type guard\n const baseRepo = repo as unknown as SoftDeleteBaseRepository\n\n // Check if table supports soft delete\n const supportsSoftDelete = !tables || tables.includes(baseRepo.tableName)\n\n // If table doesn't support soft delete, return unmodified repo\n if (!supportsSoftDelete) {\n logger.debug(`Table ${baseRepo.tableName} does not support soft delete, skipping extension`)\n return repo\n }\n\n logger.debug(`Extending repository for table ${baseRepo.tableName} with soft delete methods`)\n\n // Get raw db for queries that need to bypass interceptors\n const rawDb = getRawDb(baseRepo.executor)\n\n const extendedRepo = {\n ...baseRepo,\n\n // Override findAll() and findById() to support custom primaryKeyColumn\n // We use rawDb to bypass the interceptor and manually apply filtering here.\n // This is intentional - there is NO duplicate filtering because:\n // - rawDb bypasses the interceptor (no filter #1)\n // - We manually add WHERE deleted_at IS NULL (filter #2)\n // Result: Only ONE filter is applied, which is correct.\n // Alternative approach (using baseRepo.executor) would apply interceptor\n // but wouldn't support custom primaryKeyColumn option.\n async findAll(): Promise<unknown[]> {\n if (!includeDeleted) {\n // Use rawDb + manual filter (avoids double filtering from interceptor)\n const result = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(deletedAtColumn as never, 'is', null)\n .execute()\n return result as unknown[]\n }\n // Include deleted: return all records\n const result = await rawDb.selectFrom(baseRepo.tableName).selectAll().execute()\n return result as unknown[]\n },\n\n async findById(id: number | string): Promise<unknown> {\n if (!includeDeleted) {\n // Use rawDb + manual filter (avoids double filtering from interceptor)\n const result = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, '=', id as never)\n .where(deletedAtColumn as never, 'is', null)\n .executeTakeFirst()\n return result ?? null\n }\n // Include deleted: find by id regardless of deleted status\n const result = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, '=', id as never)\n .executeTakeFirst()\n return result ?? null\n },\n\n async softDelete(id: number | string): Promise<unknown> {\n logger.info(`Soft deleting record ${id} from ${baseRepo.tableName}`)\n // Use rawDb to bypass interceptors (UPDATE doesn't need filtering anyway)\n await rawDb\n .updateTable(baseRepo.tableName)\n .set({ [deletedAtColumn]: sql`CURRENT_TIMESTAMP` } as never)\n .where(primaryKeyColumn as never, '=', id as never)\n .execute()\n\n // Fetch the updated record - use rawDb to see the just-deleted record\n const record = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, '=', id as never)\n .executeTakeFirst()\n\n if (!record) {\n logger.warn(`Record ${id} not found in ${baseRepo.tableName} for soft delete`)\n throw new NotFoundError('Record', { id })\n }\n\n return record\n },\n\n async restore(id: number | string): Promise<unknown> {\n logger.info(`Restoring soft-deleted record ${id} from ${baseRepo.tableName}`)\n await rawDb\n .updateTable(baseRepo.tableName)\n .set({ [deletedAtColumn]: null } as never)\n .where(primaryKeyColumn as never, '=', id as never)\n .execute()\n\n // Fetch the restored record\n const record = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, '=', id as never)\n .executeTakeFirst()\n\n if (!record) {\n logger.warn(`Record ${id} not found in ${baseRepo.tableName} for restore`)\n throw new NotFoundError('Record', { id })\n }\n\n return record\n },\n\n async hardDelete(id: number | string): Promise<void> {\n logger.info(`Hard deleting record ${id} from ${baseRepo.tableName}`)\n await rawDb\n .deleteFrom(baseRepo.tableName)\n .where(primaryKeyColumn as never, '=', id as never)\n .execute()\n },\n\n async findWithDeleted(id: number | string): Promise<unknown> {\n // Use rawDb to bypass soft-delete filter\n const result = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, '=', id as never)\n .executeTakeFirst()\n return result ?? null\n },\n\n async findAllWithDeleted(): Promise<unknown[]> {\n // Use rawDb to bypass soft-delete filter and return ALL records\n const result = await rawDb.selectFrom(baseRepo.tableName).selectAll().execute()\n return result as unknown[]\n },\n\n async findDeleted(): Promise<unknown[]> {\n // Use rawDb to bypass soft-delete filter, then filter for deleted only\n const result = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(deletedAtColumn as never, 'is not', null)\n .execute()\n return result as unknown[]\n },\n\n async softDeleteMany(ids: (number | string)[]): Promise<unknown[]> {\n if (ids.length === 0) {\n return []\n }\n\n logger.info(`Soft deleting ${ids.length} records from ${baseRepo.tableName}`)\n\n await rawDb\n .updateTable(baseRepo.tableName)\n .set({ [deletedAtColumn]: sql`CURRENT_TIMESTAMP` } as never)\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n // Use rawDb to see the just-deleted records\n const records = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n if (records.length !== ids.length) {\n const foundIds = records.map((r: Record<string, unknown>) => r[primaryKeyColumn])\n const missingIds = ids.filter(id => !foundIds.includes(id))\n logger.warn(`Some records not found for soft delete: ${missingIds.join(', ')}`)\n throw new NotFoundError('Records', { ids: missingIds })\n }\n\n return records as unknown[]\n },\n\n async restoreMany(ids: (number | string)[]): Promise<unknown[]> {\n if (ids.length === 0) {\n return []\n }\n\n logger.info(`Restoring ${ids.length} soft-deleted records from ${baseRepo.tableName}`)\n\n await rawDb\n .updateTable(baseRepo.tableName)\n .set({ [deletedAtColumn]: null } as never)\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n const records = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n return records as unknown[]\n },\n\n async hardDeleteMany(ids: (number | string)[]): Promise<void> {\n if (ids.length === 0) {\n return\n }\n\n logger.info(`Hard deleting ${ids.length} records from ${baseRepo.tableName}`)\n\n await rawDb\n .deleteFrom(baseRepo.tableName)\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n }\n }\n\n return extendedRepo as T\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/version.ts","../src/index.ts"],"names":["RAW_VERSION","VERSION","softDeletePlugin","options","deletedAtColumn","includeDeleted","tables","primaryKeyColumn","logger","silentLogger","qb","context","repo","isRepositoryLike","baseRepo","rawDb","getRawDb","id","sql","record","NotFoundError","ids","records","foundIds","r","missingIds"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Package version - injected at build time by tsup\n * Falls back to development version if not replaced\n * @internal\n */\nconst RAW_VERSION = '__VERSION__'\nexport const VERSION = RAW_VERSION.startsWith('__') ? '0.0.0-dev' : RAW_VERSION\n","import type { Plugin, QueryBuilderContext, BaseRepositoryLike } from '@kysera/executor'\nimport { getRawDb, isRepositoryLike } from '@kysera/executor'\nimport type { SelectQueryBuilder } from 'kysely'\nimport { sql } from 'kysely'\nimport { NotFoundError, silentLogger } from '@kysera/core'\nimport type { KyseraLogger } from '@kysera/core'\nimport { VERSION } from './version.js'\n\n/**\n * Configuration options for the soft delete plugin.\n *\n * @example\n * ```typescript\n * const plugin = softDeletePlugin({\n * deletedAtColumn: 'deleted_at',\n * includeDeleted: false,\n * tables: ['users', 'posts'], // Only these tables support soft delete\n * primaryKeyColumn: 'id' // Default primary key column\n * })\n * ```\n */\nexport interface SoftDeleteOptions {\n /**\n * Column name for soft delete timestamp.\n *\n * @default 'deleted_at'\n */\n deletedAtColumn?: string\n\n /**\n * Include deleted records by default in queries.\n * When false, soft-deleted records are automatically filtered out.\n *\n * @default false\n */\n includeDeleted?: boolean\n\n /**\n * List of tables that support soft delete.\n * If not provided, all tables are assumed to support it.\n *\n * @example ['users', 'posts', 'comments']\n */\n tables?: string[]\n\n /**\n * Primary key column name used for identifying records.\n * Tables with different primary key names (uuid, user_id, etc.) can be configured.\n *\n * @default 'id'\n * @example 'uuid', 'user_id', 'post_id'\n */\n primaryKeyColumn?: string\n\n /**\n * Logger for plugin operations.\n * Uses KyseraLogger interface from @kysera/core.\n *\n * @default silentLogger (no output)\n */\n logger?: KyseraLogger\n}\n\n/**\n * Methods added to repositories by the soft delete plugin\n */\nexport interface SoftDeleteMethods<T> {\n softDelete(id: number | string): Promise<T>\n restore(id: number | string): Promise<T>\n hardDelete(id: number | string): Promise<void>\n findWithDeleted(id: number | string): Promise<T | null>\n findAllWithDeleted(): Promise<T[]>\n findDeleted(): Promise<T[]>\n softDeleteMany(ids: (number | string)[]): Promise<T[]>\n restoreMany(ids: (number | string)[]): Promise<T[]>\n hardDeleteMany(ids: (number | string)[]): Promise<void>\n}\n\n/**\n * Repository extended with soft delete methods.\n * Uses a generic base repository type for flexibility across different repository implementations.\n *\n * @typeParam Entity - The entity type managed by the repository\n * @typeParam BaseRepo - The base repository type to extend (defaults to Record<string, never>)\n *\n * @example\n * ```typescript\n * // Type-safe usage with base repository\n * type User = { id: number; name: string; deleted_at: Date | null };\n * type UserRepo = { findAll(): Promise<User[]>; findById(id: number): Promise<User> };\n * type ExtendedUserRepo = SoftDeleteRepository<User, UserRepo>;\n *\n * // Now includes both UserRepo methods and soft delete methods\n * const repo: ExtendedUserRepo = ...;\n * await repo.findAll(); // From UserRepo\n * await repo.softDelete(1); // From SoftDeleteMethods\n * ```\n */\nexport type SoftDeleteRepository<\n Entity,\n BaseRepo extends object = Record<string, never>\n> = BaseRepo & SoftDeleteMethods<Entity>\n\n/**\n * Internal repository interface for soft-delete operations.\n * Uses the unified BaseRepositoryLike interface from @kysera/executor.\n */\ntype SoftDeleteBaseRepository = BaseRepositoryLike<Record<string, unknown>>\n\n/**\n * Soft Delete Plugin for Kysera\n *\n * This plugin implements soft delete functionality using the Method Override pattern:\n * - Automatically filters out soft-deleted records from SELECT queries\n * - Adds softDelete(), restore(), and hardDelete() methods to repositories\n * - Provides findWithDeleted() and findDeleted() utility methods\n *\n * ## Usage\n *\n * ```typescript\n * import { softDeletePlugin } from '@kysera/soft-delete'\n * import { createORM } from '@kysera/repository'\n *\n * const orm = await createORM(db, [\n * softDeletePlugin({\n * deletedAtColumn: 'deleted_at',\n * tables: ['users', 'posts']\n * })\n * ])\n *\n * const userRepo = orm.createRepository(createUserRepository)\n *\n * // Soft delete a user (sets deleted_at)\n * await userRepo.softDelete(1)\n *\n * // Find all users (excludes soft-deleted)\n * await userRepo.findAll()\n *\n * // Find including deleted\n * await userRepo.findAllWithDeleted()\n *\n * // Restore a soft-deleted user\n * await userRepo.restore(1)\n *\n * // Permanently delete (real DELETE)\n * await userRepo.hardDelete(1)\n * ```\n *\n * ## Architecture Note\n *\n * This plugin uses Method Override, not full query interception:\n * - ✅ SELECT queries are automatically filtered\n * - ❌ DELETE queries are NOT automatically converted to soft deletes\n * - Use softDelete() method explicitly instead of delete()\n *\n * This design is intentional for simplicity and explicitness.\n *\n * ## Transaction Behavior\n *\n * **IMPORTANT**: Soft delete operations respect ACID properties and work correctly with transactions:\n *\n * - ✅ **Commits with transaction**: softDelete/restore operations use the same executor\n * as other repository operations, so they commit together\n * - ✅ **Rolls back with transaction**: If a transaction is rolled back, soft delete\n * operations are also rolled back\n * - ✅ **Atomic operations**: All soft delete operations (including bulk) are atomic\n *\n * ### Correct Transaction Usage\n *\n * ```typescript\n * // ✅ CORRECT: Soft delete is part of transaction\n * await db.transaction().execute(async (trx) => {\n * const repos = createRepositories(trx) // Use transaction executor\n * await repos.users.softDelete(1)\n * await repos.posts.softDeleteMany([1, 2, 3])\n * // If transaction rolls back, both operations roll back\n * })\n * ```\n *\n * ### Cascade Soft Delete Pattern\n *\n * For related entities, you need to manually implement cascade soft delete:\n *\n * ```typescript\n * // Cascade soft delete pattern\n * await db.transaction().execute(async (trx) => {\n * const repos = createRepositories(trx)\n * const userId = 123\n *\n * // First, soft delete child records\n * const userPosts = await repos.posts.findBy({ user_id: userId })\n * await repos.posts.softDeleteMany(userPosts.map(p => p.id))\n *\n * // Then, soft delete parent\n * await repos.users.softDelete(userId)\n * })\n * ```\n *\n * @param options - Configuration options for soft delete behavior\n * @returns Plugin instance that can be used with createORM\n */\nexport const softDeletePlugin = (options: SoftDeleteOptions = {}): Plugin => {\n const {\n deletedAtColumn = 'deleted_at',\n includeDeleted = false,\n tables,\n primaryKeyColumn = 'id',\n logger = silentLogger\n } = options\n\n return {\n name: '@kysera/soft-delete',\n version: VERSION,\n priority: 100, // Run first to filter soft-deleted records before other plugins\n\n /**\n * Lifecycle: No initialization needed for soft-delete plugin\n */\n onInit() {\n // No initialization required\n },\n\n /**\n * Lifecycle: Cleanup resources when executor is destroyed\n */\n onDestroy(): Promise<void> {\n // No cleanup required - soft-delete plugin has no persistent resources\n logger.debug('Soft-delete plugin destroyed')\n return Promise.resolve()\n },\n\n /**\n * Intercept queries to automatically filter soft-deleted records.\n *\n * NOTE: This plugin uses the Method Override pattern, not full query interception.\n * - SELECT queries are automatically filtered to exclude soft-deleted records\n * - DELETE operations are NOT automatically converted to soft deletes\n * - Use the softDelete() method instead of delete() to perform soft deletes\n * - Use hardDelete() method to bypass soft delete and perform a real DELETE\n *\n * This approach is simpler and more explicit than full query interception.\n *\n * Works with both Repository and DAL patterns through the unified executor layer.\n */\n interceptQuery<QB>(qb: QB, context: QueryBuilderContext): QB {\n // Check if table supports soft delete\n const supportsSoftDelete = !tables || tables.includes(context.table)\n\n // Only filter SELECT queries when not explicitly including deleted\n if (\n supportsSoftDelete &&\n context.operation === 'select' &&\n !context.metadata['includeDeleted'] &&\n !includeDeleted\n ) {\n logger.debug(`Filtering soft-deleted records from ${context.table}`)\n // Add WHERE deleted_at IS NULL to the query builder\n type GenericSelectQueryBuilder = SelectQueryBuilder<\n Record<string, unknown>,\n string,\n Record<string, unknown>\n >\n return (qb as unknown as GenericSelectQueryBuilder).where(\n `${context.table}.${deletedAtColumn}` as never,\n 'is',\n null\n ) as QB\n }\n\n // Note: DELETE operations are NOT intercepted here\n // Use softDelete() method instead of delete() to perform soft deletes\n // This is by design - method override is simpler and more explicit\n\n return qb\n },\n\n /**\n * Extend repository with soft delete methods.\n *\n * Adds the following methods to repositories:\n * - softDelete(id): Marks record as deleted by setting deleted_at timestamp\n * - restore(id): Restores a soft-deleted record by setting deleted_at to null\n * - hardDelete(id): Permanently deletes a record (bypasses soft delete)\n * - findWithDeleted(id): Find a record including soft-deleted ones\n * - findAllWithDeleted(): Find all records including soft-deleted ones\n * - findDeleted(): Find only soft-deleted records\n * - softDeleteMany(ids): Soft delete multiple records (bulk operation)\n * - restoreMany(ids): Restore multiple soft-deleted records (bulk operation)\n * - hardDeleteMany(ids): Permanently delete multiple records (bulk operation)\n *\n * Also overrides findAll() and findById() to automatically filter out\n * soft-deleted records (unless includeDeleted option is set).\n */\n extendRepository<T extends object>(repo: T): T {\n // Use the shared type guard from @kysera/executor\n if (!isRepositoryLike(repo)) {\n return repo\n }\n\n // Type assertion is safe after type guard\n const baseRepo = repo as unknown as SoftDeleteBaseRepository\n\n // Check if table supports soft delete\n const supportsSoftDelete = !tables || tables.includes(baseRepo.tableName)\n\n // If table doesn't support soft delete, return unmodified repo\n if (!supportsSoftDelete) {\n logger.debug(`Table ${baseRepo.tableName} does not support soft delete, skipping extension`)\n return repo\n }\n\n logger.debug(`Extending repository for table ${baseRepo.tableName} with soft delete methods`)\n\n // Get raw db for queries that need to bypass interceptors\n const rawDb = getRawDb(baseRepo.executor)\n\n const extendedRepo = {\n ...baseRepo,\n\n // Override findAll() and findById() to support custom primaryKeyColumn\n // We use rawDb to bypass the interceptor and manually apply filtering here.\n // This is intentional - there is NO duplicate filtering because:\n // - rawDb bypasses the interceptor (no filter #1)\n // - We manually add WHERE deleted_at IS NULL (filter #2)\n // Result: Only ONE filter is applied, which is correct.\n // Alternative approach (using baseRepo.executor) would apply interceptor\n // but wouldn't support custom primaryKeyColumn option.\n async findAll(): Promise<unknown[]> {\n if (!includeDeleted) {\n // Use rawDb + manual filter (avoids double filtering from interceptor)\n const result = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(deletedAtColumn as never, 'is', null)\n .execute()\n return result as unknown[]\n }\n // Include deleted: return all records\n const result = await rawDb.selectFrom(baseRepo.tableName).selectAll().execute()\n return result as unknown[]\n },\n\n async findById(id: number | string): Promise<unknown> {\n if (!includeDeleted) {\n // Use rawDb + manual filter (avoids double filtering from interceptor)\n const result = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, '=', id as never)\n .where(deletedAtColumn as never, 'is', null)\n .executeTakeFirst()\n return result ?? null\n }\n // Include deleted: find by id regardless of deleted status\n const result = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, '=', id as never)\n .executeTakeFirst()\n return result ?? null\n },\n\n async softDelete(id: number | string): Promise<unknown> {\n logger.info(`Soft deleting record ${id} from ${baseRepo.tableName}`)\n // Use rawDb to bypass interceptors (UPDATE doesn't need filtering anyway)\n await rawDb\n .updateTable(baseRepo.tableName)\n .set({ [deletedAtColumn]: sql`CURRENT_TIMESTAMP` } as never)\n .where(primaryKeyColumn as never, '=', id as never)\n .execute()\n\n // Fetch the updated record - use rawDb to see the just-deleted record\n const record = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, '=', id as never)\n .executeTakeFirst()\n\n if (!record) {\n logger.warn(`Record ${id} not found in ${baseRepo.tableName} for soft delete`)\n throw new NotFoundError('Record', { id })\n }\n\n return record\n },\n\n async restore(id: number | string): Promise<unknown> {\n logger.info(`Restoring soft-deleted record ${id} from ${baseRepo.tableName}`)\n await rawDb\n .updateTable(baseRepo.tableName)\n .set({ [deletedAtColumn]: null } as never)\n .where(primaryKeyColumn as never, '=', id as never)\n .execute()\n\n // Fetch the restored record\n const record = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, '=', id as never)\n .executeTakeFirst()\n\n if (!record) {\n logger.warn(`Record ${id} not found in ${baseRepo.tableName} for restore`)\n throw new NotFoundError('Record', { id })\n }\n\n return record\n },\n\n async hardDelete(id: number | string): Promise<void> {\n logger.info(`Hard deleting record ${id} from ${baseRepo.tableName}`)\n await rawDb\n .deleteFrom(baseRepo.tableName)\n .where(primaryKeyColumn as never, '=', id as never)\n .execute()\n },\n\n async findWithDeleted(id: number | string): Promise<unknown> {\n // Use rawDb to bypass soft-delete filter\n const result = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, '=', id as never)\n .executeTakeFirst()\n return result ?? null\n },\n\n async findAllWithDeleted(): Promise<unknown[]> {\n // Use rawDb to bypass soft-delete filter and return ALL records\n const result = await rawDb.selectFrom(baseRepo.tableName).selectAll().execute()\n return result as unknown[]\n },\n\n async findDeleted(): Promise<unknown[]> {\n // Use rawDb to bypass soft-delete filter, then filter for deleted only\n const result = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(deletedAtColumn as never, 'is not', null)\n .execute()\n return result as unknown[]\n },\n\n async softDeleteMany(ids: (number | string)[]): Promise<unknown[]> {\n if (ids.length === 0) {\n return []\n }\n\n logger.info(`Soft deleting ${ids.length} records from ${baseRepo.tableName}`)\n\n await rawDb\n .updateTable(baseRepo.tableName)\n .set({ [deletedAtColumn]: sql`CURRENT_TIMESTAMP` } as never)\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n // Use rawDb to see the just-deleted records\n const records = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n if (records.length !== ids.length) {\n const foundIds = records.map((r: Record<string, unknown>) => r[primaryKeyColumn])\n const missingIds = ids.filter(id => !foundIds.includes(id))\n logger.warn(`Some records not found for soft delete: ${missingIds.join(', ')}`)\n throw new NotFoundError('Records', { ids: missingIds })\n }\n\n return records as unknown[]\n },\n\n async restoreMany(ids: (number | string)[]): Promise<unknown[]> {\n if (ids.length === 0) {\n return []\n }\n\n logger.info(`Restoring ${ids.length} soft-deleted records from ${baseRepo.tableName}`)\n\n await rawDb\n .updateTable(baseRepo.tableName)\n .set({ [deletedAtColumn]: null } as never)\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n const records = await rawDb\n .selectFrom(baseRepo.tableName)\n .selectAll()\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n return records as unknown[]\n },\n\n async hardDeleteMany(ids: (number | string)[]): Promise<void> {\n if (ids.length === 0) {\n return\n }\n\n logger.info(`Hard deleting ${ids.length} records from ${baseRepo.tableName}`)\n\n await rawDb\n .deleteFrom(baseRepo.tableName)\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n }\n }\n\n return extendedRepo as T\n }\n }\n}\n"]}
package/dist/schema.d.ts CHANGED
@@ -31,6 +31,7 @@ declare const SoftDeleteOptionsSchema: z.ZodObject<{
31
31
  deletedAtColumn: z.ZodOptional<z.ZodString>;
32
32
  includeDeleted: z.ZodOptional<z.ZodBoolean>;
33
33
  tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
34
+ excludeTables: z.ZodOptional<z.ZodArray<z.ZodString>>;
34
35
  primaryKeyColumn: z.ZodOptional<z.ZodString>;
35
36
  }, z.core.$strip>;
36
37
  /**
package/dist/schema.js CHANGED
@@ -1,2 +1,2 @@
1
- import {z}from'zod';var o=z.object({deletedAtColumn:z.string().optional(),includeDeleted:z.boolean().optional(),tables:z.array(z.string()).optional(),primaryKeyColumn:z.string().optional()});export{o as SoftDeleteOptionsSchema};//# sourceMappingURL=schema.js.map
1
+ import {z}from'zod';var o=z.object({deletedAtColumn:z.string().optional(),includeDeleted:z.boolean().optional(),tables:z.array(z.string()).optional(),excludeTables:z.array(z.string()).optional(),primaryKeyColumn:z.string().optional()});export{o as SoftDeleteOptionsSchema};//# sourceMappingURL=schema.js.map
2
2
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/schema.ts"],"names":["SoftDeleteOptionsSchema","z"],"mappings":"oBA6BO,IAAMA,EAA0BC,CAAAA,CAAE,MAAA,CAAO,CAC9C,eAAA,CAAiBA,EAAE,MAAA,EAAO,CAAE,QAAA,EAAS,CACrC,eAAgBA,CAAAA,CAAE,OAAA,EAAQ,CAAE,QAAA,GAC5B,MAAA,CAAQA,CAAAA,CAAE,KAAA,CAAMA,CAAAA,CAAE,QAAQ,CAAA,CAAE,QAAA,EAAS,CACrC,iBAAkBA,CAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAC/B,CAAC","file":"schema.js","sourcesContent":["/**\n * Zod schemas for soft-delete plugin configuration.\n * This file is separate from the main index to allow the package to work without Zod installed.\n * Only import this file if you need Zod validation (e.g., for CLI or configuration validation).\n *\n * @packageDocumentation\n */\n\nimport { z } from 'zod'\n\n/**\n * Zod schema for SoftDeleteOptions\n * Used for validation and configuration in the kysera-cli\n *\n * @example\n * ```typescript\n * import { SoftDeleteOptionsSchema } from '@kysera/soft-delete/schema'\n *\n * const result = SoftDeleteOptionsSchema.safeParse({\n * deletedAtColumn: 'deleted_at',\n * includeDeleted: false,\n * tables: ['users', 'posts']\n * })\n *\n * if (result.success) {\n * console.log('Valid options:', result.data)\n * }\n * ```\n */\nexport const SoftDeleteOptionsSchema = z.object({\n deletedAtColumn: z.string().optional(),\n includeDeleted: z.boolean().optional(),\n tables: z.array(z.string()).optional(),\n primaryKeyColumn: z.string().optional()\n})\n\n/**\n * Type inferred from SoftDeleteOptionsSchema\n */\nexport type SoftDeleteOptionsSchemaType = z.infer<typeof SoftDeleteOptionsSchema>\n"]}
1
+ {"version":3,"sources":["../src/schema.ts"],"names":["SoftDeleteOptionsSchema","z"],"mappings":"oBA6BO,IAAMA,EAA0BC,CAAAA,CAAE,MAAA,CAAO,CAC9C,eAAA,CAAiBA,CAAAA,CAAE,QAAO,CAAE,QAAA,GAC5B,cAAA,CAAgBA,CAAAA,CAAE,SAAQ,CAAE,QAAA,GAC5B,MAAA,CAAQA,CAAAA,CAAE,MAAMA,CAAAA,CAAE,MAAA,EAAQ,CAAA,CAAE,QAAA,GAC5B,aAAA,CAAeA,CAAAA,CAAE,MAAMA,CAAAA,CAAE,MAAA,EAAQ,CAAA,CAAE,QAAA,GACnC,gBAAA,CAAkBA,CAAAA,CAAE,QAAO,CAAE,QAAA,EAG/B,CAAC","file":"schema.js","sourcesContent":["/**\n * Zod schemas for soft-delete plugin configuration.\n * This file is separate from the main index to allow the package to work without Zod installed.\n * Only import this file if you need Zod validation (e.g., for CLI or configuration validation).\n *\n * @packageDocumentation\n */\n\nimport { z } from 'zod'\n\n/**\n * Zod schema for SoftDeleteOptions\n * Used for validation and configuration in the kysera-cli\n *\n * @example\n * ```typescript\n * import { SoftDeleteOptionsSchema } from '@kysera/soft-delete/schema'\n *\n * const result = SoftDeleteOptionsSchema.safeParse({\n * deletedAtColumn: 'deleted_at',\n * includeDeleted: false,\n * tables: ['users', 'posts']\n * })\n *\n * if (result.success) {\n * console.log('Valid options:', result.data)\n * }\n * ```\n */\nexport const SoftDeleteOptionsSchema = z.object({\n deletedAtColumn: z.string().optional(),\n includeDeleted: z.boolean().optional(),\n tables: z.array(z.string()).optional(),\n excludeTables: z.array(z.string()).optional(),\n primaryKeyColumn: z.string().optional(),\n // Note: logger is not validated as it's a function interface\n // CLI tools should handle logger separately\n})\n\n/**\n * Type inferred from SoftDeleteOptionsSchema\n */\nexport type SoftDeleteOptionsSchemaType = z.infer<typeof SoftDeleteOptionsSchema>\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kysera/soft-delete",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Soft delete plugin for Kysely repositories",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,7 +20,7 @@
20
20
  "src"
21
21
  ],
22
22
  "dependencies": {
23
- "@kysera/core": "0.8.0"
23
+ "@kysera/core": "0.8.2"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/better-sqlite3": "^7.6.13",
@@ -31,14 +31,14 @@
31
31
  "tsup": "^8.5.1",
32
32
  "typescript": "^5.9.3",
33
33
  "vitest": "^4.0.16",
34
- "zod": "^4.1.13",
35
- "@kysera/dal": "0.8.0",
36
- "@kysera/repository": "0.8.0"
34
+ "zod": "^4.2.1",
35
+ "@kysera/repository": "0.8.2",
36
+ "@kysera/dal": "0.8.2"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "kysely": ">=0.28.8",
40
- "zod": "^4.1.13",
41
- "@kysera/executor": "0.8.0"
40
+ "zod": "^4.2.1",
41
+ "@kysera/executor": "0.8.2"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "zod": {
package/src/index.ts CHANGED
@@ -223,9 +223,10 @@ export const softDeletePlugin = (options: SoftDeleteOptions = {}): Plugin => {
223
223
  /**
224
224
  * Lifecycle: Cleanup resources when executor is destroyed
225
225
  */
226
- async onDestroy() {
226
+ onDestroy(): Promise<void> {
227
227
  // No cleanup required - soft-delete plugin has no persistent resources
228
228
  logger.debug('Soft-delete plugin destroyed')
229
+ return Promise.resolve()
229
230
  },
230
231
 
231
232
  /**
package/src/schema.ts CHANGED
@@ -31,7 +31,10 @@ export const SoftDeleteOptionsSchema = z.object({
31
31
  deletedAtColumn: z.string().optional(),
32
32
  includeDeleted: z.boolean().optional(),
33
33
  tables: z.array(z.string()).optional(),
34
- primaryKeyColumn: z.string().optional()
34
+ excludeTables: z.array(z.string()).optional(),
35
+ primaryKeyColumn: z.string().optional(),
36
+ // Note: logger is not validated as it's a function interface
37
+ // CLI tools should handle logger separately
35
38
  })
36
39
 
37
40
  /**