elseware-nodejs 1.0.0

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/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var m=require('mongoose'),h=require('dotenv'),w=require('pino');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var m__default=/*#__PURE__*/_interopDefault(m);var h__default=/*#__PURE__*/_interopDefault(h);var w__default=/*#__PURE__*/_interopDefault(w);var n=class extends Error{statusCode;status;isOperational;code;details;constructor(e,t=500,o){super(e),this.statusCode=t,this.status=`${t}`.startsWith("4")?"fail":"error",this.isOperational=true,this.code=o?.code,this.details=o?.details,Error.captureStackTrace(this,this.constructor);}};var a=class s{static send(e,t){let{statusCode:o=200,success:i=true,message:r="Success",data:u,meta:p}=t;if(o===204)return e.status(204).send();let l={success:i,message:r};return u!==void 0&&(l.data=u),p!==void 0&&(l.meta=p),e.status(o).json(l)}static ok(e,t,o,i){return s.send(e,{statusCode:200,data:t,message:o,meta:i})}static created(e,t,o){return s.send(e,{statusCode:201,data:t,message:o})}static noContent(e){return s.send(e,{statusCode:204})}static error(e,t,o=500){return s.send(e,{success:false,statusCode:o,message:t})}};var c=s=>(e,t,o)=>Promise.resolve(s(e,t,o)).catch(o);var d=class{query;queryString;page;limit;constructor(e,t){this.query=e,this.queryString=t,this.page=1,this.limit=100;}filter(){let e={...this.queryString};["page","sort","limit","fields"].forEach(o=>delete e[o]);let t=JSON.stringify(e);return t=t.replace(/\b(gte|gt|lte|lt|in)\b/g,o=>`$${o}`),this.query=this.query.find(JSON.parse(t)),this}sort(){if(this.queryString.sort){let e=String(this.queryString.sort).split(",").join(" ");this.query=this.query.sort(e);}else this.query=this.query.sort("-createdAt");return this}limitFields(){if(this.queryString.fields){let e=String(this.queryString.fields).split(",").join(" ");this.query=this.query.select(e);}else this.query=this.query.select("-__v");return this}paginate(){this.page=Number(this.queryString.page)||1,this.limit=Number(this.queryString.limit)||100;let e=(this.page-1)*this.limit;return this.query=this.query.skip(e).limit(this.limit),this}populate(e){return e&&(this.query=this.query.populate(e)),this}},f=d;var g=class{static getAll(e,t={}){return c(async(o,i)=>{let r=t.filter?t.filter(o):{},u=new f(e.find(r),o.query).filter().sort().limitFields().paginate().populate(t.populate),p=await u.query;a.ok(i,p,t.message??"Records fetched successfully",{count:p.length,page:u.page,limit:u.limit});})}static getOne(e,t={}){return c(async(o,i)=>{let r=e.findById(o.params.id);t.populate&&(r=r.populate(t.populate));let u=await r;if(!u)throw new n(t.notFoundMessage??"Resource not found",404,{code:"RESOURCE_NOT_FOUND"});a.ok(i,u);})}static createOne(e,t={}){return c(async(o,i)=>{let r=await e.create(o.body);a.created(i,r,t.message??"Resource created successfully");})}static updateOne(e,t={}){return c(async(o,i)=>{let r=await e.findByIdAndUpdate(o.params.id,o.body,{new:true,runValidators:true});if(!r)throw new n(t.notFoundMessage??"Resource not found",404);a.ok(i,r,t.message??"Resource updated successfully");})}static deleteOne(e,t={}){return c(async(o,i)=>{if(!await e.findByIdAndDelete(o.params.id))throw new n(t.notFoundMessage??"Resource not found",404);a.noContent(i);})}};async function B(s){try{m__default.default.set("strictQuery",!0),await m__default.default.connect(s.uri),console.log("\u2705 Database connected successfully"),process.on("SIGINT",async()=>{await m__default.default.connection.close(),console.log("\u2705 Database connection closed"),process.exit(0);});}catch(e){console.error("\u274C Database connection failed"),e instanceof Error&&console.error(e.message),process.exit(1);}}h__default.default.config();function j(s){let{value:e,error:t}=s.validate(process.env,{abortEarly:false,allowUnknown:true});if(t)throw new Error(`\u274C Env validation error: ${t.message}`);return e}function Q(s={}){let e=s.env==="production";return w__default.default(e?{level:s.level??"info"}:{level:s.level??"debug",transport:{target:"pino-pretty",options:{colorize:true,translateTime:"HH:MM:ss",ignore:"pid,hostname"}}})}var Z=(s,e,t)=>{t();};var x=s=>new n(`Invalid ${s.path}: ${s.value}`,400,{code:"INVALID_ID"}),E=s=>{let e=s.keyValue?JSON.stringify(s.keyValue):"duplicate value";return new n(`Duplicate field value: ${e}`,400,{code:"DUPLICATE_FIELD"})},T=s=>{let e=Object.values(s.errors).map(t=>t.message);return new n("Invalid input data",400,{code:"VALIDATION_ERROR",details:e})},R=()=>new n("Invalid token. Please log in again.",401),q=()=>new n("Your token has expired. Please log in again.",401),re=(s=false)=>(e,t,o,i)=>{let r;return e instanceof n?r=e:e instanceof Error?(r=new n(e.message,500),r.isOperational=false):(r=new n("Internal Server Error",500),r.isOperational=false),e&&e.name==="CastError"&&(r=x(e)),e&&typeof e=="object"&&e.code===11e3&&(r=E(e)),e&&e.name==="ValidationError"&&(r=T(e)),e?.name==="JsonWebTokenError"&&(r=R()),e?.name==="TokenExpiredError"&&(r=q()),r.isOperational?console.log({message:r.message},"\u26A0\uFE0F Operational error"):console.error({err:e},"\u{1F4A3} Programming error"),s?a.send(o,{success:false,statusCode:r.statusCode,message:r.isOperational?r.message:"Something went wrong"}):a.send(o,{success:false,statusCode:r.statusCode,message:r.message,data:{stack:e?.stack,details:r.details}})};var ne=s=>(e,t,o)=>{let{error:i}=s.validate(e.body);if(i)return o(i);o();};var y=class{model;resourceName;constructor(e,t){this.model=e,this.resourceName=t;}async create(e){return await this.model.create(e)}async findAll(){return this.model.find()}async findById(e){let t=await this.model.findById(e);if(!t)throw new n(`${this.resourceName} not found`,404,{code:`${this.resourceName.toUpperCase()}_NOT_FOUND`});return t}async updateById(e,t){let o=await this.model.findByIdAndUpdate(e,t,{new:true,runValidators:true});if(!o)throw new n(`${this.resourceName} not found`,404,{code:`${this.resourceName.toUpperCase()}_NOT_FOUND`});return o}async deleteById(e){let t=await this.model.findByIdAndDelete(e);if(!t)throw new n(`${this.resourceName} not found`,404,{code:`${this.resourceName.toUpperCase()}_NOT_FOUND`});return t}};exports.APIFactory=g;exports.ApiResponse=a;exports.AppError=n;exports.BaseService=y;exports.GlobalErrorHandler=re;exports.asyncHandler=c;exports.authMiddleware=Z;exports.connectMongoDB=B;exports.createLogger=Q;exports.loadEnv=j;exports.validate=ne;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors/appError.ts","../src/api/apiResponse.ts","../src/api/asyncHandler.ts","../src/api/apiFeatures.ts","../src/api/apiFactory.ts","../src/configs/database.ts","../src/configs/env.ts","../src/configs/logger.ts","../src/middlewares/auth.middleware.ts","../src/middlewares/error.middleware.ts","../src/middlewares/validate.middleware.ts","../src/services/base.service.ts"],"names":["AppError","message","statusCode","options","ApiResponse","_ApiResponse","res","success","data","meta","body","asyncHandler","fn","req","next","APIFeatures","query","queryString","queryObj","el","queryStr","match","sortBy","fields","skip","populateOptions","apiFeatures_default","APIFactory","Model","filter","features","docs","doc","connectMongoDB","config","mongoose","error","dotenv","loadEnv","schema","value","createLogger","isProd","pino","authMiddleware","_req","_res","handleCastErrorDB","err","handleDuplicateFieldsDB","handleValidationErrorDB","errors","handleJWTError","handleJWTExpiredError","GlobalErrorHandler","_next","validate","BaseService","model","resourceName","id"],"mappings":"2RAAO,IAAMA,CAAAA,CAAN,cAAuB,KAAM,CAClC,WACA,MAAA,CACA,aAAA,CACA,IAAA,CACA,OAAA,CAEA,YACEC,CAAAA,CACAC,CAAAA,CAAqB,GAAA,CACrBC,CAAAA,CACA,CACA,KAAA,CAAMF,CAAO,CAAA,CAEb,IAAA,CAAK,UAAA,CAAaC,CAAAA,CAClB,IAAA,CAAK,MAAA,CAAS,GAAGA,CAAU,CAAA,CAAA,CAAG,UAAA,CAAW,GAAG,EAAI,MAAA,CAAS,OAAA,CACzD,IAAA,CAAK,aAAA,CAAgB,KACrB,IAAA,CAAK,IAAA,CAAOC,CAAAA,EAAS,IAAA,CACrB,IAAA,CAAK,OAAA,CAAUA,CAAAA,EAAS,OAAA,CAExB,MAAM,iBAAA,CAAkB,IAAA,CAAM,IAAA,CAAK,WAAW,EAChD,CACF,ECpBO,IAAMC,CAAAA,CAAN,MAAMC,CAAY,CACvB,OAAO,IAAA,CACLC,EACAH,CAAAA,CAOA,CACA,GAAM,CACJ,WAAAD,CAAAA,CAAa,GAAA,CACb,OAAA,CAAAK,CAAAA,CAAU,KACV,OAAA,CAAAN,CAAAA,CAAU,SAAA,CACV,IAAA,CAAAO,EACA,IAAA,CAAAC,CACF,CAAA,CAAIN,CAAAA,CAEJ,GAAID,CAAAA,GAAe,GAAA,CACjB,OAAOI,EAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,GAGzB,IAAMI,CAAAA,CAAgC,CAAE,OAAA,CAAAH,EAAS,OAAA,CAAAN,CAAQ,CAAA,CACzD,OAAIO,CAAAA,GAAS,MAAA,GAAWE,CAAAA,CAAK,IAAA,CAAOF,GAChCC,CAAAA,GAAS,MAAA,GAAWC,CAAAA,CAAK,IAAA,CAAOD,GAE7BH,CAAAA,CAAI,MAAA,CAAOJ,CAAU,CAAA,CAAE,KAAKQ,CAAI,CACzC,CAEA,OAAO,EAAA,CAAGJ,CAAAA,CAAeE,CAAAA,CAAeP,CAAAA,CAAkBQ,EAAgB,CACxE,OAAOJ,CAAAA,CAAY,IAAA,CAAKC,EAAK,CAAE,UAAA,CAAY,GAAA,CAAK,IAAA,CAAAE,EAAM,OAAA,CAAAP,CAAAA,CAAS,IAAA,CAAAQ,CAAK,CAAC,CACvE,CAEA,OAAO,OAAA,CAAQH,EAAeE,CAAAA,CAAeP,CAAAA,CAAkB,CAC7D,OAAOI,EAAY,IAAA,CAAKC,CAAAA,CAAK,CAAE,UAAA,CAAY,IAAK,IAAA,CAAAE,CAAAA,CAAM,OAAA,CAAAP,CAAQ,CAAC,CACjE,CAEA,OAAO,UAAUK,CAAAA,CAAe,CAC9B,OAAOD,CAAAA,CAAY,KAAKC,CAAAA,CAAK,CAAE,UAAA,CAAY,GAAI,CAAC,CAClD,CAGA,OAAO,KAAA,CAAMA,CAAAA,CAAeL,CAAAA,CAAkBC,CAAAA,CAAqB,GAAA,CAAK,CACtE,OAAOG,CAAAA,CAAY,IAAA,CAAKC,CAAAA,CAAK,CAC3B,OAAA,CAAS,KAAA,CACT,UAAA,CAAAJ,CAAAA,CACA,QAAAD,CACF,CAAC,CACH,CACF,EClDO,IAAMU,CAAAA,CACVC,CAAAA,EACD,CAACC,CAAAA,CAAcP,CAAAA,CAAeQ,CAAAA,GAC5B,OAAA,CAAQ,QAAQF,CAAAA,CAAGC,CAAAA,CAAKP,CAAAA,CAAKQ,CAAI,CAAC,CAAA,CAAE,KAAA,CAAMA,CAAI,ECAlD,IAAMC,CAAAA,CAAN,KAAqB,CACZ,KAAA,CACA,YACA,IAAA,CACA,KAAA,CAEP,WAAA,CAAYC,CAAAA,CAAsBC,EAAsC,CACtE,IAAA,CAAK,KAAA,CAAQD,CAAAA,CACb,KAAK,WAAA,CAAcC,CAAAA,CACnB,IAAA,CAAK,IAAA,CAAO,CAAA,CACZ,IAAA,CAAK,KAAA,CAAQ,IACf,CAEA,MAAA,EAAe,CACb,IAAMC,CAAAA,CAAW,CAAE,GAAG,IAAA,CAAK,WAAY,CAAA,CAEvC,CAAC,MAAA,CAAQ,MAAA,CAAQ,OAAA,CAAS,QAAQ,EAAE,OAAA,CAASC,CAAAA,EAAO,OAAOD,CAAAA,CAASC,CAAE,CAAC,CAAA,CAEvE,IAAIC,CAAAA,CAAW,KAAK,SAAA,CAAUF,CAAQ,CAAA,CACtC,OAAAE,EAAWA,CAAAA,CAAS,OAAA,CAClB,yBAAA,CACCC,CAAAA,EAAU,CAAA,CAAA,EAAIA,CAAK,CAAA,CACtB,CAAA,CAEA,KAAK,KAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,KAAK,KAAA,CAAMD,CAAQ,CAAC,CAAA,CAE1C,IACT,CAEA,IAAA,EAAa,CACX,GAAI,KAAK,WAAA,CAAY,IAAA,CAAM,CACzB,IAAME,EAAS,MAAA,CAAO,IAAA,CAAK,WAAA,CAAY,IAAI,EAAE,KAAA,CAAM,GAAG,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA,CAChE,IAAA,CAAK,KAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,CAAKA,CAAM,EACrC,MACE,IAAA,CAAK,KAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,KAAK,YAAY,CAAA,CAG3C,OAAO,IACT,CAEA,WAAA,EAAoB,CAClB,GAAI,IAAA,CAAK,WAAA,CAAY,MAAA,CAAQ,CAC3B,IAAMC,EAAS,MAAA,CAAO,IAAA,CAAK,WAAA,CAAY,MAAM,EAAE,KAAA,CAAM,GAAG,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA,CAClE,IAAA,CAAK,KAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,MAAA,CAAOA,CAAM,EACvC,MACE,IAAA,CAAK,KAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,OAAO,MAAM,CAAA,CAGvC,OAAO,IACT,CAEA,QAAA,EAAiB,CACf,IAAA,CAAK,IAAA,CAAO,OAAO,IAAA,CAAK,WAAA,CAAY,IAAI,CAAA,EAAK,EAC7C,IAAA,CAAK,KAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,YAAY,KAAK,CAAA,EAAK,GAAA,CAE/C,IAAMC,GAAQ,IAAA,CAAK,IAAA,CAAO,CAAA,EAAK,IAAA,CAAK,KAAA,CAEpC,OAAA,IAAA,CAAK,KAAA,CAAQ,IAAA,CAAK,MAAM,IAAA,CAAKA,CAAI,CAAA,CAAE,KAAA,CAAM,KAAK,KAAK,CAAA,CAE5C,IACT,CAEA,SAASC,CAAAA,CAA6D,CACpE,OAAIA,CAAAA,GACF,KAAK,KAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,QAAA,CAASA,CAAe,CAAA,CAAA,CAG3C,IACT,CACF,CAAA,CAEOC,EAAQX,CAAAA,CCtDR,IAAMY,CAAAA,CAAN,KAAiB,CACtB,OAAO,MAAA,CAAUC,CAAAA,CAAiBzB,CAAAA,CAAgC,EAAC,CAAG,CACpE,OAAOQ,EAAa,MAAOE,CAAAA,CAAKP,CAAAA,GAAQ,CACtC,IAAMuB,CAAAA,CAAS1B,CAAAA,CAAQ,MAAA,CAASA,CAAAA,CAAQ,OAAOU,CAAG,CAAA,CAAI,EAAC,CAEjDiB,EAAW,IAAIJ,CAAAA,CAAYE,CAAAA,CAAM,IAAA,CAAKC,CAAM,CAAA,CAAGhB,CAAAA,CAAI,KAAK,CAAA,CAC3D,QAAO,CACP,IAAA,EAAK,CACL,WAAA,GACA,QAAA,EAAS,CACT,QAAA,CAASV,CAAAA,CAAQ,QAAQ,CAAA,CAEtB4B,CAAAA,CAAO,MAAMD,EAAS,KAAA,CAE5B1B,CAAAA,CAAY,EAAA,CACVE,CAAAA,CACAyB,EACA5B,CAAAA,CAAQ,OAAA,EAAW,8BAAA,CACnB,CACE,MAAO4B,CAAAA,CAAK,MAAA,CACZ,IAAA,CAAMD,CAAAA,CAAS,IAAA,CACf,KAAA,CAAOA,CAAAA,CAAS,KAClB,CACF,EACF,CAAC,CACH,CAEA,OAAO,MAAA,CAAUF,CAAAA,CAAiBzB,CAAAA,CAAgC,GAAI,CACpE,OAAOQ,CAAAA,CAAa,MAAOE,CAAAA,CAAKP,CAAAA,GAAQ,CACtC,IAAIU,EAAQY,CAAAA,CAAM,QAAA,CAASf,CAAAA,CAAI,MAAA,CAAO,EAAE,CAAA,CAEpCV,CAAAA,CAAQ,QAAA,GACVa,CAAAA,CAAQA,EAAM,QAAA,CACZb,CAAAA,CAAQ,QACV,CAAA,CAAA,CAGF,IAAM6B,CAAAA,CAAM,MAAMhB,CAAAA,CAElB,GAAI,CAACgB,CAAAA,CACH,MAAM,IAAIhC,CAAAA,CACRG,EAAQ,eAAA,EAAmB,oBAAA,CAC3B,GAAA,CACA,CAAE,KAAM,oBAAqB,CAC/B,CAAA,CAGFC,CAAAA,CAAY,EAAA,CAAGE,CAAAA,CAAK0B,CAAG,EACzB,CAAC,CACH,CAEA,OAAO,SAAA,CAAaJ,EAAiBzB,CAAAA,CAAgC,EAAC,CAAG,CACvE,OAAOQ,CAAAA,CAAa,MAAOE,CAAAA,CAAKP,CAAAA,GAAQ,CACtC,IAAM0B,CAAAA,CAAM,MAAMJ,CAAAA,CAAM,OAAOf,CAAAA,CAAI,IAAI,CAAA,CAEvCT,CAAAA,CAAY,QACVE,CAAAA,CACA0B,CAAAA,CACA7B,CAAAA,CAAQ,OAAA,EAAW,+BACrB,EACF,CAAC,CACH,CAEA,OAAO,SAAA,CAAayB,CAAAA,CAAiBzB,CAAAA,CAAgC,EAAC,CAAG,CACvE,OAAOQ,CAAAA,CAAa,MAAOE,CAAAA,CAAKP,CAAAA,GAAQ,CACtC,IAAM0B,EAAM,MAAMJ,CAAAA,CAAM,iBAAA,CACtBf,CAAAA,CAAI,OAAO,EAAA,CACXA,CAAAA,CAAI,IAAA,CACJ,CACE,IAAK,IAAA,CACL,aAAA,CAAe,IACjB,CACF,EAEA,GAAI,CAACmB,CAAAA,CACH,MAAM,IAAIhC,CAAAA,CACRG,CAAAA,CAAQ,eAAA,EAAmB,oBAAA,CAC3B,GACF,CAAA,CAGFC,CAAAA,CAAY,EAAA,CACVE,EACA0B,CAAAA,CACA7B,CAAAA,CAAQ,OAAA,EAAW,+BACrB,EACF,CAAC,CACH,CAEA,OAAO,UAAayB,CAAAA,CAAiBzB,CAAAA,CAAgC,EAAC,CAAG,CACvE,OAAOQ,CAAAA,CAAa,MAAOE,EAAKP,CAAAA,GAAQ,CAGtC,GAAI,CAFQ,MAAMsB,CAAAA,CAAM,iBAAA,CAAkBf,CAAAA,CAAI,MAAA,CAAO,EAAE,CAAA,CAGrD,MAAM,IAAIb,CAAAA,CACRG,CAAAA,CAAQ,eAAA,EAAmB,oBAAA,CAC3B,GACF,EAGFC,CAAAA,CAAY,SAAA,CAAUE,CAAG,EAC3B,CAAC,CACH,CACF,ECvHA,eAAsB2B,CAAAA,CAAeC,CAAAA,CAAuC,CAC1E,GAAI,CACFC,kBAAAA,CAAS,GAAA,CAAI,cAAe,CAAA,CAAI,CAAA,CAEhC,MAAMA,kBAAAA,CAAS,QAAQD,CAAAA,CAAO,GAAG,CAAA,CAEjC,OAAA,CAAQ,IAAI,wCAAmC,CAAA,CAE/C,OAAA,CAAQ,EAAA,CAAG,QAAA,CAAU,SAAY,CAC/B,MAAMC,mBAAS,UAAA,CAAW,KAAA,EAAM,CAChC,OAAA,CAAQ,IAAI,mCAA8B,CAAA,CAC1C,OAAA,CAAQ,IAAA,CAAK,CAAC,EAChB,CAAC,EACH,CAAA,MAASC,CAAAA,CAAgB,CACvB,OAAA,CAAQ,KAAA,CAAM,mCAA8B,CAAA,CAExCA,CAAAA,YAAiB,KAAA,EACnB,OAAA,CAAQ,MAAMA,CAAAA,CAAM,OAAO,CAAA,CAG7B,OAAA,CAAQ,KAAK,CAAC,EAChB,CACF,CCzBAC,kBAAAA,CAAO,QAAO,CAMP,SAASC,CAAAA,CAAWC,CAAAA,CAAgC,CACzD,GAAM,CAAE,KAAA,CAAAC,CAAAA,CAAO,MAAAJ,CAAM,CAAA,CAAIG,CAAAA,CAAO,QAAA,CAAS,QAAQ,GAAA,CAAK,CACpD,UAAA,CAAY,KAAA,CACZ,aAAc,IAChB,CAAC,CAAA,CAED,GAAIH,EACF,MAAM,IAAI,KAAA,CAAM,CAAA,6BAAA,EAA2BA,EAAM,OAAO,CAAA,CAAE,CAAA,CAG5D,OAAOI,CACT,CCbO,SAASC,CAAAA,CAAaP,CAAAA,CAAuB,GAAI,CACtD,IAAMQ,CAAAA,CAASR,CAAAA,CAAO,MAAQ,YAAA,CAE9B,OAAOS,kBAAAA,CACLD,CAAAA,CACI,CACE,KAAA,CAAOR,CAAAA,CAAO,KAAA,EAAS,MACzB,CAAA,CACA,CACE,KAAA,CAAOA,CAAAA,CAAO,OAAS,OAAA,CACvB,SAAA,CAAW,CACT,MAAA,CAAQ,cACR,OAAA,CAAS,CACP,QAAA,CAAU,IAAA,CACV,aAAA,CAAe,UAAA,CACf,MAAA,CAAQ,cACV,CACF,CACF,CACN,CACF,KCzBaU,CAAAA,CAAiB,CAC5BC,CAAAA,CACAC,CAAAA,CACAhC,IACS,CACTA,CAAAA,GACF,MCEMiC,CAAAA,CAAqBC,CAAAA,EACzB,IAAIhD,CAAAA,CAAS,WAAWgD,CAAAA,CAAI,IAAI,CAAA,EAAA,EAAKA,CAAAA,CAAI,KAAK,CAAA,CAAA,CAAI,GAAA,CAAK,CACrD,IAAA,CAAM,YACR,CAAC,CAAA,CAEGC,CAAAA,CAA2BD,CAAAA,EAEjB,CACd,IAAMR,CAAAA,CAAQQ,CAAAA,CAAI,SAAW,IAAA,CAAK,SAAA,CAAUA,CAAAA,CAAI,QAAQ,EAAI,iBAAA,CAE5D,OAAO,IAAIhD,CAAAA,CAAS,0BAA0BwC,CAAK,CAAA,CAAA,CAAI,GAAA,CAAK,CAC1D,KAAM,iBACR,CAAC,CACH,CAAA,CAEMU,EACJF,CAAAA,EACa,CACb,IAAMG,CAAAA,CAAS,OAAO,MAAA,CAAOH,CAAAA,CAAI,MAAM,CAAA,CAAE,IAAK7B,CAAAA,EAAOA,CAAAA,CAAG,OAAO,CAAA,CAE/D,OAAO,IAAInB,CAAAA,CAAS,oBAAA,CAAsB,IAAK,CAC7C,IAAA,CAAM,kBAAA,CACN,OAAA,CAASmD,CACX,CAAC,CACH,CAAA,CAMMC,CAAAA,CAAiB,IACrB,IAAIpD,CAAAA,CAAS,qCAAA,CAAuC,GAAG,EAEnDqD,CAAAA,CAAwB,IAC5B,IAAIrD,CAAAA,CAAS,+CAAgD,GAAG,CAAA,CAMrDsD,EAAAA,CACX,CAACZ,EAAS,KAAA,GACV,CACEM,CAAAA,CACAH,CAAAA,CACAvC,EACAiD,CAAAA,GACa,CACb,IAAInB,CAAAA,CAgEJ,OA1DIY,CAAAA,YAAehD,CAAAA,CACjBoC,CAAAA,CAAQY,EACCA,CAAAA,YAAe,KAAA,EACxBZ,CAAAA,CAAQ,IAAIpC,EAASgD,CAAAA,CAAI,OAAA,CAAS,GAAG,CAAA,CACrCZ,EAAM,aAAA,CAAgB,KAAA,GAEtBA,CAAAA,CAAQ,IAAIpC,CAAAA,CAAS,uBAAA,CAAyB,GAAG,CAAA,CACjDoC,EAAM,aAAA,CAAgB,KAAA,CAAA,CAOpBY,CAAAA,EAAQA,CAAAA,CAAsB,OAAS,WAAA,GACzCZ,CAAAA,CAAQW,CAAAA,CAAkBC,CAA8B,GAIxDA,CAAAA,EACA,OAAOA,CAAAA,EAAQ,QAAA,EACdA,CAAAA,CAA0B,IAAA,GAAS,IAAA,GAEpCZ,CAAAA,CAAQa,EACND,CACF,CAAA,CAAA,CAGEA,CAAAA,EAAQA,CAAAA,CAAsB,OAAS,iBAAA,GACzCZ,CAAAA,CAAQc,CAAAA,CAAwBF,CAAoC,GAOjEA,CAAAA,EAAe,IAAA,GAAS,mBAAA,GAC3BZ,CAAAA,CAAQgB,GAAe,CAAA,CAGpBJ,CAAAA,EAAe,IAAA,GAAS,mBAAA,GAC3BZ,EAAQiB,CAAAA,EAAsB,CAAA,CAO3BjB,CAAAA,CAAM,aAAA,CAGT,QAAQ,GAAA,CAAI,CAAE,OAAA,CAASA,CAAAA,CAAM,OAAQ,CAAA,CAAG,gCAAsB,CAAA,CAF9D,OAAA,CAAQ,KAAA,CAAM,CAAE,GAAA,CAAAY,CAAI,EAAG,6BAAsB,CAAA,CAS1CN,CAAAA,CAcEtC,CAAAA,CAAY,KAAKE,CAAAA,CAAK,CAC3B,OAAA,CAAS,KAAA,CACT,WAAY8B,CAAAA,CAAM,UAAA,CAClB,OAAA,CAASA,CAAAA,CAAM,cAAgBA,CAAAA,CAAM,OAAA,CAAU,sBACjD,CAAC,EAhBQhC,CAAAA,CAAY,IAAA,CAAKE,CAAAA,CAAK,CAC3B,QAAS,KAAA,CACT,UAAA,CAAY8B,CAAAA,CAAM,UAAA,CAClB,QAASA,CAAAA,CAAM,OAAA,CACf,IAAA,CAAM,CACJ,KAAA,CAAQY,CAAAA,EAAe,KAAA,CACvB,OAAA,CAASZ,EAAM,OACjB,CACF,CAAC,CASL,EC1IK,IAAMoB,EAAAA,CACVjB,CAAAA,EACD,CAAC1B,EAAciC,CAAAA,CAAgBhC,CAAAA,GAA6B,CAC1D,GAAM,CAAE,KAAA,CAAAsB,CAAM,CAAA,CAAIG,CAAAA,CAAO,SAAS1B,CAAAA,CAAI,IAAI,CAAA,CAE1C,GAAIuB,EACF,OAAOtB,CAAAA,CAAKsB,CAAK,CAAA,CAGnBtB,IACF,ECNK,IAAe2C,CAAAA,CAAf,KAA8B,CACzB,KAAA,CACA,YAAA,CAEV,YAAYC,CAAAA,CAAiBC,CAAAA,CAAsB,CACjD,IAAA,CAAK,MAAQD,CAAAA,CACb,IAAA,CAAK,YAAA,CAAeC,EACtB,CAEA,MAAM,MAAA,CAAOnD,CAAAA,CAA8B,CAEzC,OADY,MAAM,IAAA,CAAK,KAAA,CAAM,OAAOA,CAAW,CAEjD,CAEA,MAAM,SAAwB,CAC5B,OAAO,IAAA,CAAK,KAAA,CAAM,MACpB,CAEA,MAAM,QAAA,CAASoD,CAAAA,CAAwB,CACrC,IAAM5B,CAAAA,CAAM,MAAM,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS4B,CAAE,EAExC,GAAI,CAAC5B,CAAAA,CACH,MAAM,IAAIhC,CAAAA,CAAS,CAAA,EAAG,IAAA,CAAK,YAAY,aAAc,GAAA,CAAK,CACxD,IAAA,CAAM,CAAA,EAAG,KAAK,YAAA,CAAa,WAAA,EAAa,CAAA,UAAA,CAC1C,CAAC,CAAA,CAGH,OAAOgC,CACT,CAEA,MAAM,UAAA,CAAW4B,CAAAA,CAAYpD,CAAAA,CAAkC,CAC7D,IAAMwB,CAAAA,CAAM,MAAM,IAAA,CAAK,MAAM,iBAAA,CAAkB4B,CAAAA,CAAIpD,CAAAA,CAAM,CACvD,IAAK,IAAA,CACL,aAAA,CAAe,IACjB,CAAC,EAED,GAAI,CAACwB,CAAAA,CACH,MAAM,IAAIhC,CAAAA,CAAS,CAAA,EAAG,IAAA,CAAK,YAAY,aAAc,GAAA,CAAK,CACxD,IAAA,CAAM,CAAA,EAAG,KAAK,YAAA,CAAa,WAAA,EAAa,CAAA,UAAA,CAC1C,CAAC,CAAA,CAGH,OAAOgC,CACT,CAEA,MAAM,UAAA,CAAW4B,CAAAA,CAAwB,CACvC,IAAM5B,CAAAA,CAAM,MAAM,IAAA,CAAK,KAAA,CAAM,kBAAkB4B,CAAE,CAAA,CAEjD,GAAI,CAAC5B,EACH,MAAM,IAAIhC,CAAAA,CAAS,CAAA,EAAG,KAAK,YAAY,CAAA,UAAA,CAAA,CAAc,GAAA,CAAK,CACxD,KAAM,CAAA,EAAG,IAAA,CAAK,YAAA,CAAa,WAAA,EAAa,CAAA,UAAA,CAC1C,CAAC,CAAA,CAGH,OAAOgC,CACT,CACF","file":"index.cjs","sourcesContent":["export class AppError extends Error {\n statusCode: number;\n status: string;\n isOperational: boolean;\n code?: string;\n details?: unknown;\n\n constructor(\n message: string,\n statusCode: number = 500,\n options?: { code?: string; details?: unknown }\n ) {\n super(message);\n\n this.statusCode = statusCode;\n this.status = `${statusCode}`.startsWith(\"4\") ? \"fail\" : \"error\";\n this.isOperational = true;\n this.code = options?.code;\n this.details = options?.details;\n\n Error.captureStackTrace(this, this.constructor);\n }\n}\n","import { Response } from \"express\";\n\nexport class ApiResponse {\n static send(\n res: Response,\n options: {\n statusCode?: number;\n success?: boolean;\n message?: string;\n data?: unknown;\n meta?: unknown;\n }\n ) {\n const {\n statusCode = 200,\n success = true,\n message = \"Success\",\n data,\n meta,\n } = options;\n\n if (statusCode === 204) {\n return res.status(204).send();\n }\n\n const body: Record<string, unknown> = { success, message };\n if (data !== undefined) body.data = data;\n if (meta !== undefined) body.meta = meta;\n\n return res.status(statusCode).json(body);\n }\n\n static ok(res: Response, data: unknown, message?: string, meta?: unknown) {\n return ApiResponse.send(res, { statusCode: 200, data, message, meta });\n }\n\n static created(res: Response, data: unknown, message?: string) {\n return ApiResponse.send(res, { statusCode: 201, data, message });\n }\n\n static noContent(res: Response) {\n return ApiResponse.send(res, { statusCode: 204 });\n }\n\n // ❌ Error response (optional use in controllers)\n static error(res: Response, message?: string, statusCode: number = 500) {\n return ApiResponse.send(res, {\n success: false,\n statusCode,\n message,\n });\n }\n}\n","import { Request, Response, NextFunction } from \"express\";\n\nexport const asyncHandler =\n (fn: (req: Request, res: Response, next: NextFunction) => Promise<void>) =>\n (req: Request, res: Response, next: NextFunction) =>\n Promise.resolve(fn(req, res, next)).catch(next);\n","import type { Query, PopulateOptions } from \"mongoose\";\n\n/**\n * Generic API Features helper for Mongoose queries\n */\nclass APIFeatures<T> {\n public query: Query<T[], T>;\n public queryString: Record<string, unknown>;\n public page: number;\n public limit: number;\n\n constructor(query: Query<T[], T>, queryString: Record<string, unknown>) {\n this.query = query;\n this.queryString = queryString;\n this.page = 1;\n this.limit = 100;\n }\n\n filter(): this {\n const queryObj = { ...this.queryString };\n\n [\"page\", \"sort\", \"limit\", \"fields\"].forEach((el) => delete queryObj[el]);\n\n let queryStr = JSON.stringify(queryObj);\n queryStr = queryStr.replace(\n /\\b(gte|gt|lte|lt|in)\\b/g,\n (match) => `$${match}`\n );\n\n this.query = this.query.find(JSON.parse(queryStr));\n\n return this;\n }\n\n sort(): this {\n if (this.queryString.sort) {\n const sortBy = String(this.queryString.sort).split(\",\").join(\" \");\n this.query = this.query.sort(sortBy);\n } else {\n this.query = this.query.sort(\"-createdAt\");\n }\n\n return this;\n }\n\n limitFields(): this {\n if (this.queryString.fields) {\n const fields = String(this.queryString.fields).split(\",\").join(\" \");\n this.query = this.query.select(fields);\n } else {\n this.query = this.query.select(\"-__v\");\n }\n\n return this;\n }\n\n paginate(): this {\n this.page = Number(this.queryString.page) || 1;\n this.limit = Number(this.queryString.limit) || 100;\n\n const skip = (this.page - 1) * this.limit;\n\n this.query = this.query.skip(skip).limit(this.limit);\n\n return this;\n }\n\n populate(populateOptions?: PopulateOptions | PopulateOptions[]): this {\n if (populateOptions) {\n this.query = this.query.populate(populateOptions);\n }\n\n return this;\n }\n}\n\nexport default APIFeatures;\n","import type { Request } from \"express\";\nimport type { Model, PopulateOptions } from \"mongoose\";\n\nimport { AppError } from \"../errors/appError.js\";\nimport { ApiResponse } from \"./apiResponse.js\";\nimport { asyncHandler } from \"./asyncHandler.js\";\nimport APIFeatures from \"./apiFeatures.js\";\n\n/**\n * Options for API factory methods\n */\ninterface ApiFactoryOptions<T = unknown> {\n message?: string;\n notFoundMessage?: string;\n populate?: PopulateOptions | PopulateOptions[];\n filter?: (req: Request) => Record<string, unknown>;\n}\n\n/**\n * Generic API Factory (Hybrid Pattern)\n * For simple CRUD operations without business logic\n */\nexport class APIFactory {\n static getAll<T>(Model: Model<T>, options: ApiFactoryOptions<T> = {}) {\n return asyncHandler(async (req, res) => {\n const filter = options.filter ? options.filter(req) : {};\n\n const features = new APIFeatures(Model.find(filter), req.query)\n .filter()\n .sort()\n .limitFields()\n .paginate()\n .populate(options.populate);\n\n const docs = await features.query;\n\n ApiResponse.ok(\n res,\n docs,\n options.message ?? \"Records fetched successfully\",\n {\n count: docs.length,\n page: features.page,\n limit: features.limit,\n }\n );\n });\n }\n\n static getOne<T>(Model: Model<T>, options: ApiFactoryOptions<T> = {}) {\n return asyncHandler(async (req, res) => {\n let query = Model.findById(req.params.id);\n\n if (options.populate) {\n query = query.populate(\n options.populate as PopulateOptions | PopulateOptions[]\n );\n }\n\n const doc = await query;\n\n if (!doc) {\n throw new AppError(\n options.notFoundMessage ?? \"Resource not found\",\n 404,\n { code: \"RESOURCE_NOT_FOUND\" }\n );\n }\n\n ApiResponse.ok(res, doc);\n });\n }\n\n static createOne<T>(Model: Model<T>, options: ApiFactoryOptions<T> = {}) {\n return asyncHandler(async (req, res) => {\n const doc = await Model.create(req.body);\n\n ApiResponse.created(\n res,\n doc,\n options.message ?? \"Resource created successfully\"\n );\n });\n }\n\n static updateOne<T>(Model: Model<T>, options: ApiFactoryOptions<T> = {}) {\n return asyncHandler(async (req, res) => {\n const doc = await Model.findByIdAndUpdate(\n req.params.id,\n req.body as Partial<T>,\n {\n new: true,\n runValidators: true,\n }\n );\n\n if (!doc) {\n throw new AppError(\n options.notFoundMessage ?? \"Resource not found\",\n 404\n );\n }\n\n ApiResponse.ok(\n res,\n doc,\n options.message ?? \"Resource updated successfully\"\n );\n });\n }\n\n static deleteOne<T>(Model: Model<T>, options: ApiFactoryOptions<T> = {}) {\n return asyncHandler(async (req, res) => {\n const doc = await Model.findByIdAndDelete(req.params.id);\n\n if (!doc) {\n throw new AppError(\n options.notFoundMessage ?? \"Resource not found\",\n 404\n );\n }\n\n ApiResponse.noContent(res);\n });\n }\n}\n","import mongoose from \"mongoose\";\n\nexport interface DatabaseConfig {\n uri: string;\n}\n\nexport async function connectMongoDB(config: DatabaseConfig): Promise<void> {\n try {\n mongoose.set(\"strictQuery\", true);\n\n await mongoose.connect(config.uri);\n\n console.log(\"✅ Database connected successfully\");\n\n process.on(\"SIGINT\", async () => {\n await mongoose.connection.close();\n console.log(\"✅ Database connection closed\");\n process.exit(0);\n });\n } catch (error: unknown) {\n console.error(\"❌ Database connection failed\");\n\n if (error instanceof Error) {\n console.error(error.message);\n }\n\n process.exit(1);\n }\n}\n","import dotenv from \"dotenv\";\nimport type Joi from \"joi\";\n\ndotenv.config();\n\n/**\n * Generic env loader for Elseware apps\n * App provides its own Joi schema\n */\nexport function loadEnv<T>(schema: Joi.ObjectSchema<T>): T {\n const { value, error } = schema.validate(process.env, {\n abortEarly: false,\n allowUnknown: true,\n });\n\n if (error) {\n throw new Error(`❌ Env validation error: ${error.message}`);\n }\n\n return value;\n}\n","import pino from \"pino\";\n\nexport interface LoggerConfig {\n level?: string;\n env?: \"development\" | \"production\" | \"test\";\n}\n\nexport function createLogger(config: LoggerConfig = {}) {\n const isProd = config.env === \"production\";\n\n return pino(\n isProd\n ? {\n level: config.level ?? \"info\",\n }\n : {\n level: config.level ?? \"debug\",\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"HH:MM:ss\",\n ignore: \"pid,hostname\",\n },\n },\n }\n );\n}\n","import type { Request, Response, NextFunction } from \"express\";\n\nexport const authMiddleware = (\n _req: Request,\n _res: Response,\n next: NextFunction\n): void => {\n next(); // placeholder\n};\n","import type { Request, Response, NextFunction } from \"express\";\nimport type { Error as MongooseError } from \"mongoose\";\n\nimport { AppError } from \"../errors/appError.js\";\nimport { ApiResponse } from \"../api/apiResponse.js\";\n\n/* ======================================================\n MongoDB Error Handlers\n====================================================== */\n\nconst handleCastErrorDB = (err: MongooseError.CastError): AppError =>\n new AppError(`Invalid ${err.path}: ${err.value}`, 400, {\n code: \"INVALID_ID\",\n });\n\nconst handleDuplicateFieldsDB = (err: {\n keyValue?: Record<string, unknown>;\n}): AppError => {\n const value = err.keyValue ? JSON.stringify(err.keyValue) : \"duplicate value\";\n\n return new AppError(`Duplicate field value: ${value}`, 400, {\n code: \"DUPLICATE_FIELD\",\n });\n};\n\nconst handleValidationErrorDB = (\n err: MongooseError.ValidationError\n): AppError => {\n const errors = Object.values(err.errors).map((el) => el.message);\n\n return new AppError(\"Invalid input data\", 400, {\n code: \"VALIDATION_ERROR\",\n details: errors,\n });\n};\n\n/* ======================================================\n JWT Error Handlers\n====================================================== */\n\nconst handleJWTError = (): AppError =>\n new AppError(\"Invalid token. Please log in again.\", 401);\n\nconst handleJWTExpiredError = (): AppError =>\n new AppError(\"Your token has expired. Please log in again.\", 401);\n\n/* ======================================================\n Global Error Middleware\n====================================================== */\n\nexport const GlobalErrorHandler =\n (isProd = false) =>\n (\n err: unknown,\n _req: Request,\n res: Response,\n _next: NextFunction\n ): Response => {\n let error: AppError;\n\n /* --------------------\n Normalize error\n -------------------- */\n\n if (err instanceof AppError) {\n error = err;\n } else if (err instanceof Error) {\n error = new AppError(err.message, 500);\n error.isOperational = false;\n } else {\n error = new AppError(\"Internal Server Error\", 500);\n error.isOperational = false;\n }\n\n /* --------------------\n MongoDB errors\n -------------------- */\n\n if (err && (err as MongooseError).name === \"CastError\") {\n error = handleCastErrorDB(err as MongooseError.CastError);\n }\n\n if (\n err &&\n typeof err === \"object\" &&\n (err as { code?: number }).code === 11000\n ) {\n error = handleDuplicateFieldsDB(\n err as { keyValue?: Record<string, unknown> }\n );\n }\n\n if (err && (err as MongooseError).name === \"ValidationError\") {\n error = handleValidationErrorDB(err as MongooseError.ValidationError);\n }\n\n /* --------------------\n JWT errors\n -------------------- */\n\n if ((err as Error)?.name === \"JsonWebTokenError\") {\n error = handleJWTError();\n }\n\n if ((err as Error)?.name === \"TokenExpiredError\") {\n error = handleJWTExpiredError();\n }\n\n /* --------------------\n Logging\n -------------------- */\n\n if (!error.isOperational) {\n console.error({ err }, \"💣 Programming error\");\n } else {\n console.log({ message: error.message }, \"⚠️ Operational error\");\n }\n\n /* --------------------\n Response\n -------------------- */\n\n if (!isProd) {\n // DEV: detailed error\n return ApiResponse.send(res, {\n success: false,\n statusCode: error.statusCode,\n message: error.message,\n data: {\n stack: (err as Error)?.stack,\n details: error.details,\n },\n });\n }\n\n // PROD: safe error\n return ApiResponse.send(res, {\n success: false,\n statusCode: error.statusCode,\n message: error.isOperational ? error.message : \"Something went wrong\",\n });\n };\n","import type { Request, Response, NextFunction } from \"express\";\nimport type { Schema } from \"joi\";\n\nexport const validate =\n (schema: Schema) =>\n (req: Request, _res: Response, next: NextFunction): void => {\n const { error } = schema.validate(req.body);\n\n if (error) {\n return next(error);\n }\n\n next();\n };\n","import type { Model, UpdateQuery } from \"mongoose\";\nimport { AppError } from \"../errors/appError.js\";\n\n/**\n * Generic Base Service\n * Provides default CRUD operations\n */\nexport abstract class BaseService<T> {\n protected model: Model<T>;\n protected resourceName: string;\n\n constructor(model: Model<T>, resourceName: string) {\n this.model = model;\n this.resourceName = resourceName;\n }\n\n async create(data: Partial<T>): Promise<T> {\n const doc = await this.model.create(data as any);\n return doc as T;\n }\n\n async findAll(): Promise<T[]> {\n return this.model.find();\n }\n\n async findById(id: string): Promise<T> {\n const doc = await this.model.findById(id);\n\n if (!doc) {\n throw new AppError(`${this.resourceName} not found`, 404, {\n code: `${this.resourceName.toUpperCase()}_NOT_FOUND`,\n });\n }\n\n return doc;\n }\n\n async updateById(id: string, data: UpdateQuery<T>): Promise<T> {\n const doc = await this.model.findByIdAndUpdate(id, data, {\n new: true,\n runValidators: true,\n });\n\n if (!doc) {\n throw new AppError(`${this.resourceName} not found`, 404, {\n code: `${this.resourceName.toUpperCase()}_NOT_FOUND`,\n });\n }\n\n return doc;\n }\n\n async deleteById(id: string): Promise<T> {\n const doc = await this.model.findByIdAndDelete(id);\n\n if (!doc) {\n throw new AppError(`${this.resourceName} not found`, 404, {\n code: `${this.resourceName.toUpperCase()}_NOT_FOUND`,\n });\n }\n\n return doc;\n }\n}\n"]}
@@ -0,0 +1,102 @@
1
+ import * as express from 'express';
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import { Model, PopulateOptions, UpdateQuery } from 'mongoose';
4
+ import Joi, { Schema } from 'joi';
5
+ import pino from 'pino';
6
+
7
+ /**
8
+ * Options for API factory methods
9
+ */
10
+ interface ApiFactoryOptions<T = unknown> {
11
+ message?: string;
12
+ notFoundMessage?: string;
13
+ populate?: PopulateOptions | PopulateOptions[];
14
+ filter?: (req: Request) => Record<string, unknown>;
15
+ }
16
+ /**
17
+ * Generic API Factory (Hybrid Pattern)
18
+ * For simple CRUD operations without business logic
19
+ */
20
+ declare class APIFactory {
21
+ static getAll<T>(Model: Model<T>, options?: ApiFactoryOptions<T>): (req: Request, res: express.Response, next: express.NextFunction) => Promise<void>;
22
+ static getOne<T>(Model: Model<T>, options?: ApiFactoryOptions<T>): (req: Request, res: express.Response, next: express.NextFunction) => Promise<void>;
23
+ static createOne<T>(Model: Model<T>, options?: ApiFactoryOptions<T>): (req: Request, res: express.Response, next: express.NextFunction) => Promise<void>;
24
+ static updateOne<T>(Model: Model<T>, options?: ApiFactoryOptions<T>): (req: Request, res: express.Response, next: express.NextFunction) => Promise<void>;
25
+ static deleteOne<T>(Model: Model<T>, options?: ApiFactoryOptions<T>): (req: Request, res: express.Response, next: express.NextFunction) => Promise<void>;
26
+ }
27
+
28
+ declare class ApiResponse {
29
+ static send(res: Response, options: {
30
+ statusCode?: number;
31
+ success?: boolean;
32
+ message?: string;
33
+ data?: unknown;
34
+ meta?: unknown;
35
+ }): Response<any, Record<string, any>>;
36
+ static ok(res: Response, data: unknown, message?: string, meta?: unknown): Response<any, Record<string, any>>;
37
+ static created(res: Response, data: unknown, message?: string): Response<any, Record<string, any>>;
38
+ static noContent(res: Response): Response<any, Record<string, any>>;
39
+ static error(res: Response, message?: string, statusCode?: number): Response<any, Record<string, any>>;
40
+ }
41
+
42
+ declare const asyncHandler: (fn: (req: Request, res: Response, next: NextFunction) => Promise<void>) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
43
+
44
+ interface DatabaseConfig {
45
+ uri: string;
46
+ }
47
+ declare function connectMongoDB(config: DatabaseConfig): Promise<void>;
48
+
49
+ /**
50
+ * Generic env loader for Elseware apps
51
+ * App provides its own Joi schema
52
+ */
53
+ declare function loadEnv<T>(schema: Joi.ObjectSchema<T>): T;
54
+
55
+ interface LoggerConfig {
56
+ level?: string;
57
+ env?: "development" | "production" | "test";
58
+ }
59
+ declare function createLogger(config?: LoggerConfig): pino.Logger<never, boolean>;
60
+
61
+ declare class AppError extends Error {
62
+ statusCode: number;
63
+ status: string;
64
+ isOperational: boolean;
65
+ code?: string;
66
+ details?: unknown;
67
+ constructor(message: string, statusCode?: number, options?: {
68
+ code?: string;
69
+ details?: unknown;
70
+ });
71
+ }
72
+
73
+ declare const authMiddleware: (_req: Request, _res: Response, next: NextFunction) => void;
74
+
75
+ declare const GlobalErrorHandler: (isProd?: boolean) => (err: unknown, _req: Request, res: Response, _next: NextFunction) => Response;
76
+
77
+ declare const validate: (schema: Schema) => (req: Request, _res: Response, next: NextFunction) => void;
78
+
79
+ /**
80
+ * Generic Base Service
81
+ * Provides default CRUD operations
82
+ */
83
+ declare abstract class BaseService<T> {
84
+ protected model: Model<T>;
85
+ protected resourceName: string;
86
+ constructor(model: Model<T>, resourceName: string);
87
+ create(data: Partial<T>): Promise<T>;
88
+ findAll(): Promise<T[]>;
89
+ findById(id: string): Promise<T>;
90
+ updateById(id: string, data: UpdateQuery<T>): Promise<T>;
91
+ deleteById(id: string): Promise<T>;
92
+ }
93
+
94
+ interface CrudService<T> {
95
+ create(data: Partial<T>): Promise<T>;
96
+ findAll(): Promise<T[]>;
97
+ findById(id: string): Promise<T>;
98
+ updateById(id: string, data: UpdateQuery<T>): Promise<T>;
99
+ deleteById(id: string): Promise<T>;
100
+ }
101
+
102
+ export { APIFactory, ApiResponse, AppError, BaseService, type CrudService, type DatabaseConfig, GlobalErrorHandler, type LoggerConfig, asyncHandler, authMiddleware, connectMongoDB, createLogger, loadEnv, validate };
@@ -0,0 +1,102 @@
1
+ import * as express from 'express';
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import { Model, PopulateOptions, UpdateQuery } from 'mongoose';
4
+ import Joi, { Schema } from 'joi';
5
+ import pino from 'pino';
6
+
7
+ /**
8
+ * Options for API factory methods
9
+ */
10
+ interface ApiFactoryOptions<T = unknown> {
11
+ message?: string;
12
+ notFoundMessage?: string;
13
+ populate?: PopulateOptions | PopulateOptions[];
14
+ filter?: (req: Request) => Record<string, unknown>;
15
+ }
16
+ /**
17
+ * Generic API Factory (Hybrid Pattern)
18
+ * For simple CRUD operations without business logic
19
+ */
20
+ declare class APIFactory {
21
+ static getAll<T>(Model: Model<T>, options?: ApiFactoryOptions<T>): (req: Request, res: express.Response, next: express.NextFunction) => Promise<void>;
22
+ static getOne<T>(Model: Model<T>, options?: ApiFactoryOptions<T>): (req: Request, res: express.Response, next: express.NextFunction) => Promise<void>;
23
+ static createOne<T>(Model: Model<T>, options?: ApiFactoryOptions<T>): (req: Request, res: express.Response, next: express.NextFunction) => Promise<void>;
24
+ static updateOne<T>(Model: Model<T>, options?: ApiFactoryOptions<T>): (req: Request, res: express.Response, next: express.NextFunction) => Promise<void>;
25
+ static deleteOne<T>(Model: Model<T>, options?: ApiFactoryOptions<T>): (req: Request, res: express.Response, next: express.NextFunction) => Promise<void>;
26
+ }
27
+
28
+ declare class ApiResponse {
29
+ static send(res: Response, options: {
30
+ statusCode?: number;
31
+ success?: boolean;
32
+ message?: string;
33
+ data?: unknown;
34
+ meta?: unknown;
35
+ }): Response<any, Record<string, any>>;
36
+ static ok(res: Response, data: unknown, message?: string, meta?: unknown): Response<any, Record<string, any>>;
37
+ static created(res: Response, data: unknown, message?: string): Response<any, Record<string, any>>;
38
+ static noContent(res: Response): Response<any, Record<string, any>>;
39
+ static error(res: Response, message?: string, statusCode?: number): Response<any, Record<string, any>>;
40
+ }
41
+
42
+ declare const asyncHandler: (fn: (req: Request, res: Response, next: NextFunction) => Promise<void>) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
43
+
44
+ interface DatabaseConfig {
45
+ uri: string;
46
+ }
47
+ declare function connectMongoDB(config: DatabaseConfig): Promise<void>;
48
+
49
+ /**
50
+ * Generic env loader for Elseware apps
51
+ * App provides its own Joi schema
52
+ */
53
+ declare function loadEnv<T>(schema: Joi.ObjectSchema<T>): T;
54
+
55
+ interface LoggerConfig {
56
+ level?: string;
57
+ env?: "development" | "production" | "test";
58
+ }
59
+ declare function createLogger(config?: LoggerConfig): pino.Logger<never, boolean>;
60
+
61
+ declare class AppError extends Error {
62
+ statusCode: number;
63
+ status: string;
64
+ isOperational: boolean;
65
+ code?: string;
66
+ details?: unknown;
67
+ constructor(message: string, statusCode?: number, options?: {
68
+ code?: string;
69
+ details?: unknown;
70
+ });
71
+ }
72
+
73
+ declare const authMiddleware: (_req: Request, _res: Response, next: NextFunction) => void;
74
+
75
+ declare const GlobalErrorHandler: (isProd?: boolean) => (err: unknown, _req: Request, res: Response, _next: NextFunction) => Response;
76
+
77
+ declare const validate: (schema: Schema) => (req: Request, _res: Response, next: NextFunction) => void;
78
+
79
+ /**
80
+ * Generic Base Service
81
+ * Provides default CRUD operations
82
+ */
83
+ declare abstract class BaseService<T> {
84
+ protected model: Model<T>;
85
+ protected resourceName: string;
86
+ constructor(model: Model<T>, resourceName: string);
87
+ create(data: Partial<T>): Promise<T>;
88
+ findAll(): Promise<T[]>;
89
+ findById(id: string): Promise<T>;
90
+ updateById(id: string, data: UpdateQuery<T>): Promise<T>;
91
+ deleteById(id: string): Promise<T>;
92
+ }
93
+
94
+ interface CrudService<T> {
95
+ create(data: Partial<T>): Promise<T>;
96
+ findAll(): Promise<T[]>;
97
+ findById(id: string): Promise<T>;
98
+ updateById(id: string, data: UpdateQuery<T>): Promise<T>;
99
+ deleteById(id: string): Promise<T>;
100
+ }
101
+
102
+ export { APIFactory, ApiResponse, AppError, BaseService, type CrudService, type DatabaseConfig, GlobalErrorHandler, type LoggerConfig, asyncHandler, authMiddleware, connectMongoDB, createLogger, loadEnv, validate };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import m from'mongoose';import h from'dotenv';import w from'pino';var n=class extends Error{statusCode;status;isOperational;code;details;constructor(e,t=500,o){super(e),this.statusCode=t,this.status=`${t}`.startsWith("4")?"fail":"error",this.isOperational=true,this.code=o?.code,this.details=o?.details,Error.captureStackTrace(this,this.constructor);}};var a=class s{static send(e,t){let{statusCode:o=200,success:i=true,message:r="Success",data:u,meta:p}=t;if(o===204)return e.status(204).send();let l={success:i,message:r};return u!==void 0&&(l.data=u),p!==void 0&&(l.meta=p),e.status(o).json(l)}static ok(e,t,o,i){return s.send(e,{statusCode:200,data:t,message:o,meta:i})}static created(e,t,o){return s.send(e,{statusCode:201,data:t,message:o})}static noContent(e){return s.send(e,{statusCode:204})}static error(e,t,o=500){return s.send(e,{success:false,statusCode:o,message:t})}};var c=s=>(e,t,o)=>Promise.resolve(s(e,t,o)).catch(o);var d=class{query;queryString;page;limit;constructor(e,t){this.query=e,this.queryString=t,this.page=1,this.limit=100;}filter(){let e={...this.queryString};["page","sort","limit","fields"].forEach(o=>delete e[o]);let t=JSON.stringify(e);return t=t.replace(/\b(gte|gt|lte|lt|in)\b/g,o=>`$${o}`),this.query=this.query.find(JSON.parse(t)),this}sort(){if(this.queryString.sort){let e=String(this.queryString.sort).split(",").join(" ");this.query=this.query.sort(e);}else this.query=this.query.sort("-createdAt");return this}limitFields(){if(this.queryString.fields){let e=String(this.queryString.fields).split(",").join(" ");this.query=this.query.select(e);}else this.query=this.query.select("-__v");return this}paginate(){this.page=Number(this.queryString.page)||1,this.limit=Number(this.queryString.limit)||100;let e=(this.page-1)*this.limit;return this.query=this.query.skip(e).limit(this.limit),this}populate(e){return e&&(this.query=this.query.populate(e)),this}},f=d;var g=class{static getAll(e,t={}){return c(async(o,i)=>{let r=t.filter?t.filter(o):{},u=new f(e.find(r),o.query).filter().sort().limitFields().paginate().populate(t.populate),p=await u.query;a.ok(i,p,t.message??"Records fetched successfully",{count:p.length,page:u.page,limit:u.limit});})}static getOne(e,t={}){return c(async(o,i)=>{let r=e.findById(o.params.id);t.populate&&(r=r.populate(t.populate));let u=await r;if(!u)throw new n(t.notFoundMessage??"Resource not found",404,{code:"RESOURCE_NOT_FOUND"});a.ok(i,u);})}static createOne(e,t={}){return c(async(o,i)=>{let r=await e.create(o.body);a.created(i,r,t.message??"Resource created successfully");})}static updateOne(e,t={}){return c(async(o,i)=>{let r=await e.findByIdAndUpdate(o.params.id,o.body,{new:true,runValidators:true});if(!r)throw new n(t.notFoundMessage??"Resource not found",404);a.ok(i,r,t.message??"Resource updated successfully");})}static deleteOne(e,t={}){return c(async(o,i)=>{if(!await e.findByIdAndDelete(o.params.id))throw new n(t.notFoundMessage??"Resource not found",404);a.noContent(i);})}};async function B(s){try{m.set("strictQuery",!0),await m.connect(s.uri),console.log("\u2705 Database connected successfully"),process.on("SIGINT",async()=>{await m.connection.close(),console.log("\u2705 Database connection closed"),process.exit(0);});}catch(e){console.error("\u274C Database connection failed"),e instanceof Error&&console.error(e.message),process.exit(1);}}h.config();function j(s){let{value:e,error:t}=s.validate(process.env,{abortEarly:false,allowUnknown:true});if(t)throw new Error(`\u274C Env validation error: ${t.message}`);return e}function Q(s={}){let e=s.env==="production";return w(e?{level:s.level??"info"}:{level:s.level??"debug",transport:{target:"pino-pretty",options:{colorize:true,translateTime:"HH:MM:ss",ignore:"pid,hostname"}}})}var Z=(s,e,t)=>{t();};var x=s=>new n(`Invalid ${s.path}: ${s.value}`,400,{code:"INVALID_ID"}),E=s=>{let e=s.keyValue?JSON.stringify(s.keyValue):"duplicate value";return new n(`Duplicate field value: ${e}`,400,{code:"DUPLICATE_FIELD"})},T=s=>{let e=Object.values(s.errors).map(t=>t.message);return new n("Invalid input data",400,{code:"VALIDATION_ERROR",details:e})},R=()=>new n("Invalid token. Please log in again.",401),q=()=>new n("Your token has expired. Please log in again.",401),re=(s=false)=>(e,t,o,i)=>{let r;return e instanceof n?r=e:e instanceof Error?(r=new n(e.message,500),r.isOperational=false):(r=new n("Internal Server Error",500),r.isOperational=false),e&&e.name==="CastError"&&(r=x(e)),e&&typeof e=="object"&&e.code===11e3&&(r=E(e)),e&&e.name==="ValidationError"&&(r=T(e)),e?.name==="JsonWebTokenError"&&(r=R()),e?.name==="TokenExpiredError"&&(r=q()),r.isOperational?console.log({message:r.message},"\u26A0\uFE0F Operational error"):console.error({err:e},"\u{1F4A3} Programming error"),s?a.send(o,{success:false,statusCode:r.statusCode,message:r.isOperational?r.message:"Something went wrong"}):a.send(o,{success:false,statusCode:r.statusCode,message:r.message,data:{stack:e?.stack,details:r.details}})};var ne=s=>(e,t,o)=>{let{error:i}=s.validate(e.body);if(i)return o(i);o();};var y=class{model;resourceName;constructor(e,t){this.model=e,this.resourceName=t;}async create(e){return await this.model.create(e)}async findAll(){return this.model.find()}async findById(e){let t=await this.model.findById(e);if(!t)throw new n(`${this.resourceName} not found`,404,{code:`${this.resourceName.toUpperCase()}_NOT_FOUND`});return t}async updateById(e,t){let o=await this.model.findByIdAndUpdate(e,t,{new:true,runValidators:true});if(!o)throw new n(`${this.resourceName} not found`,404,{code:`${this.resourceName.toUpperCase()}_NOT_FOUND`});return o}async deleteById(e){let t=await this.model.findByIdAndDelete(e);if(!t)throw new n(`${this.resourceName} not found`,404,{code:`${this.resourceName.toUpperCase()}_NOT_FOUND`});return t}};export{g as APIFactory,a as ApiResponse,n as AppError,y as BaseService,re as GlobalErrorHandler,c as asyncHandler,Z as authMiddleware,B as connectMongoDB,Q as createLogger,j as loadEnv,ne as validate};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors/appError.ts","../src/api/apiResponse.ts","../src/api/asyncHandler.ts","../src/api/apiFeatures.ts","../src/api/apiFactory.ts","../src/configs/database.ts","../src/configs/env.ts","../src/configs/logger.ts","../src/middlewares/auth.middleware.ts","../src/middlewares/error.middleware.ts","../src/middlewares/validate.middleware.ts","../src/services/base.service.ts"],"names":["AppError","message","statusCode","options","ApiResponse","_ApiResponse","res","success","data","meta","body","asyncHandler","fn","req","next","APIFeatures","query","queryString","queryObj","el","queryStr","match","sortBy","fields","skip","populateOptions","apiFeatures_default","APIFactory","Model","filter","features","docs","doc","connectMongoDB","config","mongoose","error","dotenv","loadEnv","schema","value","createLogger","isProd","pino","authMiddleware","_req","_res","handleCastErrorDB","err","handleDuplicateFieldsDB","handleValidationErrorDB","errors","handleJWTError","handleJWTExpiredError","GlobalErrorHandler","_next","validate","BaseService","model","resourceName","id"],"mappings":"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","file":"index.js","sourcesContent":["export class AppError extends Error {\n statusCode: number;\n status: string;\n isOperational: boolean;\n code?: string;\n details?: unknown;\n\n constructor(\n message: string,\n statusCode: number = 500,\n options?: { code?: string; details?: unknown }\n ) {\n super(message);\n\n this.statusCode = statusCode;\n this.status = `${statusCode}`.startsWith(\"4\") ? \"fail\" : \"error\";\n this.isOperational = true;\n this.code = options?.code;\n this.details = options?.details;\n\n Error.captureStackTrace(this, this.constructor);\n }\n}\n","import { Response } from \"express\";\n\nexport class ApiResponse {\n static send(\n res: Response,\n options: {\n statusCode?: number;\n success?: boolean;\n message?: string;\n data?: unknown;\n meta?: unknown;\n }\n ) {\n const {\n statusCode = 200,\n success = true,\n message = \"Success\",\n data,\n meta,\n } = options;\n\n if (statusCode === 204) {\n return res.status(204).send();\n }\n\n const body: Record<string, unknown> = { success, message };\n if (data !== undefined) body.data = data;\n if (meta !== undefined) body.meta = meta;\n\n return res.status(statusCode).json(body);\n }\n\n static ok(res: Response, data: unknown, message?: string, meta?: unknown) {\n return ApiResponse.send(res, { statusCode: 200, data, message, meta });\n }\n\n static created(res: Response, data: unknown, message?: string) {\n return ApiResponse.send(res, { statusCode: 201, data, message });\n }\n\n static noContent(res: Response) {\n return ApiResponse.send(res, { statusCode: 204 });\n }\n\n // ❌ Error response (optional use in controllers)\n static error(res: Response, message?: string, statusCode: number = 500) {\n return ApiResponse.send(res, {\n success: false,\n statusCode,\n message,\n });\n }\n}\n","import { Request, Response, NextFunction } from \"express\";\n\nexport const asyncHandler =\n (fn: (req: Request, res: Response, next: NextFunction) => Promise<void>) =>\n (req: Request, res: Response, next: NextFunction) =>\n Promise.resolve(fn(req, res, next)).catch(next);\n","import type { Query, PopulateOptions } from \"mongoose\";\n\n/**\n * Generic API Features helper for Mongoose queries\n */\nclass APIFeatures<T> {\n public query: Query<T[], T>;\n public queryString: Record<string, unknown>;\n public page: number;\n public limit: number;\n\n constructor(query: Query<T[], T>, queryString: Record<string, unknown>) {\n this.query = query;\n this.queryString = queryString;\n this.page = 1;\n this.limit = 100;\n }\n\n filter(): this {\n const queryObj = { ...this.queryString };\n\n [\"page\", \"sort\", \"limit\", \"fields\"].forEach((el) => delete queryObj[el]);\n\n let queryStr = JSON.stringify(queryObj);\n queryStr = queryStr.replace(\n /\\b(gte|gt|lte|lt|in)\\b/g,\n (match) => `$${match}`\n );\n\n this.query = this.query.find(JSON.parse(queryStr));\n\n return this;\n }\n\n sort(): this {\n if (this.queryString.sort) {\n const sortBy = String(this.queryString.sort).split(\",\").join(\" \");\n this.query = this.query.sort(sortBy);\n } else {\n this.query = this.query.sort(\"-createdAt\");\n }\n\n return this;\n }\n\n limitFields(): this {\n if (this.queryString.fields) {\n const fields = String(this.queryString.fields).split(\",\").join(\" \");\n this.query = this.query.select(fields);\n } else {\n this.query = this.query.select(\"-__v\");\n }\n\n return this;\n }\n\n paginate(): this {\n this.page = Number(this.queryString.page) || 1;\n this.limit = Number(this.queryString.limit) || 100;\n\n const skip = (this.page - 1) * this.limit;\n\n this.query = this.query.skip(skip).limit(this.limit);\n\n return this;\n }\n\n populate(populateOptions?: PopulateOptions | PopulateOptions[]): this {\n if (populateOptions) {\n this.query = this.query.populate(populateOptions);\n }\n\n return this;\n }\n}\n\nexport default APIFeatures;\n","import type { Request } from \"express\";\nimport type { Model, PopulateOptions } from \"mongoose\";\n\nimport { AppError } from \"../errors/appError.js\";\nimport { ApiResponse } from \"./apiResponse.js\";\nimport { asyncHandler } from \"./asyncHandler.js\";\nimport APIFeatures from \"./apiFeatures.js\";\n\n/**\n * Options for API factory methods\n */\ninterface ApiFactoryOptions<T = unknown> {\n message?: string;\n notFoundMessage?: string;\n populate?: PopulateOptions | PopulateOptions[];\n filter?: (req: Request) => Record<string, unknown>;\n}\n\n/**\n * Generic API Factory (Hybrid Pattern)\n * For simple CRUD operations without business logic\n */\nexport class APIFactory {\n static getAll<T>(Model: Model<T>, options: ApiFactoryOptions<T> = {}) {\n return asyncHandler(async (req, res) => {\n const filter = options.filter ? options.filter(req) : {};\n\n const features = new APIFeatures(Model.find(filter), req.query)\n .filter()\n .sort()\n .limitFields()\n .paginate()\n .populate(options.populate);\n\n const docs = await features.query;\n\n ApiResponse.ok(\n res,\n docs,\n options.message ?? \"Records fetched successfully\",\n {\n count: docs.length,\n page: features.page,\n limit: features.limit,\n }\n );\n });\n }\n\n static getOne<T>(Model: Model<T>, options: ApiFactoryOptions<T> = {}) {\n return asyncHandler(async (req, res) => {\n let query = Model.findById(req.params.id);\n\n if (options.populate) {\n query = query.populate(\n options.populate as PopulateOptions | PopulateOptions[]\n );\n }\n\n const doc = await query;\n\n if (!doc) {\n throw new AppError(\n options.notFoundMessage ?? \"Resource not found\",\n 404,\n { code: \"RESOURCE_NOT_FOUND\" }\n );\n }\n\n ApiResponse.ok(res, doc);\n });\n }\n\n static createOne<T>(Model: Model<T>, options: ApiFactoryOptions<T> = {}) {\n return asyncHandler(async (req, res) => {\n const doc = await Model.create(req.body);\n\n ApiResponse.created(\n res,\n doc,\n options.message ?? \"Resource created successfully\"\n );\n });\n }\n\n static updateOne<T>(Model: Model<T>, options: ApiFactoryOptions<T> = {}) {\n return asyncHandler(async (req, res) => {\n const doc = await Model.findByIdAndUpdate(\n req.params.id,\n req.body as Partial<T>,\n {\n new: true,\n runValidators: true,\n }\n );\n\n if (!doc) {\n throw new AppError(\n options.notFoundMessage ?? \"Resource not found\",\n 404\n );\n }\n\n ApiResponse.ok(\n res,\n doc,\n options.message ?? \"Resource updated successfully\"\n );\n });\n }\n\n static deleteOne<T>(Model: Model<T>, options: ApiFactoryOptions<T> = {}) {\n return asyncHandler(async (req, res) => {\n const doc = await Model.findByIdAndDelete(req.params.id);\n\n if (!doc) {\n throw new AppError(\n options.notFoundMessage ?? \"Resource not found\",\n 404\n );\n }\n\n ApiResponse.noContent(res);\n });\n }\n}\n","import mongoose from \"mongoose\";\n\nexport interface DatabaseConfig {\n uri: string;\n}\n\nexport async function connectMongoDB(config: DatabaseConfig): Promise<void> {\n try {\n mongoose.set(\"strictQuery\", true);\n\n await mongoose.connect(config.uri);\n\n console.log(\"✅ Database connected successfully\");\n\n process.on(\"SIGINT\", async () => {\n await mongoose.connection.close();\n console.log(\"✅ Database connection closed\");\n process.exit(0);\n });\n } catch (error: unknown) {\n console.error(\"❌ Database connection failed\");\n\n if (error instanceof Error) {\n console.error(error.message);\n }\n\n process.exit(1);\n }\n}\n","import dotenv from \"dotenv\";\nimport type Joi from \"joi\";\n\ndotenv.config();\n\n/**\n * Generic env loader for Elseware apps\n * App provides its own Joi schema\n */\nexport function loadEnv<T>(schema: Joi.ObjectSchema<T>): T {\n const { value, error } = schema.validate(process.env, {\n abortEarly: false,\n allowUnknown: true,\n });\n\n if (error) {\n throw new Error(`❌ Env validation error: ${error.message}`);\n }\n\n return value;\n}\n","import pino from \"pino\";\n\nexport interface LoggerConfig {\n level?: string;\n env?: \"development\" | \"production\" | \"test\";\n}\n\nexport function createLogger(config: LoggerConfig = {}) {\n const isProd = config.env === \"production\";\n\n return pino(\n isProd\n ? {\n level: config.level ?? \"info\",\n }\n : {\n level: config.level ?? \"debug\",\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"HH:MM:ss\",\n ignore: \"pid,hostname\",\n },\n },\n }\n );\n}\n","import type { Request, Response, NextFunction } from \"express\";\n\nexport const authMiddleware = (\n _req: Request,\n _res: Response,\n next: NextFunction\n): void => {\n next(); // placeholder\n};\n","import type { Request, Response, NextFunction } from \"express\";\nimport type { Error as MongooseError } from \"mongoose\";\n\nimport { AppError } from \"../errors/appError.js\";\nimport { ApiResponse } from \"../api/apiResponse.js\";\n\n/* ======================================================\n MongoDB Error Handlers\n====================================================== */\n\nconst handleCastErrorDB = (err: MongooseError.CastError): AppError =>\n new AppError(`Invalid ${err.path}: ${err.value}`, 400, {\n code: \"INVALID_ID\",\n });\n\nconst handleDuplicateFieldsDB = (err: {\n keyValue?: Record<string, unknown>;\n}): AppError => {\n const value = err.keyValue ? JSON.stringify(err.keyValue) : \"duplicate value\";\n\n return new AppError(`Duplicate field value: ${value}`, 400, {\n code: \"DUPLICATE_FIELD\",\n });\n};\n\nconst handleValidationErrorDB = (\n err: MongooseError.ValidationError\n): AppError => {\n const errors = Object.values(err.errors).map((el) => el.message);\n\n return new AppError(\"Invalid input data\", 400, {\n code: \"VALIDATION_ERROR\",\n details: errors,\n });\n};\n\n/* ======================================================\n JWT Error Handlers\n====================================================== */\n\nconst handleJWTError = (): AppError =>\n new AppError(\"Invalid token. Please log in again.\", 401);\n\nconst handleJWTExpiredError = (): AppError =>\n new AppError(\"Your token has expired. Please log in again.\", 401);\n\n/* ======================================================\n Global Error Middleware\n====================================================== */\n\nexport const GlobalErrorHandler =\n (isProd = false) =>\n (\n err: unknown,\n _req: Request,\n res: Response,\n _next: NextFunction\n ): Response => {\n let error: AppError;\n\n /* --------------------\n Normalize error\n -------------------- */\n\n if (err instanceof AppError) {\n error = err;\n } else if (err instanceof Error) {\n error = new AppError(err.message, 500);\n error.isOperational = false;\n } else {\n error = new AppError(\"Internal Server Error\", 500);\n error.isOperational = false;\n }\n\n /* --------------------\n MongoDB errors\n -------------------- */\n\n if (err && (err as MongooseError).name === \"CastError\") {\n error = handleCastErrorDB(err as MongooseError.CastError);\n }\n\n if (\n err &&\n typeof err === \"object\" &&\n (err as { code?: number }).code === 11000\n ) {\n error = handleDuplicateFieldsDB(\n err as { keyValue?: Record<string, unknown> }\n );\n }\n\n if (err && (err as MongooseError).name === \"ValidationError\") {\n error = handleValidationErrorDB(err as MongooseError.ValidationError);\n }\n\n /* --------------------\n JWT errors\n -------------------- */\n\n if ((err as Error)?.name === \"JsonWebTokenError\") {\n error = handleJWTError();\n }\n\n if ((err as Error)?.name === \"TokenExpiredError\") {\n error = handleJWTExpiredError();\n }\n\n /* --------------------\n Logging\n -------------------- */\n\n if (!error.isOperational) {\n console.error({ err }, \"💣 Programming error\");\n } else {\n console.log({ message: error.message }, \"⚠️ Operational error\");\n }\n\n /* --------------------\n Response\n -------------------- */\n\n if (!isProd) {\n // DEV: detailed error\n return ApiResponse.send(res, {\n success: false,\n statusCode: error.statusCode,\n message: error.message,\n data: {\n stack: (err as Error)?.stack,\n details: error.details,\n },\n });\n }\n\n // PROD: safe error\n return ApiResponse.send(res, {\n success: false,\n statusCode: error.statusCode,\n message: error.isOperational ? error.message : \"Something went wrong\",\n });\n };\n","import type { Request, Response, NextFunction } from \"express\";\nimport type { Schema } from \"joi\";\n\nexport const validate =\n (schema: Schema) =>\n (req: Request, _res: Response, next: NextFunction): void => {\n const { error } = schema.validate(req.body);\n\n if (error) {\n return next(error);\n }\n\n next();\n };\n","import type { Model, UpdateQuery } from \"mongoose\";\nimport { AppError } from \"../errors/appError.js\";\n\n/**\n * Generic Base Service\n * Provides default CRUD operations\n */\nexport abstract class BaseService<T> {\n protected model: Model<T>;\n protected resourceName: string;\n\n constructor(model: Model<T>, resourceName: string) {\n this.model = model;\n this.resourceName = resourceName;\n }\n\n async create(data: Partial<T>): Promise<T> {\n const doc = await this.model.create(data as any);\n return doc as T;\n }\n\n async findAll(): Promise<T[]> {\n return this.model.find();\n }\n\n async findById(id: string): Promise<T> {\n const doc = await this.model.findById(id);\n\n if (!doc) {\n throw new AppError(`${this.resourceName} not found`, 404, {\n code: `${this.resourceName.toUpperCase()}_NOT_FOUND`,\n });\n }\n\n return doc;\n }\n\n async updateById(id: string, data: UpdateQuery<T>): Promise<T> {\n const doc = await this.model.findByIdAndUpdate(id, data, {\n new: true,\n runValidators: true,\n });\n\n if (!doc) {\n throw new AppError(`${this.resourceName} not found`, 404, {\n code: `${this.resourceName.toUpperCase()}_NOT_FOUND`,\n });\n }\n\n return doc;\n }\n\n async deleteById(id: string): Promise<T> {\n const doc = await this.model.findByIdAndDelete(id);\n\n if (!doc) {\n throw new AppError(`${this.resourceName} not found`, 404, {\n code: `${this.resourceName.toUpperCase()}_NOT_FOUND`,\n });\n }\n\n return doc;\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "elseware-nodejs",
3
+ "version": "1.0.0",
4
+ "private": false,
5
+ "description": "Core framework utilities for Elseware NodeJS applications",
6
+ "type": "module",
7
+ "main": "dist/index.cjs",
8
+ "module": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "clean": "rm -rf dist",
23
+ "lint": "eslint src",
24
+ "prepublishOnly": "npm run build",
25
+ "release": "npm publish --access public"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/elsewaretechnologies/elseware-nodejs.git"
30
+ },
31
+ "author": "Dhanushka Sandakelum",
32
+ "license": "ISC",
33
+ "bugs": {
34
+ "url": "https://github.com/elsewaretechnologies/elseware-nodejs/issues"
35
+ },
36
+ "homepage": "https://github.com/elsewaretechnologies/elseware-nodejs#readme",
37
+ "dependencies": {
38
+ "dotenv": "^17.2.3",
39
+ "express": "^5.2.1",
40
+ "joi": "^18.0.2",
41
+ "mongoose": "^9.1.1",
42
+ "pino": "^10.1.0"
43
+ },
44
+ "peerDependencies": {
45
+ "mongoose": "^9",
46
+ "express": "^5",
47
+ "pino": "^10"
48
+ },
49
+ "devDependencies": {
50
+ "@eslint/js": "^9.39.2",
51
+ "@types/express": "^5.0.6",
52
+ "@types/jest": "^30.0.0",
53
+ "@types/node": "^25.0.3",
54
+ "@types/supertest": "^6.0.3",
55
+ "eslint": "^9.39.2",
56
+ "eslint-plugin-jest": "^28.14.0",
57
+ "globals": "^17.0.0",
58
+ "pino-pretty": "^13.1.3",
59
+ "ts-node": "^10.9.2",
60
+ "tsup": "^8.5.1",
61
+ "tsx": "^4.21.0",
62
+ "typescript": "^5.9.3",
63
+ "typescript-eslint": "^8.51.0"
64
+ }
65
+ }