@twasik4/pocket-service 1.0.3 → 1.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/api/express-types.ts","../src/modules/clickhouse/index.ts","../src/modules/mongo/collection.ts","../src/modules/mongo/mongo.ts","../src/core/types.ts","../src/core/service.ts","../src/runtime/stream-handler.ts","../src/auth/strategies.ts"],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,4 @@
1
+ import e,{Router as t}from"express";import n,{Logger as r,format as i,transports as a}from"winston";import o from"zod";import*as s from"crypto";import c from"cors";import{existsSync as l,readdirSync as u}from"fs";import d from"path";import{fileURLToPath as f,pathToFileURL as p}from"url";import{Worker as ee}from"worker_threads";import{createClient as te}from"redis";import{createClient as m}from"@clickhouse/client";import{MongoClient as h,ObjectId as g}from"mongodb";import{createRemoteJWKSet as _,jwtVerify as v}from"jose";function y(e){let t=e.headers[`x-user-id`],n=e.headers[`x-user-meta`];if(typeof n==`string`)try{let e=JSON.parse(n);if(typeof e.userId==`string`)return e}catch{return null}return typeof t==`string`?{userId:t}:null}function b(e,t={}){return async(n,r)=>{for(let t of e){if(t.canHandle&&!await t.canHandle(n,r))continue;let e=await t.authenticate(n,r);if(e)return e}return t.fallbackResolver?t.fallbackResolver(n,r):null}}const ne=[];function x(){return ne}function S(e){let n=t(),r=[],i=e?.authResolver??(e=>y(e)),a=e?.onUnauthorized??((e,t)=>{t.status(401).json({isSuccess:!1,message:`Unauthorized`})}),s=e?.onAuthError??((e,t,n)=>{n.status(401).json({isSuccess:!1,message:`Unauthorized`})});function c(e,t){let c=[],l=e.bodyValidator,u=e.paramsValidator;r.push({method:e.method,fullPath:e.fullPath,requireAuth:e.requireAuth??!1,meta:e.meta,bodyValidator:l,paramsValidator:u}),e.requireAuth&&c.push(async(t,n,r)=>{try{let o=await i(t,e);if(!o){a(t,n);return}t.user=o,r()}catch(e){s(e,t,n)}}),l&&e.method!==`GET`&&c.push((e,t,n)=>{let r=e.body;if(typeof r==`string`){if(l instanceof o.ZodObject||l instanceof o.ZodArray||l.def.type===`object`||l.def.type===`array`)try{r=JSON.parse(r)}catch(e){return t.status(400).json({isSuccess:!1,message:`Invalid JSON in request body`,error:e instanceof Error?e.message:e})}else if(l instanceof o.ZodNumber||l.def.type===`number`)try{r=Number(r)}catch(e){return t.status(400).json({isSuccess:!1,message:`Invalid number in request body`,error:e instanceof Error?e.message:e})}}let i=l.safeParse(r);i.success?(e.body=i.data,n()):t.status(400).json({isSuccess:!1,message:`Invalid request body`,error:i.error})}),u&&c.push((e,t,n)=>{let r=u.safeParse(e.params);r.success?(e.params=r.data,n()):t.status(400).json({isSuccess:!1,message:`Invalid URL parameters`,error:r.error})}),n[e.method.toLowerCase()](e.fullPath,...c,t)}return{router:n,routes:r,addRoute:c}}const C=e=>n.createLogger({format:i.json({bigint:!0}),defaultMeta:{service:e},transports:[new a.Console({format:i.combine(i.colorize(),i.timestamp(),i.printf(({level:t,message:n,timestamp:r})=>`${r} [${e}] ${t}: ${n}`))})]});function w(e){if(e%2!=0)throw Error(`Length must be an even number`);let t=e/2;return s.randomBytes(t).toString(`hex`)}function T(e){let t=Math.floor(e/1e3%60),n=Math.floor(e/(1e3*60)%60),r=Math.floor(e/(1e3*60*60)%24);return{days:Math.floor(e/(1e3*60*60*24)),hours:r,minutes:n,seconds:t}}function E(e){Promise.allSettled(e.dependencyOrder.filter(e=>e!==`customDependencies`).map(t=>{switch(t){case`redis`:e.dependencies.has(`workers`)?(e.loadHandlers(),e.runWorkers()):e.log.warn(`Worker module not initialized. Call withWorkers() to enable worker threads and dynamically load handlers.`),e.connectRedis().then(async()=>{e.log.info(`Redis module loaded`),await e.register(),e.log.info(`Service registered in Redis`),await e.heartbeat(),e.log.info(`Heartbeat started`),process.on(`SIGINT`,()=>{e.log.info(`SIGINT received. Shutting down gracefully...`),e.shutdown()})}).catch(t=>{e.log.error(`Failed to load Redis module: ${t}`)});break;case`mongoClient`:e.log.info(`Connecting to MongoDB...`),e.connectMongo().then(()=>{e.log.info(`Mongo module loaded`)}).catch(t=>{throw e.log.error(`Failed to connect to MongoDB: ${t}`),t});break;case`clickhouse`:e.log.info(`Connecting to ClickHouse...`),e.ensureClickhouseTables().then(()=>{e.log.info(`ClickHouse module loaded`)}).catch(t=>{e.log.error(`Failed to load ClickHouse module: ${t}`)});break;default:e.log.info(`Unknown module ${t} during initialization.`)}})).then(t=>{if(t.forEach((t,n)=>{let r=e.dependencyOrder[n];t.status===`fulfilled`?e.log.info(`Module "${r}" initialized successfully.`):e.log.error(`Failed to initialize module "${r}": ${t.reason}`)}),e.dependencies.has(`customDependencies`)){let t=e.dependencies.get(`customDependencies`);for(let n of t)try{n.init(e.log)}catch(t){e.log.error(`Failed to initialize custom module "${n.name}": ${t}`)}}})}function D(e){k(e),e.dependencies.has(`redis`)&&e.dependencies.has(`workers`)&&(e.log.info(`Redis and handler modules detected. Registering worker routes.`),A(e)),e.log.info(`Default routes registered.`)}function O(t){if(t.withoutExpressFlag){t.log.warn(`Express server is disabled. Service built without Express.`);return}t.expressOptions.asJson&&(t.log.info(`Configuring Express to parse JSON bodies`),t.api.use(e.json()));for(let e of t.expressOptions.customMiddleware||[])t.log.info(`Adding custom middleware to Express`),t.api.use(e);`corsWhitelist`in t.expressOptions?(t.log.info(`Configuring CORS with whitelist: `+t.expressOptions.corsWhitelist),t.api.use(c({origin:t.expressOptions.corsWhitelist,credentials:t.expressOptions.credentials}))):`corsFn`in t.expressOptions&&(t.log.info(`Configuring CORS with function`),t.api.use(c({origin:t.expressOptions.corsFn,credentials:t.expressOptions.credentials}))),t.expressOptions.omitDefaultRoutes||D(t),t.log.info(`Registering ${t.routers.length} routers`),t.routers.forEach(({base:e,router:n})=>{t.api.use(e,n)}),t.log.info(`Routers registered:`+t.routers.map(e=>{let t=e.routes?Math.max(...e.routes.map(e=>e.method.length)):0,n=e.routes?Math.max(...e.routes.map(e=>e.fullPath.length)):0,r=e.routes?Math.max(...e.routes.map(e=>e.requireAuth?6:8)):0;return`\nRouter ${e.base}: ${e.routes?.length||0} routes registered.\n${e.routes?.map(e=>`\t${(`[`+e.method+`]`).padEnd(t+2)} ${(e.requireAuth?`[Auth]`:`[Public]`).padEnd(r)} ${e.fullPath.padEnd(n)} | ${e.meta?.description||``}`).join(`
2
+ `)||``}`}).join(`
3
+ `)),t.api.listen(t.port,()=>{t.log.info(`API listening at ${t.fullUrl}`)})}function k(e){e.log.info(`Registering base routes`);let{router:t,routes:n,addRoute:r}=S({authResolver:e.getAuthResolver()});r({method:`GET`,fullPath:`/`,requireAuth:!1,meta:{description:`Base route`}},(t,n)=>{n.send(`OK from "`+e.name+`" service`)}),r({method:`GET`,fullPath:`/health`,requireAuth:!1,meta:{description:`Health check route that returns service status and uptime`}},(t,n)=>{n.status(200).json({service:e.name,status:e.getStatus(),startedAt:new Date(e.startedAt),uptime:T(Math.floor(process.uptime()*1e3)),workerRunning:e.getIsWorkerRunning(),modulesLoaded:Array.from(e.dependencies.keys()).filter(e=>e!==`customDependencies`).concat(Array.from(e.dependencies.has(`customDependencies`)?e.dependencies.get(`customDependencies`).map(e=>`custom:${e.name}`):[])),routesLoaded:e.getRoutes(),handlersLoaded:e.getLoadedEventListeners()})}),e.internalAddRouter(`/`,t,n)}function A(e){let{router:t,routes:n,addRoute:r}=S({authResolver:e.getAuthResolver()});r({method:`GET`,fullPath:`/`,requireAuth:!0,meta:{description:`Get worker status`}},(t,n)=>{n.json({status:e.getIsWorkerRunning()?`Running`:`Stopped`,handlersLoaded:Object.keys(e.handlers)})}),r({method:`GET`,fullPath:`/:stream`,requireAuth:!0,meta:{description:`Get stream messages`}},async(t,n)=>{let r=e.getRedis();if(!r)return n.status(500).json({error:`Redis client not connected`});let i=await r.xRange(e.streamKey,`-`,`+`,{COUNT:10});n.json({status:`Stream`,messages:i})}),r({method:`POST`,fullPath:`/:stream/:id/ack`,requireAuth:!0,meta:{description:`Acknowledge a message in the stream`}},async(t,n)=>{let{stream:r,id:i}=t.params,a=e.getRedis();if(!a)return n.status(500).json({error:`Redis client not connected`});let o=await a.xAck(r,`${e.simpleName}:consumer-group`,i);n.json({status:`Message acknowledged`,result:o})}),r({method:`GET`,fullPath:`/:stream/dlq`,requireAuth:!0,meta:{description:`Get DLQ stream messages`}},async(t,n)=>{let r=e.getRedis();if(!r)return n.status(500).json({error:`Redis client not connected`});let i=await r.xRange(e.streamKey,`-`,`+`,{COUNT:10});n.json({status:`DLQ Stream`,messages:i})}),r({method:`POST`,fullPath:`/:stream/dlq/:id/retry`,requireAuth:!0,meta:{description:`Retry a message in the DLQ`}},async(t,n)=>{let{stream:r,id:i}=t.params,a=e.getRedis();if(!a)return n.status(500).json({error:`Redis client not connected`});let o=await a.xRange(r,i,i);if(o.length===0||o[0].id!==i)return n.status(404).json({error:`Message not found in DLQ`});let s=await a.xAdd(r,`${e.simpleName}:consumer-group`,o[0].message);n.json({status:`Message retried`,result:s})}),e.internalAddRouter(`/workers`,t,n)}function j(e){if(!e.dependencies.has(`redis`)){e.log.warn(`Redis module not initialized. Skipping handler loading.`);return}let t=d.resolve(process.cwd(),`src/handlers`);if(!l(t)){e.log.warn(`Handlers directory does not exist.`);return}let n=u(t).filter(e=>e.endsWith(`.ts`)||e.endsWith(`.js`));for(let r of n){let n=d.join(t,r);e.handlers[d.basename(r,d.extname(r))]=n}e.log.info(`Loaded ${Object.keys(e.handlers).length} handlers:`);for(let t of Object.keys(e.handlers))e.log.info(`- ${t}`)}function M(e,{workerCount:t}={workerCount:1}){if(!e.name){e.log.error(`Service name is not set. Cannot start workers.`);return}if(!e.dependencies.has(`redis`)){e.log.warn(`Redis module not initialized. Skipping worker setup.`);return}if(isNaN(t)||t<1)throw Error(`Worker count must be a number greater than 0.`);let n=N();if(!n){e.log.warn(`Worker thread entry file not found. Skipping worker setup.`);return}e.workerCount=t<1?1:t;for(let r=0;r<t;r++){let t=new ee(n,{workerData:{name:e.name,redisUrl:e.redis.options?.url,handlers:e.handlers,index:r,subscriptions:e.serviceStreamSubscriptions??[`${e.name}:events`]}});e.isWorkerRunning=!0,t.on(`error`,t=>{e.log.error(`Worker ${r} error: ${t.message}`)}),t.on(`message`,t=>{switch(t.level){case`info`:e.log.info(t.message);break;case`warn`:e.log.warn(t.message);break;case`error`:e.log.error(t.message);break;case`create`:e.loadedEventListeners=t.message;break;default:e.log.info(t.message)}}),t.on(`exit`,t=>{t===0?e.log.info(`Worker ${r} exited gracefully.`):e.log.error(`Worker ${r} stopped with exit code ${t}`),e.isWorkerRunning=!1}),e.log.info(`Worker ${r} started for service ${e.name}`)}}function N(){let e=d.dirname(f(import.meta.url)),t=[d.resolve(e,`../../runtime/worker-thread.js`),d.resolve(e,`../../runtime/worker-thread.mjs`),d.resolve(process.cwd(),`dist/runtime/worker-thread.mjs`)].find(e=>l(e));return t?p(t):null}function P(e){return e}function F(e){return e==null?`NULL`:Array.isArray(e)?`[${e.map(e=>F(e)).join(`, `)}]`:e instanceof Date?`'${e.toISOString().replace(/'/g,`''`)}'`:typeof e==`number`||typeof e==`bigint`?`${e}`:typeof e==`boolean`?e?`1`:`0`:`'${String(e).replace(/'/g,`''`)}'`}function I(e,t){let n=t||{};return e.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(e,t)=>{if(!(t in n))throw Error(`Missing ClickHouse method input parameter: ${t}`);return F(n[t])})}function L(e){let t=m({host:e.url,username:e.username,password:e.password});async function n(){for(let{createSQL:n}of Object.values(e.tables||{}))await t.command({query:n})}return{client:t,ensureTables:n,...Object.fromEntries(Object.entries(e.tables||{}).map(([e,n])=>{let r=async e=>{let r=`SELECT * FROM ${n.name}`;if(e&&Object.keys(e).length>0){let t=Object.entries(e).map(([e,t])=>typeof t==`string`?`${e} = '${t}'`:`${e} = ${t}`).join(` AND `);r+=` WHERE ${t}`}return(await(await t.query({query:r})).json()).data},i=async e=>{await t.insert({table:n.name,values:e,format:`JSONEachRow`})},a=n.methods?.({client:t,table:n.name,schema:n.schema})||{};return[e,{select:r,insert:i,...Object.fromEntries(Object.entries(a).map(([e,n])=>[e,async e=>{let r=I(n.sql,e);if(n.mode===`command`){let i=await t.command({query:r});return n.transform?n.transform(i,e):void 0}let i=await(await t.query({query:r})).json();return n.transform?n.transform(i.data,e):i.data}]))}]}))}}var R=class{collection;schema;revision;constructor(e,t,n){this.collection=e,this.schema=t,this.revision=n}parseWrite(e){return this.schema?this.schema.parse(e):e}parseRead(e){return this.schema?{...this.schema.parse(e),_id:e._id}:e}async trackRevision(e){if(this.revision){let t={...e,_id:new g};await this.revision.insertOne(t)}}async get(e={}){try{return this.hasProperty(`deletedAt`)&&(e={...e,deletedAt:{$exists:!1}}),(await this.collection.find(e).toArray()).map(e=>this.parseRead(e))}catch{return[]}}async getOne(e={}){try{this.hasProperty(`deletedAt`)&&(e={...e,deletedAt:{$exists:!1}});let t=await this.collection.findOne(e);return t?this.parseRead(t):null}catch{return null}}async getRevisions(e={}){try{return this.revision?(await this.revision.find(e,{sort:{revision:1}}).toArray()).map(e=>this.parseRead(e)):[]}catch{return[]}}async getMostRecentRevision(){try{if(!this.revision)return null;let e=await this.revision.find({},{sort:{revision:-1}}).limit(1).toArray();return e.length>0?this.parseRead(e[0]):null}catch{return null}}async insert(e){try{let t=this.parseWrite(e);await this.collection.insertOne(t),await this.trackRevision(t)}catch(e){throw Error(`Failed to insert document: ${e.message}`)}}async update(e,t){try{let n=await this.collection.find(e).toArray();if(!n.length)return;if(await this.collection.updateMany(e,{$set:t}),this.revision){let e=n.map(e=>({...e,...t,_id:new g}));await this.revision.insertMany(e)}}catch(e){throw Error(`Failed to update documents: ${e.message}`)}}async updateOne(e,t){try{let n={$set:this.hasProperty(`updatedAt`)?{...t,updatedAt:new Date}:t,...this.revision?{$inc:{revision:1}}:{}};await this.collection.updateOne(e,n),this.revision&&await this.collection.aggregate([{$match:e},{$set:{...t,_id:new g}},{$merge:{into:this.revision.collectionName,on:`_id`,whenNotMatched:`insert`}}]).toArray()}catch(e){throw console.error(`Failed to update document:`,e),Error(`Failed to update document: ${e.message}`)}}async delete(e,t){try{if(this.hasProperty(`deletedAt`)){let n=new Date,r=t??`system`,i={deletedAt:n,...this.hasProperty(`deletedBy`)?{deletedBy:r}:{}},a={$set:i};await this.collection.updateMany(e,a),this.revision&&await this.collection.aggregate([{$match:e},{$set:{_id:new g,...i}},{$merge:{into:this.revision.collectionName,on:`_id`,whenNotMatched:`insert`}}]).toArray()}else await this.collection.deleteMany(e)}catch(e){throw Error(`Failed to delete documents: ${e.message}`)}}hasProperty(e){if(!this.schema)return!1;let t=this.schema,n=t.shape??(typeof t._def?.shape==`function`?t._def.shape():t._def?.shape);return typeof n==`object`&&n&&!Array.isArray(n)?e in n:!1}};function z(e){return{name:e.name,schema:e.schema,methods:e.methods,indexes:e.indexes,timeSeries:e.timeSeries,ttl:e.ttl,trackRevisions:!1}}function B(e,t,n={}){let r=new h(n.uri||process.env.MONGO_URI||`mongodb://mongo:27017`),i=r.db(e),a={},o=[];for(let e in t){let n=t[e],s=n.name||e,c=i.collection(s),l=n.trackRevisions?i.collection(`${s}_revisions`):void 0,u=new R(c,n.schema,l),d=n.methods?.({client:r,db:i,collection:c,base:u})||{};o.push(async()=>{if(n.timeSeries){let e=await i.listCollections({name:s},{nameOnly:!1}).next();if(!e)await i.createCollection(s,{timeseries:{timeField:n.timeSeries.timeField,...n.timeSeries.metaField?{metaField:n.timeSeries.metaField}:{},...n.timeSeries.granularity?{granularity:n.timeSeries.granularity}:{}},...typeof n.timeSeries.expireAfterSeconds==`number`?{expireAfterSeconds:n.timeSeries.expireAfterSeconds}:{}});else{if(!(e.type===`timeseries`||e.options?.timeseries))throw Error(`Collection ${s} already exists and is not a time-series collection. Migration is required before enabling timeSeries.`);let t=e.options?.timeseries;if(t?.timeField&&t.timeField!==n.timeSeries.timeField)throw Error(`Collection ${s} has timeField '${t.timeField}' but config expects '${n.timeSeries.timeField}'.`);if(n.timeSeries.metaField&&t?.metaField&&t.metaField!==n.timeSeries.metaField)throw Error(`Collection ${s} has metaField '${t.metaField}' but config expects '${n.timeSeries.metaField}'.`)}}if(n.indexes&&n.indexes.length>0)for(let e of n.indexes){let{key:t,...n}=e;await c.createIndex(t,n).catch(e=>{console.error(`Failed to create index on ${s}: ${e.message}`)})}if(n.ttl){if(!n.ttl.field||n.ttl.field.trim().length===0)throw Error(`Collection ${s} has invalid ttl.field configuration.`);await c.createIndex({[n.ttl.field]:1},{expireAfterSeconds:n.ttl.expireAfterSeconds,...n.ttl.name?{name:n.ttl.name}:{}}).catch(e=>{console.error(`Failed to create TTL index on ${s}: ${e.message}`)})}}),a[e]=Object.assign(u,d)}let s=null,c=async()=>(s||=(async()=>{for(let e of o)await e()})(),s);return{client:r,db:i,collections:a,...a,disconnect:async()=>r.close(),connect:async()=>(await r.connect(),await c(),r)}}function re(e,t){if(!e.dependencies.has(`mongoClient`)){let n=B(t.dbName,t.collections,{uri:t.uri});e.dependencies.set(`mongoClient`,n),e.dependencyOrder.push(`mongoClient`)}}function V(e,t){e.dependencies.has(`clickhouse`)||(e.dependencies.set(`clickhouse`,L(t)),e.dependencyOrder.push(`clickhouse`))}function H(e,t=`redis://redis:6379`){e.dependencies.has(`redis`)||(e.dependencies.set(`redis`,te({url:t})),e.dependencyOrder.push(`redis`))}function U(e,t,n=[]){if(!e.dependencies.has(`redis`)){e.log.warn(`Redis module not initialized. Call withRedis() before setting worker subscriptions.`);return}!e.dependencies.has(`workers`)&&n.length>0&&(e.dependencies.set(`workers`,!0),e.serviceStreamSubscriptions=n,e.workerCount=t<1?1:t,e.dependencyOrder.push(`workers`))}function W(e,t){e.dependencies.has(`customDependencies`)||(e.dependencies.set(`customDependencies`,t.map(e=>({name:e.name,init:e.init,shutdown:e.shutdown}))),e.dependencyOrder.push(`customDependencies`))}function G(e){e.withoutExpressFlag=!0}function K(e,t){if(e.withoutExpressFlag){e.log.warn(`Express server is disabled. Express options will not be applied.`);return}e.expressOptions={...e.expressOptions,...t}}function q(e,t){if(e.withoutExpressFlag){e.log.warn(`Express server is disabled. Auth strategies will not be applied.`);return}e.authStrategies.push(t)}function J(e,t){if(e.withoutExpressFlag){e.log.warn(`Express server is disabled. Auth strategies will not be applied.`);return}e.authStrategies.push(...t)}function Y(e,t){if(e.withoutExpressFlag){e.log.warn(`Express server is disabled. Auth resolver will not be applied.`);return}e.authResolverOverride=t}function X(e){e.useDefaultHeaderAuthFallback=!1}function ie(e,t,n,r=[]){if(e.withoutExpressFlag){e.log.warn(`Express server is disabled. Routers will not be registered.`);return}if(t===`/`){e.log.warn(`Base path '/' is not allowed. Skipping.`);return}e.routers.push({base:t,router:n,routes:r})}async function ae(e,t={}){if(await t.beforeShutdown?.(),e.setStatus(`stopping`),e.dependencies.has(`redis`)&&await e.quitRedis(),e.dependencies.has(`mongoClient`)&&await e.disconnectMongo(),e.dependencies.has(`clickhouse`)&&await e.closeClickhouse(),e.dependencies.has(`customDependencies`)){for(let t of e.dependencies.get(`customDependencies`))if(t.shutdown)try{let n=await t.shutdown(e.log);e.log.info(`Custom dependency "${t.name}" shutdown with result: ${n}`)}catch(n){e.log.error(`Failed to shutdown custom dependency "${t.name}": ${n}`)}}e.log.info(`Service ${e.name} shutting down.`),e.setStatus(`stopped`),await t.afterShutdown?.(),process.exit(0)}var oe=class t{static CONFIG_SYNC_KEYS={dependencyGraph:[`dependencies`,`dependencyOrder`],workers:[`serviceStreamSubscriptions`,`workerCount`],modules:[`dependencies`,`dependencyOrder`],expressMode:[`withoutExpressFlag`],expressOptions:[`expressOptions`],authStrategies:[`authStrategies`],authResolver:[`authResolverOverride`],authFallback:[`useDefaultHeaderAuthFallback`],routers:[`routers`]};static CONFIG_SYNC_UNIQUE_KEYS=[`dependencies`,`dependencyOrder`,`serviceStreamSubscriptions`,`workerCount`,`withoutExpressFlag`,`expressOptions`,`authStrategies`,`authResolverOverride`,`useDefaultHeaderAuthFallback`,`routers`];static CONFIG_SYNC_ALL_KEYS=t.CONFIG_SYNC_UNIQUE_KEYS;static CONFIG_SYNC_KEY_SET=new Set(t.CONFIG_SYNC_UNIQUE_KEYS);static isConfigSyncKey(e){return t.CONFIG_SYNC_KEY_SET.has(e)}name=process.env.SERVICE_NAME??w(12);url=process.env.SERVICE_URL??`http://localhost`;port=process.env.SERVICE_PORT?Number(process.env.SERVICE_PORT):3100;workerCount=1;handlers={};loadedEventListeners=[];routers=[];startedAt=Date.now();dependencies=new Map;dependencyOrder=[];isWorkerRunning=!1;serviceStreamSubscriptions=[];_status=`starting`;HEARTBEAT_INTERVAL=1e3*5;authStrategies=[];authResolverOverride;useDefaultHeaderAuthFallback=!0;expressOptions={corsWhitelist:[`*`],asJson:!0,customMiddleware:[]};withoutExpressFlag=!1;getAuthResolver(){return this.authResolverOverride?this.authResolverOverride:b(this.authStrategies,{fallbackResolver:this.useDefaultHeaderAuthFallback?e=>y(e):void 0})}toConfigContext(){return{dependencies:this.dependencies,dependencyOrder:this.dependencyOrder,log:this.log,withoutExpressFlag:this.withoutExpressFlag,expressOptions:this.expressOptions,authStrategies:this.authStrategies,authResolverOverride:this.authResolverOverride,useDefaultHeaderAuthFallback:this.useDefaultHeaderAuthFallback,serviceStreamSubscriptions:this.serviceStreamSubscriptions,workerCount:this.workerCount,routers:this.routers}}applyConfigContext(e,n=t.CONFIG_SYNC_ALL_KEYS){for(let r of n)if(t.isConfigSyncKey(r))switch(r){case`dependencies`:this.dependencies=e.dependencies;break;case`dependencyOrder`:this.dependencyOrder=e.dependencyOrder;break;case`serviceStreamSubscriptions`:this.serviceStreamSubscriptions=e.serviceStreamSubscriptions;break;case`workerCount`:this.workerCount=e.workerCount;break;case`withoutExpressFlag`:this.withoutExpressFlag=e.withoutExpressFlag;break;case`expressOptions`:this.expressOptions=e.expressOptions;break;case`authStrategies`:this.authStrategies=e.authStrategies;break;case`authResolverOverride`:this.authResolverOverride=e.authResolverOverride;break;case`useDefaultHeaderAuthFallback`:this.useDefaultHeaderAuthFallback=e.useDefaultHeaderAuthFallback;break;case`routers`:this.routers=e.routers;break}}toShutdownContext(){return{dependencies:this.dependencies,quitRedis:()=>this.redis.quit(),disconnectMongo:()=>this.mongo.disconnect(),closeClickhouse:()=>this.clickhouse.client.close(),log:this.log,name:this.name,setStatus:e=>{this._status=e}}}toLifecycleContext(){return{dependencyOrder:this.dependencyOrder,dependencies:this.dependencies,log:this.log,loadHandlers:()=>this.loadHandlers(),runWorkers:()=>this.runWorkers(),connectRedis:()=>this.redis.connect(),register:()=>this.register(),heartbeat:()=>this.heartbeat(),shutdown:()=>this.shutdown(),connectMongo:()=>this.mongo.connect(),ensureClickhouseTables:()=>this.clickhouse.ensureTables()}}toApiRuntimeContext(){return{withoutExpressFlag:this.withoutExpressFlag,expressOptions:this.expressOptions,api:this.withoutExpressFlag?void 0:this.api,routers:this.routers,fullUrl:this.fullUrl,port:this.port,log:this.log,name:this.name,getStatus:()=>this.status,startedAt:this.startedAt,dependencies:this.dependencies,getRoutes:()=>this.routes,getLoadedEventListeners:()=>this.loadedEventListeners,handlers:this.handlers,getIsWorkerRunning:()=>this.isWorkerRunning,streamKey:this.streamKey,simpleName:this.simpleName,getRedis:()=>this.redis,getAuthResolver:()=>this.getAuthResolver(),internalAddRouter:(e,t,n)=>this.internalAddRouter(e,t,n)}}get streamKey(){return this.simpleName+`:events`}get simpleName(){return this.name.split(`-`)[0]}get fullUrl(){return`${this.url}:${this.port}`}get api(){if(this.withoutExpressFlag)throw Error(`Express server is disabled. Service built without Express.`);return this.dependencies.has(`expressApp`)||this.dependencies.set(`expressApp`,e()),this.dependencies.get(`expressApp`)}get log(){this.dependencies.has(`logger`)||this.dependencies.set(`logger`,C(this.name));let e=this.dependencies.get(`logger`);if(!(e instanceof r))throw Error(`Logger is not of type Logger.`);return e}get routes(){return this.routers.flatMap(e=>e.routes?.map(t=>{let n=e.base===`/`?``:e.base,r=t.fullPath===`/`?``:t.fullPath;return{path:`${n}${r}`==``?`/`:`${n}${r}`,method:t.method,requireAuth:t.requireAuth??!1,meta:t.meta}})||[])}get status(){return this._status}get mongo(){if(!this.dependencies.has(`mongoClient`))throw Error(`MongoDB module has not been added.`);return this.dependencies.get(`mongoClient`)}get db(){if(!this.dependencies.has(`mongoClient`))throw Error(`MongoDB module has not been added.`);return this.mongo.collections}get clickhouse(){if(!this.dependencies.has(`clickhouse`))throw Error(`Clickhouse module has not been added.`);return this.dependencies.get(`clickhouse`)}get redis(){if(!this.dependencies.has(`redis`))throw Error(`Redis module has not been added.`);return this.dependencies.get(`redis`)}async register(){if(!this.dependencies.has(`redis`)){this.log.warn(`Redis module not initialized. Call withRedis() to enable service registration.`);return}let e=`services:registry:${this.name}`,t=JSON.stringify({name:this.name,streamKey:this.streamKey,url:this.fullUrl,dependencies:Object.keys(this.dependencies),routes:this.routes,lastHeartbeat:Date.now()});await this.redis.set(e,t,{EX:30})}async heartbeat(){if(!this.dependencies.has(`redis`)){this.log.warn(`Redis module not initialized. Call withRedis() to enable heartbeat.`);return}setInterval(async()=>{await this.register()},this.HEARTBEAT_INTERVAL)}build(){try{return this._status=`starting`,E(this.toLifecycleContext()),O(this.toApiRuntimeContext()),this._status=`running`,this}catch(e){throw this.log.error(`Failed to build service: ${e}`),e}}loadHandlers(){j(this)}runWorkers({workerCount:e}={workerCount:1}){M(this,{workerCount:e})}withMongo(e){let n=this.toConfigContext();return re(n,e),this.applyConfigContext(n,t.CONFIG_SYNC_KEYS.dependencyGraph),this}withClickhouse(e){let n=this.toConfigContext();return V(n,e),this.applyConfigContext(n,t.CONFIG_SYNC_KEYS.dependencyGraph),this}withPort(e=Number(process.env.PORT)||3100){return this.port=e,this}withUrl(e){return this.url=e,this}withName(e){return this.name=e,this}withRedis(e=`redis://redis:6379`){let n=this.toConfigContext();return H(n,e),this.applyConfigContext(n,t.CONFIG_SYNC_KEYS.dependencyGraph),this}withWorkers(e,n=[]){let r=this.toConfigContext();return U(r,e,n),this.applyConfigContext(r,[...t.CONFIG_SYNC_KEYS.dependencyGraph,...t.CONFIG_SYNC_KEYS.workers]),this}withModules(e){let n=this.toConfigContext();return W(n,e),this.applyConfigContext(n,t.CONFIG_SYNC_KEYS.modules),this}withoutExpress(){let e=this.toConfigContext();return G(e),this.applyConfigContext(e,t.CONFIG_SYNC_KEYS.expressMode),this}withExpressOptions(e){let n=this.toConfigContext();return K(n,e),this.applyConfigContext(n,t.CONFIG_SYNC_KEYS.expressOptions),this}withAuthStrategy(e){let n=this.toConfigContext();return q(n,e),this.applyConfigContext(n,t.CONFIG_SYNC_KEYS.authStrategies),this}withAuthStrategies(e){let n=this.toConfigContext();return J(n,e),this.applyConfigContext(n,t.CONFIG_SYNC_KEYS.authStrategies),this}withAuthResolver(e){let n=this.toConfigContext();return Y(n,e),this.applyConfigContext(n,t.CONFIG_SYNC_KEYS.authResolver),this}withoutDefaultHeaderAuthFallback(){let e=this.toConfigContext();return X(e),this.applyConfigContext(e,t.CONFIG_SYNC_KEYS.authFallback),this}internalAddRouter(e,t,n=[]){return this.routers.push({base:e,router:t,routes:n}),this}addRouter(e,n,r=[]){let i=this.toConfigContext();return ie(i,e,n,r),this.applyConfigContext(i,t.CONFIG_SYNC_KEYS.routers),this}async shutdown(e={}){await ae(this.toShutdownContext(),e)}[Symbol.dispose](){this.shutdown()}*[Symbol.iterator](){for(let e of Object.values(this.dependencies))yield e}};function se(e,t){return{type:e,execute:t}}function ce(e,t,n){let r=e.headers[t.toLowerCase()];if(typeof r!=`string`)return null;let i=r.trim();if(!i)return null;if(!n)return i;let a=`${n} `;return i.startsWith(a)&&i.slice(a.length).trim()||null}function Z(e){return typeof e==`string`||typeof e==`number`||typeof e==`boolean`||Array.isArray(e)&&e.every(e=>typeof e==`string`)}function le(e){if(typeof e.sub!=`string`||e.sub.length===0)return null;let t={userId:e.sub};for(let[n,r]of Object.entries(e))n!==`sub`&&Z(r)&&(t[n]=r);return t}function Q(e){let t={};for(let[n,r]of Object.entries(e))t[n]=r;return typeof e.sub==`string`&&(t.sub=e.sub),t}function $(e,t){return t.every(t=>e[t]!==void 0)}function ue(e){let t=e.requiredClaims??[`sub`],n={issuer:e.issuer,audience:e.audience,algorithms:e.algorithms,clockTolerance:e.clockTolerance};if(e.jwksUri){let r=_(new URL(e.jwksUri));return async e=>{try{let{payload:i}=await v(e,r,n);return $(i,t)?Q(i):null}catch{return null}}}let r=typeof e.secret==`string`?new TextEncoder().encode(e.secret):e.secret;if(!(r instanceof Uint8Array))throw Error(`createJoseJwtVerifier requires either jwksUri or secret.`);return async e=>{try{let{payload:i}=await v(e,r,n);return $(i,t)?Q(i):null}catch{return null}}}function de(e){return e?e.split(`,`).map(e=>e.trim()).filter(Boolean):[]}function fe(e){let t=de(e.subjectaltname),n=t.find(e=>e.startsWith(`URI:`));if(n)return n.slice(4).trim()||null;let r=t.find(e=>e.startsWith(`DNS:`));if(r)return r.slice(4).trim()||null;let i=e.subject?.CN;if(typeof i==`string`&&i.length>0)return i;if(Array.isArray(i)&&i.length>0){let e=i[0];if(typeof e==`string`&&e.length>0)return e}return e.fingerprint256?e.fingerprint256:null}function pe(e){let t=e.headerName??`authorization`,n=e.tokenPrefix??`Bearer`;return{name:e.name??`jwt`,canHandle:e.canHandle,authenticate:async(r,i)=>{let a=ce(r,t,n);if(!a)return null;let o=await e.verifyToken(a,r,i);return o?e.mapPayloadToUser?e.mapPayloadToUser(o,r,i):le(o):null}}}function me(e={}){let t=e.requireAuthorized??!0;return{name:e.name??`mtls`,canHandle:e.canHandle,authenticate:async(n,r)=>{let i=n.socket;if(typeof i.getPeerCertificate!=`function`||t&&i.authorized!==!0)return null;let a=i.getPeerCertificate(!0);if(!a||Object.keys(a).length===0)return null;if(e.mapCertificateToUser)return e.mapCertificateToUser(a,n,r);let o=fe(a);return o?{userId:o,certSubjectCn:a.subject?.CN??``,certIssuerCn:a.issuer?.CN??``,certFingerprint256:a.fingerprint256??``,certSerialNumber:a.serialNumber??``,mtlsAuthorized:i.authorized}:null}}}export{R as CollectionWrapper,oe as Service,b as createAuthResolver,ue as createJoseJwtVerifier,pe as createJwtAuthStrategy,S as createMetaRouter,B as createMongo,me as createMtlsAuthStrategy,L as createTypedClickhouse,y as defaultHeaderAuthResolver,P as defineClickhouseMethod,z as defineMongoCollection,x as getRegisteredRoutes,se as redisStreamHandler};
4
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["createClient"],"sources":["../src/api/express-types.ts","../src/core/logger.ts","../src/core/utils.ts","../src/core/service/lifecycle.ts","../src/core/service/api.ts","../src/core/service/workers.ts","../src/modules/clickhouse/index.ts","../src/modules/mongo/collection.ts","../src/modules/mongo/mongo.ts","../src/core/service/config.ts","../src/core/service/shutdown.ts","../src/core/service.ts","../src/runtime/stream-handler.ts","../src/auth/strategies.ts"],"sourcesContent":["import {\n NextFunction,\n Request,\n RequestHandler,\n Response,\n Router,\n} from \"express\";\nimport z, { ZodSchema, output } from \"zod\";\n\nexport type RouteMeta = {\n description?: string;\n deprecated?: boolean;\n tags?: string[];\n authRequired?: boolean;\n rateLimit?: \"standard\" | \"strict\" | \"none\";\n version?: string;\n [key: string]: any;\n};\n\nexport type AuthenticatedUser = {\n userId: string;\n [key: string]: string | string[] | number | boolean;\n};\n\nexport type AuthResolver = (\n req: Request,\n route: RouteDefinition<any, any, boolean>,\n) => AuthenticatedUser | null | Promise<AuthenticatedUser | null>;\n\nexport type AuthStrategy = {\n name: string;\n canHandle?: (\n req: Request,\n route: RouteDefinition<any, any, boolean>,\n ) => boolean | Promise<boolean>;\n authenticate: AuthResolver;\n};\n\nexport function defaultHeaderAuthResolver(\n req: Request,\n): AuthenticatedUser | null {\n const userIdHeader = req.headers[\"x-user-id\"];\n const userMetaHeader = req.headers[\"x-user-meta\"];\n\n if (typeof userMetaHeader === \"string\") {\n try {\n const parsed = JSON.parse(userMetaHeader) as Partial<AuthenticatedUser>;\n\n if (typeof parsed.userId === \"string\") {\n return parsed as AuthenticatedUser;\n }\n } catch {\n return null;\n }\n }\n\n if (typeof userIdHeader !== \"string\") {\n return null;\n }\n\n return {\n userId: userIdHeader,\n };\n}\n\nexport function createAuthResolver(\n strategies: AuthStrategy[],\n opts: { fallbackResolver?: AuthResolver } = {},\n): AuthResolver {\n return async (req, route) => {\n for (const strategy of strategies) {\n if (strategy.canHandle) {\n const canHandle = await strategy.canHandle(req, route);\n if (!canHandle) {\n continue;\n }\n }\n\n const user = await strategy.authenticate(req, route);\n if (user) {\n return user;\n }\n }\n\n if (opts.fallbackResolver) {\n return opts.fallbackResolver(req, route);\n }\n\n return null;\n };\n}\n\nexport type TypedRequest<\n TBody = unknown,\n TParams = unknown,\n TRequireAuth extends boolean = false,\n> = Request<\n TParams extends ZodSchema ? output<TParams> : Record<string, string>,\n any,\n TBody extends ZodSchema ? output<TBody> : any\n> &\n (TRequireAuth extends true\n ? { user: AuthenticatedUser }\n : { user?: AuthenticatedUser });\n\nexport type TypedRequestHandler<\n TBody = unknown,\n TParams = unknown,\n TRequireAuth extends boolean = false,\n> = (\n req: TypedRequest<TBody, TParams, TRequireAuth>,\n res: Response,\n next: NextFunction,\n) => unknown;\n\n/**\n * Defines a route with enhanced type safety and built-in support for request validation and authentication. This type is used in conjunction with the `addRoute` function to register routes on an Express router, allowing you to specify the HTTP method, path, authentication requirements, and Zod schemas for validating request bodies and URL parameters. The route definition also includes optional metadata that can be used for documentation or analytics purposes.\n */\nexport type RouteDefinition<\n TBody extends ZodSchema | undefined = undefined,\n TParams extends ZodSchema | undefined = undefined,\n TRequireAuth extends boolean = false,\n> = {\n method: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n fullPath: string;\n requireAuth?: TRequireAuth;\n /**\n * Optional Zod schema to validate the request body. Only applicable for non-GET requests. If provided, the middleware will automatically validate the request body against this schema and return a 400 error if validation fails.\n */\n bodyValidator?: TBody;\n /**\n * Optional Zod schema to validate URL parameters (e.g., :id in /users/:id). If provided, the middleware will automatically validate the URL parameters against this schema and return a 400 error if validation fails.\n */\n paramsValidator?: TParams;\n /**\n * Optional metadata for the route, which can be used for documentation, analytics, or other purposes.\n */\n meta?: RouteMeta;\n};\n\nconst registeredRoutes: RouteDefinition<any, any, boolean>[] = [];\n\nexport function getRegisteredRoutes() {\n return registeredRoutes;\n}\n\ntype CreateMetaRouterOptions = {\n authResolver?: AuthResolver;\n onUnauthorized?: (req: Request, res: Response) => void;\n onAuthError?: (err: unknown, req: Request, res: Response) => void;\n};\n\n/**\n * Utility function to create an Express router with enhanced type safety and built-in support for request validation and authentication. This function allows you to define routes with associated Zod schemas for validating request bodies and URL parameters, as well as specifying whether authentication is required for each route. The returned object includes the configured Express router, a list of registered routes for introspection, and a helper function to add new routes with the specified configurations.\n */\nexport function createMetaRouter(options?: CreateMetaRouterOptions): {\n /**\n * The Express router instance that can be used to register routes and middleware. This router is configured to work with the enhanced route definitions provided by the `addRoute` function, which supports request validation and authentication requirements.\n */\n router: Router;\n /**\n * List of registered routes with their configurations, which can be used for introspection, documentation generation, or analytics purposes. Each entry includes the HTTP method, full path, authentication requirement, validation schemas, and any additional metadata provided during route registration.\n */\n routes: RouteDefinition<any, any, boolean>[];\n /**\n * Adds a new route to the router with the specified configuration and request handler.\n *\n * - If `requireAuth` is set to true, the route will automatically include middleware to check for an authenticated user and return a 401 error if the user is not authenticated.\n * - If `bodyValidator` or `paramsValidator` are provided, the route will include middleware to validate the request body and URL parameters against the provided Zod schemas, returning a 400 error if validation fails.\n *\n * The route definition is also stored in an internal list for introspection purposes.\n *\n * @param route The route definition, including method, path, validation schemas, and authentication requirements.\n * @param handler The request handler function for the route.\n * @returns void\n *\n * @example\n * addRoute(\n * {\n * fullPath: \"/me\",\n * method: \"GET\",\n * requireAuth: true,\n * meta: {\n * description: \"Get current authenticated user info\",\n * },\n * },\n * async (req, res) => {\n * try {\n * ...fetch user info from database using req.user.userId...\n * res.status(200).json(...user info...);\n * } catch (err) {\n * console.error(\"Failed to fetch user info:\", err);\n * res\n * .status(500)\n * .json({ isSuccess: false, message: \"Internal server error\" });\n * }\n * },\n * );\n */\n addRoute: <\n TBody extends ZodSchema | undefined = undefined,\n TParams extends ZodSchema | undefined = undefined,\n TRequireAuth extends boolean = false,\n >(\n route: RouteDefinition<TBody, TParams, TRequireAuth>,\n /** Request handler for the route */ handler: TypedRequestHandler<\n TBody,\n TParams,\n TRequireAuth\n >,\n ) => void;\n} {\n const router = Router();\n const routes: RouteDefinition<any, any, boolean>[] = [];\n const authResolver: AuthResolver =\n options?.authResolver ?? ((req) => defaultHeaderAuthResolver(req));\n const onUnauthorized =\n options?.onUnauthorized ??\n ((_: Request, res: Response) => {\n res.status(401).json({\n isSuccess: false,\n message: \"Unauthorized\",\n });\n });\n const onAuthError =\n options?.onAuthError ??\n ((_: unknown, __: Request, res: Response) => {\n res.status(401).json({\n isSuccess: false,\n message: \"Unauthorized\",\n });\n });\n\n /**\n * Adds a new route to the router with the specified configuration and request handler.\n *\n * - If `requireAuth` is set to true, the route will automatically include middleware to check for an authenticated user and return a 401 error if the user is not authenticated.\n * - If `bodyValidator` or `paramsValidator` are provided, the route will include middleware to validate the request body and URL parameters against the provided Zod schemas, returning a 400 error if validation fails.\n *\n * The route definition is also stored in an internal list for introspection purposes.\n * @param route The route definition, including method, path, validation schemas, and authentication requirements.\n * @param handler The request handler function for the route.\n *\n * @example\n * addRoute(\n * {\n * fullPath: \"/me\",\n * method: \"GET\",\n * requireAuth: true,\n * meta: {\n * description: \"Get current authenticated user info\",\n * },\n * },\n * async (req, res) => {\n * try {\n * ...fetch user info from database using req.user.userId...\n * res.status(200).json(...user info...);\n * } catch (err) {\n * console.error(\"Failed to fetch user info:\", err);\n * res\n * .status(500)\n * .json({ isSuccess: false, message: \"Internal server error\" });\n * }\n * },\n * );\n */\n function addRoute<\n TBody extends ZodSchema | undefined = undefined,\n TParams extends ZodSchema | undefined = undefined,\n TRequireAuth extends boolean = false,\n >(\n route: RouteDefinition<TBody, TParams, TRequireAuth>,\n /** Request handler for the route */ handler: TypedRequestHandler<\n TBody,\n TParams,\n TRequireAuth\n >,\n ) {\n const middlewareStack: RequestHandler[] = [];\n const bodyValidator = route.bodyValidator;\n const paramsValidator = route.paramsValidator;\n\n routes.push({\n method: route.method,\n fullPath: route.fullPath,\n requireAuth: route.requireAuth ?? false,\n meta: route.meta,\n bodyValidator,\n paramsValidator,\n });\n if (route.requireAuth) {\n middlewareStack.push(async (req, res, next) => {\n try {\n const user = await authResolver(req, route);\n\n if (!user) {\n onUnauthorized(req, res);\n return;\n }\n\n (req as TypedRequest<TBody, TParams, true>).user = user;\n next();\n } catch (err) {\n onAuthError(err, req, res);\n }\n });\n }\n if (bodyValidator && route.method !== \"GET\") {\n middlewareStack.push((req, res, next) => {\n let value = req.body;\n if (typeof value === \"string\") {\n if (\n bodyValidator instanceof z.ZodObject ||\n bodyValidator instanceof z.ZodArray ||\n bodyValidator.def.type === \"object\" ||\n bodyValidator.def.type === \"array\"\n ) {\n try {\n value = JSON.parse(value);\n } catch (e) {\n return res.status(400).json({\n isSuccess: false,\n message: \"Invalid JSON in request body\",\n error: e instanceof Error ? e.message : e,\n });\n }\n } else if (\n bodyValidator instanceof z.ZodNumber ||\n bodyValidator.def.type === \"number\"\n ) {\n try {\n value = Number(value);\n } catch (e) {\n return res.status(400).json({\n isSuccess: false,\n message: \"Invalid number in request body\",\n error: e instanceof Error ? e.message : e,\n });\n }\n }\n }\n const validationResult = bodyValidator.safeParse(value);\n if (validationResult.success) {\n req.body = validationResult.data;\n next();\n } else {\n res.status(400).json({\n isSuccess: false,\n message: \"Invalid request body\",\n error: validationResult.error,\n });\n }\n });\n }\n if (paramsValidator) {\n middlewareStack.push((req, res, next) => {\n const validationResult = paramsValidator.safeParse(req.params);\n if (validationResult.success) {\n req.params = validationResult.data as Request[\"params\"];\n next();\n } else {\n res.status(400).json({\n isSuccess: false,\n message: \"Invalid URL parameters\",\n error: validationResult.error,\n });\n }\n });\n }\n (router as any)[route.method.toLowerCase()](\n route.fullPath,\n ...middlewareStack,\n handler as RequestHandler,\n );\n }\n\n return {\n router,\n routes,\n addRoute,\n };\n}\n","import winston, { format, transports } from \"winston\";\n\nexport const createLogger = <T = string>(service: T) =>\n winston.createLogger({\n format: format.json({ bigint: true }),\n defaultMeta: { service },\n transports: [\n new transports.Console({\n format: format.combine(\n format.colorize(),\n format.timestamp(),\n format.printf(\n ({ level, message, timestamp }) =>\n `${timestamp} [${service}] ${level}: ${message}`,\n ),\n ),\n }),\n ],\n });\n","import * as crypto from \"crypto\";\n\nexport function generateRandomHexString(length: number): string {\n if (length % 2 !== 0) {\n throw new Error(\"Length must be an even number\");\n }\n\n const numBytes = length / 2;\n const randomBytes = crypto.randomBytes(numBytes);\n return randomBytes.toString(\"hex\");\n}\n\nexport function getUseableDatesFromMs(ms: number) {\n const seconds = Math.floor((ms / 1000) % 60);\n const minutes = Math.floor((ms / (1000 * 60)) % 60);\n const hours = Math.floor((ms / (1000 * 60 * 60)) % 24);\n const days = Math.floor(ms / (1000 * 60 * 60 * 24));\n return { days, hours, minutes, seconds };\n}\n","import { CustomModules } from \"../types\";\n\ntype Log = {\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n};\n\nexport type ServiceLifecycleContext = {\n dependencyOrder: string[];\n dependencies: Map<string, unknown>;\n log: Log;\n loadHandlers: () => void;\n runWorkers: () => void;\n connectRedis: () => Promise<unknown>;\n register: () => Promise<void>;\n heartbeat: () => Promise<void>;\n shutdown: () => Promise<void>;\n connectMongo: () => Promise<unknown>;\n ensureClickhouseTables: () => Promise<void>;\n};\n\nexport function initializeDependencies(ctx: ServiceLifecycleContext) {\n Promise.allSettled(\n ctx.dependencyOrder\n .filter((f) => f !== \"customDependencies\")\n .map((key) => {\n switch (key) {\n case \"redis\": {\n if (ctx.dependencies.has(\"workers\")) {\n ctx.loadHandlers();\n ctx.runWorkers();\n } else {\n ctx.log.warn(\n \"Worker module not initialized. Call withWorkers() to enable worker threads and dynamically load handlers.\",\n );\n }\n\n ctx\n .connectRedis()\n .then(async () => {\n ctx.log.info(\"Redis module loaded\");\n await ctx.register();\n ctx.log.info(\"Service registered in Redis\");\n await ctx.heartbeat();\n ctx.log.info(\"Heartbeat started\");\n\n process.on(\"SIGINT\", () => {\n ctx.log.info(\"SIGINT received. Shutting down gracefully...\");\n void ctx.shutdown();\n });\n })\n .catch((err) => {\n ctx.log.error(`Failed to load Redis module: ${err}`);\n });\n break;\n }\n case \"mongoClient\": {\n ctx.log.info(\"Connecting to MongoDB...\");\n ctx\n .connectMongo()\n .then(() => {\n ctx.log.info(\"Mongo module loaded\");\n })\n .catch((err) => {\n ctx.log.error(`Failed to connect to MongoDB: ${err}`);\n throw err;\n });\n break;\n }\n case \"clickhouse\": {\n ctx.log.info(\"Connecting to ClickHouse...\");\n ctx\n .ensureClickhouseTables()\n .then(() => {\n ctx.log.info(\"ClickHouse module loaded\");\n })\n .catch((err) => {\n ctx.log.error(`Failed to load ClickHouse module: ${err}`);\n });\n break;\n }\n default:\n ctx.log.info(`Unknown module ${key} during initialization.`);\n }\n }),\n ).then((results) => {\n results.forEach((result, index) => {\n const key = ctx.dependencyOrder[index];\n\n if (result.status === \"fulfilled\") {\n ctx.log.info(`Module \"${key}\" initialized successfully.`);\n } else {\n ctx.log.error(`Failed to initialize module \"${key}\": ${result.reason}`);\n }\n });\n\n if (ctx.dependencies.has(\"customDependencies\")) {\n const customModules = ctx.dependencies.get(\n \"customDependencies\",\n ) as CustomModules;\n\n for (const module of customModules) {\n try {\n module.init(ctx.log as any);\n } catch (err) {\n ctx.log.error(\n `Failed to initialize custom module \"${module.name}\": ${err}`,\n );\n }\n }\n }\n });\n}\n","import express, { Express, Router } from \"express\";\nimport cors from \"cors\";\nimport {\n AuthResolver,\n createMetaRouter,\n RouteDefinition,\n} from \"../../api/express-types\";\nimport { ExpressOptions, CustomModules } from \"../types\";\nimport { getUseableDatesFromMs } from \"../utils\";\n\ntype Log = {\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n};\n\ntype RouteRecord = {\n base: string;\n router: Router;\n routes?: RouteDefinition<any, any, boolean>[];\n};\n\ntype ServiceApiBaseContext = {\n log: Log;\n name: string;\n getStatus: () => \"starting\" | \"running\" | \"stopped\" | \"stopping\";\n startedAt: number;\n dependencies: Map<string, unknown>;\n getRoutes: () => {\n path: string;\n method: RouteDefinition<any, any, boolean>[\"method\"];\n requireAuth: boolean;\n meta: RouteDefinition<any, any, boolean>[\"meta\"];\n }[];\n getLoadedEventListeners: () => string[];\n handlers: Record<string, string>;\n getIsWorkerRunning: () => boolean;\n streamKey: string;\n simpleName: string;\n getRedis: () => {\n xRange: (...args: any[]) => Promise<any>;\n xAck: (...args: any[]) => Promise<any>;\n xAdd: (...args: any[]) => Promise<any>;\n };\n getAuthResolver: () => AuthResolver;\n internalAddRouter: (\n base: string,\n router: Router,\n routes?: RouteDefinition<any, any, boolean>[],\n ) => void;\n};\n\nexport type ServiceApiRuntimeContext = ServiceApiBaseContext & {\n withoutExpressFlag: boolean;\n expressOptions: ExpressOptions;\n api: Express;\n routers: RouteRecord[];\n fullUrl: string;\n port: number;\n};\n\nexport function registerDefaultRoutes(ctx: ServiceApiBaseContext) {\n initBaseRoutes(ctx);\n if (ctx.dependencies.has(\"redis\") && ctx.dependencies.has(\"workers\")) {\n ctx.log.info(\n \"Redis and handler modules detected. Registering worker routes.\",\n );\n initWorkerRoutes(ctx);\n }\n\n ctx.log.info(\"Default routes registered.\");\n}\n\nexport function setupExpressApp(ctx: ServiceApiRuntimeContext) {\n if (ctx.withoutExpressFlag) {\n ctx.log.warn(\"Express server is disabled. Service built without Express.\");\n return;\n }\n\n if (ctx.expressOptions.asJson) {\n ctx.log.info(\"Configuring Express to parse JSON bodies\");\n ctx.api.use(express.json());\n }\n\n for (const mw of ctx.expressOptions.customMiddleware || []) {\n ctx.log.info(\"Adding custom middleware to Express\");\n ctx.api.use(mw);\n }\n\n if (\"corsWhitelist\" in ctx.expressOptions) {\n ctx.log.info(\n \"Configuring CORS with whitelist: \" + ctx.expressOptions.corsWhitelist,\n );\n ctx.api.use(\n cors({\n origin: ctx.expressOptions.corsWhitelist,\n credentials: ctx.expressOptions.credentials,\n }),\n );\n } else if (\"corsFn\" in ctx.expressOptions) {\n ctx.log.info(\"Configuring CORS with function\");\n ctx.api.use(\n cors({\n origin: ctx.expressOptions.corsFn,\n credentials: ctx.expressOptions.credentials,\n }),\n );\n }\n\n if (!ctx.expressOptions.omitDefaultRoutes) {\n registerDefaultRoutes(ctx);\n }\n\n ctx.log.info(`Registering ${ctx.routers.length} routers`);\n ctx.routers.forEach(({ base, router }) => {\n ctx.api.use(base, router);\n });\n\n ctx.log.info(\n \"Routers registered:\" +\n ctx.routers\n .map((rt) => {\n const maxMethodLength = rt.routes\n ? Math.max(...rt.routes.map((r) => r.method.length))\n : 0;\n const maxPathLength = rt.routes\n ? Math.max(...rt.routes.map((r) => r.fullPath.length))\n : 0;\n const maxAuthLength = rt.routes\n ? Math.max(...rt.routes.map((r) => (r.requireAuth ? 6 : 8)))\n : 0;\n\n return `\\nRouter ${rt.base}: ${\n rt.routes?.length || 0\n } routes registered.\\n${\n rt.routes\n ?.map(\n (r) =>\n `\\t${(\"[\" + r.method + \"]\").padEnd(maxMethodLength + 2)} ${(r.requireAuth ? \"[Auth]\" : \"[Public]\").padEnd(maxAuthLength)} ${r.fullPath.padEnd(maxPathLength)} | ${r.meta?.description || \"\"}`,\n )\n .join(\"\\n\") || \"\"\n }`;\n })\n .join(\"\\n\"),\n );\n\n ctx.api.listen(ctx.port, () => {\n ctx.log.info(`API listening at ${ctx.fullUrl}`);\n });\n}\n\nfunction initBaseRoutes(ctx: ServiceApiBaseContext) {\n ctx.log.info(\"Registering base routes\");\n const { router, routes, addRoute } = createMetaRouter({\n authResolver: ctx.getAuthResolver(),\n });\n\n addRoute(\n {\n method: \"GET\",\n fullPath: \"/\",\n requireAuth: false,\n meta: {\n description: \"Base route\",\n },\n },\n (_, res) => {\n res.send('OK from \"' + ctx.name + '\" service');\n },\n );\n\n addRoute(\n {\n method: \"GET\",\n fullPath: \"/health\",\n requireAuth: false,\n meta: {\n description:\n \"Health check route that returns service status and uptime\",\n },\n },\n (_, res) => {\n res.status(200).json({\n service: ctx.name,\n status: ctx.getStatus(),\n startedAt: new Date(ctx.startedAt),\n uptime: getUseableDatesFromMs(Math.floor(process.uptime() * 1000)),\n workerRunning: ctx.getIsWorkerRunning(),\n modulesLoaded: Array.from(ctx.dependencies.keys())\n .filter((f) => f !== \"customDependencies\")\n .concat(\n Array.from(\n ctx.dependencies.has(\"customDependencies\")\n ? (\n ctx.dependencies.get(\"customDependencies\") as CustomModules\n ).map((d) => `custom:${d.name}`)\n : [],\n ),\n ),\n routesLoaded: ctx.getRoutes(),\n handlersLoaded: ctx.getLoadedEventListeners(),\n });\n },\n );\n\n ctx.internalAddRouter(\"/\", router, routes);\n}\n\nfunction initWorkerRoutes(ctx: ServiceApiBaseContext): void {\n const { router, routes, addRoute } = createMetaRouter({\n authResolver: ctx.getAuthResolver(),\n });\n\n addRoute(\n {\n method: \"GET\",\n fullPath: \"/\",\n requireAuth: true,\n meta: {\n description: \"Get worker status\",\n },\n },\n (_, res) => {\n res.json({\n status: ctx.getIsWorkerRunning() ? \"Running\" : \"Stopped\",\n handlersLoaded: Object.keys(ctx.handlers),\n });\n },\n );\n\n addRoute(\n {\n method: \"GET\",\n fullPath: \"/:stream\",\n requireAuth: true,\n meta: {\n description: \"Get stream messages\",\n },\n },\n async (_, res) => {\n const redis = ctx.getRedis();\n if (!redis) {\n return res.status(500).json({ error: \"Redis client not connected\" });\n }\n const messages = await redis.xRange(ctx.streamKey, \"-\", \"+\", {\n COUNT: 10,\n });\n res.json({ status: \"Stream\", messages });\n },\n );\n\n addRoute(\n {\n method: \"POST\",\n fullPath: \"/:stream/:id/ack\",\n requireAuth: true,\n meta: {\n description: \"Acknowledge a message in the stream\",\n },\n },\n async (req, res) => {\n const { stream, id } = req.params;\n const redis = ctx.getRedis();\n if (!redis) {\n return res.status(500).json({ error: \"Redis client not connected\" });\n }\n const result = await redis.xAck(\n stream,\n `${ctx.simpleName}:consumer-group`,\n id,\n );\n res.json({ status: \"Message acknowledged\", result });\n },\n );\n\n addRoute(\n {\n method: \"GET\",\n fullPath: \"/:stream/dlq\",\n requireAuth: true,\n meta: {\n description: \"Get DLQ stream messages\",\n },\n },\n async (_, res) => {\n const redis = ctx.getRedis();\n if (!redis) {\n return res.status(500).json({ error: \"Redis client not connected\" });\n }\n const messages = await redis.xRange(ctx.streamKey, \"-\", \"+\", {\n COUNT: 10,\n });\n res.json({ status: \"DLQ Stream\", messages });\n },\n );\n\n addRoute(\n {\n method: \"POST\",\n fullPath: \"/:stream/dlq/:id/retry\",\n requireAuth: true,\n meta: {\n description: \"Retry a message in the DLQ\",\n },\n },\n async (req, res) => {\n const { stream, id } = req.params;\n const redis = ctx.getRedis();\n if (!redis) {\n return res.status(500).json({ error: \"Redis client not connected\" });\n }\n const dlqMessage = await redis.xRange(stream, id, id);\n if (dlqMessage.length === 0 || dlqMessage[0].id !== id) {\n return res.status(404).json({ error: \"Message not found in DLQ\" });\n }\n const result = await redis.xAdd(\n stream,\n `${ctx.simpleName}:consumer-group`,\n dlqMessage[0].message,\n );\n res.json({ status: \"Message retried\", result });\n },\n );\n\n ctx.internalAddRouter(\"/workers\", router, routes);\n}\n","import { existsSync, readdirSync } from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath, pathToFileURL } from \"url\";\nimport { Worker } from \"worker_threads\";\n\ntype Log = {\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n};\n\nexport type ServiceWorkerContext = {\n name: string;\n dependencies: Map<string, unknown>;\n log: Log;\n handlers: Record<string, string>;\n redis: {\n options?: {\n url?: string;\n };\n };\n workerCount: number;\n serviceStreamSubscriptions: string[];\n isWorkerRunning: boolean;\n loadedEventListeners: string[];\n};\n\nexport function loadHandlers(ctx: ServiceWorkerContext) {\n if (!ctx.dependencies.has(\"redis\")) {\n ctx.log.warn(\"Redis module not initialized. Skipping handler loading.\");\n return;\n }\n\n const handlersDir = path.resolve(process.cwd(), \"src/handlers\");\n if (!existsSync(handlersDir)) {\n ctx.log.warn(\"Handlers directory does not exist.\");\n return;\n }\n\n const files = readdirSync(handlersDir).filter(\n (f) => f.endsWith(\".ts\") || f.endsWith(\".js\"),\n );\n\n for (const file of files) {\n const modulePath = path.join(handlersDir, file);\n ctx.handlers[path.basename(file, path.extname(file))] = modulePath;\n }\n\n ctx.log.info(`Loaded ${Object.keys(ctx.handlers).length} handlers:`);\n for (const handlerName of Object.keys(ctx.handlers)) {\n ctx.log.info(`- ${handlerName}`);\n }\n}\n\nexport function runWorkers(\n ctx: ServiceWorkerContext,\n { workerCount }: { workerCount: number } = { workerCount: 1 },\n) {\n if (!ctx.name) {\n ctx.log.error(\"Service name is not set. Cannot start workers.\");\n return;\n }\n\n if (!ctx.dependencies.has(\"redis\")) {\n ctx.log.warn(\"Redis module not initialized. Skipping worker setup.\");\n return;\n }\n\n if (isNaN(workerCount) || workerCount < 1) {\n throw new Error(\"Worker count must be a number greater than 0.\");\n }\n\n const workerEntry = resolveWorkerThreadEntryUrl();\n if (!workerEntry) {\n ctx.log.warn(\"Worker thread entry file not found. Skipping worker setup.\");\n return;\n }\n\n ctx.workerCount = workerCount < 1 ? 1 : workerCount;\n\n for (let i = 0; i < workerCount; i++) {\n const workerThread = new Worker(workerEntry, {\n workerData: {\n name: ctx.name,\n redisUrl: ctx.redis.options?.url,\n handlers: ctx.handlers,\n index: i,\n subscriptions: ctx.serviceStreamSubscriptions ?? [`${ctx.name}:events`],\n },\n });\n\n ctx.isWorkerRunning = true;\n\n workerThread.on(\"error\", (err) => {\n ctx.log.error(`Worker ${i} error: ${err.message}`);\n });\n\n workerThread.on(\"message\", (msg) => {\n switch (msg.level) {\n case \"info\":\n ctx.log.info(msg.message);\n break;\n case \"warn\":\n ctx.log.warn(msg.message);\n break;\n case \"error\":\n ctx.log.error(msg.message);\n break;\n case \"create\":\n ctx.loadedEventListeners = msg.message;\n break;\n default:\n ctx.log.info(msg.message);\n }\n });\n\n workerThread.on(\"exit\", (code) => {\n if (code !== 0) {\n ctx.log.error(`Worker ${i} stopped with exit code ${code}`);\n } else {\n ctx.log.info(`Worker ${i} exited gracefully.`);\n }\n ctx.isWorkerRunning = false;\n });\n\n ctx.log.info(`Worker ${i} started for service ${ctx.name}`);\n }\n}\n\nfunction resolveWorkerThreadEntryUrl(): URL | null {\n const sourceDir = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(sourceDir, \"../../runtime/worker-thread.js\"),\n path.resolve(sourceDir, \"../../runtime/worker-thread.mjs\"),\n path.resolve(process.cwd(), \"dist/runtime/worker-thread.mjs\"),\n ];\n\n const match = candidates.find((candidate) => existsSync(candidate));\n return match ? pathToFileURL(match) : null;\n}\n","import { createClient } from \"@clickhouse/client\";\n\ntype AnyClickhouseMethod = ClickhouseMethodDefinition<any, any>;\n\nexport type ClickhouseMethodMode = \"query\" | \"command\";\n\nexport type ClickhouseMethodDefinition<\n TInput extends Record<string, any> | void = void,\n TResult = any[],\n> = {\n /**\n * SQL statement using named input placeholders like `:userId`.\n */\n sql: string;\n /**\n * Defaults to `query` and returns parsed rows from ClickHouse.\n */\n mode?: ClickhouseMethodMode;\n /**\n * Optional result mapper for shaping the output.\n */\n transform?: (result: unknown, input: TInput) => TResult;\n};\n\nexport type ClickhouseTableMethods = Record<string, AnyClickhouseMethod>;\n\nexport type ClickhouseMethodContext<TSchema extends Record<string, any>> = {\n client: ReturnType<typeof createClient>;\n table: string;\n schema: () => TSchema;\n};\n\nexport interface ClickhouseTable<\n TSchema extends Record<string, any>,\n TMethods extends ClickhouseTableMethods = {},\n> {\n name: string;\n createSQL: string;\n schema: () => TSchema;\n methods?: (ctx: ClickhouseMethodContext<TSchema>) => TMethods;\n}\n\nexport interface ClickhouseConfig<\n TTables extends Record<string, ClickhouseTable<any, ClickhouseTableMethods>>,\n> {\n url: string;\n username?: string;\n password?: string;\n tables: TTables;\n}\n\ntype ResolveClickhouseMethod<TMethod extends AnyClickhouseMethod> =\n TMethod extends ClickhouseMethodDefinition<infer TInput, infer TResult>\n ? TInput extends void\n ? () => Promise<TResult>\n : (input: TInput) => Promise<TResult>\n : never;\n\ntype InferClickhouseMethods<TTable> = TTable extends {\n methods?: (...args: any[]) => infer TMethods;\n}\n ? TMethods extends ClickhouseTableMethods\n ? {\n [K in keyof TMethods]: ResolveClickhouseMethod<TMethods[K]>;\n }\n : {}\n : {};\n\nexport function defineClickhouseMethod<\n TInput extends Record<string, any> | void = void,\n TResult = any[],\n>(\n config: ClickhouseMethodDefinition<TInput, TResult>,\n): ClickhouseMethodDefinition<TInput, TResult> {\n return config;\n}\n\nfunction escapeSqlValue(value: unknown): string {\n if (value === null || value === undefined) {\n return \"NULL\";\n }\n\n if (Array.isArray(value)) {\n return `[${value.map((v) => escapeSqlValue(v)).join(\", \")}]`;\n }\n\n if (value instanceof Date) {\n return `'${value.toISOString().replace(/'/g, \"''\")}'`;\n }\n\n if (typeof value === \"number\" || typeof value === \"bigint\") {\n return `${value}`;\n }\n\n if (typeof value === \"boolean\") {\n return value ? \"1\" : \"0\";\n }\n\n return `'${String(value).replace(/'/g, \"''\")}'`;\n}\n\nfunction bindNamedParams(sql: string, input?: Record<string, unknown>): string {\n const params = input || {};\n return sql.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, key: string) => {\n if (!(key in params)) {\n throw new Error(`Missing ClickHouse method input parameter: ${key}`);\n }\n return escapeSqlValue(params[key]);\n });\n}\n\nexport function createTypedClickhouse<\n TTables extends Record<string, ClickhouseTable<any, ClickhouseTableMethods>>,\n>(config: ClickhouseConfig<TTables>) {\n const client = createClient({\n host: config.url,\n username: config.username,\n password: config.password,\n });\n\n async function ensureTables() {\n for (const { createSQL } of Object.values(config.tables || {})) {\n await client.command({ query: createSQL });\n }\n }\n\n const api = Object.fromEntries(\n Object.entries(config.tables || {}).map(([key, table]) => {\n type Row = ReturnType<typeof table.schema>;\n type Filter = Partial<Row>;\n\n const select = async (where?: Filter): Promise<Row[]> => {\n let query = `SELECT * FROM ${table.name}`;\n if (where && Object.keys(where).length > 0) {\n const conditions = Object.entries(where)\n .map(([k, value]) => {\n if (typeof value === \"string\") return `${k} = '${value}'`;\n return `${k} = ${value}`;\n })\n .join(\" AND \");\n query += ` WHERE ${conditions}`;\n }\n const res = await client.query({\n query,\n });\n const rows = await res.json<{ data: any[] }>();\n return rows.data as Row[];\n };\n\n const insert = async (rows: Row[]) => {\n await client.insert({\n table: table.name,\n values: rows,\n format: \"JSONEachRow\",\n });\n };\n\n const customMethodDefinitions =\n table.methods?.({\n client,\n table: table.name,\n schema: table.schema,\n }) || {};\n\n const customMethods = Object.fromEntries(\n Object.entries(customMethodDefinitions).map(([methodName, def]) => {\n const method = async (input?: Record<string, unknown>) => {\n const query = bindNamedParams(def.sql, input);\n\n if (def.mode === \"command\") {\n const result = await client.command({ query });\n return def.transform\n ? def.transform(result, input as any)\n : (undefined as any);\n }\n\n const result = await client.query({ query });\n const parsed = await result.json<{ data: any[] }>();\n\n return def.transform\n ? def.transform(parsed.data, input as any)\n : (parsed.data as any);\n };\n\n return [methodName, method];\n }),\n );\n\n return [key, { select, insert, ...customMethods }];\n }),\n ) as {\n [K in keyof TTables]: {\n select(\n where?: Partial<ReturnType<TTables[K][\"schema\"]>>,\n ): Promise<ReturnType<TTables[K][\"schema\"]>[]>;\n insert(rows: ReturnType<TTables[K][\"schema\"]>[]): Promise<void>;\n } & InferClickhouseMethods<TTables[K]>;\n };\n\n return { client, ensureTables, ...api };\n}\n","import {\n Collection,\n Document,\n Filter,\n ObjectId,\n OptionalUnlessRequiredId,\n UpdateFilter,\n WithId,\n} from \"mongodb\";\n\nexport type SchemaParser<T extends Document> = {\n parse: (input: unknown) => T;\n shape?: unknown;\n _def?: {\n shape?: unknown | (() => unknown);\n };\n};\n\nexport class CollectionWrapper<T extends Document> {\n constructor(\n private readonly collection: Collection<T>,\n private readonly schema?: SchemaParser<T>,\n private readonly revision?: Collection<T>,\n ) {}\n\n private parseWrite(doc: unknown): T {\n if (!this.schema) {\n return doc as T;\n }\n return this.schema.parse(doc);\n }\n\n private parseRead(doc: WithId<T>): WithId<T> {\n if (!this.schema) {\n return doc;\n }\n\n const parsed = this.schema.parse(doc) as T;\n\n return {\n ...(parsed as Document),\n _id: doc._id,\n } as WithId<T>;\n }\n\n private async trackRevision(doc: T) {\n if (this.revision) {\n const clone = {\n ...doc,\n _id: new ObjectId(),\n } as OptionalUnlessRequiredId<T>;\n await this.revision.insertOne(clone);\n }\n }\n\n /**\n * Get all documents from the collection.\n * @param filter - The filter to find documents to retrieve. If no filter is provided, it will return all documents in the collection.\n * @returns\n */\n async get(filter: Filter<T> = {}): Promise<WithId<T>[]> {\n try {\n if (this.hasProperty(\"deletedAt\")) {\n // If the schema has a `deletedAt` field, filter out soft-deleted documents\n filter = { ...filter, deletedAt: { $exists: false } };\n }\n const result = await this.collection.find(filter).toArray();\n return result.map((doc) => this.parseRead(doc));\n } catch (err) {\n return [];\n }\n }\n\n /**\n * Get a single document from the collection.\n * @param filter - The filter to find the document to retrieve. If no filter is provided, it will return the first document in the collection.\n * @returns\n */\n async getOne(filter: Filter<T> = {}): Promise<WithId<T> | null> {\n try {\n if (this.hasProperty(\"deletedAt\")) {\n // If the schema has a `deletedAt` field, filter out soft-deleted documents\n filter = { ...filter, deletedAt: { $exists: false } };\n }\n const result = await this.collection.findOne(filter);\n return result ? this.parseRead(result) : null;\n } catch (err) {\n return null;\n }\n }\n\n /**\n * Get all revisions for a document.\n * @param filter - The filter to find revisions to retrieve. If no filter is provided, it will return all revisions in the revision collection.\n * @returns An array of revision documents.\n */\n async getRevisions(filter: Filter<T> = {}): Promise<WithId<T>[]> {\n try {\n if (!this.revision) {\n return [];\n }\n const result = await this.revision\n .find(filter, { sort: { revision: 1 } })\n .toArray();\n return result.map((doc) => this.parseRead(doc as WithId<T>));\n } catch (err) {\n return [];\n }\n }\n\n /**\n * Get the most recent revision for a document.\n * @returns Revision document or null if no revisions exist or if revision tracking is not enabled.\n */\n async getMostRecentRevision(): Promise<WithId<T> | null> {\n try {\n if (!this.revision) {\n return null;\n }\n const result = await this.revision\n .find({}, { sort: { revision: -1 } })\n .limit(1)\n .toArray();\n return result.length > 0 ? this.parseRead(result[0] as WithId<T>) : null;\n } catch (err) {\n return null;\n }\n }\n\n /**\n * Insert a document into the collection.\n *\n * If this collection has revision tracking enabled, it will also insert the document into the revision collection.\n *\n * @param doc - The document to insert into the collection.\n */\n async insert(doc: T): Promise<void> {\n try {\n const parsed = this.parseWrite(doc);\n await this.collection.insertOne(parsed as OptionalUnlessRequiredId<T>);\n await this.trackRevision(parsed);\n } catch (err) {\n throw new Error(`Failed to insert document: ${(err as Error).message}`);\n }\n }\n\n /**\n * Update multiple documents by filter with a partial document.\n * @param filter - The filter to find documents to update.\n * @param update - The partial document to update.\n */\n async update(filter: Filter<T>, update: Partial<T>): Promise<void> {\n try {\n const existing = await this.collection.find(filter).toArray();\n if (!existing.length) return;\n\n await this.collection.updateMany(filter, { $set: update });\n\n // record updated versions in revisions\n if (this.revision) {\n const updatedDocs = existing.map((doc) => ({\n ...doc,\n ...update,\n _id: new ObjectId(),\n }));\n await this.revision.insertMany(\n updatedDocs as OptionalUnlessRequiredId<T>[],\n );\n }\n } catch (err) {\n throw new Error(`Failed to update documents: ${(err as Error).message}`);\n }\n }\n\n /**\n * Update a single document by filter with a partial document. This will automatically set the `updatedAt` field to the current date if it exists in the schema, and increment the `revision` field by 1 if it exists in the schema. It will also insert the updated document into the revision collection if revision tracking is enabled.\n * @param filter - The filter to find the document to update.\n * @param update - The partial document to update.\n */\n async updateOne(filter: Filter<T>, update: Partial<T>): Promise<void> {\n try {\n const incomingUpdate = this.hasProperty(\"updatedAt\")\n ? { ...update, updatedAt: new Date() }\n : update;\n const updateDoc = {\n $set: incomingUpdate,\n ...(this.revision ? { $inc: { revision: 1 } } : {}),\n } as UpdateFilter<T>;\n\n await this.collection.updateOne(filter, updateDoc);\n if (this.revision) {\n await this.collection\n .aggregate([\n {\n $match: filter,\n },\n {\n $set: { ...update, _id: new ObjectId() },\n },\n {\n $merge: {\n into: this.revision.collectionName,\n on: \"_id\",\n whenNotMatched: \"insert\",\n },\n },\n ])\n .toArray();\n }\n } catch (err) {\n console.error(\"Failed to update document:\", err);\n throw new Error(`Failed to update document: ${(err as Error).message}`);\n }\n }\n\n /**\n * Soft delete a document by setting the `deletedAt` field to the current date.\n * If the schema has a `deletedBy` field, it is set to the provided actor or defaults to `system`.\n * If the schema does not have a `deletedAt` field, it will perform a hard delete.\n * @param filter - The filter to find documents to delete.\n * @param deletedBy - Optional actor identifier for soft deletes.\n */\n async delete(filter: Filter<T>, deletedBy?: string): Promise<void> {\n try {\n if (this.hasProperty(\"deletedAt\")) {\n const deletedAt = new Date();\n const deletedByValue = deletedBy ?? \"system\";\n const setFields: Record<string, unknown> = {\n deletedAt,\n ...(this.hasProperty(\"deletedBy\")\n ? { deletedBy: deletedByValue }\n : {}),\n };\n const update = {\n $set: setFields,\n } as unknown as UpdateFilter<T>;\n await this.collection.updateMany(filter, update);\n if (this.revision) {\n await this.collection\n .aggregate([\n {\n $match: filter,\n },\n {\n $set: { _id: new ObjectId(), ...setFields },\n },\n {\n $merge: {\n into: this.revision.collectionName,\n on: \"_id\",\n whenNotMatched: \"insert\",\n },\n },\n ])\n .toArray();\n }\n } else {\n await this.collection.deleteMany(filter);\n }\n } catch (err) {\n throw new Error(`Failed to delete documents: ${(err as Error).message}`);\n }\n }\n\n /**\n * Checks if the schema has a given property.\n * @param schema - The Zod schema to check against.\n * @param property - The property to check for in the schema.\n * @returns\n */\n private hasProperty(property: string): boolean {\n if (!this.schema) {\n return false;\n }\n\n const schemaLike = this.schema as SchemaParser<T>;\n const shapeSource =\n schemaLike.shape ??\n (typeof schemaLike._def?.shape === \"function\"\n ? schemaLike._def.shape()\n : schemaLike._def?.shape);\n\n if (\n shapeSource !== null &&\n typeof shapeSource === \"object\" &&\n !Array.isArray(shapeSource)\n ) {\n return property in shapeSource;\n }\n\n return false;\n }\n}\n","import { Collection, Db, Document, MongoClient } from \"mongodb\";\nimport { CollectionWrapper, SchemaParser } from \"./collection\";\n\ntype AnyMongoMethod = (...args: any[]) => any;\n\nexport type MongoCollectionMethods = Record<string, AnyMongoMethod>;\n\nexport type MongoIndexSpec = {\n /**\n * Index key specification. Example: { userId: 1 } for ascending, { userId: -1 } for descending\n */\n key: Record<string, 1 | -1>;\n /**\n * Optional name for the index. If not provided, MongoDB will auto-generate.\n */\n name?: string;\n /**\n * Whether the index should enforce uniqueness.\n */\n unique?: boolean;\n /**\n * Whether the index should be sparse (only index documents with the indexed field).\n */\n sparse?: boolean;\n /**\n * Whether to build the index in the background without blocking other operations.\n */\n background?: boolean;\n /**\n * The time-to-live (TTL) for documents in the collection, in seconds. Only applicable for TTL indexes.\n */\n expireAfterSeconds?: number;\n};\n\nexport type MongoTimeSeriesSpec = {\n /**\n * Name of the date field that MongoDB uses as the event time.\n */\n timeField: string;\n /**\n * Optional metadata field used to group measurements.\n */\n metaField?: string;\n /**\n * Optional bucket granularity hint used by MongoDB.\n */\n granularity?: \"seconds\" | \"minutes\" | \"hours\";\n /**\n * Optional retention in seconds for automatic expiry.\n */\n expireAfterSeconds?: number;\n};\n\nexport type MongoTtlSpec = {\n /**\n * Document field to use for TTL expiration. Must reference a date-like field in MongoDB.\n */\n field: string;\n /**\n * Retention window in seconds.\n */\n expireAfterSeconds: number;\n /**\n * Optional index name for the TTL index.\n */\n name?: string;\n};\n\nexport type MongoCollectionMethodContext<TDocument extends Document> = {\n /**\n * The MongoDB client instance\n */\n client: MongoClient;\n /**\n * The MongoDB database instance\n */\n db: Db;\n /**\n * The MongoDB collection instance\n */\n collection: Collection<TDocument>;\n /**\n * The base collection wrapper instance\n */\n base: CollectionWrapper<TDocument>;\n};\n\nexport type MongoCollectionDefinition<\n TDocument extends Document = Document,\n TMethods extends MongoCollectionMethods = {},\n> = {\n name?: string;\n /**\n * Whether to automatically track revisions of documents in this collection. If enabled, every update will insert a copy of the updated document into a separate revision collection, allowing you to keep a history of changes. The revision collection will have the same name as the main collection with `_revisions` appended to it (e.g. `users` -> `users_revisions`).\n */\n trackRevisions: boolean;\n schema: SchemaParser<TDocument>;\n /**\n * Custom methods to add to the collection wrapper. These methods receive a context object with the MongoDB client, database, collection, and base wrapper instance, allowing you to build complex queries and operations while still benefiting from the schema validation and soft delete functionality provided by the base wrapper.\n * @param ctx\n * @returns\n */\n methods?: (ctx: MongoCollectionMethodContext<TDocument>) => TMethods;\n /**\n * Optional array of index specifications to create on this collection. Indexes are created idempotently on service startup, so duplicate indexes are safely ignored.\n */\n indexes?: MongoIndexSpec[];\n /**\n * Optional MongoDB time-series settings. When provided, the collection is created as a time-series collection if it does not already exist.\n */\n timeSeries?: MongoTimeSeriesSpec;\n /**\n * Optional collection-level TTL configuration. This creates a TTL index without requiring `indexes` to be defined.\n */\n ttl?: MongoTtlSpec;\n};\n\nexport type MongoCollectionsConfig = Record<\n string,\n MongoCollectionDefinition<any, MongoCollectionMethods>\n>;\n\ntype InferDocument<TDefinition> = TDefinition extends {\n schema: SchemaParser<infer TDocument>;\n}\n ? TDocument\n : Document;\n\ntype NormalizeDocument<TDocument> = TDocument extends Document\n ? TDocument\n : Document;\n\ntype InferMethods<TDefinition> = TDefinition extends {\n methods: (...args: any[]) => infer TMethods;\n}\n ? TMethods\n : TDefinition extends {\n methods?: (...args: any[]) => infer TMethods;\n }\n ? TMethods extends undefined\n ? {}\n : TMethods\n : {};\n\nexport type MongoBoundCollection<\n TDocument extends Document,\n TMethods extends MongoCollectionMethods = {},\n> = CollectionWrapper<TDocument> & TMethods;\n\nexport type InferMongoCollections<TCollections extends MongoCollectionsConfig> =\n {\n [K in keyof TCollections]: MongoBoundCollection<\n NormalizeDocument<InferDocument<TCollections[K]>>,\n InferMethods<TCollections[K]>\n >;\n };\n\nexport type MongoDatabase<TCollections extends MongoCollectionsConfig> = {\n client: MongoClient;\n db: Db;\n collections: InferMongoCollections<TCollections>;\n disconnect: () => Promise<void>;\n connect: () => Promise<MongoClient>;\n} & InferMongoCollections<TCollections>;\n\ntype SchemaOutput<TSchema> = TSchema extends {\n parse: (input: unknown) => infer TOutput;\n}\n ? NormalizeDocument<TOutput>\n : Document;\n\n/**\n * Define a MongoDB collection with an associated Zod schema for validation and optional custom methods. The returned collection definition can then be used to create a MongoDatabase instance, which will automatically wrap the collection with the provided schema validation and methods.\n * @param config The configuration object for the MongoDB collection, including the schema and optional custom methods.\n */\nexport function defineMongoCollection<\n TSchema extends SchemaParser<any>,\n>(config: {\n /**\n * Optional name for the collection. If not provided, the key used in the collections config will be used as the collection name. This allows you to have more control over the actual collection names in the database, while still using descriptive keys in your code.\n */\n name?: string;\n /**\n * Zod schema for validating documents in the collection. The schema will be used to parse and validate documents returned from the database, ensuring that they conform to the expected structure and types. If a document does not match the schema, an error will be thrown.\n */\n schema: TSchema;\n methods?: undefined;\n /**\n * Optional array of index specifications to create on this collection.\n */\n indexes?: MongoIndexSpec[];\n /**\n * Optional MongoDB time-series settings.\n */\n timeSeries?: MongoTimeSeriesSpec;\n /**\n * Optional collection-level TTL configuration.\n */\n ttl?: MongoTtlSpec;\n}): MongoCollectionDefinition<SchemaOutput<TSchema>, {}>;\n\n/**\n * Define a MongoDB collection with an associated Zod schema for validation and optional custom methods. The returned collection definition can then be used to create a MongoDatabase instance, which will automatically wrap the collection with the provided schema validation and methods.\n * @param config The configuration object for the MongoDB collection, including the schema and optional custom methods.\n */\nexport function defineMongoCollection<\n TSchema extends SchemaParser<any>,\n TMethods extends MongoCollectionMethods,\n>(config: {\n /**\n * Optional name for the collection. If not provided, the key used in the collections config will be used as the collection name. This allows you to have more control over the actual collection names in the database, while still using descriptive keys in your code.\n */\n name?: string;\n /**\n * Zod schema for validating documents in the collection. The schema will be used to parse and validate documents returned from the database, ensuring that they conform to the expected structure and types. If a document does not match the schema, an error will be thrown.\n */\n schema: TSchema;\n /**\n * Optional custom methods for the collection. These methods will be added to the collection instance and can be used to implement custom queries or operations.\n * @param ctx The context object containing the MongoDB client, database, collection, and base collection wrapper.\n * @returns An object containing the custom methods to be added to the collection instance.\n */\n methods: (\n ctx: MongoCollectionMethodContext<SchemaOutput<TSchema>>,\n ) => TMethods;\n /**\n * Optional array of index specifications to create on this collection.\n */\n indexes?: MongoIndexSpec[];\n /**\n * Optional MongoDB time-series settings.\n */\n timeSeries?: MongoTimeSeriesSpec;\n /**\n * Optional collection-level TTL configuration.\n */\n ttl?: MongoTtlSpec;\n}): MongoCollectionDefinition<SchemaOutput<TSchema>, TMethods>;\n\n/**\n * Define a MongoDB collection with an associated Zod schema for validation and optional custom methods. The returned collection definition can then be used to create a MongoDatabase instance, which will automatically wrap the collection with the provided schema validation and methods.\n * @param config The configuration object for the MongoDB collection, including the schema and optional custom methods.\n */\nexport function defineMongoCollection<\n TSchema extends SchemaParser<any>,\n TMethods extends MongoCollectionMethods = {},\n>(config: {\n /**\n * Optional name for the collection. If not provided, the key used in the collections config will be used as the collection name. This allows you to have more control over the actual collection names in the database, while still using descriptive keys in your code.\n */\n name?: string;\n /**\n * Zod schema for validating documents in the collection. The schema will be used to parse and validate documents returned from the database, ensuring that they conform to the expected structure and types. If a document does not match the schema, an error will be thrown.\n */\n schema: TSchema;\n /**\n * Optional custom methods for the collection. These methods will be added to the collection instance and can be used to implement custom queries or operations.\n * @param ctx The context object containing the MongoDB client, database, collection, and base collection wrapper.\n * @returns An object containing the custom methods to be added to the collection instance.\n */\n methods?: (\n ctx: MongoCollectionMethodContext<SchemaOutput<TSchema>>,\n ) => TMethods;\n /**\n * Optional array of index specifications to create on this collection.\n */\n indexes?: MongoIndexSpec[];\n /**\n * Optional MongoDB time-series settings.\n */\n timeSeries?: MongoTimeSeriesSpec;\n /**\n * Optional collection-level TTL configuration.\n */\n ttl?: MongoTtlSpec;\n}): MongoCollectionDefinition<SchemaOutput<TSchema>, TMethods> {\n return {\n name: config.name,\n schema: config.schema,\n /**\n * Optional custom methods for the collection. These methods will be added to the collection instance and can be used to implement custom queries or operations.\n * @param ctx The context object containing the MongoDB client, database, collection, and base collection wrapper.\n * @returns An object containing the custom methods to be added to the collection instance.\n */\n methods: config.methods,\n indexes: config.indexes,\n timeSeries: config.timeSeries,\n ttl: config.ttl,\n /**\n * Whether to automatically track revisions of documents in this collection. If enabled, every update will insert a copy of the updated document into a separate revision collection, allowing you to keep a history of changes. The revision collection will have the same name as the main collection with `_revisions` appended to it (e.g. `users` -> `users_revisions`).\n */\n trackRevisions: false,\n } as MongoCollectionDefinition<SchemaOutput<TSchema>, TMethods>;\n}\n\nexport function createMongo<TCollections extends MongoCollectionsConfig>(\n dbName: string,\n config: TCollections,\n options: { uri?: string } = {},\n): MongoDatabase<TCollections> {\n const client = new MongoClient(\n options.uri || process.env.MONGO_URI || \"mongodb://mongo:27017\",\n );\n const db = client.db(dbName);\n\n const wrapped = {} as InferMongoCollections<TCollections>;\n const setupFns: Array<() => Promise<void>> = [];\n\n for (const key in config) {\n const def = config[key];\n const collectionName = def.name || key;\n const collection = db.collection(collectionName);\n const revisionCollection = def.trackRevisions\n ? db.collection(`${collectionName}_revisions`)\n : undefined;\n const base = new CollectionWrapper(\n collection as Collection<any>,\n def.schema,\n revisionCollection,\n );\n const methods =\n def.methods?.({\n client,\n db,\n collection: collection as Collection<any>,\n base,\n }) || {};\n\n setupFns.push(async () => {\n if (def.timeSeries) {\n const existingCollection = await db\n .listCollections({ name: collectionName }, { nameOnly: false })\n .next();\n\n if (!existingCollection) {\n await db.createCollection(collectionName, {\n timeseries: {\n timeField: def.timeSeries.timeField,\n ...(def.timeSeries.metaField\n ? { metaField: def.timeSeries.metaField }\n : {}),\n ...(def.timeSeries.granularity\n ? { granularity: def.timeSeries.granularity }\n : {}),\n },\n ...(typeof def.timeSeries.expireAfterSeconds === \"number\"\n ? { expireAfterSeconds: def.timeSeries.expireAfterSeconds }\n : {}),\n });\n } else {\n const hasTimeSeriesOptions =\n existingCollection.type === \"timeseries\" ||\n Boolean((existingCollection.options as any)?.timeseries);\n\n if (!hasTimeSeriesOptions) {\n throw new Error(\n `Collection ${collectionName} already exists and is not a time-series collection. Migration is required before enabling timeSeries.`,\n );\n }\n\n const existingTimeSeries = (existingCollection.options as any)\n ?.timeseries as\n | {\n timeField?: string;\n metaField?: string;\n }\n | undefined;\n\n if (\n existingTimeSeries?.timeField &&\n existingTimeSeries.timeField !== def.timeSeries.timeField\n ) {\n throw new Error(\n `Collection ${collectionName} has timeField '${existingTimeSeries.timeField}' but config expects '${def.timeSeries.timeField}'.`,\n );\n }\n\n if (\n def.timeSeries.metaField &&\n existingTimeSeries?.metaField &&\n existingTimeSeries.metaField !== def.timeSeries.metaField\n ) {\n throw new Error(\n `Collection ${collectionName} has metaField '${existingTimeSeries.metaField}' but config expects '${def.timeSeries.metaField}'.`,\n );\n }\n }\n }\n\n if (def.indexes && def.indexes.length > 0) {\n for (const indexSpec of def.indexes) {\n const { key, ...indexOptions } = indexSpec;\n await collection.createIndex(key, indexOptions).catch((err) => {\n console.error(\n `Failed to create index on ${collectionName}: ${err.message}`,\n );\n });\n }\n }\n\n if (def.ttl) {\n if (!def.ttl.field || def.ttl.field.trim().length === 0) {\n throw new Error(\n `Collection ${collectionName} has invalid ttl.field configuration.`,\n );\n }\n\n await collection\n .createIndex(\n { [def.ttl.field]: 1 },\n {\n expireAfterSeconds: def.ttl.expireAfterSeconds,\n ...(def.ttl.name ? { name: def.ttl.name } : {}),\n },\n )\n .catch((err) => {\n console.error(\n `Failed to create TTL index on ${collectionName}: ${err.message}`,\n );\n });\n }\n });\n\n wrapped[key] = Object.assign(\n base,\n methods,\n ) as InferMongoCollections<TCollections>[typeof key];\n }\n\n let setupPromise: Promise<void> | null = null;\n\n const ensureSetup = async () => {\n if (!setupPromise) {\n setupPromise = (async () => {\n for (const setup of setupFns) {\n await setup();\n }\n })();\n }\n return setupPromise;\n };\n\n return {\n client,\n db,\n collections: wrapped,\n ...wrapped,\n disconnect: async () => client.close(),\n connect: async () => {\n await client.connect();\n await ensureSetup();\n return client;\n },\n } as MongoDatabase<TCollections>;\n}\n","import { Router } from \"express\";\nimport { createClient } from \"redis\";\nimport {\n AuthResolver,\n AuthStrategy,\n RouteDefinition,\n} from \"../../api/express-types\";\nimport {\n ClickhouseConfig,\n createTypedClickhouse,\n} from \"../../modules/clickhouse\";\nimport { createMongo, MongoCollectionsConfig } from \"../../modules/mongo/mongo\";\nimport { CustomModules, ExpressOptions } from \"../types\";\n\ntype Log = {\n warn: (message: string) => void;\n};\n\nexport type ServiceConfigContext = {\n dependencies: Map<string, unknown>;\n dependencyOrder: string[];\n log: Log;\n withoutExpressFlag: boolean;\n expressOptions: ExpressOptions;\n authStrategies: AuthStrategy[];\n authResolverOverride?: AuthResolver;\n useDefaultHeaderAuthFallback: boolean;\n serviceStreamSubscriptions: string[];\n workerCount: number;\n routers: {\n base: string;\n router: Router;\n routes?: RouteDefinition<any, any, boolean>[];\n }[];\n};\n\nexport function addMongoDependency<\n TNextCollections extends MongoCollectionsConfig,\n>(\n ctx: ServiceConfigContext,\n config: {\n dbName: string;\n uri?: string;\n collections: TNextCollections;\n },\n) {\n if (!ctx.dependencies.has(\"mongoClient\")) {\n const mongo = createMongo(config.dbName, config.collections, {\n uri: config.uri,\n });\n ctx.dependencies.set(\"mongoClient\", mongo);\n ctx.dependencyOrder.push(\"mongoClient\");\n }\n}\n\nexport function addClickhouseDependency<TTables extends Record<string, any>>(\n ctx: ServiceConfigContext,\n config: ClickhouseConfig<TTables>,\n) {\n if (!ctx.dependencies.has(\"clickhouse\")) {\n ctx.dependencies.set(\"clickhouse\", createTypedClickhouse(config));\n ctx.dependencyOrder.push(\"clickhouse\");\n }\n}\n\nexport function addRedisDependency(\n ctx: ServiceConfigContext,\n url: string = \"redis://redis:6379\",\n) {\n if (!ctx.dependencies.has(\"redis\")) {\n ctx.dependencies.set(\"redis\", createClient({ url }));\n ctx.dependencyOrder.push(\"redis\");\n }\n}\n\nexport function addWorkerDependency(\n ctx: ServiceConfigContext,\n workerCount: number,\n serviceStreams: string[] = [],\n) {\n if (!ctx.dependencies.has(\"redis\")) {\n ctx.log.warn(\n \"Redis module not initialized. Call withRedis() before setting worker subscriptions.\",\n );\n return;\n }\n\n if (!ctx.dependencies.has(\"workers\") && serviceStreams.length > 0) {\n ctx.dependencies.set(\"workers\", true);\n ctx.serviceStreamSubscriptions = serviceStreams;\n ctx.workerCount = workerCount < 1 ? 1 : workerCount;\n ctx.dependencyOrder.push(\"workers\");\n }\n}\n\nexport function addCustomModules(\n ctx: ServiceConfigContext,\n modules: CustomModules,\n) {\n if (!ctx.dependencies.has(\"customDependencies\")) {\n ctx.dependencies.set(\n \"customDependencies\",\n modules.map((dep) => ({\n name: dep.name,\n init: dep.init,\n shutdown: dep.shutdown,\n })),\n );\n ctx.dependencyOrder.push(\"customDependencies\");\n }\n}\n\nexport function disableExpress(ctx: ServiceConfigContext) {\n ctx.withoutExpressFlag = true;\n}\n\nexport function setExpressOptions(\n ctx: ServiceConfigContext,\n options: Partial<ExpressOptions>,\n) {\n if (ctx.withoutExpressFlag) {\n ctx.log.warn(\n \"Express server is disabled. Express options will not be applied.\",\n );\n return;\n }\n ctx.expressOptions = { ...ctx.expressOptions, ...options };\n}\n\nexport function addAuthStrategy(\n ctx: ServiceConfigContext,\n strategy: AuthStrategy,\n) {\n if (ctx.withoutExpressFlag) {\n ctx.log.warn(\n \"Express server is disabled. Auth strategies will not be applied.\",\n );\n return;\n }\n ctx.authStrategies.push(strategy);\n}\n\nexport function addAuthStrategies(\n ctx: ServiceConfigContext,\n strategies: AuthStrategy[],\n) {\n if (ctx.withoutExpressFlag) {\n ctx.log.warn(\n \"Express server is disabled. Auth strategies will not be applied.\",\n );\n return;\n }\n ctx.authStrategies.push(...strategies);\n}\n\nexport function setAuthResolver(\n ctx: ServiceConfigContext,\n resolver: AuthResolver,\n) {\n if (ctx.withoutExpressFlag) {\n ctx.log.warn(\n \"Express server is disabled. Auth resolver will not be applied.\",\n );\n return;\n }\n ctx.authResolverOverride = resolver;\n}\n\nexport function disableDefaultHeaderAuthFallback(ctx: ServiceConfigContext) {\n ctx.useDefaultHeaderAuthFallback = false;\n}\n\nexport function addRouterRegistration(\n ctx: ServiceConfigContext,\n base: string,\n router: Router,\n routes: RouteDefinition<any, any, boolean>[] = [],\n) {\n if (ctx.withoutExpressFlag) {\n ctx.log.warn(\"Express server is disabled. Routers will not be registered.\");\n return;\n }\n if (base === \"/\") {\n ctx.log.warn(\"Base path '/' is not allowed. Skipping.\");\n return;\n }\n ctx.routers.push({ base, router, routes });\n}\n","import { CustomModules } from \"../types\";\n\ntype Log = {\n info: (message: string) => void;\n error: (message: string) => void;\n};\n\nexport type ServiceShutdownContext = {\n dependencies: Map<string, unknown>;\n quitRedis: () => Promise<unknown>;\n disconnectMongo: () => Promise<unknown>;\n closeClickhouse: () => Promise<unknown>;\n log: Log;\n name: string;\n setStatus: (status: \"stopping\" | \"stopped\") => void;\n};\n\nexport async function shutdownService(\n ctx: ServiceShutdownContext,\n opts: {\n beforeShutdown?: () => Promise<void>;\n afterShutdown?: () => Promise<void>;\n } = {},\n) {\n await opts.beforeShutdown?.();\n ctx.setStatus(\"stopping\");\n\n if (ctx.dependencies.has(\"redis\")) {\n await ctx.quitRedis();\n }\n if (ctx.dependencies.has(\"mongoClient\")) {\n await ctx.disconnectMongo();\n }\n if (ctx.dependencies.has(\"clickhouse\")) {\n await ctx.closeClickhouse();\n }\n if (ctx.dependencies.has(\"customDependencies\")) {\n for (const dep of ctx.dependencies.get(\n \"customDependencies\",\n ) as CustomModules) {\n if (dep.shutdown) {\n try {\n const result = await dep.shutdown(ctx.log as any);\n ctx.log.info(\n `Custom dependency \"${dep.name}\" shutdown with result: ${result}`,\n );\n } catch (err) {\n ctx.log.error(\n `Failed to shutdown custom dependency \"${dep.name}\": ${err}`,\n );\n }\n }\n }\n }\n\n ctx.log.info(`Service ${ctx.name} shutting down.`);\n ctx.setStatus(\"stopped\");\n await opts.afterShutdown?.();\n process.exit(0);\n}\n","import express, { Express, Router } from \"express\";\nimport { createClient, RedisClientType } from \"redis\";\nimport { Logger } from \"winston\";\nimport {\n AuthResolver,\n AuthStrategy,\n createAuthResolver,\n defaultHeaderAuthResolver,\n RouteDefinition,\n} from \"../api/express-types\";\nimport { ClickhouseConfig, createTypedClickhouse } from \"../modules/clickhouse\";\nimport {\n createMongo,\n InferMongoCollections,\n MongoCollectionsConfig,\n MongoDatabase,\n} from \"../modules/mongo/mongo\";\nimport { createLogger } from \"./logger\";\nimport { generateRandomHexString } from \"./utils\";\nimport { CustomModules, ExpressOptions } from \"./types\";\nimport {\n initializeDependencies,\n ServiceLifecycleContext,\n} from \"./service/lifecycle\";\nimport { setupExpressApp, ServiceApiRuntimeContext } from \"./service/api\";\nimport {\n loadHandlers as loadWorkerHandlers,\n runWorkers as runWorkerRuntime,\n} from \"./service/workers\";\nimport {\n addAuthStrategies,\n addAuthStrategy,\n addClickhouseDependency,\n addCustomModules,\n addMongoDependency,\n addRedisDependency,\n addRouterRegistration,\n addWorkerDependency,\n disableDefaultHeaderAuthFallback,\n disableExpress,\n setAuthResolver,\n setExpressOptions,\n ServiceConfigContext,\n} from \"./service/config\";\nimport { shutdownService, ServiceShutdownContext } from \"./service/shutdown\";\n\nexport class Service<TCollections extends MongoCollectionsConfig = {}> {\n private static readonly CONFIG_SYNC_KEYS = {\n dependencyGraph: [\"dependencies\", \"dependencyOrder\"] as const,\n workers: [\"serviceStreamSubscriptions\", \"workerCount\"] as const,\n modules: [\"dependencies\", \"dependencyOrder\"] as const,\n expressMode: [\"withoutExpressFlag\"] as const,\n expressOptions: [\"expressOptions\"] as const,\n authStrategies: [\"authStrategies\"] as const,\n authResolver: [\"authResolverOverride\"] as const,\n authFallback: [\"useDefaultHeaderAuthFallback\"] as const,\n routers: [\"routers\"] as const,\n };\n\n private static readonly CONFIG_SYNC_UNIQUE_KEYS = [\n \"dependencies\",\n \"dependencyOrder\",\n \"serviceStreamSubscriptions\",\n \"workerCount\",\n \"withoutExpressFlag\",\n \"expressOptions\",\n \"authStrategies\",\n \"authResolverOverride\",\n \"useDefaultHeaderAuthFallback\",\n \"routers\",\n ] as const;\n\n private static readonly CONFIG_SYNC_ALL_KEYS =\n Service.CONFIG_SYNC_UNIQUE_KEYS;\n\n private static readonly CONFIG_SYNC_KEY_SET = new Set<string>(\n Service.CONFIG_SYNC_UNIQUE_KEYS,\n );\n\n private static isConfigSyncKey(\n key: string,\n ): key is (typeof Service.CONFIG_SYNC_UNIQUE_KEYS)[number] {\n return Service.CONFIG_SYNC_KEY_SET.has(key);\n }\n\n private name: string =\n process.env.SERVICE_NAME ?? generateRandomHexString(12);\n private url: string = process.env.SERVICE_URL ?? \"http://localhost\";\n private port: number = process.env.SERVICE_PORT\n ? Number(process.env.SERVICE_PORT)\n : 3100;\n private workerCount: number = 1;\n private handlers: Record<string, string> = {};\n private loadedEventListeners: string[] = [];\n private routers: {\n base: string;\n router: Router;\n routes?: RouteDefinition<any, any, boolean>[];\n }[] = [];\n private startedAt: number = Date.now();\n private dependencies: Map<string, unknown> = new Map<string, unknown>();\n private dependencyOrder: string[] = [];\n private isWorkerRunning: boolean = false;\n private serviceStreamSubscriptions: string[] = [];\n private _status: \"starting\" | \"running\" | \"stopped\" | \"stopping\" = \"starting\";\n private readonly HEARTBEAT_INTERVAL = 1000 * 5;\n private authStrategies: AuthStrategy[] = [];\n private authResolverOverride?: AuthResolver;\n private useDefaultHeaderAuthFallback = true;\n private expressOptions: ExpressOptions = {\n corsWhitelist: [\"*\"],\n asJson: true,\n customMiddleware: [],\n };\n private withoutExpressFlag: boolean = false;\n\n private getAuthResolver(): AuthResolver {\n if (this.authResolverOverride) {\n return this.authResolverOverride;\n }\n\n return createAuthResolver(this.authStrategies, {\n fallbackResolver: this.useDefaultHeaderAuthFallback\n ? (req) => defaultHeaderAuthResolver(req)\n : undefined,\n });\n }\n\n private toConfigContext(): ServiceConfigContext {\n return {\n dependencies: this.dependencies,\n dependencyOrder: this.dependencyOrder,\n log: this.log,\n withoutExpressFlag: this.withoutExpressFlag,\n expressOptions: this.expressOptions,\n authStrategies: this.authStrategies,\n authResolverOverride: this.authResolverOverride,\n useDefaultHeaderAuthFallback: this.useDefaultHeaderAuthFallback,\n serviceStreamSubscriptions: this.serviceStreamSubscriptions,\n workerCount: this.workerCount,\n routers: this.routers,\n };\n }\n\n private applyConfigContext(\n ctx: ServiceConfigContext,\n keys: readonly (keyof ServiceConfigContext)[] = Service.CONFIG_SYNC_ALL_KEYS,\n ) {\n for (const key of keys) {\n if (!Service.isConfigSyncKey(key)) {\n continue;\n }\n switch (key) {\n case \"dependencies\":\n this.dependencies = ctx.dependencies;\n break;\n case \"dependencyOrder\":\n this.dependencyOrder = ctx.dependencyOrder;\n break;\n case \"serviceStreamSubscriptions\":\n this.serviceStreamSubscriptions = ctx.serviceStreamSubscriptions;\n break;\n case \"workerCount\":\n this.workerCount = ctx.workerCount;\n break;\n case \"withoutExpressFlag\":\n this.withoutExpressFlag = ctx.withoutExpressFlag;\n break;\n case \"expressOptions\":\n this.expressOptions = ctx.expressOptions;\n break;\n case \"authStrategies\":\n this.authStrategies = ctx.authStrategies;\n break;\n case \"authResolverOverride\":\n this.authResolverOverride = ctx.authResolverOverride;\n break;\n case \"useDefaultHeaderAuthFallback\":\n this.useDefaultHeaderAuthFallback = ctx.useDefaultHeaderAuthFallback;\n break;\n case \"routers\":\n this.routers = ctx.routers;\n break;\n }\n }\n }\n\n private toShutdownContext(): ServiceShutdownContext {\n return {\n dependencies: this.dependencies,\n quitRedis: () => this.redis.quit(),\n disconnectMongo: () => this.mongo.disconnect(),\n closeClickhouse: () => this.clickhouse.client.close(),\n log: this.log,\n name: this.name,\n setStatus: (status) => {\n this._status = status;\n },\n };\n }\n\n private toLifecycleContext(): ServiceLifecycleContext {\n return {\n dependencyOrder: this.dependencyOrder,\n dependencies: this.dependencies,\n log: this.log,\n loadHandlers: () => this.loadHandlers(),\n runWorkers: () => this.runWorkers(),\n connectRedis: () => this.redis.connect(),\n register: () => this.register(),\n heartbeat: () => this.heartbeat(),\n shutdown: () => this.shutdown(),\n connectMongo: () => this.mongo.connect(),\n ensureClickhouseTables: () => this.clickhouse.ensureTables(),\n };\n }\n\n private toApiRuntimeContext(): ServiceApiRuntimeContext {\n return {\n withoutExpressFlag: this.withoutExpressFlag,\n expressOptions: this.expressOptions,\n api: this.withoutExpressFlag\n ? (undefined as unknown as Express)\n : this.api,\n routers: this.routers,\n fullUrl: this.fullUrl,\n port: this.port,\n log: this.log,\n name: this.name,\n getStatus: () => this.status,\n startedAt: this.startedAt,\n dependencies: this.dependencies,\n getRoutes: () => this.routes,\n getLoadedEventListeners: () => this.loadedEventListeners,\n handlers: this.handlers,\n getIsWorkerRunning: () => this.isWorkerRunning,\n streamKey: this.streamKey,\n simpleName: this.simpleName,\n getRedis: () => this.redis,\n getAuthResolver: () => this.getAuthResolver(),\n internalAddRouter: (base, router, routes) =>\n this.internalAddRouter(base, router, routes),\n };\n }\n\n get streamKey() {\n return this.simpleName + \":events\";\n }\n\n get simpleName() {\n return this.name.split(\"-\")[0];\n }\n\n get fullUrl() {\n return `${this.url}:${this.port}`;\n }\n\n get api(): Express {\n if (this.withoutExpressFlag) {\n throw new Error(\n \"Express server is disabled. Service built without Express.\",\n );\n }\n if (!this.dependencies.has(\"expressApp\")) {\n this.dependencies.set(\"expressApp\", express());\n }\n return this.dependencies.get(\"expressApp\") as Express;\n }\n\n get log(): Logger {\n if (!this.dependencies.has(\"logger\")) {\n this.dependencies.set(\"logger\", createLogger(this.name));\n }\n const logger = this.dependencies.get(\"logger\");\n if (!(logger instanceof Logger)) {\n throw new Error(\"Logger is not of type Logger.\");\n }\n return logger;\n }\n\n get routes() {\n return this.routers.flatMap(\n (r) =>\n r.routes?.map((route) => {\n const base = r.base === \"/\" ? \"\" : r.base;\n const fullPath = route.fullPath === \"/\" ? \"\" : route.fullPath;\n const combined =\n `${base}${fullPath}` === \"\" ? \"/\" : `${base}${fullPath}`;\n\n return {\n path: combined,\n method: route.method,\n requireAuth: route.requireAuth ?? false,\n meta: route.meta,\n };\n }) || [],\n );\n }\n\n get status() {\n return this._status;\n }\n\n /**\n * MongoDB client instance.\n *\n * @requires MongoDB module to be added via withMongo()\n */\n get mongo() {\n if (!this.dependencies.has(\"mongoClient\")) {\n throw new Error(\"MongoDB module has not been added.\");\n }\n return this.dependencies.get(\"mongoClient\") as MongoDatabase<TCollections>;\n }\n\n /**\n * Typed MongoDB collection wrappers registered through `withMongo()`.\n *\n * @requires MongoDB module to be added via withMongo()\n */\n get db(): InferMongoCollections<TCollections> {\n if (!this.dependencies.has(\"mongoClient\")) {\n throw new Error(\"MongoDB module has not been added.\");\n }\n return this.mongo.collections;\n }\n\n /**\n * Clickhouse client instance. This will throw an error if Clickhouse module has not been added.\n *\n * @requires ClickhouseModule to be added via withClickhouse()\n * @beta\n */\n get clickhouse() {\n if (!this.dependencies.has(\"clickhouse\")) {\n throw new Error(\"Clickhouse module has not been added.\");\n }\n return this.dependencies.get(\"clickhouse\") as ReturnType<\n typeof createTypedClickhouse\n >;\n }\n\n /**\n * Redis client instance. This will throw an error if Redis module has not been added.\n *\n * @requires RedisModule to be added via withRedis()\n */\n get redis(): RedisClientType {\n if (!this.dependencies.has(\"redis\")) {\n throw new Error(\"Redis module has not been added.\");\n }\n return this.dependencies.get(\"redis\") as RedisClientType;\n }\n\n /**\n * Registers the service in Redis for service discovery.\n *\n * @requires RedisModule to be added via withRedis()\n */\n async register() {\n if (!this.dependencies.has(\"redis\")) {\n this.log.warn(\n \"Redis module not initialized. Call withRedis() to enable service registration.\",\n );\n return;\n }\n const key = `services:registry:${this.name}`;\n const value = JSON.stringify({\n name: this.name,\n streamKey: this.streamKey,\n url: this.fullUrl,\n dependencies: Object.keys(this.dependencies),\n routes: this.routes,\n lastHeartbeat: Date.now(),\n });\n\n await this.redis.set(key, value, {\n EX: 30, // TTL of 30 seconds\n });\n }\n\n /**\n * Periodically update the service registration in Redis.\n *\n * @requires RedisModule to be added via withRedis()\n */\n async heartbeat() {\n if (!this.dependencies.has(\"redis\")) {\n this.log.warn(\n \"Redis module not initialized. Call withRedis() to enable heartbeat.\",\n );\n return;\n }\n setInterval(async () => {\n await this.register();\n }, this.HEARTBEAT_INTERVAL);\n }\n\n /**\n * Builds and starts the service by initializing dependencies, setting up routes, and starting the API server.\n *\n * The build process includes:\n * 1. Initializing dependencies in the order they were added (MongoDB, Redis, Clickhouse). This includes the redis workers module if withWorkers() was called.\n * 2. Initializing any custom modules provided through withModules().\n * 3. Setting up Express middleware and CORS based on provided options.\n * 4. Registering default routes like /health and /workers.\n * 5. Registering any custom routers added through addRouter().\n * 6. Starting the Express server on the configured port.\n */\n build() {\n try {\n this._status = \"starting\";\n\n initializeDependencies(this.toLifecycleContext());\n setupExpressApp(this.toApiRuntimeContext());\n\n this._status = \"running\";\n return this;\n } catch (err) {\n this.log.error(`Failed to build service: ${err}`);\n throw err;\n }\n }\n\n /**\n * Reads all files in the src/handlers directory and imports them dynamically.\n */\n private loadHandlers() {\n loadWorkerHandlers(\n this as unknown as Parameters<typeof loadWorkerHandlers>[0],\n );\n }\n\n /**\n * Spawns worker threads to process messages from Redis streams.\n * Each worker thread runs the worker-thread.ts file.\n * Each worker thread is passed the service name, redis URL, and handlers.\n * Workers communicate back via parentPort to log messages.\n */\n private runWorkers(\n { workerCount }: { workerCount: number } = { workerCount: 1 },\n ) {\n runWorkerRuntime(\n this as unknown as Parameters<typeof runWorkerRuntime>[0],\n { workerCount },\n );\n }\n\n /**\n * The MongoDB module. This is initialized by calling withMongo() and provides access to the configured MongoDB collections through the `db` property. The MongoDB client will automatically connect when the service is built.\n *\n * @param config MongoDB configuration including URI, database name, and collections.\n *\n * @example\n * const t = new Service()\n * .withMongo({\n * dbName: \"test\",\n * collections: {\n * users: { schema: z.object({ id: z.string(), name: z.string() }) },\n * },\n * })\n * .build();\n *\n * const l = await t.db.users.get({ id: \"123\" });\n */\n withMongo<TNextCollections extends MongoCollectionsConfig>(config: {\n dbName: string;\n uri?: string;\n collections: TNextCollections;\n }) {\n const ctx = this.toConfigContext();\n addMongoDependency(ctx, config);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.dependencyGraph);\n return this as unknown as Service<TNextCollections>;\n }\n\n /**\n * The Clickhouse module. This is initialized by calling withClickhouse() and provides access to the configured Clickhouse tables through the `clickhouse` property. The Clickhouse client will automatically connect when the service is built.\n *\n * This module is a **WIP**.\n *\n * @param config Clickhouse configuration including tables.\n * @beta\n */\n withClickhouse<TTables extends Record<string, any>>(\n config: ClickhouseConfig<TTables>,\n ) {\n const ctx = this.toConfigContext();\n addClickhouseDependency(ctx, config);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.dependencyGraph);\n return this as Service<TCollections> & {\n clickhouse: ReturnType<typeof createTypedClickhouse<TTables>>;\n };\n }\n\n /**\n * Optional. Port number for the express server.\n *\n * Defaults in the following order:\n * 1. PORT environment variable\n * 2. 3100\n *\n * @param port port number\n */\n withPort(port: number = Number(process.env.PORT) || 3100) {\n this.port = port;\n return this;\n }\n\n /**\n * Optional. Base URL without port.\n *\n * Defaults in the following order:\n * 1. SERVICE_URL environment variable\n * 2. \"http://localhost\"\n *\n * @param url base url without port\n */\n withUrl(url: string) {\n this.url = url;\n return this;\n }\n\n /**\n * Optional. Name of the service. Used for logging and service discovery.\n *\n * Defaults in the following order:\n * 1. SERVICE_NAME environment variable\n * 2. A random hex string\n * @param name Name of the service\n */\n withName(name: string) {\n this.name = name;\n return this;\n }\n\n /**\n * The Redis module. This is initialized by calling withRedis() and provides a Redis client instance through the `redis` property. The Redis client will automatically connect when the service is built.\n * @param url Redis URL. Defaults to: redis://redis:6379\n */\n withRedis(url: string = \"redis://redis:6379\") {\n const ctx = this.toConfigContext();\n addRedisDependency(ctx, url);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.dependencyGraph);\n return this;\n }\n\n /**\n * Adds worker threads to the service. These are used to process messages from Redis streams. Worker threads will be spawned when build() is called. This must be called after withRedis() to work properly.\n *\n * **Note**: This will not initialize without at least one service stream.\n * @param workerCount Defaults to 1\n * @param serviceStreams Defaults to empty array\n * @returns\n */\n withWorkers(workerCount: number, serviceStreams: string[] = []) {\n const ctx = this.toConfigContext();\n addWorkerDependency(ctx, workerCount, serviceStreams);\n this.applyConfigContext(ctx, [\n ...Service.CONFIG_SYNC_KEYS.dependencyGraph,\n ...Service.CONFIG_SYNC_KEYS.workers,\n ]);\n return this;\n }\n\n /**\n * Loads custom dependencies that can be used in the service. The factory functions will be called during build() after all other dependencies are loaded and can be used to initialize any custom logic or connections. The result of each factory function will be logged.\n * @param dependencies Object where keys are dependency names and values are factory functions that return a boolean indicating success or failure of initialization.\n */\n withModules(dependencies: CustomModules) {\n const ctx = this.toConfigContext();\n addCustomModules(ctx, dependencies);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.modules);\n return this;\n }\n\n withoutExpress() {\n const ctx = this.toConfigContext();\n disableExpress(ctx);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.expressMode);\n return this;\n }\n\n withExpressOptions(options: Partial<ExpressOptions>) {\n const ctx = this.toConfigContext();\n setExpressOptions(ctx, options);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.expressOptions);\n return this;\n }\n\n withAuthStrategy(strategy: AuthStrategy) {\n const ctx = this.toConfigContext();\n addAuthStrategy(ctx, strategy);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.authStrategies);\n return this;\n }\n\n withAuthStrategies(strategies: AuthStrategy[]) {\n const ctx = this.toConfigContext();\n addAuthStrategies(ctx, strategies);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.authStrategies);\n return this;\n }\n\n withAuthResolver(resolver: AuthResolver) {\n const ctx = this.toConfigContext();\n setAuthResolver(ctx, resolver);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.authResolver);\n return this;\n }\n\n withoutDefaultHeaderAuthFallback() {\n const ctx = this.toConfigContext();\n disableDefaultHeaderAuthFallback(ctx);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.authFallback);\n return this;\n }\n\n private internalAddRouter(\n base: string,\n router: Router,\n routes: RouteDefinition<any, any, boolean>[] = [],\n ) {\n this.routers.push({ base, router, routes });\n return this;\n }\n\n /**\n * Registers an express router under a base path.\n * @param base Base path for this router.\n * @param router Router instance.\n * @param routes List of route definitions.\n * @returns\n */\n addRouter(\n base: string,\n router: Router,\n routes: RouteDefinition<any, any, boolean>[] = [],\n ) {\n const ctx = this.toConfigContext();\n addRouterRegistration(ctx, base, router, routes);\n this.applyConfigContext(ctx, Service.CONFIG_SYNC_KEYS.routers);\n return this;\n }\n\n /**\n * Gracefully shuts down the service by closing all connections and active workers.\n */\n async shutdown(\n opts: {\n /**\n * Optional callback to run before shutdown. Can be used to perform cleanup tasks like closing database connections, etc.\n */\n beforeShutdown?: () => Promise<void>;\n /**\n * Optional callback to run after shutdown. Can be used to perform any final tasks before the process exits.\n */\n afterShutdown?: () => Promise<void>;\n } = {},\n ) {\n await shutdownService(this.toShutdownContext(), opts);\n }\n\n [Symbol.dispose]() {\n this.shutdown();\n }\n\n *[Symbol.iterator]() {\n for (const dependency of Object.values(this.dependencies)) {\n yield dependency;\n }\n }\n}\n","export type RedisStreamHandlerContext = {\n service: string;\n emit: (ev: {\n type: string;\n data: Record<string, string | number | boolean | null>;\n }) => Promise<void>;\n log: {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n };\n};\nexport type RedisStreamEventHandler = {\n type: string;\n execute: (\n ev: Record<string, any>,\n ctx: RedisStreamHandlerContext,\n ) => Promise<void>;\n};\nexport function redisStreamHandler(\n type: string,\n fn: RedisStreamEventHandler[\"execute\"],\n): RedisStreamEventHandler {\n return { type, execute: fn };\n}\n","import { Request } from \"express\";\nimport { createRemoteJWKSet, JWTPayload, jwtVerify } from \"jose\";\nimport { PeerCertificate, TLSSocket } from \"tls\";\nimport {\n AuthenticatedUser,\n AuthStrategy,\n RouteDefinition,\n} from \"../api/express-types\";\n\ntype JwtClaimValue = string | string[] | number | boolean | null | undefined;\n\nexport type JwtStrategyClaims = {\n sub?: string;\n [key: string]: unknown;\n};\n\nexport type JwtTokenVerifier = (\n token: string,\n req: Request,\n route: RouteDefinition<any, any, boolean>,\n) => JwtStrategyClaims | null | Promise<JwtStrategyClaims | null>;\n\nexport type CreateJwtAuthStrategyOptions = {\n name?: string;\n headerName?: string;\n tokenPrefix?: string;\n verifyToken: JwtTokenVerifier;\n mapPayloadToUser?: (\n claims: JwtStrategyClaims,\n req: Request,\n route: RouteDefinition<any, any, boolean>,\n ) => AuthenticatedUser | null | Promise<AuthenticatedUser | null>;\n canHandle?: AuthStrategy[\"canHandle\"];\n};\n\nexport type CreateJoseJwtVerifierOptions = {\n jwksUri?: string;\n secret?: string | Uint8Array;\n issuer?: string | string[];\n audience?: string | string[];\n algorithms?: string[];\n clockTolerance?: string | number;\n requiredClaims?: string[];\n};\n\nexport type CreateMtlsAuthStrategyOptions = {\n name?: string;\n requireAuthorized?: boolean;\n mapCertificateToUser?: (\n certificate: PeerCertificate,\n req: Request,\n route: RouteDefinition<any, any, boolean>,\n ) => AuthenticatedUser | null | Promise<AuthenticatedUser | null>;\n canHandle?: AuthStrategy[\"canHandle\"];\n};\n\nfunction readAuthTokenFromHeader(\n req: Request,\n headerName: string,\n tokenPrefix: string,\n): string | null {\n const header = req.headers[headerName.toLowerCase()];\n\n if (typeof header !== \"string\") {\n return null;\n }\n\n const value = header.trim();\n if (!value) {\n return null;\n }\n\n if (!tokenPrefix) {\n return value;\n }\n\n const prefix = `${tokenPrefix} `;\n if (!value.startsWith(prefix)) {\n return null;\n }\n\n return value.slice(prefix.length).trim() || null;\n}\n\nfunction isUserValue(\n value: unknown,\n): value is Exclude<JwtClaimValue, null | undefined> {\n return (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n (Array.isArray(value) && value.every((v) => typeof v === \"string\"))\n );\n}\n\nfunction claimsToAuthenticatedUser(\n claims: JwtStrategyClaims,\n): AuthenticatedUser | null {\n if (typeof claims.sub !== \"string\" || claims.sub.length === 0) {\n return null;\n }\n\n const user: AuthenticatedUser = { userId: claims.sub };\n for (const [key, value] of Object.entries(claims)) {\n if (key === \"sub\") {\n continue;\n }\n\n if (isUserValue(value)) {\n user[key] = value;\n }\n }\n\n return user;\n}\n\nfunction jwtPayloadToStrategyClaims(payload: JWTPayload): JwtStrategyClaims {\n const claims: JwtStrategyClaims = {};\n\n for (const [key, value] of Object.entries(payload)) {\n claims[key] = value;\n }\n\n if (typeof payload.sub === \"string\") {\n claims.sub = payload.sub;\n }\n\n return claims;\n}\n\nfunction hasRequiredClaims(payload: JWTPayload, requiredClaims: string[]) {\n return requiredClaims.every((claim) => payload[claim] !== undefined);\n}\n\nexport function createJoseJwtVerifier(\n options: CreateJoseJwtVerifierOptions,\n): JwtTokenVerifier {\n const requiredClaims = options.requiredClaims ?? [\"sub\"];\n const jwtVerifyOptions = {\n issuer: options.issuer,\n audience: options.audience,\n algorithms: options.algorithms,\n clockTolerance: options.clockTolerance,\n };\n\n if (options.jwksUri) {\n const jwksKeySource = createRemoteJWKSet(new URL(options.jwksUri));\n\n return async (token) => {\n try {\n const { payload } = await jwtVerify(\n token,\n jwksKeySource,\n jwtVerifyOptions,\n );\n\n if (!hasRequiredClaims(payload, requiredClaims)) {\n return null;\n }\n\n return jwtPayloadToStrategyClaims(payload);\n } catch {\n return null;\n }\n };\n }\n\n const secretKey =\n typeof options.secret === \"string\"\n ? new TextEncoder().encode(options.secret)\n : options.secret;\n\n if (!(secretKey instanceof Uint8Array)) {\n throw new Error(\"createJoseJwtVerifier requires either jwksUri or secret.\");\n }\n\n return async (token) => {\n try {\n const { payload } = await jwtVerify(token, secretKey, jwtVerifyOptions);\n\n if (!hasRequiredClaims(payload, requiredClaims)) {\n return null;\n }\n\n return jwtPayloadToStrategyClaims(payload);\n } catch {\n return null;\n }\n };\n}\n\nfunction parseSubjectAltNames(subjectAltName?: string): string[] {\n if (!subjectAltName) {\n return [];\n }\n\n return subjectAltName\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n\nfunction resolveMtlsIdentity(certificate: PeerCertificate): string | null {\n const sans = parseSubjectAltNames(certificate.subjectaltname);\n const uriSan = sans.find((value) => value.startsWith(\"URI:\"));\n if (uriSan) {\n return uriSan.slice(4).trim() || null;\n }\n\n const dnsSan = sans.find((value) => value.startsWith(\"DNS:\"));\n if (dnsSan) {\n return dnsSan.slice(4).trim() || null;\n }\n\n const subjectCn = certificate.subject?.CN;\n if (typeof subjectCn === \"string\" && subjectCn.length > 0) {\n return subjectCn;\n }\n if (Array.isArray(subjectCn) && subjectCn.length > 0) {\n const first = subjectCn[0];\n if (typeof first === \"string\" && first.length > 0) {\n return first;\n }\n }\n\n if (certificate.fingerprint256) {\n return certificate.fingerprint256;\n }\n\n return null;\n}\n\nexport function createJwtAuthStrategy(\n options: CreateJwtAuthStrategyOptions,\n): AuthStrategy {\n const headerName = options.headerName ?? \"authorization\";\n const tokenPrefix = options.tokenPrefix ?? \"Bearer\";\n\n return {\n name: options.name ?? \"jwt\",\n canHandle: options.canHandle,\n authenticate: async (req, route) => {\n const token = readAuthTokenFromHeader(req, headerName, tokenPrefix);\n if (!token) {\n return null;\n }\n\n const claims = await options.verifyToken(token, req, route);\n if (!claims) {\n return null;\n }\n\n if (options.mapPayloadToUser) {\n return options.mapPayloadToUser(claims, req, route);\n }\n\n return claimsToAuthenticatedUser(claims);\n },\n };\n}\n\nexport function createMtlsAuthStrategy(\n options: CreateMtlsAuthStrategyOptions = {},\n): AuthStrategy {\n const requireAuthorized = options.requireAuthorized ?? true;\n\n return {\n name: options.name ?? \"mtls\",\n canHandle: options.canHandle,\n authenticate: async (req, route) => {\n const socket = req.socket as TLSSocket;\n if (typeof socket.getPeerCertificate !== \"function\") {\n return null;\n }\n\n if (requireAuthorized && socket.authorized !== true) {\n return null;\n }\n\n const certificate = socket.getPeerCertificate(true);\n if (!certificate || Object.keys(certificate).length === 0) {\n return null;\n }\n\n if (options.mapCertificateToUser) {\n return options.mapCertificateToUser(certificate, req, route);\n }\n\n const userId = resolveMtlsIdentity(certificate);\n if (!userId) {\n return null;\n }\n\n return {\n userId,\n certSubjectCn: certificate.subject?.CN ?? \"\",\n certIssuerCn: certificate.issuer?.CN ?? \"\",\n certFingerprint256: certificate.fingerprint256 ?? \"\",\n certSerialNumber: certificate.serialNumber ?? \"\",\n mtlsAuthorized: socket.authorized,\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,2 @@
1
+ import{parentPort as e,workerData as t}from"worker_threads";import{createClient as n}from"redis";(async()=>{let{name:r,redisUrl:i,handlers:a,index:o,subscriptions:s}=t,c=n({url:i});await c.connect(),e?.postMessage({level:`info`,message:`Connected to Redis at ${i}`});let l=new Map;for(let[e,t]of Object.entries(a)){let e=await import(t);e.default?.type&&e.default?.execute&&l.set(e.default.type,e.default)}e?.postMessage({level:`create`,loadedHandlers:Object.keys(l)});let u=`${r}:events`,d=`${r}-group`,f=`${r}-worker-${o}`,p=`dlq:${u}`;for(let t of s)try{await c.xGroupCreate(t,d,`0`,{MKSTREAM:!0})}catch(n){n.message.includes(`BUSYGROUP`)?e?.postMessage({level:`warn`,message:`Group ${d} already exists for stream ${t}, skipping creation`}):e?.postMessage({level:`error`,message:`Error creating group: ${n.message}`})}for(;;){let t=s.map(e=>({key:e,id:`>`})),n=await c.xReadGroup(d,f,t,{COUNT:10,BLOCK:5e3});if(n&&Array.isArray(n))for(let{name:t,messages:r}of n)for(let{id:n,message:i}of r){let r=i.eventType,a=l.get(r);if(!a){e?.postMessage({level:`error`,message:`No handler found for event type: ${r}`}),await c.xAck(u,d,n);continue}let o=3e3,s=0;for(;s<3;){s++;try{if(await Promise.race([await a.execute(i,{emit:async e=>{c.xAdd(`${e.type.split(`:`)[0]}:events`,`*`,e)},log:{info:t=>e?.postMessage({level:`info`,message:t}),warn:t=>e?.postMessage({level:`warn`,message:t}),error:t=>e?.postMessage({level:`error`,message:t})},service:t}),new Promise(e=>setTimeout(()=>e(`timeout`),o))])===`timeout`)throw Error(`Message processing timed out after ${o}ms`);await c.xAck(d,d,n),e?.postMessage({level:`info`,message:`Message ${n} processed successfully after ${s} attempts`});return}catch(t){e?.postMessage({level:`error`,message:`Error processing message ${n}: ${t instanceof Error?t.message:t}`}),s===3&&await c.xAdd(p,`*`,{originalId:n,message:JSON.stringify(i),error:t instanceof Error?t.message:String(t),timestamp:new Date().toISOString()})}}}}})();export{};
2
+ //# sourceMappingURL=worker-thread.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-thread.mjs","names":[],"sources":["../../src/runtime/worker-thread.ts"],"sourcesContent":["import { createClient } from \"redis\";\nimport { parentPort, workerData } from \"worker_threads\";\nimport { RedisStreamEventHandler } from \"./stream-handler\";\n\n(async () => {\n const { name, redisUrl, handlers, index, subscriptions } = workerData as {\n name: string;\n redisUrl: string;\n handlers: Record<string, string>;\n index: number;\n subscriptions: string[];\n };\n const redis = createClient({ url: redisUrl });\n await redis.connect();\n\n parentPort?.postMessage({\n level: \"info\",\n message: `Connected to Redis at ${redisUrl}`,\n });\n\n const loadedHandlers = new Map();\n\n for (const [_, filePath] of Object.entries(handlers)) {\n const mod = await import(filePath);\n if (mod.default?.type && mod.default?.execute) {\n loadedHandlers.set(mod.default.type, mod.default);\n }\n }\n\n parentPort?.postMessage({\n level: \"create\",\n loadedHandlers: Object.keys(loadedHandlers),\n });\n\n const stream = `${name}:events`;\n const group = `${name}-group`;\n const consumer = `${name}-worker-${index}`;\n const dlqStream = `dlq:${stream}`;\n\n for (const streamName of subscriptions) {\n // TODO: infer from handlers\n try {\n await redis.xGroupCreate(streamName, group, \"0\", {\n MKSTREAM: true,\n });\n } catch (error) {\n if ((error as Error).message.includes(\"BUSYGROUP\")) {\n parentPort?.postMessage({\n level: \"warn\",\n message: `Group ${group} already exists for stream ${streamName}, skipping creation`,\n });\n } else {\n parentPort?.postMessage({\n level: \"error\",\n message: `Error creating group: ${(error as Error).message}`,\n });\n }\n }\n }\n\n while (true) {\n const keys = subscriptions.map((s) => ({ key: s, id: \">\" })); // TODO: infer from handlers\n const res = await redis.xReadGroup(group, consumer, keys, {\n COUNT: 10,\n BLOCK: 5000,\n });\n\n if (!res) {\n continue;\n }\n\n if (Array.isArray(res)) {\n for (const { name: streamName, messages } of res as {\n name: string;\n messages: {\n id: string;\n message: { eventType: string };\n }[];\n }[]) {\n for (const { id, message } of messages) {\n const eventType = message.eventType;\n const handler = loadedHandlers.get(\n eventType,\n ) as RedisStreamEventHandler;\n\n if (!handler) {\n parentPort?.postMessage({\n level: \"error\",\n message: `No handler found for event type: ${eventType}`,\n });\n await redis.xAck(stream, group, id);\n continue;\n }\n\n const timeoutMs = 3000;\n const maxRetries = 3;\n let attempts = 0;\n\n while (attempts < maxRetries) {\n attempts++;\n try {\n const result = await Promise.race([\n await handler.execute(message, {\n emit: async (ev) => {\n redis.xAdd(\n `${ev.type.split(\":\")[0]}:events`,\n \"*\",\n ev as Record<string, any>,\n );\n },\n log: {\n info: (msg) =>\n parentPort?.postMessage({ level: \"info\", message: msg }),\n warn: (msg) =>\n parentPort?.postMessage({ level: \"warn\", message: msg }),\n error: (msg) =>\n parentPort?.postMessage({ level: \"error\", message: msg }),\n },\n service: streamName,\n }),\n new Promise((resolve) =>\n setTimeout(() => resolve(\"timeout\"), timeoutMs),\n ),\n ]);\n\n if (result === \"timeout\") {\n throw new Error(\n `Message processing timed out after ${timeoutMs}ms`,\n );\n }\n\n await redis.xAck(group, group, id);\n parentPort?.postMessage({\n level: \"info\",\n message: `Message ${id} processed successfully after ${attempts} attempts`,\n });\n return;\n } catch (error) {\n parentPort?.postMessage({\n level: \"error\",\n message: `Error processing message ${id}: ${\n error instanceof Error ? error.message : error\n }`,\n });\n if (attempts === maxRetries) {\n await redis.xAdd(dlqStream, \"*\", {\n originalId: id,\n message: JSON.stringify(message),\n error: error instanceof Error ? error.message : String(error),\n timestamp: new Date().toISOString(),\n });\n }\n }\n }\n }\n }\n }\n }\n})();\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,57 @@
1
1
  {
2
2
  "name": "@twasik4/pocket-service",
3
- "version": "1.0.3",
4
- "main": "dist/index.js",
5
- "types": "dist/index.d.ts",
3
+ "version": "1.1.0",
4
+ "description": "Boilerplate-free TypeScript service toolkit with Express, MongoDB, Redis, and ClickHouse support.",
5
+ "license": "MIT",
6
+ "packageManager": "pnpm@9.0.0",
7
+ "main": "dist/index.mjs",
8
+ "types": "dist/index.d.mts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.mts",
12
+ "import": "./dist/index.mjs"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "CHANGELOG.md",
20
+ "LICENSE"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/TristonWasik/pocket-service.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/TristonWasik/pocket-service/issues"
31
+ },
32
+ "homepage": "https://github.com/TristonWasik/pocket-service#readme",
33
+ "keywords": [
34
+ "typescript",
35
+ "express",
36
+ "service",
37
+ "mongo",
38
+ "redis",
39
+ "clickhouse"
40
+ ],
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
6
44
  "type": "module",
45
+ "scripts": {
46
+ "build": "tsdown",
47
+ "dev": "tsdown --watch",
48
+ "test": "vitest",
49
+ "test:unit": "vitest --run tests/unit",
50
+ "test:integration": "vitest --run tests/integration",
51
+ "test:watch": "vitest --watch",
52
+ "prepublishOnly": "pnpm test -- --run && pnpm build",
53
+ "pack:check": "npm pack --dry-run"
54
+ },
7
55
  "peerDependencies": {
8
56
  "@clickhouse/client": "^1.12.1"
9
57
  },
@@ -22,14 +70,9 @@
22
70
  "@types/cors": "^2.8.19",
23
71
  "@types/express": "^5.0.3",
24
72
  "@types/node": "^24.5.2",
25
- "@types/winston": "^2.4.4",
26
73
  "mongodb-memory-server": "^11.0.1",
74
+ "tsdown": "^0.22.13",
75
+ "typescript": "^7.0.2",
27
76
  "vitest": "^3.2.4"
28
- },
29
- "scripts": {
30
- "build": "tsup",
31
- "dev": "tsup --watch",
32
- "test": "vitest",
33
- "test:watch": "vitest --watch"
34
77
  }
35
78
  }