nucleus-core-ts 0.10.82 → 0.10.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-ok +1 -1
- package/dist/index.js +1 -1
- package/dist/server.js +1 -1
- package/package.json +1 -1
package/dist/.build-ok
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.10.
|
|
1
|
+
0.10.83
|
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ and limitations under the License.
|
|
|
94
94
|
`).filter((l)=>l.length>0).length}catch{}try{let stat3=readFileSync2(`${cgroupRoot}/memory.stat`,"utf8");breakdown.cgroupAnon=statField(stat3,"anon"),breakdown.cgroupFile=statField(stat3,"file"),breakdown.cgroupSock=statField(stat3,"sock"),breakdown.cgroupSlab=statField(stat3,"slab"),breakdown.cgroupKernelStack=statField(stat3,"kernel_stack")}catch{}return breakdown}var init_nativeMemory=()=>{};import*as fs2 from"fs";import*as os from"os";function readCgroupLimitBytes(){let read=(path)=>{try{return fs2.readFileSync(path,"utf-8")}catch{return null}};return parseCgroupLimit(read("/sys/fs/cgroup/memory.max")??read("/sys/fs/cgroup/memory/memory.limit_in_bytes"))}function readRuntimeMemoryAdvice(){let read=(path)=>{try{return fs2.readFileSync(path,"utf-8")}catch{return null}},limitRaw=read("/sys/fs/cgroup/memory.max")??read("/sys/fs/cgroup/memory/memory.limit_in_bytes"),forced=Number(process.env.BUN_JSC_forceRAMSize);return runtimeMemoryAdvice({cgroupLimit:parseCgroupLimit(limitRaw),hostTotal:os.totalmem(),forcedRamSize:Number.isFinite(forced)&&forced>0?forced:null})}class SystemCollector{config;lastCpuInfo=null;constructor(config){this.config=config}async collect(){if(!this.config?.enabled)return null;let metrics={cpu:{usage:0,cores:0},memory:{total:0,used:0,free:0,usagePercent:0,rss:0,heapUsed:0,heapTotal:0,external:0,arrayBuffers:0,scope:"host"},disk:{total:0,used:0,free:0,usagePercent:0},network:{bytesIn:0,bytesOut:0},process:{uptime:0,pid:0,eventLoopLag:0}};if(this.config.metrics?.cpu!==!1)metrics.cpu=this.collectCpu();if(this.config.metrics?.memory!==!1)metrics.memory=this.collectMemory();if(this.config.metrics?.disk!==!1)metrics.disk=await this.collectDisk();if(this.config.metrics?.network)metrics.network=this.collectNetwork();if(this.config.metrics?.process!==!1)metrics.process=await this.collectProcess();return metrics}collectCpu(){let cpus2=os.cpus(),cores=cpus2.length,idle=0,total=0;for(let cpu of cpus2)idle+=cpu.times.idle,total+=cpu.times.user+cpu.times.nice+cpu.times.sys+cpu.times.idle+cpu.times.irq;let usage=0;if(this.lastCpuInfo){let idleDiff=idle-this.lastCpuInfo.idle,totalDiff=total-this.lastCpuInfo.total;usage=totalDiff>0?Math.round((1-idleDiff/totalDiff)*100*100)/100:0}return this.lastCpuInfo={idle,total},{usage,cores}}read(path){try{return fs2.readFileSync(path,"utf-8")}catch{return null}}collectMemory(){let memUsage=process.memoryUsage(),limitRaw=this.read("/sys/fs/cgroup/memory.max")??this.read("/sys/fs/cgroup/memory/memory.limit_in_bytes"),usageRaw=this.read("/sys/fs/cgroup/memory.current")??this.read("/sys/fs/cgroup/memory/memory.usage_in_bytes"),statRaw=this.read("/sys/fs/cgroup/memory.stat")??this.read("/sys/fs/cgroup/memory/memory.stat");return resolveMemory({limit:parseCgroupLimit(limitRaw),current:parseCgroupUsage(usageRaw),inactiveFile:parseInactiveFile(statRaw),hostTotal:os.totalmem(),hostAvailable:parseMemAvailable(this.read("/proc/meminfo")),hostFree:os.freemem(),process:{rss:memUsage.rss,heapUsed:memUsage.heapUsed,heapTotal:memUsage.heapTotal,external:memUsage.external,arrayBuffers:memUsage.arrayBuffers}})}async collectDisk(){try{let stats=fs2.statfsSync("/"),total=stats.blocks*stats.bsize,free=stats.bfree*stats.bsize,used=total-free,usagePercent=Math.round(used/total*100*100)/100;return{total,used,free,usagePercent}}catch{return{total:0,used:0,free:0,usagePercent:0}}}collectNetwork(){try{let content=fs2.readFileSync("/proc/net/dev","utf-8"),bytesIn=0,bytesOut=0;for(let line of content.split(`
|
|
95
95
|
`).slice(2)){let[namePart,dataPart]=line.split(":");if(!namePart||!dataPart)continue;if(namePart.trim()==="lo")continue;let fields=dataPart.trim().split(/\s+/);bytesIn+=Number.parseInt(fields[0]??"0",10)||0,bytesOut+=Number.parseInt(fields[8]??"0",10)||0}return{bytesIn,bytesOut}}catch{return{bytesIn:0,bytesOut:0}}}async collectProcess(){let uptime=process.uptime(),pid=process.pid,lagStart=Date.now(),eventLoopLag=await new Promise((resolve)=>{setImmediate(()=>{resolve(Date.now()-lagStart)})});return{uptime,pid,eventLoopLag,handles:collectHandles(),nativeMemory:collectNativeMemory()}}}var init_SystemCollector=__esm(()=>{init_handles();init_nativeMemory()});function peakContext(snapshot){let app=snapshot.application,busiest=Object.entries(app?.requests?.byEndpoint??{}).sort((a,b)=>b[1]-a[1])[0],tags={};if(busiest)tags.busiestEndpoint=busiest[0],tags.busiestEndpointCount=String(busiest[1]);if(app?.requests?.perMinute!==void 0)tags.requestsPerMinute=String(app.requests.perMinute);if(app?.responseTime?.p95!==void 0)tags.p95=String(app.responseTime.p95);if(app?.errors?.rate!==void 0)tags.errorRate=String(app.errors.rate);if(snapshot.system?.cpu?.usage!==void 0)tags.cpu=String(snapshot.system.cpu.usage);if(snapshot.system?.memory?.usagePercent!==void 0)tags.memory=String(snapshot.system.memory.usagePercent);if(snapshot.database?.connections?.active!==void 0)tags.dbConnections=String(snapshot.database.connections.active);return tags}function newPeaks(snapshot,previous,timestamp){let peaks=new Map(previous),points=[],tags=peakContext(snapshot);for(let watched of WATCHED_PEAKS){let value=watched.of(snapshot);if(value===void 0||!Number.isFinite(value))continue;let before=peaks.get(watched.name);if(before!==void 0&&value<=before)continue;peaks.set(watched.name,value),points.push({timestamp,metricType:"peak",metricName:watched.name,value,tags})}return{points,peaks}}var WATCHED_PEAKS;var init_peaks=__esm(()=>{WATCHED_PEAKS=[{name:"requests.perMinute",of:(s)=>s.application?.requests?.perMinute},{name:"responseTime.p95",of:(s)=>s.application?.responseTime?.p95},{name:"errors.rate",of:(s)=>s.application?.errors?.rate},{name:"cpu.usage",of:(s)=>s.system?.cpu?.usage},{name:"memory.usagePercent",of:(s)=>s.system?.memory?.usagePercent},{name:"database.connections.active",of:(s)=>s.database?.connections?.active}]});function median(values){if(values.length===0)return null;let sorted=[...values].sort((a,b)=>a-b),mid=Math.floor(sorted.length/2);return sorted.length%2===1?sorted[mid]:(sorted[mid-1]+sorted[mid])/2}function summariseRealtime(events,options={}){let windowMs=options.windowMs??900000,since=(options.now??Date.now())-windowMs,recent=events.filter((e)=>e.timestamp>=since),opened=recent.filter((e)=>e.type==="connection").length,closes=recent.filter((e)=>e.type==="close"),refusals={};for(let event of recent){if(event.type!=="error")continue;let reason=event.metadata?.reason??"unspecified";refusals[reason]=(refusals[reason]??0)+1}let refusalTotal=Object.values(refusals).reduce((a,b)=>a+b,0),lifetimes=closes.map((e)=>e.metadata?.livedMs).filter((v)=>typeof v==="number"),shortLivedCloses=closes.filter((e)=>e.metadata?.shortLived===!0).length,findings=[];if(refusals.expired)findings.push(`${refusals.expired} handshake refused as expired \u2014 the session is likely fine and the pre-handshake refresh is not producing a fresh access token`);if(refusals["invalid-signature"])findings.push(`${refusals["invalid-signature"]} handshake refused as invalid-signature \u2014 a real credential problem, and the shape of the reconnect storm`);if(refusals["no-token"])findings.push(`${refusals["no-token"]} handshake arrived with no credential \u2014 usually a signed-out tab still open somewhere`);if(refusals.malformed)findings.push(`${refusals.malformed} handshake carried something that is not a token at all \u2014 look at what the client is putting in the bearer, not at the session`);if(refusals["wrong-audience"])findings.push(`${refusals["wrong-audience"]} handshake carried a token minted for somewhere else \u2014 check the issuer/audience this install expects`);if(refusals["not-yet-valid"])findings.push(`${refusals["not-yet-valid"]} handshake carried a token from the future \u2014 the clocks disagree`);if(closes.length>0&&shortLivedCloses/closes.length>0.5)findings.push(`${shortLivedCloses} of ${closes.length} sockets lived under a minute \u2014 they are failing, not disconnecting`);return{windowMs,opened,closed:closes.length,netOpen:opened-closes.length,refusals,refusalTotal,shortLivedCloses,medianLifetimeMs:median(lifetimes),longestLifetimeMs:lifetimes.length?Math.max(...lifetimes):null,findings}}import{randomUUID as randomUUID2}from"crypto";import*as os2 from"os";class LiveMonitoringService{store;memoryInterval=null;cpuInterval=null;lastCpuInfo=null;isRunning=!1;constructor(config){let merged={...DEFAULT_LIVE_CONFIG,...config};this.store={requests:[],configs:{logMemory:merged.logMemory,logCpu:merged.logCpu,logDapr:merged.logDapr,logWebSocket:merged.logWebSocket,cpuLogInterval:merged.cpuLogInterval,memoryLogInterval:merged.memoryLogInterval},logs:{memory:[],cpu:[],dapr:[],ws:[]},logLimits:{memory:merged.memoryLogLimit,cpu:merged.cpuLogLimit,dapr:merged.daprLogLimit,ws:merged.wsLogLimit,request:merged.requestLogLimit},worker:{pid:process.pid,workerId:null,memory:null,cpu:null,updatedAt:Date.now()},allWorkers:[],daprEvents:[],wsEvents:[]}}start(){if(this.isRunning)return;if(this.isRunning=!0,this.store.configs.logMemory)this.startMemoryCollector();if(this.store.configs.logCpu)this.startCpuCollector()}stop(){if(!this.isRunning)return;if(this.isRunning=!1,this.memoryInterval)clearInterval(this.memoryInterval),this.memoryInterval=null;if(this.cpuInterval)clearInterval(this.cpuInterval),this.cpuInterval=null}startMemoryCollector(){if(this.memoryInterval)clearInterval(this.memoryInterval);let collect=()=>{if(!this.store.configs.logMemory)return;let mem=process.memoryUsage(),entry={timestamp:Date.now(),rss:mem.rss,heapUsed:mem.heapUsed,heapTotal:mem.heapTotal};if(this.store.logs.memory.push(entry),this.store.logs.memory.length>this.store.logLimits.memory*2)this.store.logs.memory=this.store.logs.memory.slice(-this.store.logLimits.memory);this.store.worker.memory=entry,this.store.worker.updatedAt=Date.now()};collect(),this.memoryInterval=setInterval(collect,this.store.configs.memoryLogInterval)}startCpuCollector(){if(this.cpuInterval)clearInterval(this.cpuInterval);let collect=()=>{if(!this.store.configs.logCpu)return;let cpus3=os2.cpus(),userTime=0,sysTime=0,idle=0,total=0;for(let cpu of cpus3)userTime+=cpu.times.user,sysTime+=cpu.times.sys,idle+=cpu.times.idle,total+=cpu.times.user+cpu.times.nice+cpu.times.sys+cpu.times.idle+cpu.times.irq;let userPercent=0,sysPercent=0;if(this.lastCpuInfo){let totalDiff=total-this.lastCpuInfo.total,idleDiff=idle-this.lastCpuInfo.idle;if(totalDiff>0){let activeDiff=totalDiff-idleDiff;userPercent=Math.round((userTime-0)/(activeDiff||1)*100*100)/100,sysPercent=Math.round((sysTime-0)/(activeDiff||1)*100*100)/100;let totalActive=Math.round((1-idleDiff/totalDiff)*100*100)/100;userPercent=Math.round(totalActive*0.7*100)/100,sysPercent=Math.round(totalActive*0.3*100)/100}}this.lastCpuInfo={idle,total};let entry={timestamp:Date.now(),user:userPercent,system:sysPercent};if(this.store.logs.cpu.push(entry),this.store.logs.cpu.length>this.store.logLimits.cpu*2)this.store.logs.cpu=this.store.logs.cpu.slice(-this.store.logLimits.cpu);this.store.worker.cpu=entry,this.store.worker.updatedAt=Date.now()};collect(),this.cpuInterval=setInterval(collect,this.store.configs.cpuLogInterval)}recordRequest(request){if(this.store.requests.push(request),this.store.requests.length>this.store.logLimits.request*2)this.store.requests=this.store.requests.slice(-this.store.logLimits.request)}recordDaprEvent(type,details){if(!this.store.configs.logDapr)return;let event={id:randomUUID2(),type,timestamp:Date.now(),...details};if(this.store.logs.dapr.push(event),this.store.daprEvents.push(event),this.store.logs.dapr.length>this.store.logLimits.dapr*2)this.store.logs.dapr=this.store.logs.dapr.slice(-this.store.logLimits.dapr);if(this.store.daprEvents.length>this.store.logLimits.dapr*2)this.store.daprEvents=this.store.daprEvents.slice(-this.store.logLimits.dapr)}recordWsEvent(type,details){if(!this.store.configs.logWebSocket)return;let event={id:randomUUID2(),type,timestamp:Date.now(),...details};if(this.store.logs.ws.push(event),this.store.wsEvents.push(event),this.store.logs.ws.length>this.store.logLimits.ws*2)this.store.logs.ws=this.store.logs.ws.slice(-this.store.logLimits.ws);if(this.store.wsEvents.length>this.store.logLimits.ws*2)this.store.wsEvents=this.store.wsEvents.slice(-this.store.logLimits.ws)}getSnapshot(){return{memory:this.store.logs.memory.slice(-this.store.logLimits.memory),cpu:this.store.logs.cpu.slice(-this.store.logLimits.cpu),requests:this.store.requests.slice(-this.store.logLimits.request),dapr:this.store.logs.dapr.slice(-this.store.logLimits.dapr),ws:this.store.logs.ws.slice(-this.store.logLimits.ws),workers:this.store.allWorkers.length?this.store.allWorkers:[this.store.worker],logLimits:{...this.store.logLimits},configs:{...this.store.configs}}}getUpdatesSince(timestamps){let memoryUpdates=this.store.logs.memory.filter((m)=>m.timestamp>timestamps.memory),cpuUpdates=this.store.logs.cpu.filter((c)=>c.timestamp>timestamps.cpu),requestUpdates=this.store.requests.filter((r)=>r.timestamp>timestamps.request),daprUpdates=this.store.logs.dapr.filter((d)=>d.timestamp>timestamps.dapr),wsUpdates=this.store.logs.ws.filter((w)=>w.timestamp>timestamps.ws);if(!(memoryUpdates.length>0||cpuUpdates.length>0||requestUpdates.length>0||daprUpdates.length>0||wsUpdates.length>0))return null;return{memory:memoryUpdates,cpu:cpuUpdates,requests:requestUpdates,dapr:daprUpdates,ws:wsUpdates,timestamp:Date.now()}}getRealtimeHealth(windowMs){return summariseRealtime(this.store.wsEvents,{windowMs})}getLogs(){return{memory:this.store.logs.memory,cpu:this.store.logs.cpu,requests:this.store.requests,dapr:this.store.logs.dapr,ws:this.store.logs.ws,daprEvents:this.store.daprEvents,wsEvents:this.store.wsEvents,configs:{logMemory:this.store.configs.logMemory,logCpu:this.store.configs.logCpu,logDapr:this.store.configs.logDapr,logWebSocket:this.store.configs.logWebSocket},limits:{...this.store.logLimits}}}getSettings(){return{configs:{...this.store.configs},logLimits:{...this.store.logLimits}}}changeSettings(payload){if(payload.logMemory!==void 0)this.store.configs.logMemory=payload.logMemory;if(payload.logCpu!==void 0)this.store.configs.logCpu=payload.logCpu;if(payload.logDapr!==void 0)this.store.configs.logDapr=payload.logDapr;if(payload.logWebSocket!==void 0)this.store.configs.logWebSocket=payload.logWebSocket;if(payload.cpuLogInterval!==void 0){if(this.store.configs.cpuLogInterval=payload.cpuLogInterval,this.isRunning&&this.store.configs.logCpu)this.startCpuCollector()}if(payload.memoryLogInterval!==void 0){if(this.store.configs.memoryLogInterval=payload.memoryLogInterval,this.isRunning&&this.store.configs.logMemory)this.startMemoryCollector()}if(payload.memoryLogLimit!==void 0)this.store.logLimits.memory=payload.memoryLogLimit;if(payload.cpuLogLimit!==void 0)this.store.logLimits.cpu=payload.cpuLogLimit;if(payload.daprLogLimit!==void 0)this.store.logLimits.dapr=payload.daprLogLimit;if(payload.wsLogLimit!==void 0)this.store.logLimits.ws=payload.wsLogLimit;if(payload.requestLogLimit!==void 0)this.store.logLimits.request=payload.requestLogLimit;return{message:"Settings updated successfully",configs:{...this.store.configs},logLimits:{...this.store.logLimits}}}getStore(){return this.store}isEnabled(){return this.isRunning}}var DEFAULT_LIVE_CONFIG;var init_LiveMonitoringService=__esm(()=>{DEFAULT_LIVE_CONFIG={enabled:!0,logMemory:!0,logCpu:!0,logDapr:!0,logWebSocket:!0,memoryLogInterval:1000,cpuLogInterval:1000,memoryLogLimit:100,cpuLogLimit:100,daprLogLimit:100,wsLogLimit:100,requestLogLimit:100,streamInterval:150}});function redisLifecycleBackend(client){return{get:(key)=>client.get(key),set:(key,value)=>client.set(key,value),del:(key)=>client.del(key),async pushCapped(key,value,keep){await client.lpush(key,value),await client.ltrim(key,0,keep-1)},list:(key,limit)=>client.lrange(key,0,limit-1)}}function judge(previous,now){let uptimeMs=Math.max(0,previous.lastSeenAt-previous.startedAt),detectedAfterMs=Math.max(0,now-previous.lastSeenAt),nearCeiling=previous.memory!==void 0&&previous.memory.scope!=="host"&&previous.memory.usagePercent>=85,verdict=previous.fatal?"fatal":nearCeiling?"killed":"vanished";return{bootId:previous.bootId,startedAt:previous.startedAt,lastSeenAt:previous.lastSeenAt,uptimeMs,detectedAfterMs,verdict,fatal:previous.fatal,memory:previous.memory,labels:previous.labels,summary:describe(verdict,uptimeMs,previous)}}function describe(verdict,uptimeMs,previous){let ran=`Ran ${duration(uptimeMs)}`;if(verdict==="fatal"&&previous.fatal)return`${ran}, then stopped on an unhandled ${previous.fatal.name}: ${previous.fatal.message}`;if(verdict==="killed"&&previous.memory)return`${ran}, then died without warning. ${memorySentence(previous.memory)} `+"Nothing ran on the way out, which is what a kill looks like \u2014 the memory ceiling "+"is the reason to check first.";let held=previous.memory?` ${memorySentence(previous.memory)} So memory is NOT the obvious answer.`:" No memory reading survived, so there is nothing here to point at.";return`${ran}, then vanished with no goodbye and no error.${held}`}function memorySentence(memory){let own=`The process itself was holding ${mb(memory.rss)}`;if(memory.scope==="host")return`${own}; the machine (not a container limit) was at ${Math.round(memory.usagePercent)}%.`;return`${own}, and its container was at ${Math.round(memory.usagePercent)}% of a ${mb(memory.total)} limit.`}function mb(bytes){return`${Math.round(bytes/1048576)} MB`}function duration(ms){let seconds=Math.round(ms/1000);if(seconds<60)return`${seconds}s`;let minutes=Math.floor(seconds/60);if(minutes<60)return`${minutes}m`;let hours=Math.floor(minutes/60),restMinutes=minutes%60;if(hours<48)return restMinutes?`${hours}h ${restMinutes}m`:`${hours}h`;return`${Math.floor(hours/24)}d ${hours%24}h`}import{randomUUID as randomUUID3}from"crypto";class LifecycleRecorder{backend;now;labels;onIncident;logger;bootId;startedAt;beat;constructor(options){this.backend=options.backend,this.now=options.now??(()=>Date.now()),this.labels=options.labels,this.onIncident=options.onIncident,this.logger=options.logger,this.bootId=options.bootId??randomUUID3(),this.startedAt=this.now(),this.beat={bootId:this.bootId,startedAt:this.startedAt,lastSeenAt:this.startedAt,labels:this.labels}}async claim(){let incident=null;try{let raw=await this.backend.get(HEARTBEAT_KEY);if(raw){let previous=JSON.parse(raw);if(!previous.stoppedAt&&previous.bootId!==this.bootId)incident={...judge(previous,this.now()),recordedAt:this.now()},await this.backend.pushCapped(INCIDENTS_KEY,JSON.stringify(incident),KEEP_INCIDENTS),this.logger?.warn(`[Lifecycle] Previous run ended abnormally: ${incident.summary}`,{bootId:incident.bootId,verdict:incident.verdict}),this.onIncident?.(incident);else if(previous.stoppedAt)this.logger?.info("[Lifecycle] Previous run shut down cleanly",{reason:previous.stopReason})}await this.write()}catch(error){this.logger?.warn("[Lifecycle] Could not read the previous run",{error:error instanceof Error?error.message:String(error)})}return incident}async touch(memory){this.beat={...this.beat,lastSeenAt:this.now(),memory:memory??this.beat.memory},await this.write()}async recordFatal(error,kind){let err=error instanceof Error?error:Error(String(error));this.beat={...this.beat,lastSeenAt:this.now(),fatal:{name:`${kind}: ${err.name}`,message:err.message,stack:err.stack?.split(`
|
|
96
96
|
`).slice(0,12).join(`
|
|
97
|
-
`),at:this.now()}},await this.write()}async recordStop(reason){this.beat={...this.beat,lastSeenAt:this.now(),stoppedAt:this.now(),stopReason:reason},await this.write()}async incidents(limit=KEEP_INCIDENTS){try{return(await this.backend.list(INCIDENTS_KEY,limit)).map((row)=>JSON.parse(row))}catch{return[]}}async write(){try{await this.backend.set(HEARTBEAT_KEY,JSON.stringify(this.beat))}catch{}}}var HEARTBEAT_KEY="nucleus:lifecycle:heartbeat",INCIDENTS_KEY="nucleus:lifecycle:incidents",KEEP_INCIDENTS=50;var init_store=()=>{};var init_lifecycle=__esm(()=>{init_store()});class MonitoringService{redis;logger;config;appId;flushToDb;systemCollector;applicationCollector;databaseCollector=null;redisCollector=null;alertService;collectInterval=null;flushInterval=null;pendingMetrics=[];isRunning=!1;peaks=new Map;peaksSeeded=!1;dbQuery;loadPeaks;constructor(deps){if(this.redis=deps.redis,this.logger=deps.logger,this.config=this.mergeConfig(deps.config),this.appId=deps.appId,this.flushToDb=deps.flushToDb,this.dbQuery=deps.dbQuery,this.loadPeaks=deps.loadPeaks,this.config.database.enabled)if(deps.dbQuery)this.databaseCollector=new DatabaseCollector(this.config.database,deps.dbQuery);else this.logger.warn("[Monitoring] monitoring.database is enabled but no database query executor is wired \u2014 database metrics are skipped");if(this.config.redis.enabled)if(deps.redis.getDirectClient())this.redisCollector=new RedisCollector(()=>deps.redis.getDirectClient());else this.logger.warn("[Monitoring] monitoring.redis is enabled but Redis runs in Dapr mode \u2014 INFO is unavailable, Redis metrics are skipped");if(this.config.persistence.enabled&&!deps.flushToDb)this.logger.warn("[Monitoring] monitoring.persistence is enabled but no flushToDb handler is wired \u2014 metrics are kept in Redis only");this.systemCollector=new SystemCollector(this.config.system),this.applicationCollector=new ApplicationCollector(this.config.application),this.alertService=new AlertService({logger:deps.logger,getEmailService:deps.getEmailService,config:this.config,appId:deps.appId})}mergeConfig(config){return{enabled:config.enabled??DEFAULT_CONFIG3.enabled,system:{enabled:config.system?.enabled??DEFAULT_CONFIG3.system.enabled,collectInterval:config.system?.collectInterval??DEFAULT_CONFIG3.system.collectInterval,metrics:{cpu:config.system?.metrics?.cpu??DEFAULT_CONFIG3.system.metrics.cpu,memory:config.system?.metrics?.memory??DEFAULT_CONFIG3.system.metrics.memory,disk:config.system?.metrics?.disk??DEFAULT_CONFIG3.system.metrics.disk,network:config.system?.metrics?.network??DEFAULT_CONFIG3.system.metrics.network,process:config.system?.metrics?.process??DEFAULT_CONFIG3.system.metrics.process}},application:{enabled:config.application?.enabled??DEFAULT_CONFIG3.application.enabled,metrics:{requests:config.application?.metrics?.requests??DEFAULT_CONFIG3.application.metrics.requests,responseTime:config.application?.metrics?.responseTime??DEFAULT_CONFIG3.application.metrics.responseTime,errors:config.application?.metrics?.errors??DEFAULT_CONFIG3.application.metrics.errors,rateLimits:config.application?.metrics?.rateLimits??DEFAULT_CONFIG3.application.metrics.rateLimits}},database:{enabled:config.database?.enabled??DEFAULT_CONFIG3.database.enabled,metrics:{connections:config.database?.metrics?.connections??DEFAULT_CONFIG3.database.metrics.connections,queryTime:config.database?.metrics?.queryTime??DEFAULT_CONFIG3.database.metrics.queryTime,slowQueryThreshold:config.database?.metrics?.slowQueryThreshold??DEFAULT_CONFIG3.database.metrics.slowQueryThreshold}},redis:{enabled:config.redis?.enabled??DEFAULT_CONFIG3.redis.enabled},persistence:{enabled:config.persistence?.enabled??DEFAULT_CONFIG3.persistence.enabled,samples:config.persistence?.samples??DEFAULT_CONFIG3.persistence.samples,flushInterval:config.persistence?.flushInterval??DEFAULT_CONFIG3.persistence.flushInterval,retentionDays:config.persistence?.retentionDays??DEFAULT_CONFIG3.persistence.retentionDays},alerts:{enabled:config.alerts?.enabled??DEFAULT_CONFIG3.alerts.enabled,email:{enabled:config.alerts?.email?.enabled??DEFAULT_CONFIG3.alerts.email.enabled,recipients:config.alerts?.email?.recipients??DEFAULT_CONFIG3.alerts.email.recipients},thresholds:{cpuPercent:config.alerts?.thresholds?.cpuPercent??DEFAULT_CONFIG3.alerts.thresholds.cpuPercent,memoryPercent:config.alerts?.thresholds?.memoryPercent??DEFAULT_CONFIG3.alerts.thresholds.memoryPercent,diskPercent:config.alerts?.thresholds?.diskPercent??DEFAULT_CONFIG3.alerts.thresholds.diskPercent,errorRatePercent:config.alerts?.thresholds?.errorRatePercent??DEFAULT_CONFIG3.alerts.thresholds.errorRatePercent,responseTimeMs:config.alerts?.thresholds?.responseTimeMs??DEFAULT_CONFIG3.alerts.thresholds.responseTimeMs,rateLimitBlocksPerMinute:config.alerts?.thresholds?.rateLimitBlocksPerMinute??DEFAULT_CONFIG3.alerts.thresholds.rateLimitBlocksPerMinute},cooldown:config.alerts?.cooldown??DEFAULT_CONFIG3.alerts.cooldown}}}parseTimeToMs(time){let match=time.match(/^(\d+)(ms|s|m|h|d)$/);if(!match||!match[1]||!match[2])return 1e4;let value=parseInt(match[1],10);switch(match[2]){case"ms":return value;case"s":return value*1000;case"m":return value*60*1000;case"h":return value*60*60*1000;case"d":return value*24*60*60*1000;default:return 1e4}}start(){if(!this.config.enabled||this.isRunning)return;this.isRunning=!0,this.logger.info("[Monitoring] Starting monitoring service");let collectIntervalMs=this.parseTimeToMs(this.config.system.collectInterval);if(this.collectInterval=setInterval(()=>{this.collect()},collectIntervalMs),this.config.persistence.enabled&&this.flushToDb){let flushIntervalMs=this.parseTimeToMs(this.config.persistence.flushInterval);this.flushInterval=setInterval(()=>{this.flush()},flushIntervalMs)}this.collect()}stop(){if(!this.isRunning)return;if(this.isRunning=!1,this.logger.info("[Monitoring] Stopping monitoring service"),this.collectInterval)clearInterval(this.collectInterval),this.collectInterval=null;if(this.flushInterval)clearInterval(this.flushInterval),this.flushInterval=null;this.flush()}async collect(){let now=Date.now(),snapshot={timestamp:now};if(this.config.system.enabled){let systemMetrics=await this.systemCollector.collect();if(systemMetrics)snapshot.system=systemMetrics,this.addMetricPoints("system",systemMetrics,now)}if(this.config.application.enabled){let appMetrics=this.applicationCollector.collect();if(appMetrics)snapshot.application=appMetrics,this.addMetricPoints("application",appMetrics,now)}if(this.databaseCollector){let dbMetrics=await this.databaseCollector.collect();if(dbMetrics)snapshot.database=dbMetrics,this.addMetricPoints("database",dbMetrics,now)}if(this.redisCollector){let redisMetrics=await this.redisCollector.collect();if(redisMetrics)snapshot.redis=redisMetrics,this.addMetricPoints("redis",redisMetrics,now)}if(await this.recordPeaks(snapshot,now),await this.storeSnapshot(snapshot),this.config.alerts.enabled)await this.alertService.checkAndAlert(snapshot)}addMetricPoints(type,metrics,timestamp){if(!this.config.persistence.samples)return;let flatten=(obj,prefix="")=>{for(let key in obj){let value=obj[key],newKey=prefix?`${prefix}.${key}`:key;if(typeof value==="number")this.pendingMetrics.push({timestamp,metricType:type,metricName:newKey,value});else if(typeof value==="object"&&value!==null&&!Array.isArray(value))flatten(value,newKey)}};flatten(metrics)}async seedPeaks(){if(this.peaksSeeded)return;if(!this.loadPeaks)return;try{for(let row of await this.loadPeaks()){if(!row.metricName||this.peaks.has(row.metricName))continue;this.peaks.set(row.metricName,{value:row.value,at:row.at,context:row.context})}this.peaksSeeded=!0}catch(error){this.logger.warn(`[Monitoring] peak seed failed, peaks not recorded this cycle: ${error}`)}}async recordPeaks(snapshot,timestamp){if(await this.seedPeaks(),!this.peaksSeeded)return;let previous=new Map([...this.peaks].map(([name,peak])=>[name,peak.value])),{points}=newPeaks(snapshot,previous,timestamp);for(let point of points)this.peaks.set(point.metricName,{value:point.value,at:timestamp,context:point.tags}),this.pendingMetrics.push(point),this.logger.info(`[Monitoring] yeni tepe \u2014 ${point.metricName} = ${point.value}`);if(this.peaks.size>0)snapshot.peaks=Object.fromEntries(this.peaks)}async appendToHistoryList(client,historyKey,snapshot){let append=async()=>{let results=await client.multi().rpush(historyKey,JSON.stringify(snapshot)).ltrim(historyKey,-HISTORY_MAX_SNAPSHOTS,-1).expire(historyKey,3600).exec();if(!results)return Error("history MULTI returned no result");for(let[error]of results)if(error)return error;return null};try{let first=await append();if(!first)return!0;if(/WRONGTYPE/i.test(first.message)){this.logger.warn("[Monitoring] history key held the pre-0.9.988 shape; replacing it with a list (the hour before this upgrade is lost)"),await client.del(historyKey);let second=await append();if(!second)return!0;return this.logger.warn(`[Monitoring] history append failed after reset: ${second.message}`),!1}return this.logger.warn(`[Monitoring] history append failed: ${first.message}`),!1}catch(error){return this.logger.warn(`[Monitoring] history append threw: ${error instanceof Error?error.message:String(error)}`),!1}}async storeSnapshot(snapshot){let key=`monitoring:${this.appId}:latest`;await this.redis.create(key,snapshot,3600);let historyKey=`monitoring:${this.appId}:history`,client=this.redis.getDirectClient?.();if(client){if(await this.appendToHistoryList(client,historyKey,snapshot))return}let historyResult=await this.redis.read(historyKey),history=historyResult.success&&historyResult.data?historyResult.data:[];history.push(snapshot);let oneHourAgo=Date.now()-3600000,filteredHistory=history.filter((s)=>s.timestamp>oneHourAgo);await this.redis.create(historyKey,filteredHistory,3600)}async flush(){if(this.pendingMetrics.length===0)return;if(!this.flushToDb)return;let metricsToFlush=[...this.pendingMetrics];this.pendingMetrics=[];try{await this.flushToDb(metricsToFlush),this.logger.debug(`[Monitoring] Flushed ${metricsToFlush.length} metrics to database`)}catch(error){this.logger.error(`[Monitoring] Failed to flush metrics: ${error}`),this.pendingMetrics=[...metricsToFlush,...this.pendingMetrics]}}recordRequest(params){if(!this.config.enabled||!this.config.application.enabled)return;this.applicationCollector.recordRequest(params)}recordRateLimitBlock(){if(!this.config.enabled||!this.config.application.enabled)return;this.applicationCollector.recordRateLimitBlock()}async getLatestSnapshot(){let key=`monitoring:${this.appId}:latest`,result=await this.redis.read(key);return result.success?result.data:null}async getHistory(minutes=60){let key=`monitoring:${this.appId}:history`,cutoff=Date.now()-minutes*60000,client=this.redis.getDirectClient?.();if(client)try{if(await client.type(key)==="list"){let rows=await client.lrange(key,0,-1),out=[];for(let row of rows)try{let snapshot=JSON.parse(row);if(snapshot.timestamp>cutoff)out.push(snapshot)}catch{}return out}}catch{}let result=await this.redis.read(key);if(!result.success||!result.data)return[];return result.data.filter((s)=>s.timestamp>cutoff)}getActiveAlerts(){return this.alertService.getActiveAlerts()}acknowledgeAlert(alertId){return this.alertService.acknowledgeAlert(alertId)}isEnabled(){return this.config.enabled}getConfig(){return this.config}}var HISTORY_MAX_SNAPSHOTS=360,DEFAULT_CONFIG3;var init_Monitoring=__esm(()=>{init_AlertService();init_ApplicationCollector();init_SystemCollector();init_peaks();init_AlertService();init_LiveMonitoringService();init_lifecycle();DEFAULT_CONFIG3={enabled:!1,system:{enabled:!0,collectInterval:"10s",metrics:{cpu:!0,memory:!0,disk:!0,network:!1,process:!0}},application:{enabled:!0,metrics:{requests:!0,responseTime:!0,errors:!0,rateLimits:!0}},database:{enabled:!1,metrics:{connections:!0,queryTime:!0,slowQueryThreshold:"100ms"}},redis:{enabled:!1},persistence:{enabled:!0,samples:!1,flushInterval:"1m",retentionDays:30},alerts:{enabled:!1,email:{enabled:!1,recipients:[]},thresholds:{cpuPercent:80,memoryPercent:85,diskPercent:90,errorRatePercent:5,responseTimeMs:1000,rateLimitBlocksPerMinute:100},cooldown:"5m"}}});function snakeToCamel(name){return name.replace(/_([a-z])/g,(_,c)=>c.toUpperCase())}function resolveSchemaTable(schemaTables,name,logger2){let direct=schemaTables[name];if(direct)return direct;let camel=snakeToCamel(name);if(camel!==name){let viaCamel=schemaTables[camel];if(viaCamel)return viaCamel}if(logger2){let keys=Object.keys(schemaTables),preview=keys.slice(0,20).join(", "),suffix=keys.length>20?`, ...(+${keys.length-20} more)`:"";logger2.warn(`[SchemaTables] Table "${name}" not found (tried "${name}" and "${camel}"). Known keys: ${preview}${suffix}`)}return}var init_types4=()=>{};import{and as and6,desc,eq as eq10,inArray as inArray2}from"drizzle-orm";function resolveEnvValue2(value){if(!value)return;return process.env[value]??value}function toCamel(obj){let result={};for(let[key,value]of Object.entries(obj)){let camelKey=key.replace(/_([a-z])/g,(_,c)=>c.toUpperCase());result[camelKey]=value}return result}function fromCamel(obj){let result={};for(let[key,value]of Object.entries(obj)){let snakeKey=key.replace(/[A-Z]/g,(c)=>`_${c.toLowerCase()}`);result[snakeKey]=value}return result}class NotificationService{db;schemaTables;config;logger;getEmailService;constructor(serviceConfig){this.db=serviceConfig.db,this.schemaTables=serviceConfig.schemaTables,this.config=serviceConfig.config,this.logger=serviceConfig.logger,this.getEmailService=serviceConfig.getEmailService}getTable(name){return resolveSchemaTable(this.schemaTables,name,this.logger)}getCol(table,col4){return table[col4]}isChannelEnabled(channel){let channels=this.config.channels;if(!channels)return channel==="portal";switch(channel){case"portal":return channels.portal!==!1;case"email":return channels.email===!0;case"telegram":return channels.telegram?.enabled===!0;case"webhook":return channels.webhook?.enabled===!0;default:return!1}}interpolateTemplate(template,context){let result=template;for(let[key,value]of Object.entries(context))result=result.replace(new RegExp(`{{${key}}}`,"g"),()=>String(value??""));for(let[key,value]of Object.entries(this.config.templateVariables||{}))result=result.replace(new RegExp(`{{${key}}}`,"g"),value);return result}async triggerNotifications(params){let{trigger,flow_id,entity_name,entity_id,node_id,context={}}=params,rulesTable=this.getTable("verificationNotificationRules"),recipientsTable=this.getTable("verificationNotificationRecipients"),channelsTable=this.getTable("verificationNotificationChannels");if(!rulesTable||!recipientsTable){this.logger.warn("[Notification] Notification tables not found");return}let now=new Date,rules=await this.db.select().from(rulesTable).where(and6(eq10(this.getCol(rulesTable,"flowId"),flow_id),eq10(this.getCol(rulesTable,"trigger"),trigger)));this.logger.info(`[Notification] Found ${rules.length} rules for trigger=${trigger} flow_id=${flow_id}, filter_node_id=${node_id||"NONE"}`);for(let r of rules)this.logger.info(`[Notification] Rule ${r.id}: nodeId=${r.nodeId}, trigger=${r.trigger}, title=${JSON.stringify(r.titleTemplate)}`);let filteredRules=rules.filter((rule)=>{if(node_id&&rule.nodeId!==node_id)return this.logger.info(`[Notification] EXCLUDED rule ${rule.id}: rule.nodeId=${rule.nodeId} !== filter_node_id=${node_id}`),!1;if(rule.startsAt&&new Date(rule.startsAt)>now)return!1;if(rule.expiresAt&&new Date(rule.expiresAt)<now)return!1;return!0});for(let rule of filteredRules){let recipients=await this.db.select().from(recipientsTable).where(eq10(this.getCol(recipientsTable,"ruleId"),rule.id)),ruleChannels=["portal"];if(channelsTable){let channelEntries=await this.db.select().from(channelsTable).where(eq10(this.getCol(channelsTable,"ruleId"),rule.id));if(channelEntries.length>0)ruleChannels=channelEntries.map((c)=>c.channel)}let enabledChannels=ruleChannels.filter((ch)=>this.isChannelEnabled(ch));if(enabledChannels.length===0)continue;this.logger.info(`[Notification] Rule ${rule.id}: ${recipients.length} recipients, ${enabledChannels.length} channels (${enabledChannels.join(",")})`);let userIds=await this.resolveRecipients(recipients,params.verifier_id,flow_id,entity_name,entity_id);this.logger.info(`[Notification] Rule ${rule.id}: resolved ${userIds.length} user IDs: ${userIds.join(", ")}`);let enrichedContext={...context,entity_name,entity_id,trigger,decision:params.decision};this.logger.info(`[Notification] Rule ${rule.id}: titleTemplate=${JSON.stringify(rule.titleTemplate)}, bodyTemplate=${JSON.stringify(rule.bodyTemplate)}, context=${JSON.stringify(enrichedContext)}`);let title=rule.titleTemplate?this.interpolateTemplate(rule.titleTemplate,enrichedContext):`Verification ${trigger.replace("on_","").replace("_"," ")}`,body=rule.bodyTemplate?this.interpolateTemplate(rule.bodyTemplate,enrichedContext):void 0;this.logger.info(`[Notification] Rule ${rule.id}: final title="${title}", body="${body}"`);for(let userId of userIds){let report=await this.send({user_id:userId,title,body,entity_name,entity_id,type:"verification",source:`flow:${flow_id}`,channels:enabledChannels});if(report.delivered.length===0)this.logger.warn(`[Notification] Rule ${rule.id}: nothing delivered to ${userId} \u2014 ${report.undelivered.map((one)=>`${one.channel}: ${one.reason}`).join("; ")}`)}}this.logger.debug(`[Notification] Triggered ${filteredRules.length} rules for ${trigger} on ${entity_name}:${entity_id}`)}async usersAwaitedNow(entityName,entityId){if(!entityName||!entityId)return[];let instancesTable=this.getTable("verificationInstances"),requirementsTable=this.getTable("verificationRequirements"),userRolesTable=this.getTable("userRoles"),rolesTable=this.getTable("roles");if(!instancesTable||!requirementsTable)return[];let inst=(await this.db.select().from(instancesTable).where(and6(eq10(this.getCol(instancesTable,"entityName"),entityName),eq10(this.getCol(instancesTable,"entityId"),entityId),eq10(this.getCol(instancesTable,"status"),"active"))).orderBy(desc(this.getCol(instancesTable,"createdAt"))).limit(1))[0];if(!inst)return[];let pending=await this.db.select().from(requirementsTable).where(and6(eq10(this.getCol(requirementsTable,"instanceId"),inst.id),eq10(this.getCol(requirementsTable,"status"),"pending"))),out=new Set;for(let req of pending){if(req.verifierUserId){out.add(req.verifierUserId);continue}let roleName=req.verifierRole;if(!roleName||!rolesTable||!userRolesTable)continue;let rCols=rolesTable,urCols=userRolesTable,members=await this.db.select({user_id:urCols.userId}).from(userRolesTable).innerJoin(rolesTable,eq10(urCols.roleId,rCols.id)).where(and6(eq10(rCols.name,roleName),activeRowsPredicate(rolesTable),activeRowsPredicate(userRolesTable)));for(let m of members)out.add(m.user_id)}return[...out]}async resolveRecipients(recipients,currentVerifierId,flowId,entityName,entityId){let userIds=new Set,userRolesTable=this.getTable("userRoles"),rolesTable=this.getTable("roles");for(let recipient of recipients)switch(recipient.recipientType){case"user":if(recipient.recipientUserId)userIds.add(recipient.recipientUserId);break;case"role":if(recipient.recipientRole&&userRolesTable&&rolesTable){let rolesCols=rolesTable,userRolesCols=userRolesTable,role=(await this.db.select().from(rolesTable).where(and6(eq10(rolesCols.name,recipient.recipientRole),activeRowsPredicate(rolesTable))).limit(1))[0];if(role){let usersInRole=await this.db.select({user_id:userRolesCols.userId}).from(userRolesTable).where(and6(eq10(userRolesCols.roleId,role.id),activeRowsPredicate(userRolesTable)));for(let ur of usersInRole)userIds.add(ur.user_id)}}break;case"step_verifier":{if(currentVerifierId){userIds.add(currentVerifierId);break}for(let uid of await this.usersAwaitedNow(entityName,entityId))userIds.add(uid);break}case"entity_creator":{if(entityName&&entityId){let instancesTable=this.getTable("verificationInstances");if(instancesTable){let inst=(await this.db.select().from(instancesTable).where(and6(eq10(this.getCol(instancesTable,"entityName"),entityName),eq10(this.getCol(instancesTable,"entityId"),entityId))).orderBy(desc(this.getCol(instancesTable,"createdAt"))).limit(1))[0];if(inst?.startedBy)userIds.add(inst.startedBy)}}break}case"all_verifiers":{if(flowId){let verifierConfigsTable=this.getTable("verificationVerifierConfigs");if(verifierConfigsTable){let configs=await this.db.select().from(verifierConfigsTable).where(eq10(this.getCol(verifierConfigsTable,"flowId"),flowId));this.logger.info(`[Notification] all_verifiers: found ${configs.length} verifier configs for flow ${flowId}`);for(let cfg of configs){let row=cfg;if(this.logger.info(`[Notification] all_verifiers: config node_id=${row.nodeId}, type=${row.verifierType}, userId=${row.verifierUserId}, role=${row.verifierRole}`),row.verifierUserId)userIds.add(row.verifierUserId);if(row.verifierType==="role"&&row.verifierRole&&userRolesTable&&rolesTable){let rCols=rolesTable,urCols=userRolesTable,roleRow=(await this.db.select().from(rolesTable).where(eq10(rCols.name,row.verifierRole)).limit(1))[0];if(roleRow){let usersInRole=await this.db.select({user_id:urCols.userId}).from(userRolesTable).where(eq10(urCols.roleId,roleRow.id));for(let ur of usersInRole)userIds.add(ur.user_id)}}}}}break}}let usersTable=this.getTable("users");if(usersTable&&userIds.size>0)return(await this.db.select({id:this.getCol(usersTable,"id")}).from(usersTable).where(and6(inArray2(this.getCol(usersTable,"id"),Array.from(userIds)),activeRowsPredicate(usersTable)))).map((r)=>r.id);return Array.from(userIds)}async send(params){let{user_id,title,body,entity_name,entity_id,type,source,channels}=params,delivered=[],undelivered=[],record=(channel,reason)=>{if(reason===null)delivered.push(channel);else undelivered.push({channel,reason})};for(let channel of channels){if(!this.isChannelEnabled(channel)){this.logger.debug?.(`[Notification] channel "${channel}" is disabled in config \u2014 skipping for user ${user_id}`),record(channel,"the channel is disabled in config");continue}switch(channel){case"portal":record(channel,await this.sendPortalNotification(user_id,title,body,entity_name,entity_id,type,source));break;case"email":record(channel,await this.sendEmailNotification(user_id,title,body));break;case"sms":this.logger.warn(`[Notification] SMS delivery is not supported \u2014 skipping sms notification for user ${user_id}`),record(channel,"sms delivery is not supported");break;case"telegram":record(channel,await this.sendTelegramNotification(params));break;case"webhook":record(channel,await this.sendWebhookNotification(params));break}}return{delivered,undelivered}}async sendPortalNotification(userId,title,body,entityName,entityId,type,source){let notificationsTable=this.getTable("notifications");if(title.length>255)title=`${title.slice(0,254)}\u2026`;if(body&&body.length>1000)body=`${body.slice(0,999)}\u2026`;if(!notificationsTable)return this.logger.warn("[Notification] notifications table not found"),"the notifications table was not found";return await this.db.insert(notificationsTable).values(toCamel({user_id:userId,title,body:body||null,entity_name:entityName||null,entity_id:entityId||null,type:type||"system",source:source||null,is_seen:!1})),this.logger.debug(`[Notification] Portal notification sent to ${userId}: ${title}`),null}async sendTelegramNotification(params){let telegramConfig=this.config.channels?.telegram,botToken=resolveEnvValue2(telegramConfig?.botToken),chatId=resolveEnvValue2(telegramConfig?.chatId);if(!botToken||!chatId)return this.logger.warn("[Notification] Telegram channel enabled but botToken/chatId is missing or unresolved \u2014 skipping delivery"),"the telegram botToken/chatId is missing or unresolved";let text=params.body?`*${params.title}*
|
|
97
|
+
`),at:this.now()}},await this.write()}async recordStop(reason){this.beat={...this.beat,lastSeenAt:this.now(),stoppedAt:this.now(),stopReason:reason},await this.write()}async incidents(limit=KEEP_INCIDENTS){try{return(await this.backend.list(INCIDENTS_KEY,limit)).map((row)=>JSON.parse(row))}catch{return[]}}async write(){try{await this.backend.set(HEARTBEAT_KEY,JSON.stringify(this.beat))}catch{}}}var HEARTBEAT_KEY="nucleus:lifecycle:heartbeat",INCIDENTS_KEY="nucleus:lifecycle:incidents",KEEP_INCIDENTS=50;var init_store=()=>{};var init_lifecycle=__esm(()=>{init_store()});class MonitoringService{redis;logger;config;appId;flushToDb;systemCollector;applicationCollector;databaseCollector=null;redisCollector=null;alertService;collectInterval=null;flushInterval=null;pendingMetrics=[];isRunning=!1;peaks=new Map;peaksSeeded=!1;dbQuery;loadPeaks;constructor(deps){if(this.redis=deps.redis,this.logger=deps.logger,this.config=this.mergeConfig(deps.config),this.appId=deps.appId,this.flushToDb=deps.flushToDb,this.dbQuery=deps.dbQuery,this.loadPeaks=deps.loadPeaks,this.config.database.enabled)if(deps.dbQuery)this.databaseCollector=new DatabaseCollector(this.config.database,deps.dbQuery);else this.logger.warn("[Monitoring] monitoring.database is enabled but no database query executor is wired \u2014 database metrics are skipped");if(this.config.redis.enabled)if(deps.redis.getDirectClient())this.redisCollector=new RedisCollector(()=>deps.redis.getDirectClient());else this.logger.warn("[Monitoring] monitoring.redis is enabled but Redis runs in Dapr mode \u2014 INFO is unavailable, Redis metrics are skipped");if(this.config.persistence.enabled&&!deps.flushToDb)this.logger.warn("[Monitoring] monitoring.persistence is enabled but no flushToDb handler is wired \u2014 metrics are kept in Redis only");this.systemCollector=new SystemCollector(this.config.system),this.applicationCollector=new ApplicationCollector(this.config.application),this.alertService=new AlertService({logger:deps.logger,getEmailService:deps.getEmailService,config:this.config,appId:deps.appId})}mergeConfig(config){return{enabled:config.enabled??DEFAULT_CONFIG3.enabled,system:{enabled:config.system?.enabled??DEFAULT_CONFIG3.system.enabled,collectInterval:config.system?.collectInterval??DEFAULT_CONFIG3.system.collectInterval,metrics:{cpu:config.system?.metrics?.cpu??DEFAULT_CONFIG3.system.metrics.cpu,memory:config.system?.metrics?.memory??DEFAULT_CONFIG3.system.metrics.memory,disk:config.system?.metrics?.disk??DEFAULT_CONFIG3.system.metrics.disk,network:config.system?.metrics?.network??DEFAULT_CONFIG3.system.metrics.network,process:config.system?.metrics?.process??DEFAULT_CONFIG3.system.metrics.process}},application:{enabled:config.application?.enabled??DEFAULT_CONFIG3.application.enabled,metrics:{requests:config.application?.metrics?.requests??DEFAULT_CONFIG3.application.metrics.requests,responseTime:config.application?.metrics?.responseTime??DEFAULT_CONFIG3.application.metrics.responseTime,errors:config.application?.metrics?.errors??DEFAULT_CONFIG3.application.metrics.errors,rateLimits:config.application?.metrics?.rateLimits??DEFAULT_CONFIG3.application.metrics.rateLimits}},database:{enabled:config.database?.enabled??DEFAULT_CONFIG3.database.enabled,metrics:{connections:config.database?.metrics?.connections??DEFAULT_CONFIG3.database.metrics.connections,queryTime:config.database?.metrics?.queryTime??DEFAULT_CONFIG3.database.metrics.queryTime,slowQueryThreshold:config.database?.metrics?.slowQueryThreshold??DEFAULT_CONFIG3.database.metrics.slowQueryThreshold}},redis:{enabled:config.redis?.enabled??DEFAULT_CONFIG3.redis.enabled},persistence:{enabled:config.persistence?.enabled??DEFAULT_CONFIG3.persistence.enabled,samples:config.persistence?.samples??DEFAULT_CONFIG3.persistence.samples,flushInterval:config.persistence?.flushInterval??DEFAULT_CONFIG3.persistence.flushInterval,retentionDays:config.persistence?.retentionDays??DEFAULT_CONFIG3.persistence.retentionDays},alerts:{enabled:config.alerts?.enabled??DEFAULT_CONFIG3.alerts.enabled,email:{enabled:config.alerts?.email?.enabled??DEFAULT_CONFIG3.alerts.email.enabled,recipients:config.alerts?.email?.recipients??DEFAULT_CONFIG3.alerts.email.recipients},thresholds:{cpuPercent:config.alerts?.thresholds?.cpuPercent??DEFAULT_CONFIG3.alerts.thresholds.cpuPercent,memoryPercent:config.alerts?.thresholds?.memoryPercent??DEFAULT_CONFIG3.alerts.thresholds.memoryPercent,diskPercent:config.alerts?.thresholds?.diskPercent??DEFAULT_CONFIG3.alerts.thresholds.diskPercent,errorRatePercent:config.alerts?.thresholds?.errorRatePercent??DEFAULT_CONFIG3.alerts.thresholds.errorRatePercent,responseTimeMs:config.alerts?.thresholds?.responseTimeMs??DEFAULT_CONFIG3.alerts.thresholds.responseTimeMs,rateLimitBlocksPerMinute:config.alerts?.thresholds?.rateLimitBlocksPerMinute??DEFAULT_CONFIG3.alerts.thresholds.rateLimitBlocksPerMinute},cooldown:config.alerts?.cooldown??DEFAULT_CONFIG3.alerts.cooldown}}}parseTimeToMs(time){let match=time.match(/^(\d+)(ms|s|m|h|d)$/);if(!match||!match[1]||!match[2])return 1e4;let value=parseInt(match[1],10);switch(match[2]){case"ms":return value;case"s":return value*1000;case"m":return value*60*1000;case"h":return value*60*60*1000;case"d":return value*24*60*60*1000;default:return 1e4}}start(){if(!this.config.enabled||this.isRunning)return;this.isRunning=!0,this.logger.info("[Monitoring] Starting monitoring service");let collectIntervalMs=this.parseTimeToMs(this.config.system.collectInterval);if(this.collectInterval=setInterval(()=>{this.collect()},collectIntervalMs),this.config.persistence.enabled&&this.flushToDb){let flushIntervalMs=this.parseTimeToMs(this.config.persistence.flushInterval);this.flushInterval=setInterval(()=>{this.flush()},flushIntervalMs)}this.collect()}stop(){if(!this.isRunning)return;if(this.isRunning=!1,this.logger.info("[Monitoring] Stopping monitoring service"),this.collectInterval)clearInterval(this.collectInterval),this.collectInterval=null;if(this.flushInterval)clearInterval(this.flushInterval),this.flushInterval=null;this.flush()}async collect(){let now=Date.now(),snapshot={timestamp:now};if(this.config.system.enabled){let systemMetrics=await this.systemCollector.collect();if(systemMetrics)snapshot.system=systemMetrics,this.addMetricPoints("system",systemMetrics,now)}if(this.config.application.enabled){let appMetrics=this.applicationCollector.collect();if(appMetrics)snapshot.application=appMetrics,this.addMetricPoints("application",appMetrics,now)}if(this.databaseCollector){let dbMetrics=await this.databaseCollector.collect();if(dbMetrics)snapshot.database=dbMetrics,this.addMetricPoints("database",dbMetrics,now)}if(this.redisCollector){let redisMetrics=await this.redisCollector.collect();if(redisMetrics)snapshot.redis=redisMetrics,this.addMetricPoints("redis",redisMetrics,now)}if(await this.recordPeaks(snapshot,now),await this.storeSnapshot(snapshot),this.config.alerts.enabled)await this.alertService.checkAndAlert(snapshot)}addMetricPoints(type,metrics,timestamp){if(!this.config.persistence.samples)return;let flatten=(obj,prefix="")=>{for(let key in obj){let value=obj[key],newKey=prefix?`${prefix}.${key}`:key;if(typeof value==="number")this.pendingMetrics.push({timestamp,metricType:type,metricName:newKey,value});else if(typeof value==="object"&&value!==null&&!Array.isArray(value))flatten(value,newKey)}};flatten(metrics)}async seedPeaks(){if(this.peaksSeeded)return;if(!this.loadPeaks)return;try{for(let row of await this.loadPeaks()){if(!row.metricName||this.peaks.has(row.metricName))continue;this.peaks.set(row.metricName,{value:row.value,at:row.at,context:row.context})}this.peaksSeeded=!0}catch(error){this.logger.warn(`[Monitoring] peak seed failed, peaks not recorded this cycle: ${error}`)}}async recordPeaks(snapshot,timestamp){if(await this.seedPeaks(),!this.peaksSeeded)return;let previous=new Map([...this.peaks].map(([name,peak])=>[name,peak.value])),{points}=newPeaks(snapshot,previous,timestamp);for(let point of points)this.peaks.set(point.metricName,{value:point.value,at:timestamp,context:point.tags}),this.pendingMetrics.push(point),this.logger.info(`[Monitoring] yeni tepe \u2014 ${point.metricName} = ${point.value}`);if(this.peaks.size>0)snapshot.peaks=Object.fromEntries(this.peaks)}async appendToHistoryList(client,historyKey,snapshot){let append=async()=>{let results=await client.multi().rpush(historyKey,JSON.stringify(snapshot)).ltrim(historyKey,-HISTORY_MAX_SNAPSHOTS,-1).expire(historyKey,3600).exec();if(!results)return Error("history MULTI returned no result");for(let[error]of results)if(error)return error;return null};try{let first=await append();if(!first)return!0;if(/WRONGTYPE/i.test(first.message)){this.logger.warn("[Monitoring] history key held the pre-0.9.988 shape; replacing it with a list (the hour before this upgrade is lost)"),await client.del(historyKey);let second=await append();if(!second)return!0;return this.logger.warn(`[Monitoring] history append failed after reset: ${second.message}`),!1}return this.logger.warn(`[Monitoring] history append failed: ${first.message}`),!1}catch(error){return this.logger.warn(`[Monitoring] history append threw: ${error instanceof Error?error.message:String(error)}`),!1}}async storeSnapshot(snapshot){let key=`monitoring:${this.appId}:latest`;await this.redis.create(key,snapshot,3600);let historyKey=`monitoring:${this.appId}:history`,client=this.redis.getDirectClient?.();if(client){if(await this.appendToHistoryList(client,historyKey,snapshot))return}let historyResult=await this.redis.read(historyKey),history=historyResult.success&&historyResult.data?historyResult.data:[];history.push(snapshot);let oneHourAgo=Date.now()-3600000,filteredHistory=history.filter((s)=>s.timestamp>oneHourAgo);await this.redis.create(historyKey,filteredHistory,3600)}async flush(){if(this.pendingMetrics.length===0)return;if(!this.flushToDb)return;let metricsToFlush=[...this.pendingMetrics];this.pendingMetrics=[];try{await this.flushToDb(metricsToFlush),this.logger.debug(`[Monitoring] Flushed ${metricsToFlush.length} metrics to database`)}catch(error){this.logger.error(`[Monitoring] Failed to flush metrics: ${error}`),this.pendingMetrics=[...metricsToFlush,...this.pendingMetrics]}}recordRequest(params){if(!this.config.enabled||!this.config.application.enabled)return;this.applicationCollector.recordRequest(params)}recordRateLimitBlock(){if(!this.config.enabled||!this.config.application.enabled)return;this.applicationCollector.recordRateLimitBlock()}async getLatestSnapshot(){let key=`monitoring:${this.appId}:latest`,result=await this.redis.read(key);return result.success?result.data:null}async getHistory(minutes=60){let key=`monitoring:${this.appId}:history`,cutoff=Date.now()-minutes*60000,client=this.redis.getDirectClient?.();if(client)try{if(await client.type(key)==="list"){let rows=await client.lrange(key,0,-1),out=[];for(let row of rows)try{let snapshot=JSON.parse(row);if(snapshot.timestamp>cutoff)out.push(snapshot)}catch{}return out}}catch{}let result=await this.redis.read(key);if(!result.success||!result.data)return[];return result.data.filter((s)=>s.timestamp>cutoff)}getActiveAlerts(){return this.alertService.getActiveAlerts()}acknowledgeAlert(alertId){return this.alertService.acknowledgeAlert(alertId)}isEnabled(){return this.config.enabled}getConfig(){return this.config}}var HISTORY_MAX_SNAPSHOTS=360,DEFAULT_CONFIG3;var init_Monitoring=__esm(()=>{init_AlertService();init_ApplicationCollector();init_SystemCollector();init_peaks();init_AlertService();init_LiveMonitoringService();init_lifecycle();DEFAULT_CONFIG3={enabled:!1,system:{enabled:!0,collectInterval:"10s",metrics:{cpu:!0,memory:!0,disk:!0,network:!1,process:!0}},application:{enabled:!0,metrics:{requests:!0,responseTime:!0,errors:!0,rateLimits:!0}},database:{enabled:!1,metrics:{connections:!0,queryTime:!0,slowQueryThreshold:"100ms"}},redis:{enabled:!1},persistence:{enabled:!0,samples:!1,flushInterval:"1m",retentionDays:30},alerts:{enabled:!1,email:{enabled:!1,recipients:[]},thresholds:{cpuPercent:80,memoryPercent:85,diskPercent:90,errorRatePercent:5,responseTimeMs:1000,rateLimitBlocksPerMinute:100},cooldown:"5m"}}});function snakeToCamel(name){return name.replace(/_([a-z])/g,(_,c)=>c.toUpperCase())}function resolveSchemaTable(schemaTables,name,logger2){let direct=schemaTables[name];if(direct)return direct;let camel=snakeToCamel(name);if(camel!==name){let viaCamel=schemaTables[camel];if(viaCamel)return viaCamel}if(logger2){let keys=Object.keys(schemaTables),preview=keys.slice(0,20).join(", "),suffix=keys.length>20?`, ...(+${keys.length-20} more)`:"";logger2.warn(`[SchemaTables] Table "${name}" not found (tried "${name}" and "${camel}"). Known keys: ${preview}${suffix}`)}return}var init_types4=()=>{};import{and as and6,desc,eq as eq10,inArray as inArray2}from"drizzle-orm";function resolveEnvValue2(value){if(!value)return;return process.env[value]??value}function toCamel(obj){let result={};for(let[key,value]of Object.entries(obj)){let camelKey=key.replace(/_([a-z])/g,(_,c)=>c.toUpperCase());result[camelKey]=value}return result}function fromCamel(obj){let result={};for(let[key,value]of Object.entries(obj)){let snakeKey=key.replace(/[A-Z]/g,(c)=>`_${c.toLowerCase()}`);result[snakeKey]=value}return result}class NotificationService{db;schemaTables;config;logger;getEmailService;constructor(serviceConfig){this.db=serviceConfig.db,this.schemaTables=serviceConfig.schemaTables,this.config=serviceConfig.config,this.logger=serviceConfig.logger,this.getEmailService=serviceConfig.getEmailService}getTable(name){return resolveSchemaTable(this.schemaTables,name,this.logger)}getCol(table,col4){return table[col4]}isChannelEnabled(channel){let channels=this.config.channels;if(!channels)return channel==="portal";switch(channel){case"portal":return channels.portal!==!1;case"email":return channels.email===!0;case"telegram":return channels.telegram?.enabled===!0;case"webhook":return channels.webhook?.enabled===!0;default:return!1}}interpolateTemplate(template,context){let result=template;for(let[key,value]of Object.entries(context))result=result.replace(new RegExp(`{{${key}}}`,"g"),()=>String(value??""));for(let[key,value]of Object.entries(this.config.templateVariables||{}))result=result.replace(new RegExp(`{{${key}}}`,"g"),()=>value);return result}async triggerNotifications(params){let{trigger,flow_id,entity_name,entity_id,node_id,context={}}=params,rulesTable=this.getTable("verificationNotificationRules"),recipientsTable=this.getTable("verificationNotificationRecipients"),channelsTable=this.getTable("verificationNotificationChannels");if(!rulesTable||!recipientsTable){this.logger.warn("[Notification] Notification tables not found");return}let now=new Date,rules=await this.db.select().from(rulesTable).where(and6(eq10(this.getCol(rulesTable,"flowId"),flow_id),eq10(this.getCol(rulesTable,"trigger"),trigger)));this.logger.info(`[Notification] Found ${rules.length} rules for trigger=${trigger} flow_id=${flow_id}, filter_node_id=${node_id||"NONE"}`);for(let r of rules)this.logger.info(`[Notification] Rule ${r.id}: nodeId=${r.nodeId}, trigger=${r.trigger}, title=${JSON.stringify(r.titleTemplate)}`);let filteredRules=rules.filter((rule)=>{if(node_id&&rule.nodeId!==node_id)return this.logger.info(`[Notification] EXCLUDED rule ${rule.id}: rule.nodeId=${rule.nodeId} !== filter_node_id=${node_id}`),!1;if(rule.startsAt&&new Date(rule.startsAt)>now)return!1;if(rule.expiresAt&&new Date(rule.expiresAt)<now)return!1;return!0});for(let rule of filteredRules){let recipients=await this.db.select().from(recipientsTable).where(eq10(this.getCol(recipientsTable,"ruleId"),rule.id)),ruleChannels=["portal"];if(channelsTable){let channelEntries=await this.db.select().from(channelsTable).where(eq10(this.getCol(channelsTable,"ruleId"),rule.id));if(channelEntries.length>0)ruleChannels=channelEntries.map((c)=>c.channel)}let enabledChannels=ruleChannels.filter((ch)=>this.isChannelEnabled(ch));if(enabledChannels.length===0)continue;this.logger.info(`[Notification] Rule ${rule.id}: ${recipients.length} recipients, ${enabledChannels.length} channels (${enabledChannels.join(",")})`);let userIds=await this.resolveRecipients(recipients,params.verifier_id,flow_id,entity_name,entity_id);this.logger.info(`[Notification] Rule ${rule.id}: resolved ${userIds.length} user IDs: ${userIds.join(", ")}`);let enrichedContext={...context,entity_name,entity_id,trigger,decision:params.decision};this.logger.info(`[Notification] Rule ${rule.id}: titleTemplate=${JSON.stringify(rule.titleTemplate)}, bodyTemplate=${JSON.stringify(rule.bodyTemplate)}, context=${JSON.stringify(enrichedContext)}`);let title=rule.titleTemplate?this.interpolateTemplate(rule.titleTemplate,enrichedContext):`Verification ${trigger.replace("on_","").replace("_"," ")}`,body=rule.bodyTemplate?this.interpolateTemplate(rule.bodyTemplate,enrichedContext):void 0;this.logger.info(`[Notification] Rule ${rule.id}: final title="${title}", body="${body}"`);for(let userId of userIds){let report=await this.send({user_id:userId,title,body,entity_name,entity_id,type:"verification",source:`flow:${flow_id}`,channels:enabledChannels});if(report.delivered.length===0)this.logger.warn(`[Notification] Rule ${rule.id}: nothing delivered to ${userId} \u2014 ${report.undelivered.map((one)=>`${one.channel}: ${one.reason}`).join("; ")}`)}}this.logger.debug(`[Notification] Triggered ${filteredRules.length} rules for ${trigger} on ${entity_name}:${entity_id}`)}async usersAwaitedNow(entityName,entityId){if(!entityName||!entityId)return[];let instancesTable=this.getTable("verificationInstances"),requirementsTable=this.getTable("verificationRequirements"),userRolesTable=this.getTable("userRoles"),rolesTable=this.getTable("roles");if(!instancesTable||!requirementsTable)return[];let inst=(await this.db.select().from(instancesTable).where(and6(eq10(this.getCol(instancesTable,"entityName"),entityName),eq10(this.getCol(instancesTable,"entityId"),entityId),eq10(this.getCol(instancesTable,"status"),"active"))).orderBy(desc(this.getCol(instancesTable,"createdAt"))).limit(1))[0];if(!inst)return[];let pending=await this.db.select().from(requirementsTable).where(and6(eq10(this.getCol(requirementsTable,"instanceId"),inst.id),eq10(this.getCol(requirementsTable,"status"),"pending"))),out=new Set;for(let req of pending){if(req.verifierUserId){out.add(req.verifierUserId);continue}let roleName=req.verifierRole;if(!roleName||!rolesTable||!userRolesTable)continue;let rCols=rolesTable,urCols=userRolesTable,members=await this.db.select({user_id:urCols.userId}).from(userRolesTable).innerJoin(rolesTable,eq10(urCols.roleId,rCols.id)).where(and6(eq10(rCols.name,roleName),activeRowsPredicate(rolesTable),activeRowsPredicate(userRolesTable)));for(let m of members)out.add(m.user_id)}return[...out]}async resolveRecipients(recipients,currentVerifierId,flowId,entityName,entityId){let userIds=new Set,userRolesTable=this.getTable("userRoles"),rolesTable=this.getTable("roles");for(let recipient of recipients)switch(recipient.recipientType){case"user":if(recipient.recipientUserId)userIds.add(recipient.recipientUserId);break;case"role":if(recipient.recipientRole&&userRolesTable&&rolesTable){let rolesCols=rolesTable,userRolesCols=userRolesTable,role=(await this.db.select().from(rolesTable).where(and6(eq10(rolesCols.name,recipient.recipientRole),activeRowsPredicate(rolesTable))).limit(1))[0];if(role){let usersInRole=await this.db.select({user_id:userRolesCols.userId}).from(userRolesTable).where(and6(eq10(userRolesCols.roleId,role.id),activeRowsPredicate(userRolesTable)));for(let ur of usersInRole)userIds.add(ur.user_id)}}break;case"step_verifier":{if(currentVerifierId){userIds.add(currentVerifierId);break}for(let uid of await this.usersAwaitedNow(entityName,entityId))userIds.add(uid);break}case"entity_creator":{if(entityName&&entityId){let instancesTable=this.getTable("verificationInstances");if(instancesTable){let inst=(await this.db.select().from(instancesTable).where(and6(eq10(this.getCol(instancesTable,"entityName"),entityName),eq10(this.getCol(instancesTable,"entityId"),entityId))).orderBy(desc(this.getCol(instancesTable,"createdAt"))).limit(1))[0];if(inst?.startedBy)userIds.add(inst.startedBy)}}break}case"all_verifiers":{if(flowId){let verifierConfigsTable=this.getTable("verificationVerifierConfigs");if(verifierConfigsTable){let configs=await this.db.select().from(verifierConfigsTable).where(eq10(this.getCol(verifierConfigsTable,"flowId"),flowId));this.logger.info(`[Notification] all_verifiers: found ${configs.length} verifier configs for flow ${flowId}`);for(let cfg of configs){let row=cfg;if(this.logger.info(`[Notification] all_verifiers: config node_id=${row.nodeId}, type=${row.verifierType}, userId=${row.verifierUserId}, role=${row.verifierRole}`),row.verifierUserId)userIds.add(row.verifierUserId);if(row.verifierType==="role"&&row.verifierRole&&userRolesTable&&rolesTable){let rCols=rolesTable,urCols=userRolesTable,roleRow=(await this.db.select().from(rolesTable).where(eq10(rCols.name,row.verifierRole)).limit(1))[0];if(roleRow){let usersInRole=await this.db.select({user_id:urCols.userId}).from(userRolesTable).where(eq10(urCols.roleId,roleRow.id));for(let ur of usersInRole)userIds.add(ur.user_id)}}}}}break}}let usersTable=this.getTable("users");if(usersTable&&userIds.size>0)return(await this.db.select({id:this.getCol(usersTable,"id")}).from(usersTable).where(and6(inArray2(this.getCol(usersTable,"id"),Array.from(userIds)),activeRowsPredicate(usersTable)))).map((r)=>r.id);return Array.from(userIds)}async send(params){let{user_id,title,body,entity_name,entity_id,type,source,channels}=params,delivered=[],undelivered=[],record=(channel,reason)=>{if(reason===null)delivered.push(channel);else undelivered.push({channel,reason})};for(let channel of channels){if(!this.isChannelEnabled(channel)){this.logger.debug?.(`[Notification] channel "${channel}" is disabled in config \u2014 skipping for user ${user_id}`),record(channel,"the channel is disabled in config");continue}switch(channel){case"portal":record(channel,await this.sendPortalNotification(user_id,title,body,entity_name,entity_id,type,source));break;case"email":record(channel,await this.sendEmailNotification(user_id,title,body));break;case"sms":this.logger.warn(`[Notification] SMS delivery is not supported \u2014 skipping sms notification for user ${user_id}`),record(channel,"sms delivery is not supported");break;case"telegram":record(channel,await this.sendTelegramNotification(params));break;case"webhook":record(channel,await this.sendWebhookNotification(params));break}}return{delivered,undelivered}}async sendPortalNotification(userId,title,body,entityName,entityId,type,source){let notificationsTable=this.getTable("notifications");if(title.length>255)title=`${title.slice(0,254)}\u2026`;if(body&&body.length>1000)body=`${body.slice(0,999)}\u2026`;if(!notificationsTable)return this.logger.warn("[Notification] notifications table not found"),"the notifications table was not found";return await this.db.insert(notificationsTable).values(toCamel({user_id:userId,title,body:body||null,entity_name:entityName||null,entity_id:entityId||null,type:type||"system",source:source||null,is_seen:!1})),this.logger.debug(`[Notification] Portal notification sent to ${userId}: ${title}`),null}async sendTelegramNotification(params){let telegramConfig=this.config.channels?.telegram,botToken=resolveEnvValue2(telegramConfig?.botToken),chatId=resolveEnvValue2(telegramConfig?.chatId);if(!botToken||!chatId)return this.logger.warn("[Notification] Telegram channel enabled but botToken/chatId is missing or unresolved \u2014 skipping delivery"),"the telegram botToken/chatId is missing or unresolved";let text=params.body?`*${params.title}*
|
|
98
98
|
|
|
99
99
|
${params.body}`:`*${params.title}*`,timeoutMs=telegramConfig?.timeoutMs??DEFAULT_DELIVERY_TIMEOUT_MS;try{let response=await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:chatId,text,parse_mode:"Markdown"}),signal:AbortSignal.timeout(timeoutMs)});if(!response.ok){let errBody=await response.text().catch(()=>"");return this.logger.error(`[Notification] Telegram delivery failed (${response.status}): ${errBody.slice(0,300)}`),`telegram refused it (${response.status})`}return this.logger.debug(`[Notification] Telegram notification sent: ${params.title}`),null}catch(error){return this.logger.error(`[Notification] Telegram delivery error: ${error.message}`),`telegram delivery error: ${error.message}`}}async sendWebhookNotification(params){let webhookConfig=this.config.channels?.webhook,url=resolveEnvValue2(webhookConfig?.url);if(!url)return this.logger.warn("[Notification] Webhook channel enabled but url is missing or unresolved \u2014 skipping delivery"),"the webhook url is missing or unresolved";let headers={"Content-Type":"application/json"};for(let[key,value]of Object.entries(webhookConfig?.headers||{})){let resolved=resolveEnvValue2(value);if(resolved)headers[key]=resolved}let timeoutMs=webhookConfig?.timeoutMs??DEFAULT_DELIVERY_TIMEOUT_MS;try{let response=await fetch(url,{method:"POST",headers,body:JSON.stringify({user_id:params.user_id,title:params.title,body:params.body??null,entity_name:params.entity_name??null,entity_id:params.entity_id??null,type:params.type??"system",source:params.source??null,timestamp:new Date().toISOString()}),signal:AbortSignal.timeout(timeoutMs)});if(!response.ok){let errBody=await response.text().catch(()=>"");return this.logger.error(`[Notification] Webhook delivery failed (${response.status}): ${errBody.slice(0,300)}`),`the webhook refused it (${response.status})`}return this.logger.debug(`[Notification] Webhook notification sent: ${params.title}`),null}catch(error){return this.logger.error(`[Notification] Webhook delivery error: ${error.message}`),`webhook delivery error: ${error.message}`}}async sendEmailNotification(userId,title,body){let emailService=this.getEmailService?.();if(!emailService?.isAvailable())return this.logger.warn("[Notification] Email service not available for email notification"),"no email provider is configured or available";let usersTable=this.getTable("users");if(!usersTable)return this.logger.warn("[Notification] users table not found"),"the users table was not found";let user=(await this.db.select({email:this.getCol(usersTable,"email")}).from(usersTable).where(eq10(this.getCol(usersTable,"id"),userId)).limit(1))[0];if(!user?.email)return this.logger.warn(`[Notification] No email found for user ${userId}`),"the recipient has no email address on file";let report=await deliver(emailService,{to:user.email,subject:title,html:body||title},`[Notification] ${title}`);if(report.level==="warn")this.logger.warn(report.message);else this.logger.debug(report.message);return report.sent?null:report.message}async getNotifications(userId,options){let notificationsTable=this.getTable("notifications");if(!notificationsTable)return[];let conditions=[eq10(this.getCol(notificationsTable,"userId"),userId)];if(options?.type)conditions.push(eq10(this.getCol(notificationsTable,"type"),options.type));return(await this.db.select().from(notificationsTable).where(and6(...conditions)).orderBy(desc(this.getCol(notificationsTable,"createdAt"))).limit(options?.limit||50).offset(options?.offset||0)).map((r)=>fromCamel(r))}async getUnseenCount(userId){let notificationsTable=this.getTable("notifications");if(!notificationsTable)return 0;return(await this.db.select().from(notificationsTable).where(and6(eq10(this.getCol(notificationsTable,"userId"),userId),eq10(this.getCol(notificationsTable,"isSeen"),!1)))).length}async markAsSeen(notificationId,userId){let notificationsTable=this.getTable("notifications");if(!notificationsTable)return!1;return(await this.db.update(notificationsTable).set(toCamel({is_seen:!0,seen_at:new Date})).where(and6(eq10(this.getCol(notificationsTable,"id"),notificationId),eq10(this.getCol(notificationsTable,"userId"),userId))).returning()).length>0}async markAllAsSeen(userId){let notificationsTable=this.getTable("notifications");if(!notificationsTable)return 0;return(await this.db.update(notificationsTable).set(toCamel({is_seen:!0,seen_at:new Date})).where(and6(eq10(this.getCol(notificationsTable,"userId"),userId),eq10(this.getCol(notificationsTable,"isSeen"),!1))).returning()).length}}var DEFAULT_DELIVERY_TIMEOUT_MS=1e4;var init_Notification=__esm(()=>{init_activeRows();init_types4()});function buildGenericAuthUrl(config,state){if(!config.authorizationUrl)throw Error("Generic OAuth provider requires authorizationUrl");let scopes2=config.scopes??[],params=new URLSearchParams({client_id:config.clientId,redirect_uri:config.redirectUri,response_type:"code",state,...scopes2.length>0?{scope:scopes2.join(" ")}:{},...config.extraAuthParams});return`${config.authorizationUrl}?${params.toString()}`}async function exchangeGenericCode(code,config){if(!config.tokenUrl)throw Error("Generic OAuth provider requires tokenUrl");let tokenRes=await fetch(config.tokenUrl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams({code,client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri,grant_type:"authorization_code"}).toString()});if(!tokenRes.ok){let err=await tokenRes.text();throw Error(`Generic OAuth token exchange failed: ${err}`)}let tokenData=await tokenRes.json();if(tokenData.error)throw Error(`OAuth error: ${tokenData.error_description||tokenData.error}`);let tokens={accessToken:tokenData.access_token,refreshToken:tokenData.refresh_token,expiresAt:tokenData.expires_in?new Date(Date.now()+tokenData.expires_in*1000):void 0,scope:tokenData.scope};if(!config.userInfoUrl)return{profile:{providerAccountId:tokenData.access_token,rawProfile:tokenData},tokens};let userRes=await fetch(config.userInfoUrl,{headers:{Authorization:`Bearer ${tokenData.access_token}`}});if(!userRes.ok)throw Error("Failed to fetch user info from generic OAuth provider");let rawProfile=await userRes.json();return{profile:{providerAccountId:rawProfile.id??rawProfile.sub??rawProfile.user_id??tokenData.access_token,email:rawProfile.email,emailVerified:rawProfile.email_verified===!0||rawProfile.email_verified==="true",name:rawProfile.name??rawProfile.display_name??rawProfile.username,avatarUrl:rawProfile.avatar_url??rawProfile.picture??rawProfile.photo,rawProfile},tokens}}function buildGithubAuthUrl(config,state){let scopes2=config.scopes??["read:user","user:email"];return`https://github.com/login/oauth/authorize?${new URLSearchParams({client_id:config.clientId,redirect_uri:config.redirectUri,scope:scopes2.join(" "),state,...config.extraAuthParams}).toString()}`}async function exchangeGithubCode(code,config){let tokenRes=await fetch("https://github.com/login/oauth/access_token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams({code,client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri}).toString()});if(!tokenRes.ok){let err=await tokenRes.text();throw Error(`GitHub token exchange failed: ${err}`)}let tokenData=await tokenRes.json();if(tokenData.error)throw Error(`GitHub OAuth error: ${tokenData.error_description||tokenData.error}`);let tokens={accessToken:tokenData.access_token,refreshToken:tokenData.refresh_token,expiresAt:tokenData.expires_in?new Date(Date.now()+tokenData.expires_in*1000):void 0,scope:tokenData.scope},userRes=await fetch("https://api.github.com/user",{headers:{Authorization:`Bearer ${tokenData.access_token}`,Accept:"application/vnd.github+json"}});if(!userRes.ok)throw Error("Failed to fetch GitHub user info");let rawProfile=await userRes.json(),email=rawProfile.email,emailVerified=!!email;if(!email)try{let emailsRes=await fetch("https://api.github.com/user/emails",{headers:{Authorization:`Bearer ${tokenData.access_token}`,Accept:"application/vnd.github+json"}});if(emailsRes.ok){let emails=await emailsRes.json(),primary=emails.find((e)=>e.primary&&e.verified);if(primary)email=primary.email,emailVerified=!0;else email=emails[0]?.email,emailVerified=!1}}catch{}return{profile:{providerAccountId:String(rawProfile.id),email,emailVerified,name:rawProfile.name??rawProfile.login,avatarUrl:rawProfile.avatar_url,rawProfile},tokens}}function buildGoogleAuthUrl(config,state){let scopes2=config.scopes??["openid","email","profile"];return`https://accounts.google.com/o/oauth2/v2/auth?${new URLSearchParams({client_id:config.clientId,redirect_uri:config.redirectUri,response_type:"code",scope:scopes2.join(" "),state,access_type:"offline",prompt:"select_account",...config.extraAuthParams}).toString()}`}async function exchangeGoogleCode(code,config){let tokenRes=await fetch("https://oauth2.googleapis.com/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({code,client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri,grant_type:"authorization_code"}).toString()});if(!tokenRes.ok){let err=await tokenRes.text();throw Error(`Google token exchange failed: ${err}`)}let tokenData=await tokenRes.json(),tokens={accessToken:tokenData.access_token,refreshToken:tokenData.refresh_token,expiresAt:tokenData.expires_in?new Date(Date.now()+tokenData.expires_in*1000):void 0,scope:tokenData.scope},userRes=await fetch("https://www.googleapis.com/oauth2/v3/userinfo",{headers:{Authorization:`Bearer ${tokenData.access_token}`}});if(!userRes.ok)throw Error("Failed to fetch Google user info");let rawProfile=await userRes.json();return{profile:{providerAccountId:rawProfile.sub,email:rawProfile.email,emailVerified:rawProfile.email_verified===!0||rawProfile.email_verified==="true",name:rawProfile.name,avatarUrl:rawProfile.picture,rawProfile},tokens}}function buildMicrosoftUserInfoUrl(graphFields){if(!graphFields||graphFields.length===0)return"https://graph.microsoft.com/v1.0/me";let roots=graphFields.map((f)=>f.split(".")[0]).filter((f)=>Boolean(f));return`https://graph.microsoft.com/v1.0/me?$select=${[...new Set([...MICROSOFT_DEFAULT_FIELDS,...roots])].join(",")}`}function mergeMicrosoftProfile(graphProfile,idTokenPayload){if(!idTokenPayload)return graphProfile;return{...idTokenPayload,...graphProfile}}function getMicrosoftBaseUrl(config){return`https://login.microsoftonline.com/${config.tenantId??"common"}/oauth2/v2.0`}function decodeJwtPayload(jwt){if(!jwt)return null;let payloadSegment=jwt.split(".")[1];if(!payloadSegment)return null;try{let json=Buffer.from(payloadSegment,"base64url").toString("utf8");return JSON.parse(json)}catch{return null}}function resolveMicrosoftEmailVerified(config,idTokenPayload){let edov=idTokenPayload?.xms_edov;if(edov===!0||edov==="true")return!0;if(edov===!1||edov==="false")return!1;let tenant=(config.tenantId??"common").toLowerCase();return!MULTI_TENANT_AUTHORITIES.has(tenant)}function describeFetchError(err){if(err&&typeof err==="object"){let e=err,cause=e.cause,parts=[];if(e.message)parts.push(`message=${e.message}`);if(e.code)parts.push(`code=${e.code}`);if(cause?.code)parts.push(`cause.code=${cause.code}`);if(cause?.name)parts.push(`cause.name=${cause.name}`);if(cause?.message)parts.push(`cause.message=${cause.message}`);return parts.join(" | ")||String(err)}return String(err)}function buildMicrosoftAuthUrl(config,state){let configured=config.scopes??["openid","email","profile","User.Read"],scopes2=configured.includes("openid")?configured:["openid",...configured],params=new URLSearchParams({client_id:config.clientId,redirect_uri:config.redirectUri,response_type:"code",scope:scopes2.join(" "),state,response_mode:"query",prompt:"select_account",...config.extraAuthParams});return`${getMicrosoftBaseUrl(config)}/authorize?${params.toString()}`}async function exchangeMicrosoftCode(code,config,graphFields){let tokenUrl=`${getMicrosoftBaseUrl(config)}/token`,tokenRes;try{tokenRes=await fetch(tokenUrl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({code,client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri,grant_type:"authorization_code"}).toString()})}catch(err){throw Error(`Microsoft token endpoint fetch failed | url=${tokenUrl} | redirect_uri=${config.redirectUri} | ${describeFetchError(err)}`,{cause:err})}if(!tokenRes.ok){let body=await tokenRes.text().catch(()=>"<unable to read body>");throw Error(`Microsoft token exchange failed | status=${tokenRes.status} | url=${tokenUrl} | redirect_uri=${config.redirectUri} | body=${body}`)}let tokenData=await tokenRes.json();if(tokenData.error)throw Error(`Microsoft OAuth error: ${tokenData.error_description||tokenData.error} | url=${tokenUrl}`);let tokens={accessToken:tokenData.access_token,refreshToken:tokenData.refresh_token,expiresAt:tokenData.expires_in?new Date(Date.now()+tokenData.expires_in*1000):void 0,scope:tokenData.scope},selectedUrl=buildMicrosoftUserInfoUrl(graphFields),fetchProfile=async(url)=>{try{return await fetch(url,{headers:{Authorization:`Bearer ${tokenData.access_token}`}})}catch(err){throw Error(`Microsoft userinfo fetch failed | url=${url} | ${describeFetchError(err)}`,{cause:err})}},userRes=await fetchProfile(selectedUrl);if(userRes.status===400&&selectedUrl!=="https://graph.microsoft.com/v1.0/me"){let body=await userRes.text().catch(()=>"<unable to read body>");console.warn("[OAuth:microsoft] Graph rejected the $select projection \u2014 falling back to the default "+`property set so login can proceed. Mappings that need a selected property will be empty this round; check every configured claim name. | url=${selectedUrl} | body=${body}`),userRes=await fetchProfile("https://graph.microsoft.com/v1.0/me")}if(!userRes.ok){let body=await userRes.text().catch(()=>"<unable to read body>");throw Error(`Microsoft userinfo failed | status=${userRes.status} | url=${selectedUrl} | body=${body}`)}let rawProfile=await userRes.json(),idTokenPayload=decodeJwtPayload(tokenData.id_token);if(!idTokenPayload)console.warn('[OAuth:microsoft] No id_token in the token response; optional and directory-extension claims are unavailable. Check that the "openid" scope is granted.');return{profile:{providerAccountId:rawProfile.id,email:rawProfile.mail??rawProfile.userPrincipalName,emailVerified:resolveMicrosoftEmailVerified(config,idTokenPayload),name:rawProfile.displayName,avatarUrl:void 0,rawProfile:mergeMicrosoftProfile(rawProfile,idTokenPayload)},tokens}}var MICROSOFT_DEFAULT_FIELDS,MULTI_TENANT_AUTHORITIES;var init_microsoft=__esm(()=>{MICROSOFT_DEFAULT_FIELDS=["id","displayName","givenName","surname","mail","userPrincipalName","mobilePhone","businessPhones","jobTitle","officeLocation","preferredLanguage"];MULTI_TENANT_AUTHORITIES=new Set(["common","organizations","consumers"])});var exports_stateStore={};__export(exports_stateStore,{createRedisOAuthStateStore:()=>createRedisOAuthStateStore,InMemoryOAuthStateStore:()=>InMemoryOAuthStateStore});class InMemoryOAuthStateStore{store=new Map;cleanupInterval=null;constructor(cleanupEverySeconds){this.cleanupInterval=setInterval(()=>{let now=Date.now();for(let[k,v]of this.store.entries())if(v.expiresAt<now)this.store.delete(k)},cleanupEverySeconds*1000),this.cleanupInterval.unref?.()}async set(state,payload,ttlSeconds){this.store.set(state,{payload,expiresAt:Date.now()+ttlSeconds*1000})}async consume(state){let entry=this.store.get(state);if(!entry)return null;if(this.store.delete(state),entry.expiresAt<Date.now())return null;return entry.payload}stop(){if(this.cleanupInterval)clearInterval(this.cleanupInterval),this.cleanupInterval=null}}var key=(state)=>`nucleus:oauth:state:${state}`,createRedisOAuthStateStore=(redis)=>({async set(state,payload,ttlSeconds){await redis.create(key(state),payload,ttlSeconds)},async consume(state){let res=await redis.read(key(state));if(!res.success||!res.data)return null;return await redis.remove(key(state)),res.data}});import{randomBytes as randomBytes3}from"crypto";class OAuthService{config;stateStore;constructor(config,stateStore){this.config=config,this.stateStore=stateStore??new InMemoryOAuthStateStore(config.stateTtlSeconds??600)}stop(){this.stateStore.stop?.()}isProviderEnabled(provider){return!!(this.config.enabled&&this.config.providers[provider])}getEnabledProviders(){if(!this.config.enabled)return[];return Object.keys(this.config.providers)}async buildAuthorizationUrl(provider,linkUserId,redirectUrl){let providerConfig=this.config.providers[provider];if(!providerConfig)throw Error(`OAuth provider "${provider}" is not configured`);let statePayload={provider,linkUserId,redirectUrl,createdAt:Date.now()},state=randomBytes3(32).toString("hex"),ttlSeconds=this.config.stateTtlSeconds??600;switch(await this.stateStore.set(state,statePayload,ttlSeconds),provider){case"google":return buildGoogleAuthUrl(providerConfig,state);case"github":return buildGithubAuthUrl(providerConfig,state);case"microsoft":return buildMicrosoftAuthUrl(providerConfig,state);default:return buildGenericAuthUrl(providerConfig,state)}}consumeState(state){return this.stateStore.consume(state)}async exchangeCode(provider,code){let providerConfig=this.config.providers[provider];if(!providerConfig)throw Error(`OAuth provider "${provider}" is not configured`);switch(provider){case"google":return exchangeGoogleCode(code,providerConfig);case"github":return exchangeGithubCode(code,providerConfig);case"microsoft":return exchangeMicrosoftCode(code,providerConfig,this.graphFieldsForSync());default:return exchangeGenericCode(code,providerConfig)}}graphFieldsForSync(){return(this.config.profileSync?.mappings??[]).filter((m)=>m.source!=="token").map((m)=>m.claim)}get profileSync(){return this.config.profileSync}get allowAccountLinking(){return this.config.allowAccountLinking??!0}get autoCreateUser(){return this.config.autoCreateUser??!0}get successRedirectUrl(){return this.config.successRedirectUrl??"/"}get errorRedirectUrl(){return this.config.errorRedirectUrl??"/login"}get sendInviteOnCreate(){return this.config.sendInviteOnCreate??!1}get basePath(){return this.config.basePath??"/auth/oauth"}}var init_OAuthService=__esm(()=>{init_microsoft()});var init_OAuth=__esm(()=>{init_OAuthService()});import*as crypto5 from"crypto";var IYZICO_PATHS,generateAuthString=(apiKey,secretKey,path,payload,randomKey)=>{let compound=payload?randomKey+path+payload:randomKey+path,encryptedData=crypto5.createHmac("sha256",secretKey).update(compound).digest("hex"),authorizationString=`apiKey:${apiKey}&randomKey:${randomKey}&signature:${encryptedData}`;return`IYZWSv2 ${Buffer.from(authorizationString).toString("base64")}`},buildRequest=(config,path,payload)=>{let body=JSON.stringify(payload),randomKey=Date.now().toString(32),headers=new Headers;return headers.set("Content-Type","application/json"),headers.set("x-iyzi-rnd",randomKey),headers.set("Authorization",generateAuthString(config.apiKey,config.secretKey,path,body,randomKey)),{url:new URL(`${config.apiUrl}${path}`).toString(),headers,body}},iyzicoFetch=async(config,path,payload)=>{let{url,headers,body}=buildRequest(config,path,payload);try{let json=await(await fetch(url,{method:"POST",headers,body})).json(),isSuccess=json.status==="success";return{success:isSuccess,data:isSuccess?json:null,errorCode:json.errorCode??void 0,errorMessage:json.errorMessage??void 0,rawResponse:json}}catch(error){let message=error instanceof Error?error.message:"Unknown fetch error";return{success:!1,data:null,errorCode:"NETWORK_ERROR",errorMessage:message,rawResponse:{error:message}}}},iyzicoUnsupported=(verb)=>({success:!1,errorCode:"NOT_SUPPORTED",errorMessage:`${verb} is not supported by iyzico: it has no balance, payout or transfer API. Sub-merchants are settled by iyzico itself from the approved split; record platform-side payouts through the marketplace payout workflow.`}),createIyzicoProvider=(config)=>{let generateConversationId=()=>`nk-${Date.now().toString(16)}`;return{get name(){return"iyzico"},async initialize3DS(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId,price:request.price,paidPrice:request.paidPrice,currency:request.currency,installment:request.installment??1,paymentChannel:request.paymentChannel??"WEB",basketId:request.basketId??Date.now().toString(16),paymentGroup:request.paymentGroup??"PRODUCT",paymentCard:request.paymentCard,buyer:request.buyer,shippingAddress:request.shippingAddress,billingAddress:request.billingAddress,basketItems:request.basketItems,callbackUrl:request.callbackUrl},result=await iyzicoFetch(config,IYZICO_PATHS.INITIALIZE_3DS,payload);return{success:result.success,threeDSHtmlContent:result.data?.threeDSHtmlContent,paymentId:result.data?.paymentId,signature:result.data?.signature,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async complete3DS(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId,paymentId:request.paymentId},result=await iyzicoFetch(config,IYZICO_PATHS.COMPLETE_3DS,payload),respConversationId=result.data?.conversationId;if(request.conversationId&&respConversationId&&respConversationId!==request.conversationId)return{success:!1,paymentId:result.data?.paymentId,errorCode:"CONVERSATION_MISMATCH",errorMessage:"Payment does not belong to this transaction",rawResponse:result.rawResponse};return{success:result.success,paymentId:result.data?.paymentId,price:result.data?.price,paidPrice:result.data?.paidPrice,currency:result.data?.currency,installment:result.data?.installment,cardType:result.data?.cardType,cardAssociation:result.data?.cardAssociation,cardFamily:result.data?.cardFamily,binNumber:result.data?.binNumber,lastFourDigits:result.data?.lastFourDigits,basketId:result.data?.basketId,authCode:result.data?.authCode,signature:result.data?.signature,fraudStatus:result.data?.fraudStatus,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async getPaymentDetail(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId??generateConversationId(),paymentId:request.paymentId,paymentConversationId:request.paymentConversationId},result=await iyzicoFetch(config,IYZICO_PATHS.PAYMENT_DETAIL,payload);return{success:result.success,paymentId:result.data?.paymentId,price:result.data?.price,paidPrice:result.data?.paidPrice,currency:result.data?.currency,basketId:result.data?.basketId,cardType:result.data?.cardType,cardAssociation:result.data?.cardAssociation,lastFourDigits:result.data?.lastFourDigits,binNumber:result.data?.binNumber,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async queryBin(request){let payload={locale:request.locale??"tr",conversationId:generateConversationId(),binNumber:request.binNumber.slice(0,6)};if(request.price!==void 0)payload.price=request.price;let result=await iyzicoFetch(config,IYZICO_PATHS.BIN_CHECK,payload);return{success:result.success,binNumber:result.data?.binNumber,cardType:result.data?.cardType,cardAssociation:result.data?.cardAssociation,cardFamily:result.data?.cardFamily,bankName:result.data?.bankName,bankCode:result.data?.bankCode,commercial:result.data?.commercial,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async saveCard(request){let payload={locale:request.locale??"tr",conversationId:generateConversationId(),card:request.card,email:request.email,externalId:request.externalId},result=await iyzicoFetch(config,IYZICO_PATHS.CARD_SAVE,payload);return{success:result.success,cardUserKey:result.data?.cardUserKey,cardToken:result.data?.cardToken,cardAlias:result.data?.cardAlias,binNumber:result.data?.binNumber,lastFourDigits:result.data?.lastFourDigits,cardType:result.data?.cardType,cardAssociation:result.data?.cardAssociation,cardFamily:result.data?.cardFamily,cardBankName:result.data?.cardBankName,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async listCards(request){let payload={locale:request.locale??"tr",conversationId:generateConversationId(),cardUserKey:request.cardUserKey},result=await iyzicoFetch(config,IYZICO_PATHS.CARD_LIST,payload),cards=(result.data?.cardDetails??[]).map((cd)=>({cardToken:cd.cardToken,cardAlias:cd.cardAlias,binNumber:cd.binNumber,lastFourDigits:cd.lastFourDigits,cardType:cd.cardType,cardAssociation:cd.cardAssociation,cardFamily:cd.cardFamily,cardBankName:cd.cardBankName,cardBankCode:cd.cardBankCode}));return{success:result.success,cardUserKey:result.data?.cardUserKey,cards,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async deleteCard(request){let payload={locale:request.locale??"tr",conversationId:generateConversationId(),cardUserKey:request.cardUserKey,cardToken:request.cardToken},result=await iyzicoFetch(config,IYZICO_PATHS.CARD_DELETE,payload);return{success:result.success,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async refund(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId??generateConversationId(),paymentTransactionId:request.paymentTransactionId,price:request.price,currency:request.currency??"TRY"};if(request.ip)payload.ip=request.ip;let result=await iyzicoFetch(config,IYZICO_PATHS.REFUND,payload);return{success:result.success,paymentId:result.data?.paymentId,price:result.data?.price,currency:result.data?.currency,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},parseCallback(formData){return{success:formData.mdStatus==="1",paymentId:formData.paymentId??"",conversationId:formData.conversationId??"",mdStatus:formData.mdStatus??"",rawData:formData}},async registerSubMerchant(request){let typeMap={personal:"PERSONAL",private_company:"PRIVATE_COMPANY",limited_or_joint_stock_company:"LIMITED_OR_JOINT_STOCK_COMPANY"},payload={locale:request.locale??"tr",conversationId:request.conversationId??generateConversationId(),subMerchantExternalId:request.subMerchantExternalId,subMerchantType:typeMap[request.subMerchantType]??"PERSONAL",name:request.name,email:request.email,gsmNumber:request.gsmNumber,address:request.address,iban:request.iban,contactName:request.contactName,contactSurname:request.contactSurname,identityNumber:request.identityNumber,currency:request.currency??"TRY"};if(request.subMerchantType==="private_company")payload.taxOffice=request.taxOffice,payload.legalCompanyTitle=request.legalCompanyTitle;if(request.subMerchantType==="limited_or_joint_stock_company")payload.taxOffice=request.taxOffice,payload.taxNumber=request.taxNumber,payload.legalCompanyTitle=request.legalCompanyTitle;let result=await iyzicoFetch(config,IYZICO_PATHS.SUB_MERCHANT_CREATE,payload);return{success:result.success,subMerchantKey:result.data?.subMerchantKey,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async updateSubMerchant(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId??generateConversationId(),subMerchantKey:request.subMerchantKey};if(request.name)payload.name=request.name;if(request.email)payload.email=request.email;if(request.gsmNumber)payload.gsmNumber=request.gsmNumber;if(request.address)payload.address=request.address;if(request.iban)payload.iban=request.iban;if(request.contactName)payload.contactName=request.contactName;if(request.contactSurname)payload.contactSurname=request.contactSurname;if(request.identityNumber)payload.identityNumber=request.identityNumber;if(request.taxOffice)payload.taxOffice=request.taxOffice;if(request.taxNumber)payload.taxNumber=request.taxNumber;if(request.legalCompanyTitle)payload.legalCompanyTitle=request.legalCompanyTitle;if(request.currency)payload.currency=request.currency;let result=await iyzicoFetch(config,IYZICO_PATHS.SUB_MERCHANT_UPDATE,payload);return{success:result.success,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async getSubMerchant(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId??generateConversationId(),subMerchantExternalId:request.subMerchantExternalId},result=await iyzicoFetch(config,IYZICO_PATHS.SUB_MERCHANT_DETAIL,payload);return{success:result.success,subMerchantKey:result.data?.subMerchantKey,name:result.data?.name,email:result.data?.email,gsmNumber:result.data?.gsmNumber,address:result.data?.address,iban:result.data?.iban,subMerchantType:result.data?.subMerchantType,currency:result.data?.currency,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async approveSplit(request){let payload={locale:request.locale??"tr",paymentTransactionId:request.paymentTransactionId},result=await iyzicoFetch(config,IYZICO_PATHS.APPROVAL,payload);return{success:result.success,paymentTransactionId:result.data?.paymentTransactionId,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async disapproveSplit(request){let payload={locale:request.locale??"tr",paymentTransactionId:request.paymentTransactionId},result=await iyzicoFetch(config,IYZICO_PATHS.DISAPPROVAL,payload);return{success:result.success,paymentTransactionId:result.data?.paymentTransactionId,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async createProduct(request){let localId=`iyzico-prod-${Date.now().toString(36)}`;return{success:!0,providerProductId:localId,name:request.name,rawResponse:{local:!0,id:localId}}},async updateProduct(request){return{success:!0,providerProductId:request.providerProductId,rawResponse:{local:!0,updated:request.providerProductId}}},async getProduct(request){return{success:!0,providerProductId:request.providerProductId,rawResponse:{local:!0,message:"Product data is stored locally for iyzico"}}},async listProducts(_request){return{success:!0,products:[],hasMore:!1,rawResponse:{local:!0,message:"Products are stored locally for iyzico"}}},async archiveProduct(request){return{success:!0,rawResponse:{local:!0,archived:request.providerProductId}}},async createPrice(request){let localId=`iyzico-price-${Date.now().toString(36)}`;return{success:!0,providerPriceId:localId,rawResponse:{local:!0,id:localId,product:request.providerProductId}}},async updatePrice(request){return{success:!0,providerPriceId:request.providerPriceId,rawResponse:{local:!0,updated:request.providerPriceId}}},async getPrice(request){return{success:!0,providerPriceId:request.providerPriceId,rawResponse:{local:!0,message:"Price data is stored locally for iyzico"}}},async listPrices(_request){return{success:!0,prices:[],hasMore:!1,rawResponse:{local:!0,message:"Prices are stored locally for iyzico"}}},async archivePrice(request){return{success:!0,rawResponse:{local:!0,archived:request.providerPriceId}}},async createCustomer(request){return{success:!0,providerCustomerId:`local_cus_${Date.now()}`,email:request.email,rawResponse:{local:!0,message:"Customer stored locally for iyzico"}}},async updateCustomer(request){return{success:!0,providerCustomerId:request.providerCustomerId,rawResponse:{local:!0,message:"Customer updated locally for iyzico"}}},async getCustomer(request){return{success:!0,providerCustomerId:request.providerCustomerId,rawResponse:{local:!0,message:"Customer data is stored locally for iyzico"}}},async listCustomers(_request){return{success:!0,customers:[],hasMore:!1,rawResponse:{local:!0,message:"Customers are stored locally for iyzico"}}},async deleteCustomer(_request){return{success:!0,rawResponse:{local:!0,message:"Customer deleted locally for iyzico"}}},async createSubscription(_request){return{success:!0,providerSubscriptionId:`local_sub_${Date.now()}`,status:"active",rawResponse:{local:!0,message:"Subscription stored locally for iyzico"}}},async updateSubscription(request){return{success:!0,providerSubscriptionId:request.providerSubscriptionId,status:"active",rawResponse:{local:!0,message:"Subscription updated locally for iyzico"}}},async cancelSubscription(request){return{success:!0,providerSubscriptionId:request.providerSubscriptionId,status:"canceled",rawResponse:{local:!0,message:"Subscription canceled locally for iyzico"}}},async getSubscription(request){return{success:!0,providerSubscriptionId:request.providerSubscriptionId,rawResponse:{local:!0,message:"Subscription data is stored locally for iyzico"}}},async listSubscriptions(_request){return{success:!0,subscriptions:[],hasMore:!1,rawResponse:{local:!0,message:"Subscriptions are stored locally for iyzico"}}},async reportUsage(request){return{success:!0,usageRecordId:`local_ur_${Date.now()}`,quantity:request.quantity,timestamp:request.timestamp??Math.floor(Date.now()/1000),rawResponse:{local:!0,message:"Usage recorded locally for iyzico"}}},async listUsageRecordSummaries(_request){return{success:!0,summaries:[],hasMore:!1,rawResponse:{local:!0,message:"Usage summaries are stored locally for iyzico"}}},async createInvoice(_request){return{success:!0,providerInvoiceId:`local_inv_${Date.now()}`,status:"draft",rawResponse:{local:!0,message:"Invoice stored locally for iyzico"}}},async finalizeInvoice(request){return{success:!0,providerInvoiceId:request.providerInvoiceId,status:"open",rawResponse:{local:!0,message:"Invoice finalized locally for iyzico"}}},async sendInvoice(request){return{success:!0,providerInvoiceId:request.providerInvoiceId,rawResponse:{local:!0,message:"Invoice send is a no-op for iyzico"}}},async voidInvoice(request){return{success:!0,providerInvoiceId:request.providerInvoiceId,rawResponse:{local:!0,message:"Invoice voided locally for iyzico"}}},async payInvoice(request){return{success:!0,providerInvoiceId:request.providerInvoiceId,status:"paid",rawResponse:{local:!0,message:"Invoice payment is a no-op for iyzico"}}},async getInvoice(request){return{success:!0,providerInvoiceId:request.providerInvoiceId,rawResponse:{local:!0,message:"Invoice data is stored locally for iyzico"}}},async listInvoices(_request){return{success:!0,invoices:[],hasMore:!1,rawResponse:{local:!0,message:"Invoices are stored locally for iyzico"}}},async getBalance(){return{...iyzicoUnsupported("Balance"),available:[],pending:[]}},async createPayout(_request){return iyzicoUnsupported("Creating a payout")},async getPayout(request){return{...iyzicoUnsupported("Reading a payout"),providerPayoutId:request.providerPayoutId}},async cancelPayout(request){return{...iyzicoUnsupported("Cancelling a payout"),providerPayoutId:request.providerPayoutId}},async listPayouts(_request){return{...iyzicoUnsupported("Listing payouts"),payouts:[],hasMore:!1}},async createTransfer(_request){return iyzicoUnsupported("Creating a transfer")},async listTransfers(_request){return{...iyzicoUnsupported("Listing transfers"),transfers:[],hasMore:!1}}}};var init_IyzicoProvider=__esm(()=>{IYZICO_PATHS={INITIALIZE_3DS:"/payment/3dsecure/initialize",COMPLETE_3DS:"/payment/3dsecure/auth",PAYMENT_AUTH:"/payment/auth",PAYMENT_DETAIL:"/payment/detail",BIN_CHECK:"/payment/bin/check",CARD_SAVE:"/cardstorage/card",CARD_LIST:"/cardstorage/cards",CARD_DELETE:"/cardstorage/card/delete",REFUND:"/payment/refund",SUB_MERCHANT_CREATE:"/onboarding/submerchant",SUB_MERCHANT_UPDATE:"/onboarding/submerchant",SUB_MERCHANT_DETAIL:"/onboarding/submerchant/detail",APPROVAL:"/payment/iyzipos/item/approve",DISAPPROVAL:"/payment/iyzipos/item/disapprove"}});var resolveMarketplaceConfig=(payment)=>{let m=payment?.marketplace;return{enabled:m?.enabled===!0,defaultSettlementDelayDays:m?.defaultSettlementDelayDays??7,newSellerDelayDays:m?.newSellerDelayDays??14,reserveRate:clampRate(m?.reserveRate??0),reserveReleaseDays:m?.reserveReleaseDays??0,minimumPayoutAmount:m?.minimumPayoutAmount??0,defaultCurrency:m?.defaultCurrency??payment?.defaultCurrency??"USD",autoReleaseReserves:m?.autoReleaseReserves===!0}},clampRate=(rate)=>{if(Number.isNaN(rate)||rate<0)return 0;if(rate>1)return 1;return rate},addDays=(base,days)=>new Date(base.getTime()+days*24*60*60*1000),computeSplitBreakdown=(input,config,policy,now=new Date)=>{let providerFee=Math.max(0,Math.round(input.providerFeeAmount??0)),platformFee=Math.max(0,Math.round(input.platformFeeAmount)),afterFees=Math.max(0,input.grossAmount-providerFee-platformFee),reserveRate=input.reserveRate??policy?.reserveRate??config.reserveRate,reserveAmount=Math.min(afterFees,Math.floor(afterFees*clampRate(reserveRate))),netPayable=afterFees-reserveAmount,delayDays=input.settlementDelayDays??(input.isNewSeller?policy?.newSellerDelayDays??config.newSellerDelayDays:policy?.defaultDelayDays??config.defaultSettlementDelayDays);return{grossAmount:input.grossAmount,providerFeeAmount:providerFee,platformFeeAmount:platformFee,reserveAmount,netPayableAmount:netPayable,availableForPayoutAt:addDays(now,Math.max(0,delayDays))}},str2=(v)=>v==null?"":String(v),strOrNull2=(v)=>v==null?null:String(v),num=(v)=>{let n=typeof v==="number"?v:Number(v);return Number.isFinite(n)?n:0},rowToSplit=(row)=>({id:str2(row.id),transactionId:strOrNull2(row.transactionId??row.transaction_id),recipientOwnerType:str2(row.recipientOwnerType??row.recipient_owner_type)||"seller",recipientOwnerId:str2(row.recipientOwnerId??row.recipient_owner_id),provider:str2(row.provider),grossAmount:num(row.grossAmount??row.gross_amount),providerFeeAmount:num(row.providerFeeAmount??row.provider_fee_amount),platformFeeAmount:num(row.platformFeeAmount??row.platform_fee_amount),reserveAmount:num(row.reserveAmount??row.reserve_amount),netPayableAmount:num(row.netPayableAmount??row.net_payable_amount),currency:str2(row.currency),availableForPayoutAt:strOrNull2(row.availableForPayoutAt??row.available_for_payout_at),status:str2(row.status)||"pending",sourceType:strOrNull2(row.sourceType??row.source_type),sourceId:strOrNull2(row.sourceId??row.source_id)}),rowToPayoutRequest=(row)=>({id:str2(row.id),recipientOwnerType:str2(row.recipientOwnerType??row.recipient_owner_type)||"seller",recipientOwnerId:str2(row.recipientOwnerId??row.recipient_owner_id),amount:num(row.amount),currency:str2(row.currency),status:str2(row.status)||"pending",provider:strOrNull2(row.provider),providerPayoutId:strOrNull2(row.providerPayoutId??row.provider_payout_id),providerTransferId:strOrNull2(row.providerTransferId??row.provider_transfer_id),destination:strOrNull2(row.destination),failureReason:strOrNull2(row.failureReason??row.failure_reason),requestedBy:strOrNull2(row.requestedBy??row.requested_by)}),rowToSettlementPolicy=(row)=>({id:str2(row.id),ownerType:str2(row.ownerType??row.owner_type),ownerId:str2(row.ownerId??row.owner_id),defaultDelayDays:num(row.defaultDelayDays??row.default_delay_days),newSellerDelayDays:num(row.newSellerDelayDays??row.new_seller_delay_days),reserveRate:num(row.reserveRate??row.reserve_rate),reserveDays:num(row.reserveDays??row.reserve_days),minimumPayoutAmount:num(row.minimumPayoutAmount??row.minimum_payout_amount),currency:strOrNull2(row.currency),earlyPayoutEnabled:Boolean(row.earlyPayoutEnabled??row.early_payout_enabled),status:str2(row.status)||"active"}),rowToDispute=(row)=>({id:str2(row.id),transactionId:strOrNull2(row.transactionId??row.transaction_id),provider:str2(row.provider),providerDisputeId:strOrNull2(row.providerDisputeId??row.provider_dispute_id),ownerType:strOrNull2(row.ownerType??row.owner_type),ownerId:strOrNull2(row.ownerId??row.owner_id),amount:row.amount==null?null:num(row.amount),currency:strOrNull2(row.currency),reason:strOrNull2(row.reason),status:str2(row.status)||"open",resolvedAt:strOrNull2(row.resolvedAt??row.resolved_at)});import{and as and7,eq as eq11,or as or2,sql as sql3}from"drizzle-orm";class MarketplacePayoutService{getDb;provider;logger;t;marketplace;constructor(cfg,marketplace){this.getDb=cfg.getDb,this.provider=cfg.provider,this.logger=cfg.logger.scoped("payment.payout"),this.t=cfg.tables,this.marketplace=marketplace}table(name){return this.t[name]}async requestPayout(input){let db=this.getDb();if(!db)throw Error("Marketplace payouts not ready");let recipientType=input.recipientOwnerType??"seller",currency=input.currency??this.marketplace.config.defaultCurrency;if(input.amount<=0)throw Error("Payout amount must be positive");let minimum=this.marketplace.config.minimumPayoutAmount;if(input.amount<minimum)throw Error(`Payout amount is below the minimum of ${minimum}`);let table=this.table("payoutRequests"),lockKey=`payout:${recipientType}:${input.recipientOwnerId}:${currency}`,insertRow=async(executor,locked)=>{let balance=await this.marketplace.getBalance(recipientType,input.recipientOwnerId,currency,locked);if(input.amount>balance.available)throw Error(`Insufficient available balance: requested ${input.amount}, available ${balance.available}`);let rows=await executor.insert(table).values({recipientOwnerType:recipientType,recipientOwnerId:input.recipientOwnerId,amount:input.amount,currency,status:"pending",destination:input.destination??null,requestedBy:input.requestedBy??null,metadata:JSON.stringify({})}).returning();return rowToPayoutRequest(rows[0])};if(typeof db.transaction==="function")return db.transaction(async(tx)=>{return await tx.execute(sql3`SELECT pg_advisory_xact_lock(hashtext(${lockKey}))`),insertRow(tx,tx)});return insertRow(db)}async listPayoutRequests(filter){let db=this.getDb();if(!db)return[];let table=this.table("payoutRequests"),where=filter.recipientOwnerId?eq11(col4(table,"recipientOwnerId"),filter.recipientOwnerId):void 0,mapped=(where?await db.select().from(table).where(where):await db.select().from(table)).map((r)=>rowToPayoutRequest(r));return filter.status?mapped.filter((p)=>p.status===filter.status):mapped}async getPayoutRequest(id){let db=this.getDb();if(!db)return null;let table=this.table("payoutRequests"),rows=await db.select().from(table).where(eq11(col4(table,"id"),id)).limit(1);return rows[0]?rowToPayoutRequest(rows[0]):null}async rejectPayout(id,deciderId){let db=this.getDb();if(!db)return null;let table=this.table("payoutRequests");return await db.update(table).set({status:"rejected",decidedBy:deciderId??null,decidedAt:new Date,updatedAt:new Date}).where(eq11(col4(table,"id"),id)),this.getPayoutRequest(id)}async reconcilePayoutByProvider(providerId,outcome,reason){let db=this.getDb();if(!db)return null;let table=this.table("payoutRequests"),row=(await db.select().from(table).where(or2(eq11(col4(table,"providerPayoutId"),providerId),eq11(col4(table,"providerTransferId"),providerId))).limit(1))[0];if(!row)return null;let previousStatus=String(row.status),nextStatus=outcome==="completed"?"completed":"failed";if(await db.update(table).set({status:nextStatus,failureReason:outcome==="failed"?reason??"Provider reported failure":null,completedAt:outcome==="completed"?new Date:row.completedAt??null,updatedAt:new Date}).where(eq11(col4(table,"id"),String(row.id))),outcome==="completed"&&previousStatus!=="completed"){if(!await this.marketplace.hasLedgerEntry({account:"seller_payable",direction:"debit",referenceType:"payout_request",referenceId:String(row.id)})){let amount=Number(row.amount),currency=String(row.currency),ownerType=String(row.recipientOwnerType??"seller"),ownerId=String(row.recipientOwnerId??""),bookkeep=async(exec)=>{await this.marketplace.recordLedgerEntry({account:"seller_payable",direction:"debit",amount,currency,entryType:"payout",ownerType,ownerId,referenceType:"payout_request",referenceId:String(row.id)},exec),await this.marketplace.recordLedgerEntry({account:"payout_completed",direction:"credit",amount,currency,entryType:"payout",ownerType,ownerId,referenceType:"payout_request",referenceId:String(row.id)},exec),await this.consumeSplits(ownerId,amount,currency,exec)};if(typeof db.transaction==="function")await db.transaction(async(tx)=>bookkeep(tx));else await bookkeep(db)}}if(outcome==="failed"&&previousStatus==="completed")await this.marketplace.recordLedgerEntry({account:"seller_payable",direction:"credit",amount:Number(row.amount),currency:String(row.currency),entryType:"adjustment",ownerType:String(row.recipientOwnerType??"seller"),ownerId:String(row.recipientOwnerId??""),referenceType:"payout_request",referenceId:String(row.id),description:`Payout reversal (${reason??"provider failure"})`});return this.getPayoutRequest(String(row.id))}async approvePayout(id,opts){let db=this.getDb();if(!db)return null;let request=await this.getPayoutRequest(id);if(!request)return null;if(request.status!=="pending")throw Error(`Payout is not pending (status: ${request.status})`);if(opts?.deciderId&&request.requestedBy&&opts.deciderId===request.requestedBy)throw Error("Approver cannot be the requester");let table=this.table("payoutRequests"),destination=opts?.destination??request.destination,lockKey=`payout:${request.recipientOwnerType}:${request.recipientOwnerId}:${request.currency}`,checkAndClaim=async(executor,locked)=>{let backing=(await this.marketplace.getBalance(request.recipientOwnerType,request.recipientOwnerId,request.currency,locked)).available+request.amount;if(backing<request.amount)throw Error(`Insufficient available balance: requested ${request.amount}, available ${backing}`);return executor.update(table).set({status:"processing",decidedBy:opts?.deciderId??null,decidedAt:new Date,updatedAt:new Date}).where(and7(eq11(col4(table,"id"),id),eq11(col4(table,"status"),"pending"))).returning()},claimed=typeof db.transaction==="function"?await db.transaction(async(tx)=>{return await tx.execute(sql3`SELECT pg_advisory_xact_lock(hashtext(${lockKey}))`),checkAndClaim(tx,tx)}):await checkAndClaim(db);if(!claimed||claimed.length===0)throw Error("Payout is no longer pending (already being processed)");let provider=this.provider;if(!provider)return this.logger.warn("No payment provider configured; payout left in processing",{id}),this.getPayoutRequest(id);let providerId,providerPayoutId,offProvider=!1;try{let result=destination?await provider.createTransfer({amount:request.amount,currency:request.currency,destination,transferGroup:`payout:${id}`,idempotencyKey:`payout:${id}`}):await provider.createPayout({amount:request.amount,currency:request.currency,idempotencyKey:`payout:${id}`});if(providerId="providerTransferId"in result?result.providerTransferId:void 0,providerPayoutId="providerPayoutId"in result?result.providerPayoutId:void 0,!result.success&&result.errorCode==="NOT_SUPPORTED")offProvider=!0,providerId=void 0,providerPayoutId=void 0,this.logger.warn("Provider has no payout API; settled on the platform ledger only",{id,provider:provider.name,reason:result.errorMessage});else if(!result.success)throw Error(result.errorMessage||"Provider payout failed")}catch(err){let reason=err instanceof Error?err.message:"Payout execution failed";return await db.update(table).set({status:"failed",failureReason:reason,updatedAt:new Date}).where(eq11(col4(table,"id"),id)),this.logger.error("Payout provider execution failed",{id,reason}),this.getPayoutRequest(id)}let settledBy=offProvider?`Settled off-provider: ${provider.name} has no payout API`:void 0,finalize=async(exec)=>{await exec.update(table).set({status:"completed",provider:provider.name,providerTransferId:providerId??null,providerPayoutId:providerPayoutId??null,completedAt:new Date,updatedAt:new Date}).where(eq11(col4(table,"id"),id)),await this.marketplace.recordLedgerEntry({account:"seller_payable",direction:"debit",amount:request.amount,currency:request.currency,entryType:"payout",ownerType:request.recipientOwnerType,ownerId:request.recipientOwnerId,referenceType:"payout_request",referenceId:id,description:settledBy},exec),await this.marketplace.recordLedgerEntry({account:"payout_completed",direction:"credit",amount:request.amount,currency:request.currency,entryType:"payout",ownerType:request.recipientOwnerType,ownerId:request.recipientOwnerId,referenceType:"payout_request",referenceId:id,description:settledBy},exec),await this.consumeSplits(request.recipientOwnerId,request.amount,request.currency,exec)};try{if(typeof db.transaction==="function")await db.transaction(async(tx)=>finalize(tx));else await finalize(db)}catch(err){let reason=err instanceof Error?err.message:"Bookkeeping failed after payout";try{await db.update(table).set({providerTransferId:providerId??null,providerPayoutId:providerPayoutId??null,failureReason:`RECONCILE_REQUIRED: ${reason}`,updatedAt:new Date}).where(eq11(col4(table,"id"),id))}catch{}this.logger.error("Payout bookkeeping failed AFTER funds moved \u2014 reconciliation required",{id,providerId,providerPayoutId,reason})}return this.getPayoutRequest(id)}async consumeSplits(ownerId,amount,currency,executor){let db=executor??this.getDb();if(!db)return;let table=this.table("splits"),splits=await this.marketplace.listSplits({recipientOwnerId:ownerId},executor),remaining=amount,now=Date.now();for(let s of splits){if(remaining<=0)break;if(s.currency!==currency)continue;if(s.status!=="pending"&&s.status!=="available")continue;if(!(s.availableForPayoutAt?new Date(s.availableForPayoutAt).getTime()<=now:!0))continue;if(s.netPayableAmount>remaining)continue;await db.update(table).set({status:"paid",updatedAt:new Date}).where(eq11(col4(table,"id"),s.id)),remaining-=s.netPayableAmount}}async openDispute(input){let db=this.getDb();if(!db)return null;let table=this.table("disputes"),rows=await db.insert(table).values({transactionId:input.transactionId??null,provider:input.provider,providerDisputeId:input.providerDisputeId??null,ownerType:input.ownerType??null,ownerId:input.ownerId??null,amount:input.amount??null,currency:input.currency??null,reason:input.reason??null,status:"open",evidenceDueAt:input.evidenceDueAt??null,metadata:JSON.stringify(input.metadata??{})}).returning(),dispute=rowToDispute(rows[0]);if(input.amount&&input.amount>0&&input.currency)await this.marketplace.recordLedgerEntry({account:"dispute_liability",direction:"credit",amount:input.amount,currency:input.currency,entryType:"dispute",ownerType:input.ownerType,ownerId:input.ownerId,referenceType:"dispute",referenceId:dispute.id});return dispute}async listDisputes(filter){let db=this.getDb();if(!db)return[];let table=this.table("disputes"),where=filter.ownerId?eq11(col4(table,"ownerId"),filter.ownerId):void 0,mapped=(where?await db.select().from(table).where(where):await db.select().from(table)).map((r)=>rowToDispute(r));return filter.status?mapped.filter((d)=>d.status===filter.status):mapped}async getDispute(id){let db=this.getDb();if(!db)return null;let table=this.table("disputes"),rows=await db.select().from(table).where(eq11(col4(table,"id"),id)).limit(1);return rows[0]?rowToDispute(rows[0]):null}async resolveDispute(id,outcome){let db=this.getDb();if(!db)return null;let table=this.table("disputes"),claimedRow=(await db.update(table).set({status:outcome,resolvedAt:new Date,updatedAt:new Date}).where(and7(eq11(col4(table,"id"),id),eq11(col4(table,"status"),"open"))).returning())[0];if(!claimedRow)return this.getDispute(id);let dispute=rowToDispute(claimedRow);if((outcome==="lost"||outcome==="accepted")&&dispute.amount&&dispute.currency)await this.marketplace.recordLedgerEntry({account:"seller_payable",direction:"debit",amount:dispute.amount,currency:dispute.currency,entryType:"dispute",ownerType:dispute.ownerType??void 0,ownerId:dispute.ownerId??void 0,referenceType:"dispute",referenceId:id});return this.getDispute(id)}async reconcileDisputeByProvider(providerDisputeId,status){let db=this.getDb();if(!db)return null;let table=this.table("disputes"),row=(await db.select().from(table).where(eq11(col4(table,"providerDisputeId"),providerDisputeId)).limit(1))[0];if(!row)return null;let dispute=rowToDispute(row);if(dispute.status!=="open")return dispute;let outcome=status==="won"||status==="warning_closed"?"won":status==="lost"?"lost":null;if(!outcome)return dispute;return this.resolveDispute(dispute.id,outcome)}}var col4=(table,name)=>table[name];var init_MarketplacePayoutService=()=>{};import{and as and8,eq as eq12}from"drizzle-orm";class MarketplaceService{getDb;config;logger;t;constructor(cfg){this.getDb=cfg.getDb,this.config=cfg.config,this.logger=cfg.logger.scoped("payment.marketplace"),this.t=cfg.tables}table(name){return this.t[name]}async recordLedgerEntry(entry,executor){let db=executor??this.getDb();if(!db)return;await db.insert(this.table("ledgerEntries")).values({account:entry.account,ownerType:entry.ownerType??null,ownerId:entry.ownerId??null,direction:entry.direction,amount:entry.amount,currency:entry.currency,entryType:entry.entryType,referenceType:entry.referenceType??null,referenceId:entry.referenceId??null,splitId:entry.splitId??null,description:entry.description??null,metadata:JSON.stringify({})})}async hasLedgerEntry(q){let db=this.getDb();if(!db)return!1;let table=this.table("ledgerEntries");return(await db.select({id:col5(table,"id")}).from(table).where(and8(eq12(col5(table,"account"),q.account),eq12(col5(table,"direction"),q.direction),eq12(col5(table,"referenceType"),q.referenceType),eq12(col5(table,"referenceId"),q.referenceId))).limit(1)).length>0}async getSettlementPolicy(ownerType,ownerId){let db=this.getDb();if(!db)return null;let table=this.table("settlementPolicies"),rows=await db.select().from(table).where(and8(eq12(col5(table,"ownerType"),ownerType),eq12(col5(table,"ownerId"),ownerId))).limit(1);return rows[0]?rowToSettlementPolicy(rows[0]):null}async upsertSettlementPolicy(input){let db=this.getDb();if(!db)return null;let table=this.table("settlementPolicies"),existing=await this.getSettlementPolicy(input.ownerType,input.ownerId),values={ownerType:input.ownerType,ownerId:input.ownerId,defaultDelayDays:input.defaultDelayDays??this.config.defaultSettlementDelayDays,newSellerDelayDays:input.newSellerDelayDays??this.config.newSellerDelayDays,reserveRate:String(input.reserveRate??this.config.reserveRate),reserveDays:input.reserveDays??this.config.reserveReleaseDays,minimumPayoutAmount:input.minimumPayoutAmount??this.config.minimumPayoutAmount,currency:input.currency??null,earlyPayoutEnabled:input.earlyPayoutEnabled??!1,updatedAt:new Date};if(existing)await db.update(table).set(values).where(eq12(col5(table,"id"),existing.id));else await db.insert(table).values(values);return this.getSettlementPolicy(input.ownerType,input.ownerId)}async recordSplit(input){let db=this.getDb();if(!db)return null;let recipientType=input.recipientOwnerType??"seller",currency=input.currency??this.config.defaultCurrency,now=new Date,policy=await this.getSettlementPolicy(recipientType,input.recipientOwnerId),breakdown=computeSplitBreakdown(input,this.config,policy,now),splitTable=this.table("splits"),rows=await db.insert(splitTable).values({transactionId:input.transactionId??null,recipientOwnerType:recipientType,recipientOwnerId:input.recipientOwnerId,provider:input.provider,grossAmount:breakdown.grossAmount,providerFeeAmount:breakdown.providerFeeAmount,platformFeeAmount:breakdown.platformFeeAmount,reserveAmount:breakdown.reserveAmount,netPayableAmount:breakdown.netPayableAmount,currency,availableForPayoutAt:breakdown.availableForPayoutAt,status:"pending",sourceType:input.sourceType??null,sourceId:input.sourceId??null,metadata:JSON.stringify(input.metadata??{})}).returning(),split=rowToSplit(rows[0]);if(await this.recordLedgerEntry({account:"seller_payable",direction:"credit",amount:breakdown.netPayableAmount,currency,entryType:"split",ownerType:recipientType,ownerId:input.recipientOwnerId,referenceType:"split",referenceId:split.id,splitId:split.id}),breakdown.platformFeeAmount>0)await this.recordLedgerEntry({account:"platform_revenue",direction:"credit",amount:breakdown.platformFeeAmount,currency,entryType:"split",referenceType:"split",referenceId:split.id,splitId:split.id});if(breakdown.providerFeeAmount>0)await this.recordLedgerEntry({account:"provider_fee",direction:"credit",amount:breakdown.providerFeeAmount,currency,entryType:"split",referenceType:"split",referenceId:split.id,splitId:split.id});if(breakdown.reserveAmount>0){let reserveDays=policy?.reserveDays??this.config.reserveReleaseDays;await db.insert(this.table("reserves")).values({ownerType:recipientType,ownerId:input.recipientOwnerId,splitId:split.id,amount:breakdown.reserveAmount,currency,reason:input.isNewSeller?"new_seller":"refund_window",status:"held",releaseAt:reserveDays>0?addDays(now,reserveDays):null,metadata:JSON.stringify({})}),await this.recordLedgerEntry({account:"reserve",direction:"credit",amount:breakdown.reserveAmount,currency,entryType:"reserve_hold",ownerType:recipientType,ownerId:input.recipientOwnerId,referenceType:"split",referenceId:split.id,splitId:split.id})}return this.logger.info("Recorded split",{splitId:split.id,gross:breakdown.grossAmount,netPayable:breakdown.netPayableAmount,reserve:breakdown.reserveAmount}),split}async listSplits(filter,executor){let db=executor??this.getDb();if(!db)return[];let table=this.table("splits"),where=filter.recipientOwnerId?eq12(col5(table,"recipientOwnerId"),filter.recipientOwnerId):void 0,mapped=(where?await db.select().from(table).where(where):await db.select().from(table)).map((r)=>rowToSplit(r));return filter.status?mapped.filter((s)=>s.status===filter.status):mapped}async releaseReserve(id){let db=this.getDb();if(!db)return!1;let table=this.table("reserves"),row=(await db.update(table).set({status:"released",releasedAt:new Date,updatedAt:new Date}).where(and8(eq12(col5(table,"id"),id),eq12(col5(table,"status"),"held"))).returning())[0];if(!row)return!1;let reserveOwnerType=String(row.ownerType??row.owner_type??""),reserveOwnerId=String(row.ownerId??row.owner_id??"");return await this.recordLedgerEntry({account:"reserve",direction:"debit",amount:Number(row.amount),currency:String(row.currency),entryType:"reserve_release",ownerType:reserveOwnerType,ownerId:reserveOwnerId,referenceType:"reserve",referenceId:id}),await this.recordLedgerEntry({account:"seller_payable",direction:"credit",amount:Number(row.amount),currency:String(row.currency),entryType:"reserve_release",ownerType:reserveOwnerType,ownerId:reserveOwnerId,referenceType:"reserve",referenceId:id}),!0}async autoReleaseDueReserves(ownerType,ownerId){if(!this.config.autoReleaseReserves)return;let db=this.getDb();if(!db)return;let table=this.table("reserves"),rows=await db.select().from(table).where(and8(eq12(col5(table,"ownerType"),ownerType),eq12(col5(table,"ownerId"),ownerId))),now=Date.now();for(let r of rows){let releaseAt=r.releaseAt??r.release_at;if(String(r.status)==="held"&&releaseAt&&new Date(String(releaseAt)).getTime()<=now)await this.releaseReserve(String(r.id))}}async getBalance(ownerType,ownerId,currency,executor){let cur=currency??this.config.defaultCurrency,db=executor??this.getDb(),base={ownerType,ownerId,currency:cur,available:0,pending:0,reserved:0,payoutInFlight:0};if(!db)return base;if(!executor)await this.autoReleaseDueReserves(ownerType,ownerId);let splits=await this.listSplits({recipientOwnerId:ownerId},executor),now=Date.now();for(let s of splits){if(s.currency!==cur)continue;if(s.status!=="pending"&&s.status!=="available")continue;if(!(s.availableForPayoutAt?new Date(s.availableForPayoutAt).getTime()<=now:!0))base.pending+=s.netPayableAmount}let ledgerTable=this.table("ledgerEntries"),ledgerRows=await db.select().from(ledgerTable).where(and8(eq12(col5(ledgerTable,"ownerType"),ownerType),eq12(col5(ledgerTable,"ownerId"),ownerId))),payableNet=0;for(let e of ledgerRows){if(String(e.account)!=="seller_payable"||String(e.currency)!==cur)continue;let amt=Number(e.amount);payableNet+=String(e.direction)==="credit"?amt:-amt}base.available=payableNet-base.pending;let reserveTable=this.table("reserves"),reserveRows=await db.select().from(reserveTable).where(and8(eq12(col5(reserveTable,"ownerType"),ownerType),eq12(col5(reserveTable,"ownerId"),ownerId)));for(let r of reserveRows)if(String(r.status)==="held"&&String(r.currency)===cur)base.reserved+=Number(r.amount);let payoutTable=this.table("payoutRequests"),payoutRows=await db.select().from(payoutTable).where(and8(eq12(col5(payoutTable,"recipientOwnerType"),ownerType),eq12(col5(payoutTable,"recipientOwnerId"),ownerId)));for(let p of payoutRows){let st=String(p.status);if((st==="pending"||st==="approved"||st==="processing")&&String(p.currency)===cur)base.payoutInFlight+=Number(p.amount),base.available-=Number(p.amount)}return base}}var col5=(table,name)=>table[name];var init_MarketplaceService=()=>{};var pick=(schemaTables,camel,snake)=>schemaTables[camel]??schemaTables[snake]??{},createMarketplaceServices=(params)=>{let config=resolveMarketplaceConfig(params.options.payment);if(!config.enabled)return null;let tables={splits:pick(params.schemaTables,"paymentSplits","payment_splits"),ledgerEntries:pick(params.schemaTables,"paymentLedgerEntries","payment_ledger_entries"),settlementPolicies:pick(params.schemaTables,"paymentSettlementPolicies","payment_settlement_policies"),reserves:pick(params.schemaTables,"paymentReserves","payment_reserves"),payoutRequests:pick(params.schemaTables,"paymentPayoutRequests","payment_payout_requests"),disputes:pick(params.schemaTables,"paymentDisputes","payment_disputes")},serviceConfig={getDb:params.getDb,provider:params.provider,config,logger:params.logger,tables},marketplaceService=new MarketplaceService(serviceConfig),payoutService=new MarketplacePayoutService(serviceConfig,marketplaceService);return{marketplaceService,payoutService}};var init_Marketplace=__esm(()=>{init_MarketplacePayoutService();init_MarketplaceService();init_MarketplacePayoutService();init_MarketplaceService()});var loadStripe=(config)=>{try{let StripeModule=__require("stripe");return new(StripeModule.default??StripeModule)(config.secretKey,{maxNetworkRetries:config.maxNetworkRetries??2,timeout:config.timeout??30000,...config.apiVersion?{apiVersion:config.apiVersion}:{}})}catch(error){return logger.scoped("payment.init").error("Failed to load stripe package. Install it: bun add stripe",error),null}},createStripeProvider=(config)=>{let stripe=loadStripe(config);if(!stripe)return null;let providerName="stripe";return{get name(){return providerName},async initialize3DS(request){try{let params={amount:Math.round(Number(request.paidPrice)*100),currency:request.currency.toLowerCase(),payment_method_types:["card"],description:`Order ${request.conversationId}`,metadata:{conversationId:request.conversationId,basketId:request.basketId??"",buyerEmail:request.buyer.email,buyerId:request.buyer.id}};if("cardToken"in request.paymentCard&&request.paymentCard.cardToken)params.payment_method=request.paymentCard.cardToken,params.confirm=!0,params.return_url=request.callbackUrl;let paymentIntent=await stripe.paymentIntents.create(params),clientSecret=paymentIntent.client_secret,piId=paymentIntent.id,status=paymentIntent.status;return{success:!0,paymentId:piId,threeDSHtmlContent:clientSecret,signature:status,rawResponse:paymentIntent}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Payment initialization failed",rawResponse:{error:err.message}}}},async complete3DS(request){try{let paymentIntent=await stripe.paymentIntents.retrieve(request.paymentId,{expand:["payment_method"]}),intentConversationId=String(paymentIntent.metadata?.conversationId??"");if(request.conversationId&&intentConversationId!==request.conversationId)return{success:!1,paymentId:paymentIntent.id,price:0,paidPrice:0,errorCode:"CONVERSATION_MISMATCH",errorMessage:"Payment does not belong to this transaction",rawResponse:paymentIntent};let status=paymentIntent.status,isSuccess=status==="succeeded",amountReceived=paymentIntent.amount_received??0,card=paymentIntent.payment_method?.card;return{success:isSuccess,paymentId:paymentIntent.id,price:amountReceived/100,paidPrice:amountReceived/100,currency:paymentIntent.currency?.toUpperCase(),cardType:card?.brand??void 0,cardAssociation:card?.brand??void 0,cardFamily:card?.funding??void 0,lastFourDigits:card?.last4??void 0,authCode:status,errorCode:isSuccess?void 0:"PAYMENT_NOT_SUCCEEDED",errorMessage:isSuccess?void 0:`Payment status: ${status}`,rawResponse:paymentIntent}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Payment completion failed",rawResponse:{error:err.message}}}},async getPaymentDetail(request){try{let paymentIntent=await stripe.paymentIntents.retrieve(request.paymentId,{expand:["payment_method"]}),amountReceived=paymentIntent.amount_received??paymentIntent.amount??0,card=paymentIntent.payment_method?.card;return{success:!0,paymentId:paymentIntent.id,price:amountReceived/100,paidPrice:amountReceived/100,currency:paymentIntent.currency?.toUpperCase(),cardType:card?.brand??void 0,cardAssociation:card?.brand??void 0,lastFourDigits:card?.last4??void 0,binNumber:void 0,rawResponse:paymentIntent}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Payment detail query failed",rawResponse:{error:err.message}}}},async queryBin(_request){return{success:!1,errorCode:"NOT_SUPPORTED",errorMessage:"BIN query is not supported by Stripe. Card info is available after payment method creation.",rawResponse:{}}},async saveCard(request){try{let customer=await stripe.customers.create({email:request.email,metadata:{externalId:request.externalId??""}}),paymentMethod=await stripe.paymentMethods.create({type:"card",card:{number:request.card.cardNumber,exp_month:Number(request.card.expireMonth),exp_year:Number(request.card.expireYear)}});await stripe.paymentMethods.attach(paymentMethod.id,{customer:customer.id});let card=paymentMethod.card;return{success:!0,cardUserKey:customer.id,cardToken:paymentMethod.id,cardAlias:request.card.cardAlias,binNumber:void 0,lastFourDigits:card?.last4??void 0,cardType:card?.brand??void 0,cardAssociation:card?.brand??void 0,cardFamily:card?.funding??void 0,cardBankName:void 0,rawResponse:{customer,paymentMethod}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to save card",rawResponse:{error:err.message}}}},async listCards(request){try{let response=await stripe.paymentMethods.list({customer:request.cardUserKey,type:"card"}),cards=response.data.map((pm)=>{let card=pm.card;return{cardToken:pm.id,cardAlias:pm.metadata?.alias??"",binNumber:"",lastFourDigits:card?.last4??"",cardType:card?.brand??"",cardAssociation:card?.brand??"",cardFamily:card?.funding??"",cardBankName:"",cardBankCode:0}});return{success:!0,cardUserKey:request.cardUserKey,cards,rawResponse:response}}catch(error){let err=error;return{success:!1,cards:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list cards",rawResponse:{error:err.message}}}},async deleteCard(request){try{return await stripe.paymentMethods.detach(request.cardToken),{success:!0,rawResponse:{detached:request.cardToken}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to delete card",rawResponse:{error:err.message}}}},async refund(request){try{let amountInCents=Math.round(Number(request.price)*100),refund=await stripe.refunds.create({payment_intent:request.paymentTransactionId,amount:amountInCents});return{success:refund.status==="succeeded",paymentId:refund.payment_intent,price:amountInCents/100,currency:refund.currency?.toUpperCase(),errorCode:refund.status!=="succeeded"?"REFUND_FAILED":void 0,errorMessage:refund.status!=="succeeded"?`Refund status: ${refund.status}`:void 0,rawResponse:refund}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Refund failed",rawResponse:{error:err.message}}}},parseCallback(formData){let paymentIntent=formData.payment_intent??formData.paymentId??"",redirectStatus=formData.redirect_status??"";return{success:redirectStatus==="succeeded",paymentId:paymentIntent,conversationId:formData.conversationId??"",mdStatus:redirectStatus,rawData:formData}},async registerSubMerchant(request){try{let account=await stripe.accounts.create({type:"express",country:request.currency==="TRY"?"TR":void 0,email:request.email,business_type:request.subMerchantType==="personal"?"individual":"company",metadata:{externalId:request.subMerchantExternalId,name:request.name,contactName:request.contactName??"",contactSurname:request.contactSurname??""},capabilities:{transfers:{requested:!0}}});return{success:!0,subMerchantKey:account.id,rawResponse:account}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create connected account",rawResponse:{error:err.message}}}},async updateSubMerchant(request){try{let params={metadata:{}},meta=params.metadata;if(request.name)meta.name=request.name;if(request.email)params.email=request.email;if(request.contactName)meta.contactName=request.contactName;if(request.contactSurname)meta.contactSurname=request.contactSurname;return await stripe.accounts.update(request.subMerchantKey,params),{success:!0,rawResponse:{updated:request.subMerchantKey}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to update connected account",rawResponse:{error:err.message}}}},async getSubMerchant(request){try{let account=await stripe.accounts.retrieve(request.subMerchantExternalId),meta=account.metadata??{};return{success:!0,subMerchantKey:account.id,name:meta.name,email:account.email,subMerchantType:account.business_type==="individual"?"personal":"company",currency:account.default_currency?.toUpperCase(),rawResponse:account}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to retrieve connected account",rawResponse:{error:err.message}}}},async approveSplit(request){return{success:!0,paymentTransactionId:request.paymentTransactionId,rawResponse:{message:"Stripe transfers are automatic; no manual approval needed."}}},async disapproveSplit(request){try{let reversal=await stripe.transfers.createReversal(request.paymentTransactionId);return{success:!0,paymentTransactionId:reversal.id,rawResponse:reversal}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to reverse transfer",rawResponse:{error:err.message}}}},async createProduct(request){try{let params={name:request.name};if(request.description)params.description=request.description;if(request.type)params.type=request.type;if(request.images)params.images=request.images;if(request.unitLabel)params.unit_label=request.unitLabel;if(request.url)params.url=request.url;if(request.metadata)params.metadata=request.metadata;let product=await stripe.products.create(params);return{success:!0,providerProductId:product.id,name:product.name,rawResponse:product}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create product",rawResponse:{error:err.message}}}},async updateProduct(request){try{let params={};if(request.name)params.name=request.name;if(request.description!==void 0)params.description=request.description;if(request.images)params.images=request.images;if(request.unitLabel!==void 0)params.unit_label=request.unitLabel;if(request.url!==void 0)params.url=request.url;if(request.active!==void 0)params.active=request.active;if(request.metadata)params.metadata=request.metadata;let product=await stripe.products.update(request.providerProductId,params);return{success:!0,providerProductId:product.id,rawResponse:product}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to update product",rawResponse:{error:err.message}}}},async getProduct(request){try{let product=await stripe.products.retrieve(request.providerProductId);return{success:!0,providerProductId:product.id,name:product.name,description:product.description,type:product.type,active:product.active,images:product.images,unitLabel:product.unit_label,url:product.url,metadata:product.metadata,rawResponse:product}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get product",rawResponse:{error:err.message}}}},async listProducts(request){try{let params={};if(request.active!==void 0)params.active=request.active;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.products.list(params);return{success:!0,products:response.data.map((p)=>({providerProductId:p.id,name:p.name,description:p.description,type:p.type,active:p.active,images:p.images,unitLabel:p.unit_label,metadata:p.metadata})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,products:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list products",rawResponse:{error:err.message}}}},async archiveProduct(request){try{return await stripe.products.update(request.providerProductId,{active:!1}),{success:!0,rawResponse:{archived:request.providerProductId}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to archive product",rawResponse:{error:err.message}}}},async createPrice(request){try{let params={product:request.providerProductId,currency:request.currency.toLowerCase(),unit_amount:request.unitAmount};if(request.nickname)params.nickname=request.nickname;if(request.billingScheme)params.billing_scheme=request.billingScheme;if(request.unitAmountDecimal)params.unit_amount_decimal=request.unitAmountDecimal;if(request.metadata)params.metadata=request.metadata;if(request.recurring)params.recurring={interval:request.recurring.interval,...request.recurring.intervalCount?{interval_count:request.recurring.intervalCount}:{},...request.recurring.usageType?{usage_type:request.recurring.usageType}:{},...request.recurring.aggregateUsage?{aggregate_usage:request.recurring.aggregateUsage}:{}};if(request.transformQuantity)params.transform_quantity={divide_by:request.transformQuantity.divideBy,round:request.transformQuantity.round};let price=await stripe.prices.create(params);return{success:!0,providerPriceId:price.id,rawResponse:price}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create price",rawResponse:{error:err.message}}}},async updatePrice(request){try{let params={};if(request.nickname!==void 0)params.nickname=request.nickname;if(request.active!==void 0)params.active=request.active;if(request.metadata)params.metadata=request.metadata;let price=await stripe.prices.update(request.providerPriceId,params);return{success:!0,providerPriceId:price.id,rawResponse:price}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to update price",rawResponse:{error:err.message}}}},async getPrice(request){try{let price=await stripe.prices.retrieve(request.providerPriceId),rec=price.recurring;return{success:!0,providerPriceId:price.id,providerProductId:price.product,currency:price.currency?.toUpperCase(),unitAmount:price.unit_amount,type:price.type,billingScheme:price.billing_scheme,nickname:price.nickname,active:price.active,recurring:rec?{interval:rec.interval,intervalCount:rec.interval_count??1,usageType:rec.usage_type}:void 0,metadata:price.metadata,rawResponse:price}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get price",rawResponse:{error:err.message}}}},async listPrices(request){try{let params={};if(request.providerProductId)params.product=request.providerProductId;if(request.active!==void 0)params.active=request.active;if(request.type)params.type=request.type;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.prices.list(params);return{success:!0,prices:response.data.map((p)=>{let rec=p.recurring;return{providerPriceId:p.id,providerProductId:p.product,currency:p.currency?.toUpperCase(),unitAmount:p.unit_amount,type:p.type,billingScheme:p.billing_scheme,nickname:p.nickname,active:p.active,recurring:rec?{interval:rec.interval,intervalCount:rec.interval_count??1,usageType:rec.usage_type}:void 0,metadata:p.metadata}}),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,prices:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list prices",rawResponse:{error:err.message}}}},async archivePrice(request){try{return await stripe.prices.update(request.providerPriceId,{active:!1}),{success:!0,rawResponse:{archived:request.providerPriceId}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to archive price",rawResponse:{error:err.message}}}},async createCustomer(request){try{let params={email:request.email};if(request.name)params.name=request.name;if(request.phone)params.phone=request.phone;if(request.description)params.description=request.description;if(request.metadata)params.metadata=request.metadata;if(request.address)params.address={line1:request.address.line1,line2:request.address.line2,city:request.address.city,state:request.address.state,postal_code:request.address.postalCode,country:request.address.country};let customer=await stripe.customers.create(params);if(request.taxId)try{let createTaxId=stripe.taxIds?.create;if(createTaxId)await createTaxId({customer:customer.id,type:request.taxId.type,value:request.taxId.value})}catch(_){}return{success:!0,providerCustomerId:customer.id,email:customer.email,rawResponse:customer}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create customer",rawResponse:{error:err.message}}}},async updateCustomer(request){try{let params={};if(request.email)params.email=request.email;if(request.name!==void 0)params.name=request.name;if(request.phone!==void 0)params.phone=request.phone;if(request.description!==void 0)params.description=request.description;if(request.metadata)params.metadata=request.metadata;if(request.address)params.address={line1:request.address.line1,line2:request.address.line2,city:request.address.city,state:request.address.state,postal_code:request.address.postalCode,country:request.address.country};let customer=await stripe.customers.update(request.providerCustomerId,params);return{success:!0,providerCustomerId:customer.id,rawResponse:customer}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to update customer",rawResponse:{error:err.message}}}},async getCustomer(request){try{let customer=await stripe.customers.retrieve(request.providerCustomerId);return{success:!0,providerCustomerId:customer.id,email:customer.email,name:customer.name,phone:customer.phone,description:customer.description,metadata:customer.metadata,rawResponse:customer}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get customer",rawResponse:{error:err.message}}}},async listCustomers(request){try{let params={};if(request.email)params.email=request.email;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.customers.list(params);return{success:!0,customers:response.data.map((c)=>({providerCustomerId:c.id,email:c.email,name:c.name,phone:c.phone})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,customers:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list customers",rawResponse:{error:err.message}}}},async deleteCustomer(request){try{return await stripe.customers.del(request.providerCustomerId),{success:!0,rawResponse:{deleted:request.providerCustomerId}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to delete customer",rawResponse:{error:err.message}}}},async createSubscription(request){try{let params={customer:request.providerCustomerId,items:request.items.map((i)=>({price:i.providerPriceId,...i.quantity!==void 0?{quantity:i.quantity}:{}}))};if(request.trialPeriodDays)params.trial_period_days=request.trialPeriodDays;if(request.collectionMethod)params.collection_method=request.collectionMethod;if(request.daysUntilDue)params.days_until_due=request.daysUntilDue;if(request.cancelAtPeriodEnd!==void 0)params.cancel_at_period_end=request.cancelAtPeriodEnd;if(request.metadata)params.metadata=request.metadata;if(request.defaultPaymentMethod)params.default_payment_method=request.defaultPaymentMethod;params.payment_behavior="default_incomplete",params.payment_settings={save_default_payment_method:"on_subscription"},params.expand=["latest_invoice.payment_intent"];let sub=await stripe.subscriptions.create(params),pi=sub.latest_invoice?.payment_intent;return{success:!0,providerSubscriptionId:sub.id,status:sub.status,currentPeriodStart:sub.current_period_start?new Date(sub.current_period_start*1000).toISOString():void 0,currentPeriodEnd:sub.current_period_end?new Date(sub.current_period_end*1000).toISOString():void 0,clientSecret:pi?.client_secret,rawResponse:sub}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create subscription",rawResponse:{error:err.message}}}},async updateSubscription(request){try{let params={};if(request.items)params.items=request.items.map((i)=>({...i.id?{id:i.id}:{},price:i.providerPriceId,...i.quantity!==void 0?{quantity:i.quantity}:{}}));if(request.cancelAtPeriodEnd!==void 0)params.cancel_at_period_end=request.cancelAtPeriodEnd;if(request.trialEnd!==void 0)params.trial_end=request.trialEnd;if(request.metadata)params.metadata=request.metadata;if(request.collectionMethod)params.collection_method=request.collectionMethod;if(request.daysUntilDue)params.days_until_due=request.daysUntilDue;if(request.defaultPaymentMethod)params.default_payment_method=request.defaultPaymentMethod;if(request.prorationBehavior)params.proration_behavior=request.prorationBehavior;let sub=await stripe.subscriptions.update(request.providerSubscriptionId,params);return{success:!0,providerSubscriptionId:sub.id,status:sub.status,rawResponse:sub}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to update subscription",rawResponse:{error:err.message}}}},async cancelSubscription(request){try{if(request.cancelAtPeriodEnd){let sub2=await stripe.subscriptions.update(request.providerSubscriptionId,{cancel_at_period_end:!0});return{success:!0,providerSubscriptionId:sub2.id,status:sub2.status,rawResponse:sub2}}let params={};if(request.invoiceNow)params.invoice_now=request.invoiceNow;if(request.prorate)params.prorate=request.prorate;let sub=await stripe.subscriptions.cancel(request.providerSubscriptionId,params);return{success:!0,providerSubscriptionId:sub.id,status:sub.status,rawResponse:sub}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to cancel subscription",rawResponse:{error:err.message}}}},async getSubscription(request){try{let sub=await stripe.subscriptions.retrieve(request.providerSubscriptionId),itemsData=sub.items?.data??[];return{success:!0,providerSubscriptionId:sub.id,providerCustomerId:sub.customer,status:sub.status,currentPeriodStart:sub.current_period_start?new Date(sub.current_period_start*1000).toISOString():void 0,currentPeriodEnd:sub.current_period_end?new Date(sub.current_period_end*1000).toISOString():void 0,cancelAtPeriodEnd:sub.cancel_at_period_end,canceledAt:sub.canceled_at?new Date(sub.canceled_at*1000).toISOString():void 0,trialStart:sub.trial_start?new Date(sub.trial_start*1000).toISOString():void 0,trialEnd:sub.trial_end?new Date(sub.trial_end*1000).toISOString():void 0,items:itemsData.map((i)=>({id:i.id,providerPriceId:i.price?.id??i.price,quantity:i.quantity??1})),metadata:sub.metadata,rawResponse:sub}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get subscription",rawResponse:{error:err.message}}}},async listSubscriptions(request){try{let params={};if(request.providerCustomerId)params.customer=request.providerCustomerId;if(request.status)params.status=request.status;if(request.providerPriceId)params.price=request.providerPriceId;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.subscriptions.list(params);return{success:!0,subscriptions:response.data.map((s)=>({providerSubscriptionId:s.id,providerCustomerId:s.customer,status:s.status,currentPeriodStart:s.current_period_start?new Date(s.current_period_start*1000).toISOString():void 0,currentPeriodEnd:s.current_period_end?new Date(s.current_period_end*1000).toISOString():void 0,cancelAtPeriodEnd:s.cancel_at_period_end})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,subscriptions:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list subscriptions",rawResponse:{error:err.message}}}},async reportUsage(request){try{let params={quantity:request.quantity,action:request.action??"increment"};if(request.timestamp)params.timestamp=request.timestamp;let record=await stripe.subscriptionItems.usageRecords.create(request.subscriptionItemId,params);return{success:!0,usageRecordId:record.id,quantity:record.quantity,timestamp:record.timestamp,rawResponse:record}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to report usage",rawResponse:{error:err.message}}}},async listUsageRecordSummaries(request){try{let params={};if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.subscriptionItems.usageRecordSummaries.list(request.subscriptionItemId,params);return{success:!0,summaries:response.data.map((s)=>({id:s.id,totalUsage:s.total_usage,period:s.period,subscriptionItem:s.subscription_item})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,summaries:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list usage summaries",rawResponse:{error:err.message}}}},async createInvoice(request){try{let params={customer:request.providerCustomerId};if(request.collectionMethod)params.collection_method=request.collectionMethod;if(request.daysUntilDue)params.days_until_due=request.daysUntilDue;if(request.description)params.description=request.description;if(request.metadata)params.metadata=request.metadata;if(request.autoAdvance!==void 0)params.auto_advance=request.autoAdvance;let invoice=await stripe.invoices.create(params);if(request.items?.length)for(let item of request.items){let lineParams={customer:request.providerCustomerId,invoice:invoice.id};if(item.providerPriceId)lineParams.price=item.providerPriceId;if(item.quantity)lineParams.quantity=item.quantity;if(item.unitAmount!==void 0)lineParams.unit_amount=item.unitAmount;if(item.currency)lineParams.currency=item.currency;if(item.description)lineParams.description=item.description;await stripe.invoiceItems.create(lineParams)}return{success:!0,providerInvoiceId:invoice.id,status:invoice.status,hostedInvoiceUrl:invoice.hosted_invoice_url,invoicePdf:invoice.invoice_pdf,rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create invoice",rawResponse:{error:err.message}}}},async finalizeInvoice(request){try{let params={};if(request.autoAdvance!==void 0)params.auto_advance=request.autoAdvance;let invoice=await stripe.invoices.finalizeInvoice(request.providerInvoiceId,params);return{success:!0,providerInvoiceId:invoice.id,status:invoice.status,hostedInvoiceUrl:invoice.hosted_invoice_url,invoicePdf:invoice.invoice_pdf,rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to finalize invoice",rawResponse:{error:err.message}}}},async sendInvoice(request){try{let invoice=await stripe.invoices.sendInvoice(request.providerInvoiceId);return{success:!0,providerInvoiceId:invoice.id,rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to send invoice",rawResponse:{error:err.message}}}},async voidInvoice(request){try{let invoice=await stripe.invoices.voidInvoice(request.providerInvoiceId);return{success:!0,providerInvoiceId:invoice.id,rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to void invoice",rawResponse:{error:err.message}}}},async payInvoice(request){try{let params={};if(request.paymentMethod)params.payment_method=request.paymentMethod;let invoice=await stripe.invoices.pay(request.providerInvoiceId,params);return{success:!0,providerInvoiceId:invoice.id,status:invoice.status,amountPaid:invoice.amount_paid,amountDue:invoice.amount_due,rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to pay invoice",rawResponse:{error:err.message}}}},async getInvoice(request){try{let invoice=await stripe.invoices.retrieve(request.providerInvoiceId),linesData=invoice.lines?.data??[];return{success:!0,providerInvoiceId:invoice.id,providerCustomerId:invoice.customer,providerSubscriptionId:invoice.subscription,status:invoice.status,amountDue:invoice.amount_due,amountPaid:invoice.amount_paid,amountRemaining:invoice.amount_remaining,currency:invoice.currency?.toUpperCase(),description:invoice.description,hostedInvoiceUrl:invoice.hosted_invoice_url,invoicePdf:invoice.invoice_pdf,dueDate:invoice.due_date?new Date(invoice.due_date*1000).toISOString():void 0,periodStart:invoice.period_start?new Date(invoice.period_start*1000).toISOString():void 0,periodEnd:invoice.period_end?new Date(invoice.period_end*1000).toISOString():void 0,metadata:invoice.metadata,lines:linesData.map((l)=>({id:l.id,description:l.description,amount:l.amount,currency:l.currency?.toUpperCase(),quantity:l.quantity,providerPriceId:l.price?.id})),rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get invoice",rawResponse:{error:err.message}}}},async listInvoices(request){try{let params={};if(request.providerCustomerId)params.customer=request.providerCustomerId;if(request.providerSubscriptionId)params.subscription=request.providerSubscriptionId;if(request.status)params.status=request.status;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.invoices.list(params);return{success:!0,invoices:response.data.map((inv)=>({providerInvoiceId:inv.id,providerCustomerId:inv.customer,status:inv.status,amountDue:inv.amount_due,amountPaid:inv.amount_paid,currency:inv.currency?.toUpperCase(),dueDate:inv.due_date?new Date(inv.due_date*1000).toISOString():void 0,hostedInvoiceUrl:inv.hosted_invoice_url})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,invoices:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list invoices",rawResponse:{error:err.message}}}},async getBalance(){try{let bal=await stripe.balance.retrieve(),mapAmounts=(arr)=>(arr??[]).map((a)=>({amount:a.amount,currency:a.currency?.toUpperCase()}));return{success:!0,available:mapAmounts(bal.available),pending:mapAmounts(bal.pending),connectReserved:mapAmounts(bal.connect_reserved),rawResponse:bal}}catch(error){let err=error;return{success:!1,available:[],pending:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get balance",rawResponse:{error:err.message}}}},async createPayout(request){try{let params={amount:request.amount,currency:request.currency.toLowerCase()};if(request.destination)params.destination=request.destination;if(request.description)params.description=request.description;if(request.metadata)params.metadata=request.metadata;if(request.method)params.method=request.method;if(request.sourceType)params.source_type=request.sourceType;if(request.statementDescriptor)params.statement_descriptor=request.statementDescriptor;let payout=await stripe.payouts.create(params,request.idempotencyKey?{idempotencyKey:request.idempotencyKey}:void 0);return{success:!0,providerPayoutId:payout.id,amount:payout.amount,currency:payout.currency?.toUpperCase(),status:payout.status,arrivalDate:payout.arrival_date?new Date(payout.arrival_date*1000).toISOString():void 0,method:payout.method,rawResponse:payout}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create payout",rawResponse:{error:err.message}}}},async getPayout(request){try{let payout=await stripe.payouts.retrieve(request.providerPayoutId);return{success:!0,providerPayoutId:payout.id,amount:payout.amount,currency:payout.currency?.toUpperCase(),status:payout.status,arrivalDate:payout.arrival_date?new Date(payout.arrival_date*1000).toISOString():void 0,method:payout.method,description:payout.description,destination:payout.destination,metadata:payout.metadata,failureCode:payout.failure_code,failureMessage:payout.failure_message,rawResponse:payout}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get payout",rawResponse:{error:err.message}}}},async cancelPayout(request){try{let payout=await stripe.payouts.cancel(request.providerPayoutId);return{success:!0,providerPayoutId:payout.id,status:payout.status,rawResponse:payout}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to cancel payout",rawResponse:{error:err.message}}}},async listPayouts(request){try{let params={};if(request.status)params.status=request.status;if(request.destination)params.destination=request.destination;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;if(request.arrivalDateGte||request.arrivalDateLte){let arrival={};if(request.arrivalDateGte)arrival.gte=request.arrivalDateGte;if(request.arrivalDateLte)arrival.lte=request.arrivalDateLte;params.arrival_date=arrival}let response=await stripe.payouts.list(params);return{success:!0,payouts:response.data.map((p)=>({providerPayoutId:p.id,amount:p.amount,currency:p.currency?.toUpperCase(),status:p.status,arrivalDate:p.arrival_date?new Date(p.arrival_date*1000).toISOString():void 0,method:p.method,description:p.description})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,payouts:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list payouts",rawResponse:{error:err.message}}}},async createTransfer(request){try{let params={amount:request.amount,currency:request.currency.toLowerCase(),destination:request.destination};if(request.description)params.description=request.description;if(request.metadata)params.metadata=request.metadata;if(request.sourceTransaction)params.source_transaction=request.sourceTransaction;if(request.transferGroup)params.transfer_group=request.transferGroup;let transfer=await stripe.transfers.create(params,request.idempotencyKey?{idempotencyKey:request.idempotencyKey}:void 0);return{success:!0,providerTransferId:transfer.id,amount:transfer.amount,currency:transfer.currency?.toUpperCase(),destination:transfer.destination,rawResponse:transfer}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create transfer",rawResponse:{error:err.message}}}},async listTransfers(request){try{let params={};if(request.destination)params.destination=request.destination;if(request.transferGroup)params.transfer_group=request.transferGroup;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.transfers.list(params);return{success:!0,transfers:response.data.map((t)=>({providerTransferId:t.id,amount:t.amount,currency:t.currency?.toUpperCase(),destination:t.destination,description:t.description,created:t.created?new Date(t.created*1000).toISOString():void 0})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,transfers:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list transfers",rawResponse:{error:err.message}}}}}},verifyStripeWebhook=async(rawBody,signature,webhookSecret)=>{try{let StripeModule=__require("stripe"),Stripe=StripeModule.default??StripeModule,stripe=new Stripe("sk_dummy_for_webhook_verification"),factory=Stripe,cryptoProvider=typeof factory.createSubtleCryptoProvider==="function"?factory.createSubtleCryptoProvider():typeof factory.createNodeCryptoProvider==="function"?factory.createNodeCryptoProvider():void 0;return{success:!0,event:await stripe.webhooks.constructEventAsync(rawBody,signature,webhookSecret,void 0,cryptoProvider)}}catch(error){return{success:!1,event:null,error:error instanceof Error?error.message:"Webhook verification failed"}}};var init_StripeProvider=__esm(()=>{init_Logger2()});var resolveEnvValue3=(value)=>{let envValue=process.env[value];if(envValue)return envValue;return value},createPaymentService=(config)=>{let paymentConfig=config.payment;if(!paymentConfig?.enabled)return null;let log2=logger.scoped("payment.init"),providerName=paymentConfig.provider;if(!providerName)return log2.warn("payment.enabled=true but no provider specified"),null;let providerConfig=null,provider=null,webhookSecret;switch(providerName){case"iyzico":{let iyzicoConf=paymentConfig.iyzico;if(!iyzicoConf)return log2.warn("payment.provider=iyzico but no iyzico config block"),null;let apiUrl=resolveEnvValue3(iyzicoConf.apiUrl),apiKey=resolveEnvValue3(iyzicoConf.apiKey),secretKey=resolveEnvValue3(iyzicoConf.secretKey);if(!apiUrl||!apiKey||!secretKey)return log2.warn("iyzico credentials missing \u2014 ensure env vars are set",{apiUrl:iyzicoConf.apiUrl,apiKeyEnv:iyzicoConf.apiKey,secretKeyEnv:iyzicoConf.secretKey}),null;providerConfig={name:"iyzico",apiUrl,apiKey,secretKey},provider=createIyzicoProvider(providerConfig);break}case"stripe":{let stripeConf=paymentConfig.stripe;if(!stripeConf)return log2.warn("payment.provider=stripe but no stripe config block"),null;let secretKey=resolveEnvValue3(stripeConf.secretKey),whSecret=resolveEnvValue3(stripeConf.webhookSecret);if(!secretKey)return log2.warn("Stripe secret key missing \u2014 ensure env var is set",{secretKeyEnv:stripeConf.secretKey}),null;if(!whSecret)log2.warn("Stripe webhook secret missing \u2014 webhook verification will fail",{webhookSecretEnv:stripeConf.webhookSecret});webhookSecret=whSecret,provider=createStripeProvider({secretKey,webhookSecret:whSecret,apiVersion:stripeConf.apiVersion,maxNetworkRetries:stripeConf.maxNetworkRetries,timeout:stripeConf.timeout});break}default:return log2.warn(`Unknown provider: ${providerName}`),null}if(!provider)return null;return log2.info(`Initialized provider: ${providerName}`),{provider:providerName,providerInstance:provider,webhookSecret}};var init_PaymentService=__esm(()=>{init_Logger2();init_IyzicoProvider();init_StripeProvider()});var exports_Payment={};__export(exports_Payment,{verifyStripeWebhook:()=>verifyStripeWebhook,resolveMarketplaceConfig:()=>resolveMarketplaceConfig,createStripeProvider:()=>createStripeProvider,createPaymentService:()=>createPaymentService,createMarketplaceServices:()=>createMarketplaceServices,createIyzicoProvider:()=>createIyzicoProvider,computeSplitBreakdown:()=>computeSplitBreakdown,MarketplaceService:()=>MarketplaceService,MarketplacePayoutService:()=>MarketplacePayoutService});var init_Payment=__esm(()=>{init_IyzicoProvider();init_Marketplace();init_PaymentService();init_StripeProvider()});import{createHash as createHash2,randomUUID as randomUUID4}from"crypto";class RateLimiter{redis;logger;config;constructor(deps){this.redis=deps.redis,this.logger=deps.logger,this.config=this.mergeConfig(deps.config);for(let type of["login","register","passwordReset","magicLink"]){let declared=deps.config.authRoutes?.[type];if(declared?.max!==void 0&&declared.perAddressMax===void 0)this.logger.warn(`[RateLimit] authRoutes.${type}.max is set but perAddressMax is not. \`max\` is now ONE ACCOUNT's budget (${declared.max}); the limit for one ADDRESS across all accounts is \`perAddressMax\`, defaulting to ${this.config.authRoutes[type].perAddressMax}. Set it explicitly if that is not what you want.`)}if(deps.config.skipSuccessfulRequests)this.logger.warn("[RateLimit] `skipSuccessfulRequests` is accepted but NOT implemented: successful requests still count toward every limit. Size your limits as if it were false.")}mergeConfig(config){return{enabled:config.enabled??DEFAULT_CONFIG4.enabled,strategy:config.strategy??DEFAULT_CONFIG4.strategy,keyPrefix:config.keyPrefix??DEFAULT_CONFIG4.keyPrefix,failClosed:config.failClosed,authRoutes:{window:config.authRoutes?.window??DEFAULT_CONFIG4.authRoutes.window,max:config.authRoutes?.max??DEFAULT_CONFIG4.authRoutes.max,login:{window:config.authRoutes?.login?.window??DEFAULT_AUTH_LOGIN.window,max:config.authRoutes?.login?.max??DEFAULT_AUTH_LOGIN.max,perAddressMax:config.authRoutes?.login?.perAddressMax??DEFAULT_AUTH_LOGIN.perAddressMax,blockDuration:config.authRoutes?.login?.blockDuration??DEFAULT_AUTH_LOGIN.blockDuration},register:{window:config.authRoutes?.register?.window??DEFAULT_AUTH_REGISTER.window,max:config.authRoutes?.register?.max??DEFAULT_AUTH_REGISTER.max,perAddressMax:config.authRoutes?.register?.perAddressMax??DEFAULT_AUTH_REGISTER.perAddressMax,blockDuration:config.authRoutes?.register?.blockDuration??DEFAULT_AUTH_REGISTER.blockDuration},passwordReset:{window:config.authRoutes?.passwordReset?.window??DEFAULT_AUTH_PASSWORD_RESET.window,max:config.authRoutes?.passwordReset?.max??DEFAULT_AUTH_PASSWORD_RESET.max,perAddressMax:config.authRoutes?.passwordReset?.perAddressMax??DEFAULT_AUTH_PASSWORD_RESET.perAddressMax,blockDuration:config.authRoutes?.passwordReset?.blockDuration??DEFAULT_AUTH_PASSWORD_RESET.blockDuration},magicLink:{window:config.authRoutes?.magicLink?.window??DEFAULT_AUTH_MAGIC_LINK.window,max:config.authRoutes?.magicLink?.max??DEFAULT_AUTH_MAGIC_LINK.max,perAddressMax:config.authRoutes?.magicLink?.perAddressMax??DEFAULT_AUTH_MAGIC_LINK.perAddressMax,blockDuration:config.authRoutes?.magicLink?.blockDuration??DEFAULT_AUTH_MAGIC_LINK.blockDuration}},publicRoutes:{window:config.publicRoutes?.window??DEFAULT_CONFIG4.publicRoutes.window,max:config.publicRoutes?.max??DEFAULT_CONFIG4.publicRoutes.max},privateRoutes:{window:config.privateRoutes?.window??DEFAULT_CONFIG4.privateRoutes.window,max:config.privateRoutes?.max??DEFAULT_CONFIG4.privateRoutes.max},byIp:config.byIp??DEFAULT_CONFIG4.byIp,byUserId:config.byUserId??DEFAULT_CONFIG4.byUserId,byEndpoint:config.byEndpoint??DEFAULT_CONFIG4.byEndpoint,skipSuccessfulRequests:config.skipSuccessfulRequests??DEFAULT_CONFIG4.skipSuccessfulRequests,headers:{remaining:config.headers?.remaining??DEFAULT_CONFIG4.headers.remaining,reset:config.headers?.reset??DEFAULT_CONFIG4.headers.reset,limit:config.headers?.limit??DEFAULT_CONFIG4.headers.limit},whitelist:config.whitelist??DEFAULT_CONFIG4.whitelist,blacklist:config.blacklist??DEFAULT_CONFIG4.blacklist}}parseTimeToMs(time){let match=time.match(/^(\d+)(ms|s|m|h|d)$/);if(!match||!match[1]||!match[2])return 60000;let value=parseInt(match[1],10);switch(match[2]){case"ms":return value;case"s":return value*1000;case"m":return value*60*1000;case"h":return value*60*60*1000;case"d":return value*24*60*60*1000;default:return 60000}}buildKey(params){let parts=[this.config.keyPrefix,params.category];if(params.authType&¶ms.authType!=="other")parts.push(params.authType);if(params.identity)parts.push(`as:${hashIdentity(params.identity)}`);if(this.config.byIp&¶ms.ip)parts.push(`ip:${params.ip}`);if(this.config.byUserId&¶ms.userId)parts.push(`user:${params.userId}`);if(this.config.byEndpoint&¶ms.endpoint)parts.push(`ep:${params.endpoint.replace(/\//g,"_")}`);return parts.join(":")}getLimits(category,authType,perIdentity=!1){if(category==="auth"){if(authType&&authType!=="other"){let authConfig=this.config.authRoutes[authType];if(authConfig)return perIdentity?authConfig:{...authConfig,max:authConfig.perAddressMax}}return{window:this.config.authRoutes.window,max:this.config.authRoutes.max}}if(category==="public")return this.config.publicRoutes;return this.config.privateRoutes}isWhitelisted(ip){return this.config.whitelist.some((pattern)=>{if(pattern.includes("*"))return new RegExp(`^${pattern.replace(/\*/g,".*")}$`).test(ip);return pattern===ip})}isBlacklisted(ip){return this.config.blacklist.some((pattern)=>{if(pattern.includes("*"))return new RegExp(`^${pattern.replace(/\*/g,".*")}$`).test(ip);return pattern===ip})}async readRedis(key2){let result=await this.redis.read(key2);if(result.success)return result.data;throw new RateLimiterBackendError(result.error?String(result.error):"rate-limit backend read failed")}async check(params){if(!this.config.enabled)return{allowed:!0,remaining:-1,resetAt:0,limit:-1};if(this.isWhitelisted(params.ip))return{allowed:!0,remaining:-1,resetAt:0,limit:-1};if(this.isBlacklisted(params.ip))return this.logger.warn(`[RateLimit] Blacklisted IP: ${params.ip}`),{allowed:!1,remaining:0,resetAt:Date.now()+86400000,limit:0,retryAfter:86400};let key2=this.buildKey(params),useStrictMax=!!params.identity||params.identityResolved===!1,limits=this.getLimits(params.category,params.authType,useStrictMax),windowMs=this.parseTimeToMs(limits.window),blockKey=`${key2}:blocked`;try{let isBlocked=await this.readRedis(blockKey);if(isBlocked&&isBlocked.until>Date.now()){let retryAfter=Math.ceil((isBlocked.until-Date.now())/1000);return this.logger.warn(`[RateLimit] Blocked: ${key2}, retry after ${retryAfter}s`),{allowed:!1,remaining:0,resetAt:isBlocked.until,limit:limits.max,retryAfter}}if(this.config.strategy==="sliding-window")return await this.slidingWindowCheck(key2,limits.max,windowMs,limits.blockDuration);if(this.config.strategy==="fixed-window")return await this.fixedWindowCheck(key2,limits.max,windowMs,limits.blockDuration);return await this.tokenBucketCheck(key2,limits.max,windowMs)}catch(err){let failClosed=this.config.failClosed===!0||this.config.failClosed===void 0&¶ms.category==="auth";if(this.logger.error(`[RateLimit] Backend error for ${key2} \u2014 failing ${failClosed?"closed":"open"}`,{error:err instanceof Error?err.message:String(err),category:params.category}),failClosed)return{allowed:!1,remaining:0,resetAt:Date.now()+windowMs,limit:limits.max,retryAfter:Math.ceil(windowMs/1000)};return{allowed:!0,remaining:-1,resetAt:0,limit:limits.max}}}async slidingWindowCheck(key2,max,windowMs,blockDuration){let client=this.redis.getDirectClient?.();if(client)return this.slidingWindowAtomic(client,key2,max,windowMs,blockDuration);let now=Date.now(),windowStart=now-windowMs,dataKey=`${key2}:sw`,timestamps=(await this.readRedis(dataKey))?.timestamps||[];timestamps=timestamps.filter((ts)=>ts>windowStart);let count=timestamps.length,firstTimestamp=timestamps[0],resetAt=firstTimestamp!==void 0?firstTimestamp+windowMs:now+windowMs;if(count>=max){if(blockDuration){let blockMs=this.parseTimeToMs(blockDuration);await this.redis.create(`${key2}:blocked`,{until:now+blockMs},Math.ceil(blockMs/1000))}return{allowed:!1,remaining:0,resetAt,limit:max,retryAfter:Math.ceil((resetAt-now)/1000)}}return timestamps.push(now),await this.redis.create(dataKey,{timestamps},Math.ceil(windowMs/1000)+1),{allowed:!0,remaining:max-timestamps.length,resetAt,limit:max}}async slidingWindowAtomic(client,key2,max,windowMs,blockDuration){let now=Date.now(),member=`${now}-${randomUUID4()}`,res=await client.eval(SLIDING_WINDOW_LUA,1,`${key2}:swz`,String(now),String(windowMs),String(max),member),allowed=Number(res?.[0])===1,count=Number(res?.[1]??0),resetAt=now+windowMs;if(!allowed){if(blockDuration){let blockMs=this.parseTimeToMs(blockDuration);await this.redis.create(`${key2}:blocked`,{until:now+blockMs},Math.ceil(blockMs/1000))}return{allowed:!1,remaining:0,resetAt,limit:max,retryAfter:Math.ceil(windowMs/1000)}}return{allowed:!0,remaining:Math.max(0,max-count),resetAt,limit:max}}async fixedWindowAtomic(client,key2,max,windowMs,blockDuration){let now=Date.now(),windowId=Math.floor(now/windowMs),dataKey=`${key2}:fw:${windowId}`,resetAt=(windowId+1)*windowMs,count=await client.incr(dataKey);if(count===1)await client.pexpire(dataKey,windowMs+1000);if(count>max){if(blockDuration){let blockMs=this.parseTimeToMs(blockDuration);await this.redis.create(`${key2}:blocked`,{until:now+blockMs},Math.ceil(blockMs/1000))}return{allowed:!1,remaining:0,resetAt,limit:max,retryAfter:Math.ceil((resetAt-now)/1000)}}return{allowed:!0,remaining:Math.max(0,max-count),resetAt,limit:max}}async fixedWindowCheck(key2,max,windowMs,blockDuration){let client=this.redis.getDirectClient?.();if(client)return this.fixedWindowAtomic(client,key2,max,windowMs,blockDuration);let now=Date.now(),windowId=Math.floor(now/windowMs),dataKey=`${key2}:fw:${windowId}`,resetAt=(windowId+1)*windowMs,count=(await this.readRedis(dataKey))?.count||0;if(count>=max){if(blockDuration){let blockMs=this.parseTimeToMs(blockDuration);await this.redis.create(`${key2}:blocked`,{until:now+blockMs},Math.ceil(blockMs/1000))}return{allowed:!1,remaining:0,resetAt,limit:max,retryAfter:Math.ceil((resetAt-now)/1000)}}return await this.redis.create(dataKey,{count:count+1},Math.ceil(windowMs/1000)+1),{allowed:!0,remaining:max-(count+1),resetAt,limit:max}}async tokenBucketCheck(key2,max,windowMs){let now=Date.now(),refillRate=max/windowMs,dataKey=`${key2}:tb`,data=await this.readRedis(dataKey),tokens=data?.tokens??max,lastRefill=data?.lastRefill??now,refill=(now-lastRefill)*refillRate;if(tokens=Math.min(max,tokens+refill),tokens<1){let waitTime=Math.ceil((1-tokens)/refillRate);return{allowed:!1,remaining:0,resetAt:now+waitTime,limit:max,retryAfter:Math.ceil(waitTime/1000)}}return tokens-=1,await this.redis.create(dataKey,{tokens,lastRefill:now},Math.ceil(windowMs/1000)*2),{allowed:!0,remaining:Math.floor(tokens),resetAt:now+windowMs,limit:max}}getHeaders(result){let headers={};return headers[this.config.headers.remaining]=String(result.remaining),headers[this.config.headers.reset]=String(Math.ceil(result.resetAt/1000)),headers[this.config.headers.limit]=String(result.limit),headers}isEnabled(){return this.config.enabled}}var SLIDING_WINDOW_LUA=`
|
|
100
100
|
local now = tonumber(ARGV[1])
|
package/dist/server.js
CHANGED
|
@@ -94,7 +94,7 @@ and limitations under the License.
|
|
|
94
94
|
`).filter((l)=>l.length>0).length}catch{}try{let stat3=readFileSync2(`${cgroupRoot}/memory.stat`,"utf8");breakdown.cgroupAnon=statField(stat3,"anon"),breakdown.cgroupFile=statField(stat3,"file"),breakdown.cgroupSock=statField(stat3,"sock"),breakdown.cgroupSlab=statField(stat3,"slab"),breakdown.cgroupKernelStack=statField(stat3,"kernel_stack")}catch{}return breakdown}var init_nativeMemory=()=>{};import*as fs2 from"fs";import*as os from"os";function readCgroupLimitBytes(){let read=(path)=>{try{return fs2.readFileSync(path,"utf-8")}catch{return null}};return parseCgroupLimit(read("/sys/fs/cgroup/memory.max")??read("/sys/fs/cgroup/memory/memory.limit_in_bytes"))}function readRuntimeMemoryAdvice(){let read=(path)=>{try{return fs2.readFileSync(path,"utf-8")}catch{return null}},limitRaw=read("/sys/fs/cgroup/memory.max")??read("/sys/fs/cgroup/memory/memory.limit_in_bytes"),forced=Number(process.env.BUN_JSC_forceRAMSize);return runtimeMemoryAdvice({cgroupLimit:parseCgroupLimit(limitRaw),hostTotal:os.totalmem(),forcedRamSize:Number.isFinite(forced)&&forced>0?forced:null})}class SystemCollector{config;lastCpuInfo=null;constructor(config){this.config=config}async collect(){if(!this.config?.enabled)return null;let metrics={cpu:{usage:0,cores:0},memory:{total:0,used:0,free:0,usagePercent:0,rss:0,heapUsed:0,heapTotal:0,external:0,arrayBuffers:0,scope:"host"},disk:{total:0,used:0,free:0,usagePercent:0},network:{bytesIn:0,bytesOut:0},process:{uptime:0,pid:0,eventLoopLag:0}};if(this.config.metrics?.cpu!==!1)metrics.cpu=this.collectCpu();if(this.config.metrics?.memory!==!1)metrics.memory=this.collectMemory();if(this.config.metrics?.disk!==!1)metrics.disk=await this.collectDisk();if(this.config.metrics?.network)metrics.network=this.collectNetwork();if(this.config.metrics?.process!==!1)metrics.process=await this.collectProcess();return metrics}collectCpu(){let cpus2=os.cpus(),cores=cpus2.length,idle=0,total=0;for(let cpu of cpus2)idle+=cpu.times.idle,total+=cpu.times.user+cpu.times.nice+cpu.times.sys+cpu.times.idle+cpu.times.irq;let usage=0;if(this.lastCpuInfo){let idleDiff=idle-this.lastCpuInfo.idle,totalDiff=total-this.lastCpuInfo.total;usage=totalDiff>0?Math.round((1-idleDiff/totalDiff)*100*100)/100:0}return this.lastCpuInfo={idle,total},{usage,cores}}read(path){try{return fs2.readFileSync(path,"utf-8")}catch{return null}}collectMemory(){let memUsage=process.memoryUsage(),limitRaw=this.read("/sys/fs/cgroup/memory.max")??this.read("/sys/fs/cgroup/memory/memory.limit_in_bytes"),usageRaw=this.read("/sys/fs/cgroup/memory.current")??this.read("/sys/fs/cgroup/memory/memory.usage_in_bytes"),statRaw=this.read("/sys/fs/cgroup/memory.stat")??this.read("/sys/fs/cgroup/memory/memory.stat");return resolveMemory({limit:parseCgroupLimit(limitRaw),current:parseCgroupUsage(usageRaw),inactiveFile:parseInactiveFile(statRaw),hostTotal:os.totalmem(),hostAvailable:parseMemAvailable(this.read("/proc/meminfo")),hostFree:os.freemem(),process:{rss:memUsage.rss,heapUsed:memUsage.heapUsed,heapTotal:memUsage.heapTotal,external:memUsage.external,arrayBuffers:memUsage.arrayBuffers}})}async collectDisk(){try{let stats=fs2.statfsSync("/"),total=stats.blocks*stats.bsize,free=stats.bfree*stats.bsize,used=total-free,usagePercent=Math.round(used/total*100*100)/100;return{total,used,free,usagePercent}}catch{return{total:0,used:0,free:0,usagePercent:0}}}collectNetwork(){try{let content=fs2.readFileSync("/proc/net/dev","utf-8"),bytesIn=0,bytesOut=0;for(let line of content.split(`
|
|
95
95
|
`).slice(2)){let[namePart,dataPart]=line.split(":");if(!namePart||!dataPart)continue;if(namePart.trim()==="lo")continue;let fields=dataPart.trim().split(/\s+/);bytesIn+=Number.parseInt(fields[0]??"0",10)||0,bytesOut+=Number.parseInt(fields[8]??"0",10)||0}return{bytesIn,bytesOut}}catch{return{bytesIn:0,bytesOut:0}}}async collectProcess(){let uptime=process.uptime(),pid=process.pid,lagStart=Date.now(),eventLoopLag=await new Promise((resolve)=>{setImmediate(()=>{resolve(Date.now()-lagStart)})});return{uptime,pid,eventLoopLag,handles:collectHandles(),nativeMemory:collectNativeMemory()}}}var init_SystemCollector=__esm(()=>{init_handles();init_nativeMemory()});function peakContext(snapshot){let app=snapshot.application,busiest=Object.entries(app?.requests?.byEndpoint??{}).sort((a,b)=>b[1]-a[1])[0],tags={};if(busiest)tags.busiestEndpoint=busiest[0],tags.busiestEndpointCount=String(busiest[1]);if(app?.requests?.perMinute!==void 0)tags.requestsPerMinute=String(app.requests.perMinute);if(app?.responseTime?.p95!==void 0)tags.p95=String(app.responseTime.p95);if(app?.errors?.rate!==void 0)tags.errorRate=String(app.errors.rate);if(snapshot.system?.cpu?.usage!==void 0)tags.cpu=String(snapshot.system.cpu.usage);if(snapshot.system?.memory?.usagePercent!==void 0)tags.memory=String(snapshot.system.memory.usagePercent);if(snapshot.database?.connections?.active!==void 0)tags.dbConnections=String(snapshot.database.connections.active);return tags}function newPeaks(snapshot,previous,timestamp){let peaks=new Map(previous),points=[],tags=peakContext(snapshot);for(let watched of WATCHED_PEAKS){let value=watched.of(snapshot);if(value===void 0||!Number.isFinite(value))continue;let before=peaks.get(watched.name);if(before!==void 0&&value<=before)continue;peaks.set(watched.name,value),points.push({timestamp,metricType:"peak",metricName:watched.name,value,tags})}return{points,peaks}}var WATCHED_PEAKS;var init_peaks=__esm(()=>{WATCHED_PEAKS=[{name:"requests.perMinute",of:(s)=>s.application?.requests?.perMinute},{name:"responseTime.p95",of:(s)=>s.application?.responseTime?.p95},{name:"errors.rate",of:(s)=>s.application?.errors?.rate},{name:"cpu.usage",of:(s)=>s.system?.cpu?.usage},{name:"memory.usagePercent",of:(s)=>s.system?.memory?.usagePercent},{name:"database.connections.active",of:(s)=>s.database?.connections?.active}]});function median(values){if(values.length===0)return null;let sorted=[...values].sort((a,b)=>a-b),mid=Math.floor(sorted.length/2);return sorted.length%2===1?sorted[mid]:(sorted[mid-1]+sorted[mid])/2}function summariseRealtime(events,options={}){let windowMs=options.windowMs??900000,since=(options.now??Date.now())-windowMs,recent=events.filter((e)=>e.timestamp>=since),opened=recent.filter((e)=>e.type==="connection").length,closes=recent.filter((e)=>e.type==="close"),refusals={};for(let event of recent){if(event.type!=="error")continue;let reason=event.metadata?.reason??"unspecified";refusals[reason]=(refusals[reason]??0)+1}let refusalTotal=Object.values(refusals).reduce((a,b)=>a+b,0),lifetimes=closes.map((e)=>e.metadata?.livedMs).filter((v)=>typeof v==="number"),shortLivedCloses=closes.filter((e)=>e.metadata?.shortLived===!0).length,findings=[];if(refusals.expired)findings.push(`${refusals.expired} handshake refused as expired \u2014 the session is likely fine and the pre-handshake refresh is not producing a fresh access token`);if(refusals["invalid-signature"])findings.push(`${refusals["invalid-signature"]} handshake refused as invalid-signature \u2014 a real credential problem, and the shape of the reconnect storm`);if(refusals["no-token"])findings.push(`${refusals["no-token"]} handshake arrived with no credential \u2014 usually a signed-out tab still open somewhere`);if(refusals.malformed)findings.push(`${refusals.malformed} handshake carried something that is not a token at all \u2014 look at what the client is putting in the bearer, not at the session`);if(refusals["wrong-audience"])findings.push(`${refusals["wrong-audience"]} handshake carried a token minted for somewhere else \u2014 check the issuer/audience this install expects`);if(refusals["not-yet-valid"])findings.push(`${refusals["not-yet-valid"]} handshake carried a token from the future \u2014 the clocks disagree`);if(closes.length>0&&shortLivedCloses/closes.length>0.5)findings.push(`${shortLivedCloses} of ${closes.length} sockets lived under a minute \u2014 they are failing, not disconnecting`);return{windowMs,opened,closed:closes.length,netOpen:opened-closes.length,refusals,refusalTotal,shortLivedCloses,medianLifetimeMs:median(lifetimes),longestLifetimeMs:lifetimes.length?Math.max(...lifetimes):null,findings}}import{randomUUID as randomUUID2}from"crypto";import*as os2 from"os";class LiveMonitoringService{store;memoryInterval=null;cpuInterval=null;lastCpuInfo=null;isRunning=!1;constructor(config){let merged={...DEFAULT_LIVE_CONFIG,...config};this.store={requests:[],configs:{logMemory:merged.logMemory,logCpu:merged.logCpu,logDapr:merged.logDapr,logWebSocket:merged.logWebSocket,cpuLogInterval:merged.cpuLogInterval,memoryLogInterval:merged.memoryLogInterval},logs:{memory:[],cpu:[],dapr:[],ws:[]},logLimits:{memory:merged.memoryLogLimit,cpu:merged.cpuLogLimit,dapr:merged.daprLogLimit,ws:merged.wsLogLimit,request:merged.requestLogLimit},worker:{pid:process.pid,workerId:null,memory:null,cpu:null,updatedAt:Date.now()},allWorkers:[],daprEvents:[],wsEvents:[]}}start(){if(this.isRunning)return;if(this.isRunning=!0,this.store.configs.logMemory)this.startMemoryCollector();if(this.store.configs.logCpu)this.startCpuCollector()}stop(){if(!this.isRunning)return;if(this.isRunning=!1,this.memoryInterval)clearInterval(this.memoryInterval),this.memoryInterval=null;if(this.cpuInterval)clearInterval(this.cpuInterval),this.cpuInterval=null}startMemoryCollector(){if(this.memoryInterval)clearInterval(this.memoryInterval);let collect=()=>{if(!this.store.configs.logMemory)return;let mem=process.memoryUsage(),entry={timestamp:Date.now(),rss:mem.rss,heapUsed:mem.heapUsed,heapTotal:mem.heapTotal};if(this.store.logs.memory.push(entry),this.store.logs.memory.length>this.store.logLimits.memory*2)this.store.logs.memory=this.store.logs.memory.slice(-this.store.logLimits.memory);this.store.worker.memory=entry,this.store.worker.updatedAt=Date.now()};collect(),this.memoryInterval=setInterval(collect,this.store.configs.memoryLogInterval)}startCpuCollector(){if(this.cpuInterval)clearInterval(this.cpuInterval);let collect=()=>{if(!this.store.configs.logCpu)return;let cpus3=os2.cpus(),userTime=0,sysTime=0,idle=0,total=0;for(let cpu of cpus3)userTime+=cpu.times.user,sysTime+=cpu.times.sys,idle+=cpu.times.idle,total+=cpu.times.user+cpu.times.nice+cpu.times.sys+cpu.times.idle+cpu.times.irq;let userPercent=0,sysPercent=0;if(this.lastCpuInfo){let totalDiff=total-this.lastCpuInfo.total,idleDiff=idle-this.lastCpuInfo.idle;if(totalDiff>0){let activeDiff=totalDiff-idleDiff;userPercent=Math.round((userTime-0)/(activeDiff||1)*100*100)/100,sysPercent=Math.round((sysTime-0)/(activeDiff||1)*100*100)/100;let totalActive=Math.round((1-idleDiff/totalDiff)*100*100)/100;userPercent=Math.round(totalActive*0.7*100)/100,sysPercent=Math.round(totalActive*0.3*100)/100}}this.lastCpuInfo={idle,total};let entry={timestamp:Date.now(),user:userPercent,system:sysPercent};if(this.store.logs.cpu.push(entry),this.store.logs.cpu.length>this.store.logLimits.cpu*2)this.store.logs.cpu=this.store.logs.cpu.slice(-this.store.logLimits.cpu);this.store.worker.cpu=entry,this.store.worker.updatedAt=Date.now()};collect(),this.cpuInterval=setInterval(collect,this.store.configs.cpuLogInterval)}recordRequest(request){if(this.store.requests.push(request),this.store.requests.length>this.store.logLimits.request*2)this.store.requests=this.store.requests.slice(-this.store.logLimits.request)}recordDaprEvent(type,details){if(!this.store.configs.logDapr)return;let event={id:randomUUID2(),type,timestamp:Date.now(),...details};if(this.store.logs.dapr.push(event),this.store.daprEvents.push(event),this.store.logs.dapr.length>this.store.logLimits.dapr*2)this.store.logs.dapr=this.store.logs.dapr.slice(-this.store.logLimits.dapr);if(this.store.daprEvents.length>this.store.logLimits.dapr*2)this.store.daprEvents=this.store.daprEvents.slice(-this.store.logLimits.dapr)}recordWsEvent(type,details){if(!this.store.configs.logWebSocket)return;let event={id:randomUUID2(),type,timestamp:Date.now(),...details};if(this.store.logs.ws.push(event),this.store.wsEvents.push(event),this.store.logs.ws.length>this.store.logLimits.ws*2)this.store.logs.ws=this.store.logs.ws.slice(-this.store.logLimits.ws);if(this.store.wsEvents.length>this.store.logLimits.ws*2)this.store.wsEvents=this.store.wsEvents.slice(-this.store.logLimits.ws)}getSnapshot(){return{memory:this.store.logs.memory.slice(-this.store.logLimits.memory),cpu:this.store.logs.cpu.slice(-this.store.logLimits.cpu),requests:this.store.requests.slice(-this.store.logLimits.request),dapr:this.store.logs.dapr.slice(-this.store.logLimits.dapr),ws:this.store.logs.ws.slice(-this.store.logLimits.ws),workers:this.store.allWorkers.length?this.store.allWorkers:[this.store.worker],logLimits:{...this.store.logLimits},configs:{...this.store.configs}}}getUpdatesSince(timestamps){let memoryUpdates=this.store.logs.memory.filter((m)=>m.timestamp>timestamps.memory),cpuUpdates=this.store.logs.cpu.filter((c)=>c.timestamp>timestamps.cpu),requestUpdates=this.store.requests.filter((r)=>r.timestamp>timestamps.request),daprUpdates=this.store.logs.dapr.filter((d)=>d.timestamp>timestamps.dapr),wsUpdates=this.store.logs.ws.filter((w)=>w.timestamp>timestamps.ws);if(!(memoryUpdates.length>0||cpuUpdates.length>0||requestUpdates.length>0||daprUpdates.length>0||wsUpdates.length>0))return null;return{memory:memoryUpdates,cpu:cpuUpdates,requests:requestUpdates,dapr:daprUpdates,ws:wsUpdates,timestamp:Date.now()}}getRealtimeHealth(windowMs){return summariseRealtime(this.store.wsEvents,{windowMs})}getLogs(){return{memory:this.store.logs.memory,cpu:this.store.logs.cpu,requests:this.store.requests,dapr:this.store.logs.dapr,ws:this.store.logs.ws,daprEvents:this.store.daprEvents,wsEvents:this.store.wsEvents,configs:{logMemory:this.store.configs.logMemory,logCpu:this.store.configs.logCpu,logDapr:this.store.configs.logDapr,logWebSocket:this.store.configs.logWebSocket},limits:{...this.store.logLimits}}}getSettings(){return{configs:{...this.store.configs},logLimits:{...this.store.logLimits}}}changeSettings(payload){if(payload.logMemory!==void 0)this.store.configs.logMemory=payload.logMemory;if(payload.logCpu!==void 0)this.store.configs.logCpu=payload.logCpu;if(payload.logDapr!==void 0)this.store.configs.logDapr=payload.logDapr;if(payload.logWebSocket!==void 0)this.store.configs.logWebSocket=payload.logWebSocket;if(payload.cpuLogInterval!==void 0){if(this.store.configs.cpuLogInterval=payload.cpuLogInterval,this.isRunning&&this.store.configs.logCpu)this.startCpuCollector()}if(payload.memoryLogInterval!==void 0){if(this.store.configs.memoryLogInterval=payload.memoryLogInterval,this.isRunning&&this.store.configs.logMemory)this.startMemoryCollector()}if(payload.memoryLogLimit!==void 0)this.store.logLimits.memory=payload.memoryLogLimit;if(payload.cpuLogLimit!==void 0)this.store.logLimits.cpu=payload.cpuLogLimit;if(payload.daprLogLimit!==void 0)this.store.logLimits.dapr=payload.daprLogLimit;if(payload.wsLogLimit!==void 0)this.store.logLimits.ws=payload.wsLogLimit;if(payload.requestLogLimit!==void 0)this.store.logLimits.request=payload.requestLogLimit;return{message:"Settings updated successfully",configs:{...this.store.configs},logLimits:{...this.store.logLimits}}}getStore(){return this.store}isEnabled(){return this.isRunning}}var DEFAULT_LIVE_CONFIG;var init_LiveMonitoringService=__esm(()=>{DEFAULT_LIVE_CONFIG={enabled:!0,logMemory:!0,logCpu:!0,logDapr:!0,logWebSocket:!0,memoryLogInterval:1000,cpuLogInterval:1000,memoryLogLimit:100,cpuLogLimit:100,daprLogLimit:100,wsLogLimit:100,requestLogLimit:100,streamInterval:150}});function redisLifecycleBackend(client){return{get:(key)=>client.get(key),set:(key,value)=>client.set(key,value),del:(key)=>client.del(key),async pushCapped(key,value,keep){await client.lpush(key,value),await client.ltrim(key,0,keep-1)},list:(key,limit)=>client.lrange(key,0,limit-1)}}function judge(previous,now){let uptimeMs=Math.max(0,previous.lastSeenAt-previous.startedAt),detectedAfterMs=Math.max(0,now-previous.lastSeenAt),nearCeiling=previous.memory!==void 0&&previous.memory.scope!=="host"&&previous.memory.usagePercent>=85,verdict=previous.fatal?"fatal":nearCeiling?"killed":"vanished";return{bootId:previous.bootId,startedAt:previous.startedAt,lastSeenAt:previous.lastSeenAt,uptimeMs,detectedAfterMs,verdict,fatal:previous.fatal,memory:previous.memory,labels:previous.labels,summary:describe(verdict,uptimeMs,previous)}}function describe(verdict,uptimeMs,previous){let ran=`Ran ${duration(uptimeMs)}`;if(verdict==="fatal"&&previous.fatal)return`${ran}, then stopped on an unhandled ${previous.fatal.name}: ${previous.fatal.message}`;if(verdict==="killed"&&previous.memory)return`${ran}, then died without warning. ${memorySentence(previous.memory)} `+"Nothing ran on the way out, which is what a kill looks like \u2014 the memory ceiling "+"is the reason to check first.";let held=previous.memory?` ${memorySentence(previous.memory)} So memory is NOT the obvious answer.`:" No memory reading survived, so there is nothing here to point at.";return`${ran}, then vanished with no goodbye and no error.${held}`}function memorySentence(memory){let own=`The process itself was holding ${mb(memory.rss)}`;if(memory.scope==="host")return`${own}; the machine (not a container limit) was at ${Math.round(memory.usagePercent)}%.`;return`${own}, and its container was at ${Math.round(memory.usagePercent)}% of a ${mb(memory.total)} limit.`}function mb(bytes){return`${Math.round(bytes/1048576)} MB`}function duration(ms){let seconds=Math.round(ms/1000);if(seconds<60)return`${seconds}s`;let minutes=Math.floor(seconds/60);if(minutes<60)return`${minutes}m`;let hours=Math.floor(minutes/60),restMinutes=minutes%60;if(hours<48)return restMinutes?`${hours}h ${restMinutes}m`:`${hours}h`;return`${Math.floor(hours/24)}d ${hours%24}h`}import{randomUUID as randomUUID3}from"crypto";class LifecycleRecorder{backend;now;labels;onIncident;logger;bootId;startedAt;beat;constructor(options){this.backend=options.backend,this.now=options.now??(()=>Date.now()),this.labels=options.labels,this.onIncident=options.onIncident,this.logger=options.logger,this.bootId=options.bootId??randomUUID3(),this.startedAt=this.now(),this.beat={bootId:this.bootId,startedAt:this.startedAt,lastSeenAt:this.startedAt,labels:this.labels}}async claim(){let incident=null;try{let raw=await this.backend.get(HEARTBEAT_KEY);if(raw){let previous=JSON.parse(raw);if(!previous.stoppedAt&&previous.bootId!==this.bootId)incident={...judge(previous,this.now()),recordedAt:this.now()},await this.backend.pushCapped(INCIDENTS_KEY,JSON.stringify(incident),KEEP_INCIDENTS),this.logger?.warn(`[Lifecycle] Previous run ended abnormally: ${incident.summary}`,{bootId:incident.bootId,verdict:incident.verdict}),this.onIncident?.(incident);else if(previous.stoppedAt)this.logger?.info("[Lifecycle] Previous run shut down cleanly",{reason:previous.stopReason})}await this.write()}catch(error){this.logger?.warn("[Lifecycle] Could not read the previous run",{error:error instanceof Error?error.message:String(error)})}return incident}async touch(memory){this.beat={...this.beat,lastSeenAt:this.now(),memory:memory??this.beat.memory},await this.write()}async recordFatal(error,kind){let err=error instanceof Error?error:Error(String(error));this.beat={...this.beat,lastSeenAt:this.now(),fatal:{name:`${kind}: ${err.name}`,message:err.message,stack:err.stack?.split(`
|
|
96
96
|
`).slice(0,12).join(`
|
|
97
|
-
`),at:this.now()}},await this.write()}async recordStop(reason){this.beat={...this.beat,lastSeenAt:this.now(),stoppedAt:this.now(),stopReason:reason},await this.write()}async incidents(limit=KEEP_INCIDENTS){try{return(await this.backend.list(INCIDENTS_KEY,limit)).map((row)=>JSON.parse(row))}catch{return[]}}async write(){try{await this.backend.set(HEARTBEAT_KEY,JSON.stringify(this.beat))}catch{}}}var HEARTBEAT_KEY="nucleus:lifecycle:heartbeat",INCIDENTS_KEY="nucleus:lifecycle:incidents",KEEP_INCIDENTS=50;var init_store=()=>{};var init_lifecycle=__esm(()=>{init_store()});class MonitoringService{redis;logger;config;appId;flushToDb;systemCollector;applicationCollector;databaseCollector=null;redisCollector=null;alertService;collectInterval=null;flushInterval=null;pendingMetrics=[];isRunning=!1;peaks=new Map;peaksSeeded=!1;dbQuery;loadPeaks;constructor(deps){if(this.redis=deps.redis,this.logger=deps.logger,this.config=this.mergeConfig(deps.config),this.appId=deps.appId,this.flushToDb=deps.flushToDb,this.dbQuery=deps.dbQuery,this.loadPeaks=deps.loadPeaks,this.config.database.enabled)if(deps.dbQuery)this.databaseCollector=new DatabaseCollector(this.config.database,deps.dbQuery);else this.logger.warn("[Monitoring] monitoring.database is enabled but no database query executor is wired \u2014 database metrics are skipped");if(this.config.redis.enabled)if(deps.redis.getDirectClient())this.redisCollector=new RedisCollector(()=>deps.redis.getDirectClient());else this.logger.warn("[Monitoring] monitoring.redis is enabled but Redis runs in Dapr mode \u2014 INFO is unavailable, Redis metrics are skipped");if(this.config.persistence.enabled&&!deps.flushToDb)this.logger.warn("[Monitoring] monitoring.persistence is enabled but no flushToDb handler is wired \u2014 metrics are kept in Redis only");this.systemCollector=new SystemCollector(this.config.system),this.applicationCollector=new ApplicationCollector(this.config.application),this.alertService=new AlertService({logger:deps.logger,getEmailService:deps.getEmailService,config:this.config,appId:deps.appId})}mergeConfig(config){return{enabled:config.enabled??DEFAULT_CONFIG2.enabled,system:{enabled:config.system?.enabled??DEFAULT_CONFIG2.system.enabled,collectInterval:config.system?.collectInterval??DEFAULT_CONFIG2.system.collectInterval,metrics:{cpu:config.system?.metrics?.cpu??DEFAULT_CONFIG2.system.metrics.cpu,memory:config.system?.metrics?.memory??DEFAULT_CONFIG2.system.metrics.memory,disk:config.system?.metrics?.disk??DEFAULT_CONFIG2.system.metrics.disk,network:config.system?.metrics?.network??DEFAULT_CONFIG2.system.metrics.network,process:config.system?.metrics?.process??DEFAULT_CONFIG2.system.metrics.process}},application:{enabled:config.application?.enabled??DEFAULT_CONFIG2.application.enabled,metrics:{requests:config.application?.metrics?.requests??DEFAULT_CONFIG2.application.metrics.requests,responseTime:config.application?.metrics?.responseTime??DEFAULT_CONFIG2.application.metrics.responseTime,errors:config.application?.metrics?.errors??DEFAULT_CONFIG2.application.metrics.errors,rateLimits:config.application?.metrics?.rateLimits??DEFAULT_CONFIG2.application.metrics.rateLimits}},database:{enabled:config.database?.enabled??DEFAULT_CONFIG2.database.enabled,metrics:{connections:config.database?.metrics?.connections??DEFAULT_CONFIG2.database.metrics.connections,queryTime:config.database?.metrics?.queryTime??DEFAULT_CONFIG2.database.metrics.queryTime,slowQueryThreshold:config.database?.metrics?.slowQueryThreshold??DEFAULT_CONFIG2.database.metrics.slowQueryThreshold}},redis:{enabled:config.redis?.enabled??DEFAULT_CONFIG2.redis.enabled},persistence:{enabled:config.persistence?.enabled??DEFAULT_CONFIG2.persistence.enabled,samples:config.persistence?.samples??DEFAULT_CONFIG2.persistence.samples,flushInterval:config.persistence?.flushInterval??DEFAULT_CONFIG2.persistence.flushInterval,retentionDays:config.persistence?.retentionDays??DEFAULT_CONFIG2.persistence.retentionDays},alerts:{enabled:config.alerts?.enabled??DEFAULT_CONFIG2.alerts.enabled,email:{enabled:config.alerts?.email?.enabled??DEFAULT_CONFIG2.alerts.email.enabled,recipients:config.alerts?.email?.recipients??DEFAULT_CONFIG2.alerts.email.recipients},thresholds:{cpuPercent:config.alerts?.thresholds?.cpuPercent??DEFAULT_CONFIG2.alerts.thresholds.cpuPercent,memoryPercent:config.alerts?.thresholds?.memoryPercent??DEFAULT_CONFIG2.alerts.thresholds.memoryPercent,diskPercent:config.alerts?.thresholds?.diskPercent??DEFAULT_CONFIG2.alerts.thresholds.diskPercent,errorRatePercent:config.alerts?.thresholds?.errorRatePercent??DEFAULT_CONFIG2.alerts.thresholds.errorRatePercent,responseTimeMs:config.alerts?.thresholds?.responseTimeMs??DEFAULT_CONFIG2.alerts.thresholds.responseTimeMs,rateLimitBlocksPerMinute:config.alerts?.thresholds?.rateLimitBlocksPerMinute??DEFAULT_CONFIG2.alerts.thresholds.rateLimitBlocksPerMinute},cooldown:config.alerts?.cooldown??DEFAULT_CONFIG2.alerts.cooldown}}}parseTimeToMs(time){let match=time.match(/^(\d+)(ms|s|m|h|d)$/);if(!match||!match[1]||!match[2])return 1e4;let value=parseInt(match[1],10);switch(match[2]){case"ms":return value;case"s":return value*1000;case"m":return value*60*1000;case"h":return value*60*60*1000;case"d":return value*24*60*60*1000;default:return 1e4}}start(){if(!this.config.enabled||this.isRunning)return;this.isRunning=!0,this.logger.info("[Monitoring] Starting monitoring service");let collectIntervalMs=this.parseTimeToMs(this.config.system.collectInterval);if(this.collectInterval=setInterval(()=>{this.collect()},collectIntervalMs),this.config.persistence.enabled&&this.flushToDb){let flushIntervalMs=this.parseTimeToMs(this.config.persistence.flushInterval);this.flushInterval=setInterval(()=>{this.flush()},flushIntervalMs)}this.collect()}stop(){if(!this.isRunning)return;if(this.isRunning=!1,this.logger.info("[Monitoring] Stopping monitoring service"),this.collectInterval)clearInterval(this.collectInterval),this.collectInterval=null;if(this.flushInterval)clearInterval(this.flushInterval),this.flushInterval=null;this.flush()}async collect(){let now=Date.now(),snapshot={timestamp:now};if(this.config.system.enabled){let systemMetrics=await this.systemCollector.collect();if(systemMetrics)snapshot.system=systemMetrics,this.addMetricPoints("system",systemMetrics,now)}if(this.config.application.enabled){let appMetrics=this.applicationCollector.collect();if(appMetrics)snapshot.application=appMetrics,this.addMetricPoints("application",appMetrics,now)}if(this.databaseCollector){let dbMetrics=await this.databaseCollector.collect();if(dbMetrics)snapshot.database=dbMetrics,this.addMetricPoints("database",dbMetrics,now)}if(this.redisCollector){let redisMetrics=await this.redisCollector.collect();if(redisMetrics)snapshot.redis=redisMetrics,this.addMetricPoints("redis",redisMetrics,now)}if(await this.recordPeaks(snapshot,now),await this.storeSnapshot(snapshot),this.config.alerts.enabled)await this.alertService.checkAndAlert(snapshot)}addMetricPoints(type,metrics,timestamp){if(!this.config.persistence.samples)return;let flatten=(obj,prefix="")=>{for(let key in obj){let value=obj[key],newKey=prefix?`${prefix}.${key}`:key;if(typeof value==="number")this.pendingMetrics.push({timestamp,metricType:type,metricName:newKey,value});else if(typeof value==="object"&&value!==null&&!Array.isArray(value))flatten(value,newKey)}};flatten(metrics)}async seedPeaks(){if(this.peaksSeeded)return;if(!this.loadPeaks)return;try{for(let row of await this.loadPeaks()){if(!row.metricName||this.peaks.has(row.metricName))continue;this.peaks.set(row.metricName,{value:row.value,at:row.at,context:row.context})}this.peaksSeeded=!0}catch(error){this.logger.warn(`[Monitoring] peak seed failed, peaks not recorded this cycle: ${error}`)}}async recordPeaks(snapshot,timestamp){if(await this.seedPeaks(),!this.peaksSeeded)return;let previous=new Map([...this.peaks].map(([name,peak])=>[name,peak.value])),{points}=newPeaks(snapshot,previous,timestamp);for(let point of points)this.peaks.set(point.metricName,{value:point.value,at:timestamp,context:point.tags}),this.pendingMetrics.push(point),this.logger.info(`[Monitoring] yeni tepe \u2014 ${point.metricName} = ${point.value}`);if(this.peaks.size>0)snapshot.peaks=Object.fromEntries(this.peaks)}async appendToHistoryList(client,historyKey,snapshot){let append=async()=>{let results=await client.multi().rpush(historyKey,JSON.stringify(snapshot)).ltrim(historyKey,-HISTORY_MAX_SNAPSHOTS,-1).expire(historyKey,3600).exec();if(!results)return Error("history MULTI returned no result");for(let[error]of results)if(error)return error;return null};try{let first=await append();if(!first)return!0;if(/WRONGTYPE/i.test(first.message)){this.logger.warn("[Monitoring] history key held the pre-0.9.988 shape; replacing it with a list (the hour before this upgrade is lost)"),await client.del(historyKey);let second=await append();if(!second)return!0;return this.logger.warn(`[Monitoring] history append failed after reset: ${second.message}`),!1}return this.logger.warn(`[Monitoring] history append failed: ${first.message}`),!1}catch(error){return this.logger.warn(`[Monitoring] history append threw: ${error instanceof Error?error.message:String(error)}`),!1}}async storeSnapshot(snapshot){let key=`monitoring:${this.appId}:latest`;await this.redis.create(key,snapshot,3600);let historyKey=`monitoring:${this.appId}:history`,client=this.redis.getDirectClient?.();if(client){if(await this.appendToHistoryList(client,historyKey,snapshot))return}let historyResult=await this.redis.read(historyKey),history=historyResult.success&&historyResult.data?historyResult.data:[];history.push(snapshot);let oneHourAgo=Date.now()-3600000,filteredHistory=history.filter((s)=>s.timestamp>oneHourAgo);await this.redis.create(historyKey,filteredHistory,3600)}async flush(){if(this.pendingMetrics.length===0)return;if(!this.flushToDb)return;let metricsToFlush=[...this.pendingMetrics];this.pendingMetrics=[];try{await this.flushToDb(metricsToFlush),this.logger.debug(`[Monitoring] Flushed ${metricsToFlush.length} metrics to database`)}catch(error){this.logger.error(`[Monitoring] Failed to flush metrics: ${error}`),this.pendingMetrics=[...metricsToFlush,...this.pendingMetrics]}}recordRequest(params){if(!this.config.enabled||!this.config.application.enabled)return;this.applicationCollector.recordRequest(params)}recordRateLimitBlock(){if(!this.config.enabled||!this.config.application.enabled)return;this.applicationCollector.recordRateLimitBlock()}async getLatestSnapshot(){let key=`monitoring:${this.appId}:latest`,result=await this.redis.read(key);return result.success?result.data:null}async getHistory(minutes=60){let key=`monitoring:${this.appId}:history`,cutoff=Date.now()-minutes*60000,client=this.redis.getDirectClient?.();if(client)try{if(await client.type(key)==="list"){let rows=await client.lrange(key,0,-1),out=[];for(let row of rows)try{let snapshot=JSON.parse(row);if(snapshot.timestamp>cutoff)out.push(snapshot)}catch{}return out}}catch{}let result=await this.redis.read(key);if(!result.success||!result.data)return[];return result.data.filter((s)=>s.timestamp>cutoff)}getActiveAlerts(){return this.alertService.getActiveAlerts()}acknowledgeAlert(alertId){return this.alertService.acknowledgeAlert(alertId)}isEnabled(){return this.config.enabled}getConfig(){return this.config}}var HISTORY_MAX_SNAPSHOTS=360,DEFAULT_CONFIG2;var init_Monitoring=__esm(()=>{init_AlertService();init_ApplicationCollector();init_SystemCollector();init_peaks();init_AlertService();init_LiveMonitoringService();init_lifecycle();DEFAULT_CONFIG2={enabled:!1,system:{enabled:!0,collectInterval:"10s",metrics:{cpu:!0,memory:!0,disk:!0,network:!1,process:!0}},application:{enabled:!0,metrics:{requests:!0,responseTime:!0,errors:!0,rateLimits:!0}},database:{enabled:!1,metrics:{connections:!0,queryTime:!0,slowQueryThreshold:"100ms"}},redis:{enabled:!1},persistence:{enabled:!0,samples:!1,flushInterval:"1m",retentionDays:30},alerts:{enabled:!1,email:{enabled:!1,recipients:[]},thresholds:{cpuPercent:80,memoryPercent:85,diskPercent:90,errorRatePercent:5,responseTimeMs:1000,rateLimitBlocksPerMinute:100},cooldown:"5m"}}});function snakeToCamel(name){return name.replace(/_([a-z])/g,(_,c)=>c.toUpperCase())}function resolveSchemaTable(schemaTables,name,logger2){let direct=schemaTables[name];if(direct)return direct;let camel=snakeToCamel(name);if(camel!==name){let viaCamel=schemaTables[camel];if(viaCamel)return viaCamel}if(logger2){let keys=Object.keys(schemaTables),preview=keys.slice(0,20).join(", "),suffix=keys.length>20?`, ...(+${keys.length-20} more)`:"";logger2.warn(`[SchemaTables] Table "${name}" not found (tried "${name}" and "${camel}"). Known keys: ${preview}${suffix}`)}return}var init_types4=()=>{};import{and as and6,desc,eq as eq10,inArray as inArray2}from"drizzle-orm";function resolveEnvValue2(value){if(!value)return;return process.env[value]??value}function toCamel(obj){let result={};for(let[key,value]of Object.entries(obj)){let camelKey=key.replace(/_([a-z])/g,(_,c)=>c.toUpperCase());result[camelKey]=value}return result}function fromCamel(obj){let result={};for(let[key,value]of Object.entries(obj)){let snakeKey=key.replace(/[A-Z]/g,(c)=>`_${c.toLowerCase()}`);result[snakeKey]=value}return result}class NotificationService{db;schemaTables;config;logger;getEmailService;constructor(serviceConfig){this.db=serviceConfig.db,this.schemaTables=serviceConfig.schemaTables,this.config=serviceConfig.config,this.logger=serviceConfig.logger,this.getEmailService=serviceConfig.getEmailService}getTable(name){return resolveSchemaTable(this.schemaTables,name,this.logger)}getCol(table,col4){return table[col4]}isChannelEnabled(channel){let channels=this.config.channels;if(!channels)return channel==="portal";switch(channel){case"portal":return channels.portal!==!1;case"email":return channels.email===!0;case"telegram":return channels.telegram?.enabled===!0;case"webhook":return channels.webhook?.enabled===!0;default:return!1}}interpolateTemplate(template,context){let result=template;for(let[key,value]of Object.entries(context))result=result.replace(new RegExp(`{{${key}}}`,"g"),()=>String(value??""));for(let[key,value]of Object.entries(this.config.templateVariables||{}))result=result.replace(new RegExp(`{{${key}}}`,"g"),value);return result}async triggerNotifications(params){let{trigger,flow_id,entity_name,entity_id,node_id,context={}}=params,rulesTable=this.getTable("verificationNotificationRules"),recipientsTable=this.getTable("verificationNotificationRecipients"),channelsTable=this.getTable("verificationNotificationChannels");if(!rulesTable||!recipientsTable){this.logger.warn("[Notification] Notification tables not found");return}let now=new Date,rules=await this.db.select().from(rulesTable).where(and6(eq10(this.getCol(rulesTable,"flowId"),flow_id),eq10(this.getCol(rulesTable,"trigger"),trigger)));this.logger.info(`[Notification] Found ${rules.length} rules for trigger=${trigger} flow_id=${flow_id}, filter_node_id=${node_id||"NONE"}`);for(let r of rules)this.logger.info(`[Notification] Rule ${r.id}: nodeId=${r.nodeId}, trigger=${r.trigger}, title=${JSON.stringify(r.titleTemplate)}`);let filteredRules=rules.filter((rule)=>{if(node_id&&rule.nodeId!==node_id)return this.logger.info(`[Notification] EXCLUDED rule ${rule.id}: rule.nodeId=${rule.nodeId} !== filter_node_id=${node_id}`),!1;if(rule.startsAt&&new Date(rule.startsAt)>now)return!1;if(rule.expiresAt&&new Date(rule.expiresAt)<now)return!1;return!0});for(let rule of filteredRules){let recipients=await this.db.select().from(recipientsTable).where(eq10(this.getCol(recipientsTable,"ruleId"),rule.id)),ruleChannels=["portal"];if(channelsTable){let channelEntries=await this.db.select().from(channelsTable).where(eq10(this.getCol(channelsTable,"ruleId"),rule.id));if(channelEntries.length>0)ruleChannels=channelEntries.map((c)=>c.channel)}let enabledChannels=ruleChannels.filter((ch)=>this.isChannelEnabled(ch));if(enabledChannels.length===0)continue;this.logger.info(`[Notification] Rule ${rule.id}: ${recipients.length} recipients, ${enabledChannels.length} channels (${enabledChannels.join(",")})`);let userIds=await this.resolveRecipients(recipients,params.verifier_id,flow_id,entity_name,entity_id);this.logger.info(`[Notification] Rule ${rule.id}: resolved ${userIds.length} user IDs: ${userIds.join(", ")}`);let enrichedContext={...context,entity_name,entity_id,trigger,decision:params.decision};this.logger.info(`[Notification] Rule ${rule.id}: titleTemplate=${JSON.stringify(rule.titleTemplate)}, bodyTemplate=${JSON.stringify(rule.bodyTemplate)}, context=${JSON.stringify(enrichedContext)}`);let title=rule.titleTemplate?this.interpolateTemplate(rule.titleTemplate,enrichedContext):`Verification ${trigger.replace("on_","").replace("_"," ")}`,body=rule.bodyTemplate?this.interpolateTemplate(rule.bodyTemplate,enrichedContext):void 0;this.logger.info(`[Notification] Rule ${rule.id}: final title="${title}", body="${body}"`);for(let userId of userIds){let report=await this.send({user_id:userId,title,body,entity_name,entity_id,type:"verification",source:`flow:${flow_id}`,channels:enabledChannels});if(report.delivered.length===0)this.logger.warn(`[Notification] Rule ${rule.id}: nothing delivered to ${userId} \u2014 ${report.undelivered.map((one)=>`${one.channel}: ${one.reason}`).join("; ")}`)}}this.logger.debug(`[Notification] Triggered ${filteredRules.length} rules for ${trigger} on ${entity_name}:${entity_id}`)}async usersAwaitedNow(entityName,entityId){if(!entityName||!entityId)return[];let instancesTable=this.getTable("verificationInstances"),requirementsTable=this.getTable("verificationRequirements"),userRolesTable=this.getTable("userRoles"),rolesTable=this.getTable("roles");if(!instancesTable||!requirementsTable)return[];let inst=(await this.db.select().from(instancesTable).where(and6(eq10(this.getCol(instancesTable,"entityName"),entityName),eq10(this.getCol(instancesTable,"entityId"),entityId),eq10(this.getCol(instancesTable,"status"),"active"))).orderBy(desc(this.getCol(instancesTable,"createdAt"))).limit(1))[0];if(!inst)return[];let pending=await this.db.select().from(requirementsTable).where(and6(eq10(this.getCol(requirementsTable,"instanceId"),inst.id),eq10(this.getCol(requirementsTable,"status"),"pending"))),out=new Set;for(let req of pending){if(req.verifierUserId){out.add(req.verifierUserId);continue}let roleName=req.verifierRole;if(!roleName||!rolesTable||!userRolesTable)continue;let rCols=rolesTable,urCols=userRolesTable,members=await this.db.select({user_id:urCols.userId}).from(userRolesTable).innerJoin(rolesTable,eq10(urCols.roleId,rCols.id)).where(and6(eq10(rCols.name,roleName),activeRowsPredicate(rolesTable),activeRowsPredicate(userRolesTable)));for(let m of members)out.add(m.user_id)}return[...out]}async resolveRecipients(recipients,currentVerifierId,flowId,entityName,entityId){let userIds=new Set,userRolesTable=this.getTable("userRoles"),rolesTable=this.getTable("roles");for(let recipient of recipients)switch(recipient.recipientType){case"user":if(recipient.recipientUserId)userIds.add(recipient.recipientUserId);break;case"role":if(recipient.recipientRole&&userRolesTable&&rolesTable){let rolesCols=rolesTable,userRolesCols=userRolesTable,role=(await this.db.select().from(rolesTable).where(and6(eq10(rolesCols.name,recipient.recipientRole),activeRowsPredicate(rolesTable))).limit(1))[0];if(role){let usersInRole=await this.db.select({user_id:userRolesCols.userId}).from(userRolesTable).where(and6(eq10(userRolesCols.roleId,role.id),activeRowsPredicate(userRolesTable)));for(let ur of usersInRole)userIds.add(ur.user_id)}}break;case"step_verifier":{if(currentVerifierId){userIds.add(currentVerifierId);break}for(let uid of await this.usersAwaitedNow(entityName,entityId))userIds.add(uid);break}case"entity_creator":{if(entityName&&entityId){let instancesTable=this.getTable("verificationInstances");if(instancesTable){let inst=(await this.db.select().from(instancesTable).where(and6(eq10(this.getCol(instancesTable,"entityName"),entityName),eq10(this.getCol(instancesTable,"entityId"),entityId))).orderBy(desc(this.getCol(instancesTable,"createdAt"))).limit(1))[0];if(inst?.startedBy)userIds.add(inst.startedBy)}}break}case"all_verifiers":{if(flowId){let verifierConfigsTable=this.getTable("verificationVerifierConfigs");if(verifierConfigsTable){let configs=await this.db.select().from(verifierConfigsTable).where(eq10(this.getCol(verifierConfigsTable,"flowId"),flowId));this.logger.info(`[Notification] all_verifiers: found ${configs.length} verifier configs for flow ${flowId}`);for(let cfg of configs){let row=cfg;if(this.logger.info(`[Notification] all_verifiers: config node_id=${row.nodeId}, type=${row.verifierType}, userId=${row.verifierUserId}, role=${row.verifierRole}`),row.verifierUserId)userIds.add(row.verifierUserId);if(row.verifierType==="role"&&row.verifierRole&&userRolesTable&&rolesTable){let rCols=rolesTable,urCols=userRolesTable,roleRow=(await this.db.select().from(rolesTable).where(eq10(rCols.name,row.verifierRole)).limit(1))[0];if(roleRow){let usersInRole=await this.db.select({user_id:urCols.userId}).from(userRolesTable).where(eq10(urCols.roleId,roleRow.id));for(let ur of usersInRole)userIds.add(ur.user_id)}}}}}break}}let usersTable=this.getTable("users");if(usersTable&&userIds.size>0)return(await this.db.select({id:this.getCol(usersTable,"id")}).from(usersTable).where(and6(inArray2(this.getCol(usersTable,"id"),Array.from(userIds)),activeRowsPredicate(usersTable)))).map((r)=>r.id);return Array.from(userIds)}async send(params){let{user_id,title,body,entity_name,entity_id,type,source,channels}=params,delivered=[],undelivered=[],record=(channel,reason)=>{if(reason===null)delivered.push(channel);else undelivered.push({channel,reason})};for(let channel of channels){if(!this.isChannelEnabled(channel)){this.logger.debug?.(`[Notification] channel "${channel}" is disabled in config \u2014 skipping for user ${user_id}`),record(channel,"the channel is disabled in config");continue}switch(channel){case"portal":record(channel,await this.sendPortalNotification(user_id,title,body,entity_name,entity_id,type,source));break;case"email":record(channel,await this.sendEmailNotification(user_id,title,body));break;case"sms":this.logger.warn(`[Notification] SMS delivery is not supported \u2014 skipping sms notification for user ${user_id}`),record(channel,"sms delivery is not supported");break;case"telegram":record(channel,await this.sendTelegramNotification(params));break;case"webhook":record(channel,await this.sendWebhookNotification(params));break}}return{delivered,undelivered}}async sendPortalNotification(userId,title,body,entityName,entityId,type,source){let notificationsTable=this.getTable("notifications");if(title.length>255)title=`${title.slice(0,254)}\u2026`;if(body&&body.length>1000)body=`${body.slice(0,999)}\u2026`;if(!notificationsTable)return this.logger.warn("[Notification] notifications table not found"),"the notifications table was not found";return await this.db.insert(notificationsTable).values(toCamel({user_id:userId,title,body:body||null,entity_name:entityName||null,entity_id:entityId||null,type:type||"system",source:source||null,is_seen:!1})),this.logger.debug(`[Notification] Portal notification sent to ${userId}: ${title}`),null}async sendTelegramNotification(params){let telegramConfig=this.config.channels?.telegram,botToken=resolveEnvValue2(telegramConfig?.botToken),chatId=resolveEnvValue2(telegramConfig?.chatId);if(!botToken||!chatId)return this.logger.warn("[Notification] Telegram channel enabled but botToken/chatId is missing or unresolved \u2014 skipping delivery"),"the telegram botToken/chatId is missing or unresolved";let text=params.body?`*${params.title}*
|
|
97
|
+
`),at:this.now()}},await this.write()}async recordStop(reason){this.beat={...this.beat,lastSeenAt:this.now(),stoppedAt:this.now(),stopReason:reason},await this.write()}async incidents(limit=KEEP_INCIDENTS){try{return(await this.backend.list(INCIDENTS_KEY,limit)).map((row)=>JSON.parse(row))}catch{return[]}}async write(){try{await this.backend.set(HEARTBEAT_KEY,JSON.stringify(this.beat))}catch{}}}var HEARTBEAT_KEY="nucleus:lifecycle:heartbeat",INCIDENTS_KEY="nucleus:lifecycle:incidents",KEEP_INCIDENTS=50;var init_store=()=>{};var init_lifecycle=__esm(()=>{init_store()});class MonitoringService{redis;logger;config;appId;flushToDb;systemCollector;applicationCollector;databaseCollector=null;redisCollector=null;alertService;collectInterval=null;flushInterval=null;pendingMetrics=[];isRunning=!1;peaks=new Map;peaksSeeded=!1;dbQuery;loadPeaks;constructor(deps){if(this.redis=deps.redis,this.logger=deps.logger,this.config=this.mergeConfig(deps.config),this.appId=deps.appId,this.flushToDb=deps.flushToDb,this.dbQuery=deps.dbQuery,this.loadPeaks=deps.loadPeaks,this.config.database.enabled)if(deps.dbQuery)this.databaseCollector=new DatabaseCollector(this.config.database,deps.dbQuery);else this.logger.warn("[Monitoring] monitoring.database is enabled but no database query executor is wired \u2014 database metrics are skipped");if(this.config.redis.enabled)if(deps.redis.getDirectClient())this.redisCollector=new RedisCollector(()=>deps.redis.getDirectClient());else this.logger.warn("[Monitoring] monitoring.redis is enabled but Redis runs in Dapr mode \u2014 INFO is unavailable, Redis metrics are skipped");if(this.config.persistence.enabled&&!deps.flushToDb)this.logger.warn("[Monitoring] monitoring.persistence is enabled but no flushToDb handler is wired \u2014 metrics are kept in Redis only");this.systemCollector=new SystemCollector(this.config.system),this.applicationCollector=new ApplicationCollector(this.config.application),this.alertService=new AlertService({logger:deps.logger,getEmailService:deps.getEmailService,config:this.config,appId:deps.appId})}mergeConfig(config){return{enabled:config.enabled??DEFAULT_CONFIG2.enabled,system:{enabled:config.system?.enabled??DEFAULT_CONFIG2.system.enabled,collectInterval:config.system?.collectInterval??DEFAULT_CONFIG2.system.collectInterval,metrics:{cpu:config.system?.metrics?.cpu??DEFAULT_CONFIG2.system.metrics.cpu,memory:config.system?.metrics?.memory??DEFAULT_CONFIG2.system.metrics.memory,disk:config.system?.metrics?.disk??DEFAULT_CONFIG2.system.metrics.disk,network:config.system?.metrics?.network??DEFAULT_CONFIG2.system.metrics.network,process:config.system?.metrics?.process??DEFAULT_CONFIG2.system.metrics.process}},application:{enabled:config.application?.enabled??DEFAULT_CONFIG2.application.enabled,metrics:{requests:config.application?.metrics?.requests??DEFAULT_CONFIG2.application.metrics.requests,responseTime:config.application?.metrics?.responseTime??DEFAULT_CONFIG2.application.metrics.responseTime,errors:config.application?.metrics?.errors??DEFAULT_CONFIG2.application.metrics.errors,rateLimits:config.application?.metrics?.rateLimits??DEFAULT_CONFIG2.application.metrics.rateLimits}},database:{enabled:config.database?.enabled??DEFAULT_CONFIG2.database.enabled,metrics:{connections:config.database?.metrics?.connections??DEFAULT_CONFIG2.database.metrics.connections,queryTime:config.database?.metrics?.queryTime??DEFAULT_CONFIG2.database.metrics.queryTime,slowQueryThreshold:config.database?.metrics?.slowQueryThreshold??DEFAULT_CONFIG2.database.metrics.slowQueryThreshold}},redis:{enabled:config.redis?.enabled??DEFAULT_CONFIG2.redis.enabled},persistence:{enabled:config.persistence?.enabled??DEFAULT_CONFIG2.persistence.enabled,samples:config.persistence?.samples??DEFAULT_CONFIG2.persistence.samples,flushInterval:config.persistence?.flushInterval??DEFAULT_CONFIG2.persistence.flushInterval,retentionDays:config.persistence?.retentionDays??DEFAULT_CONFIG2.persistence.retentionDays},alerts:{enabled:config.alerts?.enabled??DEFAULT_CONFIG2.alerts.enabled,email:{enabled:config.alerts?.email?.enabled??DEFAULT_CONFIG2.alerts.email.enabled,recipients:config.alerts?.email?.recipients??DEFAULT_CONFIG2.alerts.email.recipients},thresholds:{cpuPercent:config.alerts?.thresholds?.cpuPercent??DEFAULT_CONFIG2.alerts.thresholds.cpuPercent,memoryPercent:config.alerts?.thresholds?.memoryPercent??DEFAULT_CONFIG2.alerts.thresholds.memoryPercent,diskPercent:config.alerts?.thresholds?.diskPercent??DEFAULT_CONFIG2.alerts.thresholds.diskPercent,errorRatePercent:config.alerts?.thresholds?.errorRatePercent??DEFAULT_CONFIG2.alerts.thresholds.errorRatePercent,responseTimeMs:config.alerts?.thresholds?.responseTimeMs??DEFAULT_CONFIG2.alerts.thresholds.responseTimeMs,rateLimitBlocksPerMinute:config.alerts?.thresholds?.rateLimitBlocksPerMinute??DEFAULT_CONFIG2.alerts.thresholds.rateLimitBlocksPerMinute},cooldown:config.alerts?.cooldown??DEFAULT_CONFIG2.alerts.cooldown}}}parseTimeToMs(time){let match=time.match(/^(\d+)(ms|s|m|h|d)$/);if(!match||!match[1]||!match[2])return 1e4;let value=parseInt(match[1],10);switch(match[2]){case"ms":return value;case"s":return value*1000;case"m":return value*60*1000;case"h":return value*60*60*1000;case"d":return value*24*60*60*1000;default:return 1e4}}start(){if(!this.config.enabled||this.isRunning)return;this.isRunning=!0,this.logger.info("[Monitoring] Starting monitoring service");let collectIntervalMs=this.parseTimeToMs(this.config.system.collectInterval);if(this.collectInterval=setInterval(()=>{this.collect()},collectIntervalMs),this.config.persistence.enabled&&this.flushToDb){let flushIntervalMs=this.parseTimeToMs(this.config.persistence.flushInterval);this.flushInterval=setInterval(()=>{this.flush()},flushIntervalMs)}this.collect()}stop(){if(!this.isRunning)return;if(this.isRunning=!1,this.logger.info("[Monitoring] Stopping monitoring service"),this.collectInterval)clearInterval(this.collectInterval),this.collectInterval=null;if(this.flushInterval)clearInterval(this.flushInterval),this.flushInterval=null;this.flush()}async collect(){let now=Date.now(),snapshot={timestamp:now};if(this.config.system.enabled){let systemMetrics=await this.systemCollector.collect();if(systemMetrics)snapshot.system=systemMetrics,this.addMetricPoints("system",systemMetrics,now)}if(this.config.application.enabled){let appMetrics=this.applicationCollector.collect();if(appMetrics)snapshot.application=appMetrics,this.addMetricPoints("application",appMetrics,now)}if(this.databaseCollector){let dbMetrics=await this.databaseCollector.collect();if(dbMetrics)snapshot.database=dbMetrics,this.addMetricPoints("database",dbMetrics,now)}if(this.redisCollector){let redisMetrics=await this.redisCollector.collect();if(redisMetrics)snapshot.redis=redisMetrics,this.addMetricPoints("redis",redisMetrics,now)}if(await this.recordPeaks(snapshot,now),await this.storeSnapshot(snapshot),this.config.alerts.enabled)await this.alertService.checkAndAlert(snapshot)}addMetricPoints(type,metrics,timestamp){if(!this.config.persistence.samples)return;let flatten=(obj,prefix="")=>{for(let key in obj){let value=obj[key],newKey=prefix?`${prefix}.${key}`:key;if(typeof value==="number")this.pendingMetrics.push({timestamp,metricType:type,metricName:newKey,value});else if(typeof value==="object"&&value!==null&&!Array.isArray(value))flatten(value,newKey)}};flatten(metrics)}async seedPeaks(){if(this.peaksSeeded)return;if(!this.loadPeaks)return;try{for(let row of await this.loadPeaks()){if(!row.metricName||this.peaks.has(row.metricName))continue;this.peaks.set(row.metricName,{value:row.value,at:row.at,context:row.context})}this.peaksSeeded=!0}catch(error){this.logger.warn(`[Monitoring] peak seed failed, peaks not recorded this cycle: ${error}`)}}async recordPeaks(snapshot,timestamp){if(await this.seedPeaks(),!this.peaksSeeded)return;let previous=new Map([...this.peaks].map(([name,peak])=>[name,peak.value])),{points}=newPeaks(snapshot,previous,timestamp);for(let point of points)this.peaks.set(point.metricName,{value:point.value,at:timestamp,context:point.tags}),this.pendingMetrics.push(point),this.logger.info(`[Monitoring] yeni tepe \u2014 ${point.metricName} = ${point.value}`);if(this.peaks.size>0)snapshot.peaks=Object.fromEntries(this.peaks)}async appendToHistoryList(client,historyKey,snapshot){let append=async()=>{let results=await client.multi().rpush(historyKey,JSON.stringify(snapshot)).ltrim(historyKey,-HISTORY_MAX_SNAPSHOTS,-1).expire(historyKey,3600).exec();if(!results)return Error("history MULTI returned no result");for(let[error]of results)if(error)return error;return null};try{let first=await append();if(!first)return!0;if(/WRONGTYPE/i.test(first.message)){this.logger.warn("[Monitoring] history key held the pre-0.9.988 shape; replacing it with a list (the hour before this upgrade is lost)"),await client.del(historyKey);let second=await append();if(!second)return!0;return this.logger.warn(`[Monitoring] history append failed after reset: ${second.message}`),!1}return this.logger.warn(`[Monitoring] history append failed: ${first.message}`),!1}catch(error){return this.logger.warn(`[Monitoring] history append threw: ${error instanceof Error?error.message:String(error)}`),!1}}async storeSnapshot(snapshot){let key=`monitoring:${this.appId}:latest`;await this.redis.create(key,snapshot,3600);let historyKey=`monitoring:${this.appId}:history`,client=this.redis.getDirectClient?.();if(client){if(await this.appendToHistoryList(client,historyKey,snapshot))return}let historyResult=await this.redis.read(historyKey),history=historyResult.success&&historyResult.data?historyResult.data:[];history.push(snapshot);let oneHourAgo=Date.now()-3600000,filteredHistory=history.filter((s)=>s.timestamp>oneHourAgo);await this.redis.create(historyKey,filteredHistory,3600)}async flush(){if(this.pendingMetrics.length===0)return;if(!this.flushToDb)return;let metricsToFlush=[...this.pendingMetrics];this.pendingMetrics=[];try{await this.flushToDb(metricsToFlush),this.logger.debug(`[Monitoring] Flushed ${metricsToFlush.length} metrics to database`)}catch(error){this.logger.error(`[Monitoring] Failed to flush metrics: ${error}`),this.pendingMetrics=[...metricsToFlush,...this.pendingMetrics]}}recordRequest(params){if(!this.config.enabled||!this.config.application.enabled)return;this.applicationCollector.recordRequest(params)}recordRateLimitBlock(){if(!this.config.enabled||!this.config.application.enabled)return;this.applicationCollector.recordRateLimitBlock()}async getLatestSnapshot(){let key=`monitoring:${this.appId}:latest`,result=await this.redis.read(key);return result.success?result.data:null}async getHistory(minutes=60){let key=`monitoring:${this.appId}:history`,cutoff=Date.now()-minutes*60000,client=this.redis.getDirectClient?.();if(client)try{if(await client.type(key)==="list"){let rows=await client.lrange(key,0,-1),out=[];for(let row of rows)try{let snapshot=JSON.parse(row);if(snapshot.timestamp>cutoff)out.push(snapshot)}catch{}return out}}catch{}let result=await this.redis.read(key);if(!result.success||!result.data)return[];return result.data.filter((s)=>s.timestamp>cutoff)}getActiveAlerts(){return this.alertService.getActiveAlerts()}acknowledgeAlert(alertId){return this.alertService.acknowledgeAlert(alertId)}isEnabled(){return this.config.enabled}getConfig(){return this.config}}var HISTORY_MAX_SNAPSHOTS=360,DEFAULT_CONFIG2;var init_Monitoring=__esm(()=>{init_AlertService();init_ApplicationCollector();init_SystemCollector();init_peaks();init_AlertService();init_LiveMonitoringService();init_lifecycle();DEFAULT_CONFIG2={enabled:!1,system:{enabled:!0,collectInterval:"10s",metrics:{cpu:!0,memory:!0,disk:!0,network:!1,process:!0}},application:{enabled:!0,metrics:{requests:!0,responseTime:!0,errors:!0,rateLimits:!0}},database:{enabled:!1,metrics:{connections:!0,queryTime:!0,slowQueryThreshold:"100ms"}},redis:{enabled:!1},persistence:{enabled:!0,samples:!1,flushInterval:"1m",retentionDays:30},alerts:{enabled:!1,email:{enabled:!1,recipients:[]},thresholds:{cpuPercent:80,memoryPercent:85,diskPercent:90,errorRatePercent:5,responseTimeMs:1000,rateLimitBlocksPerMinute:100},cooldown:"5m"}}});function snakeToCamel(name){return name.replace(/_([a-z])/g,(_,c)=>c.toUpperCase())}function resolveSchemaTable(schemaTables,name,logger2){let direct=schemaTables[name];if(direct)return direct;let camel=snakeToCamel(name);if(camel!==name){let viaCamel=schemaTables[camel];if(viaCamel)return viaCamel}if(logger2){let keys=Object.keys(schemaTables),preview=keys.slice(0,20).join(", "),suffix=keys.length>20?`, ...(+${keys.length-20} more)`:"";logger2.warn(`[SchemaTables] Table "${name}" not found (tried "${name}" and "${camel}"). Known keys: ${preview}${suffix}`)}return}var init_types4=()=>{};import{and as and6,desc,eq as eq10,inArray as inArray2}from"drizzle-orm";function resolveEnvValue2(value){if(!value)return;return process.env[value]??value}function toCamel(obj){let result={};for(let[key,value]of Object.entries(obj)){let camelKey=key.replace(/_([a-z])/g,(_,c)=>c.toUpperCase());result[camelKey]=value}return result}function fromCamel(obj){let result={};for(let[key,value]of Object.entries(obj)){let snakeKey=key.replace(/[A-Z]/g,(c)=>`_${c.toLowerCase()}`);result[snakeKey]=value}return result}class NotificationService{db;schemaTables;config;logger;getEmailService;constructor(serviceConfig){this.db=serviceConfig.db,this.schemaTables=serviceConfig.schemaTables,this.config=serviceConfig.config,this.logger=serviceConfig.logger,this.getEmailService=serviceConfig.getEmailService}getTable(name){return resolveSchemaTable(this.schemaTables,name,this.logger)}getCol(table,col4){return table[col4]}isChannelEnabled(channel){let channels=this.config.channels;if(!channels)return channel==="portal";switch(channel){case"portal":return channels.portal!==!1;case"email":return channels.email===!0;case"telegram":return channels.telegram?.enabled===!0;case"webhook":return channels.webhook?.enabled===!0;default:return!1}}interpolateTemplate(template,context){let result=template;for(let[key,value]of Object.entries(context))result=result.replace(new RegExp(`{{${key}}}`,"g"),()=>String(value??""));for(let[key,value]of Object.entries(this.config.templateVariables||{}))result=result.replace(new RegExp(`{{${key}}}`,"g"),()=>value);return result}async triggerNotifications(params){let{trigger,flow_id,entity_name,entity_id,node_id,context={}}=params,rulesTable=this.getTable("verificationNotificationRules"),recipientsTable=this.getTable("verificationNotificationRecipients"),channelsTable=this.getTable("verificationNotificationChannels");if(!rulesTable||!recipientsTable){this.logger.warn("[Notification] Notification tables not found");return}let now=new Date,rules=await this.db.select().from(rulesTable).where(and6(eq10(this.getCol(rulesTable,"flowId"),flow_id),eq10(this.getCol(rulesTable,"trigger"),trigger)));this.logger.info(`[Notification] Found ${rules.length} rules for trigger=${trigger} flow_id=${flow_id}, filter_node_id=${node_id||"NONE"}`);for(let r of rules)this.logger.info(`[Notification] Rule ${r.id}: nodeId=${r.nodeId}, trigger=${r.trigger}, title=${JSON.stringify(r.titleTemplate)}`);let filteredRules=rules.filter((rule)=>{if(node_id&&rule.nodeId!==node_id)return this.logger.info(`[Notification] EXCLUDED rule ${rule.id}: rule.nodeId=${rule.nodeId} !== filter_node_id=${node_id}`),!1;if(rule.startsAt&&new Date(rule.startsAt)>now)return!1;if(rule.expiresAt&&new Date(rule.expiresAt)<now)return!1;return!0});for(let rule of filteredRules){let recipients=await this.db.select().from(recipientsTable).where(eq10(this.getCol(recipientsTable,"ruleId"),rule.id)),ruleChannels=["portal"];if(channelsTable){let channelEntries=await this.db.select().from(channelsTable).where(eq10(this.getCol(channelsTable,"ruleId"),rule.id));if(channelEntries.length>0)ruleChannels=channelEntries.map((c)=>c.channel)}let enabledChannels=ruleChannels.filter((ch)=>this.isChannelEnabled(ch));if(enabledChannels.length===0)continue;this.logger.info(`[Notification] Rule ${rule.id}: ${recipients.length} recipients, ${enabledChannels.length} channels (${enabledChannels.join(",")})`);let userIds=await this.resolveRecipients(recipients,params.verifier_id,flow_id,entity_name,entity_id);this.logger.info(`[Notification] Rule ${rule.id}: resolved ${userIds.length} user IDs: ${userIds.join(", ")}`);let enrichedContext={...context,entity_name,entity_id,trigger,decision:params.decision};this.logger.info(`[Notification] Rule ${rule.id}: titleTemplate=${JSON.stringify(rule.titleTemplate)}, bodyTemplate=${JSON.stringify(rule.bodyTemplate)}, context=${JSON.stringify(enrichedContext)}`);let title=rule.titleTemplate?this.interpolateTemplate(rule.titleTemplate,enrichedContext):`Verification ${trigger.replace("on_","").replace("_"," ")}`,body=rule.bodyTemplate?this.interpolateTemplate(rule.bodyTemplate,enrichedContext):void 0;this.logger.info(`[Notification] Rule ${rule.id}: final title="${title}", body="${body}"`);for(let userId of userIds){let report=await this.send({user_id:userId,title,body,entity_name,entity_id,type:"verification",source:`flow:${flow_id}`,channels:enabledChannels});if(report.delivered.length===0)this.logger.warn(`[Notification] Rule ${rule.id}: nothing delivered to ${userId} \u2014 ${report.undelivered.map((one)=>`${one.channel}: ${one.reason}`).join("; ")}`)}}this.logger.debug(`[Notification] Triggered ${filteredRules.length} rules for ${trigger} on ${entity_name}:${entity_id}`)}async usersAwaitedNow(entityName,entityId){if(!entityName||!entityId)return[];let instancesTable=this.getTable("verificationInstances"),requirementsTable=this.getTable("verificationRequirements"),userRolesTable=this.getTable("userRoles"),rolesTable=this.getTable("roles");if(!instancesTable||!requirementsTable)return[];let inst=(await this.db.select().from(instancesTable).where(and6(eq10(this.getCol(instancesTable,"entityName"),entityName),eq10(this.getCol(instancesTable,"entityId"),entityId),eq10(this.getCol(instancesTable,"status"),"active"))).orderBy(desc(this.getCol(instancesTable,"createdAt"))).limit(1))[0];if(!inst)return[];let pending=await this.db.select().from(requirementsTable).where(and6(eq10(this.getCol(requirementsTable,"instanceId"),inst.id),eq10(this.getCol(requirementsTable,"status"),"pending"))),out=new Set;for(let req of pending){if(req.verifierUserId){out.add(req.verifierUserId);continue}let roleName=req.verifierRole;if(!roleName||!rolesTable||!userRolesTable)continue;let rCols=rolesTable,urCols=userRolesTable,members=await this.db.select({user_id:urCols.userId}).from(userRolesTable).innerJoin(rolesTable,eq10(urCols.roleId,rCols.id)).where(and6(eq10(rCols.name,roleName),activeRowsPredicate(rolesTable),activeRowsPredicate(userRolesTable)));for(let m of members)out.add(m.user_id)}return[...out]}async resolveRecipients(recipients,currentVerifierId,flowId,entityName,entityId){let userIds=new Set,userRolesTable=this.getTable("userRoles"),rolesTable=this.getTable("roles");for(let recipient of recipients)switch(recipient.recipientType){case"user":if(recipient.recipientUserId)userIds.add(recipient.recipientUserId);break;case"role":if(recipient.recipientRole&&userRolesTable&&rolesTable){let rolesCols=rolesTable,userRolesCols=userRolesTable,role=(await this.db.select().from(rolesTable).where(and6(eq10(rolesCols.name,recipient.recipientRole),activeRowsPredicate(rolesTable))).limit(1))[0];if(role){let usersInRole=await this.db.select({user_id:userRolesCols.userId}).from(userRolesTable).where(and6(eq10(userRolesCols.roleId,role.id),activeRowsPredicate(userRolesTable)));for(let ur of usersInRole)userIds.add(ur.user_id)}}break;case"step_verifier":{if(currentVerifierId){userIds.add(currentVerifierId);break}for(let uid of await this.usersAwaitedNow(entityName,entityId))userIds.add(uid);break}case"entity_creator":{if(entityName&&entityId){let instancesTable=this.getTable("verificationInstances");if(instancesTable){let inst=(await this.db.select().from(instancesTable).where(and6(eq10(this.getCol(instancesTable,"entityName"),entityName),eq10(this.getCol(instancesTable,"entityId"),entityId))).orderBy(desc(this.getCol(instancesTable,"createdAt"))).limit(1))[0];if(inst?.startedBy)userIds.add(inst.startedBy)}}break}case"all_verifiers":{if(flowId){let verifierConfigsTable=this.getTable("verificationVerifierConfigs");if(verifierConfigsTable){let configs=await this.db.select().from(verifierConfigsTable).where(eq10(this.getCol(verifierConfigsTable,"flowId"),flowId));this.logger.info(`[Notification] all_verifiers: found ${configs.length} verifier configs for flow ${flowId}`);for(let cfg of configs){let row=cfg;if(this.logger.info(`[Notification] all_verifiers: config node_id=${row.nodeId}, type=${row.verifierType}, userId=${row.verifierUserId}, role=${row.verifierRole}`),row.verifierUserId)userIds.add(row.verifierUserId);if(row.verifierType==="role"&&row.verifierRole&&userRolesTable&&rolesTable){let rCols=rolesTable,urCols=userRolesTable,roleRow=(await this.db.select().from(rolesTable).where(eq10(rCols.name,row.verifierRole)).limit(1))[0];if(roleRow){let usersInRole=await this.db.select({user_id:urCols.userId}).from(userRolesTable).where(eq10(urCols.roleId,roleRow.id));for(let ur of usersInRole)userIds.add(ur.user_id)}}}}}break}}let usersTable=this.getTable("users");if(usersTable&&userIds.size>0)return(await this.db.select({id:this.getCol(usersTable,"id")}).from(usersTable).where(and6(inArray2(this.getCol(usersTable,"id"),Array.from(userIds)),activeRowsPredicate(usersTable)))).map((r)=>r.id);return Array.from(userIds)}async send(params){let{user_id,title,body,entity_name,entity_id,type,source,channels}=params,delivered=[],undelivered=[],record=(channel,reason)=>{if(reason===null)delivered.push(channel);else undelivered.push({channel,reason})};for(let channel of channels){if(!this.isChannelEnabled(channel)){this.logger.debug?.(`[Notification] channel "${channel}" is disabled in config \u2014 skipping for user ${user_id}`),record(channel,"the channel is disabled in config");continue}switch(channel){case"portal":record(channel,await this.sendPortalNotification(user_id,title,body,entity_name,entity_id,type,source));break;case"email":record(channel,await this.sendEmailNotification(user_id,title,body));break;case"sms":this.logger.warn(`[Notification] SMS delivery is not supported \u2014 skipping sms notification for user ${user_id}`),record(channel,"sms delivery is not supported");break;case"telegram":record(channel,await this.sendTelegramNotification(params));break;case"webhook":record(channel,await this.sendWebhookNotification(params));break}}return{delivered,undelivered}}async sendPortalNotification(userId,title,body,entityName,entityId,type,source){let notificationsTable=this.getTable("notifications");if(title.length>255)title=`${title.slice(0,254)}\u2026`;if(body&&body.length>1000)body=`${body.slice(0,999)}\u2026`;if(!notificationsTable)return this.logger.warn("[Notification] notifications table not found"),"the notifications table was not found";return await this.db.insert(notificationsTable).values(toCamel({user_id:userId,title,body:body||null,entity_name:entityName||null,entity_id:entityId||null,type:type||"system",source:source||null,is_seen:!1})),this.logger.debug(`[Notification] Portal notification sent to ${userId}: ${title}`),null}async sendTelegramNotification(params){let telegramConfig=this.config.channels?.telegram,botToken=resolveEnvValue2(telegramConfig?.botToken),chatId=resolveEnvValue2(telegramConfig?.chatId);if(!botToken||!chatId)return this.logger.warn("[Notification] Telegram channel enabled but botToken/chatId is missing or unresolved \u2014 skipping delivery"),"the telegram botToken/chatId is missing or unresolved";let text=params.body?`*${params.title}*
|
|
98
98
|
|
|
99
99
|
${params.body}`:`*${params.title}*`,timeoutMs=telegramConfig?.timeoutMs??DEFAULT_DELIVERY_TIMEOUT_MS;try{let response=await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:chatId,text,parse_mode:"Markdown"}),signal:AbortSignal.timeout(timeoutMs)});if(!response.ok){let errBody=await response.text().catch(()=>"");return this.logger.error(`[Notification] Telegram delivery failed (${response.status}): ${errBody.slice(0,300)}`),`telegram refused it (${response.status})`}return this.logger.debug(`[Notification] Telegram notification sent: ${params.title}`),null}catch(error){return this.logger.error(`[Notification] Telegram delivery error: ${error.message}`),`telegram delivery error: ${error.message}`}}async sendWebhookNotification(params){let webhookConfig=this.config.channels?.webhook,url=resolveEnvValue2(webhookConfig?.url);if(!url)return this.logger.warn("[Notification] Webhook channel enabled but url is missing or unresolved \u2014 skipping delivery"),"the webhook url is missing or unresolved";let headers={"Content-Type":"application/json"};for(let[key,value]of Object.entries(webhookConfig?.headers||{})){let resolved=resolveEnvValue2(value);if(resolved)headers[key]=resolved}let timeoutMs=webhookConfig?.timeoutMs??DEFAULT_DELIVERY_TIMEOUT_MS;try{let response=await fetch(url,{method:"POST",headers,body:JSON.stringify({user_id:params.user_id,title:params.title,body:params.body??null,entity_name:params.entity_name??null,entity_id:params.entity_id??null,type:params.type??"system",source:params.source??null,timestamp:new Date().toISOString()}),signal:AbortSignal.timeout(timeoutMs)});if(!response.ok){let errBody=await response.text().catch(()=>"");return this.logger.error(`[Notification] Webhook delivery failed (${response.status}): ${errBody.slice(0,300)}`),`the webhook refused it (${response.status})`}return this.logger.debug(`[Notification] Webhook notification sent: ${params.title}`),null}catch(error){return this.logger.error(`[Notification] Webhook delivery error: ${error.message}`),`webhook delivery error: ${error.message}`}}async sendEmailNotification(userId,title,body){let emailService=this.getEmailService?.();if(!emailService?.isAvailable())return this.logger.warn("[Notification] Email service not available for email notification"),"no email provider is configured or available";let usersTable=this.getTable("users");if(!usersTable)return this.logger.warn("[Notification] users table not found"),"the users table was not found";let user=(await this.db.select({email:this.getCol(usersTable,"email")}).from(usersTable).where(eq10(this.getCol(usersTable,"id"),userId)).limit(1))[0];if(!user?.email)return this.logger.warn(`[Notification] No email found for user ${userId}`),"the recipient has no email address on file";let report=await deliver(emailService,{to:user.email,subject:title,html:body||title},`[Notification] ${title}`);if(report.level==="warn")this.logger.warn(report.message);else this.logger.debug(report.message);return report.sent?null:report.message}async getNotifications(userId,options){let notificationsTable=this.getTable("notifications");if(!notificationsTable)return[];let conditions=[eq10(this.getCol(notificationsTable,"userId"),userId)];if(options?.type)conditions.push(eq10(this.getCol(notificationsTable,"type"),options.type));return(await this.db.select().from(notificationsTable).where(and6(...conditions)).orderBy(desc(this.getCol(notificationsTable,"createdAt"))).limit(options?.limit||50).offset(options?.offset||0)).map((r)=>fromCamel(r))}async getUnseenCount(userId){let notificationsTable=this.getTable("notifications");if(!notificationsTable)return 0;return(await this.db.select().from(notificationsTable).where(and6(eq10(this.getCol(notificationsTable,"userId"),userId),eq10(this.getCol(notificationsTable,"isSeen"),!1)))).length}async markAsSeen(notificationId,userId){let notificationsTable=this.getTable("notifications");if(!notificationsTable)return!1;return(await this.db.update(notificationsTable).set(toCamel({is_seen:!0,seen_at:new Date})).where(and6(eq10(this.getCol(notificationsTable,"id"),notificationId),eq10(this.getCol(notificationsTable,"userId"),userId))).returning()).length>0}async markAllAsSeen(userId){let notificationsTable=this.getTable("notifications");if(!notificationsTable)return 0;return(await this.db.update(notificationsTable).set(toCamel({is_seen:!0,seen_at:new Date})).where(and6(eq10(this.getCol(notificationsTable,"userId"),userId),eq10(this.getCol(notificationsTable,"isSeen"),!1))).returning()).length}}var DEFAULT_DELIVERY_TIMEOUT_MS=1e4;var init_Notification=__esm(()=>{init_activeRows();init_types4()});function buildGenericAuthUrl(config,state){if(!config.authorizationUrl)throw Error("Generic OAuth provider requires authorizationUrl");let scopes2=config.scopes??[],params=new URLSearchParams({client_id:config.clientId,redirect_uri:config.redirectUri,response_type:"code",state,...scopes2.length>0?{scope:scopes2.join(" ")}:{},...config.extraAuthParams});return`${config.authorizationUrl}?${params.toString()}`}async function exchangeGenericCode(code,config){if(!config.tokenUrl)throw Error("Generic OAuth provider requires tokenUrl");let tokenRes=await fetch(config.tokenUrl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams({code,client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri,grant_type:"authorization_code"}).toString()});if(!tokenRes.ok){let err=await tokenRes.text();throw Error(`Generic OAuth token exchange failed: ${err}`)}let tokenData=await tokenRes.json();if(tokenData.error)throw Error(`OAuth error: ${tokenData.error_description||tokenData.error}`);let tokens={accessToken:tokenData.access_token,refreshToken:tokenData.refresh_token,expiresAt:tokenData.expires_in?new Date(Date.now()+tokenData.expires_in*1000):void 0,scope:tokenData.scope};if(!config.userInfoUrl)return{profile:{providerAccountId:tokenData.access_token,rawProfile:tokenData},tokens};let userRes=await fetch(config.userInfoUrl,{headers:{Authorization:`Bearer ${tokenData.access_token}`}});if(!userRes.ok)throw Error("Failed to fetch user info from generic OAuth provider");let rawProfile=await userRes.json();return{profile:{providerAccountId:rawProfile.id??rawProfile.sub??rawProfile.user_id??tokenData.access_token,email:rawProfile.email,emailVerified:rawProfile.email_verified===!0||rawProfile.email_verified==="true",name:rawProfile.name??rawProfile.display_name??rawProfile.username,avatarUrl:rawProfile.avatar_url??rawProfile.picture??rawProfile.photo,rawProfile},tokens}}function buildGithubAuthUrl(config,state){let scopes2=config.scopes??["read:user","user:email"];return`https://github.com/login/oauth/authorize?${new URLSearchParams({client_id:config.clientId,redirect_uri:config.redirectUri,scope:scopes2.join(" "),state,...config.extraAuthParams}).toString()}`}async function exchangeGithubCode(code,config){let tokenRes=await fetch("https://github.com/login/oauth/access_token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams({code,client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri}).toString()});if(!tokenRes.ok){let err=await tokenRes.text();throw Error(`GitHub token exchange failed: ${err}`)}let tokenData=await tokenRes.json();if(tokenData.error)throw Error(`GitHub OAuth error: ${tokenData.error_description||tokenData.error}`);let tokens={accessToken:tokenData.access_token,refreshToken:tokenData.refresh_token,expiresAt:tokenData.expires_in?new Date(Date.now()+tokenData.expires_in*1000):void 0,scope:tokenData.scope},userRes=await fetch("https://api.github.com/user",{headers:{Authorization:`Bearer ${tokenData.access_token}`,Accept:"application/vnd.github+json"}});if(!userRes.ok)throw Error("Failed to fetch GitHub user info");let rawProfile=await userRes.json(),email=rawProfile.email,emailVerified=!!email;if(!email)try{let emailsRes=await fetch("https://api.github.com/user/emails",{headers:{Authorization:`Bearer ${tokenData.access_token}`,Accept:"application/vnd.github+json"}});if(emailsRes.ok){let emails=await emailsRes.json(),primary=emails.find((e)=>e.primary&&e.verified);if(primary)email=primary.email,emailVerified=!0;else email=emails[0]?.email,emailVerified=!1}}catch{}return{profile:{providerAccountId:String(rawProfile.id),email,emailVerified,name:rawProfile.name??rawProfile.login,avatarUrl:rawProfile.avatar_url,rawProfile},tokens}}function buildGoogleAuthUrl(config,state){let scopes2=config.scopes??["openid","email","profile"];return`https://accounts.google.com/o/oauth2/v2/auth?${new URLSearchParams({client_id:config.clientId,redirect_uri:config.redirectUri,response_type:"code",scope:scopes2.join(" "),state,access_type:"offline",prompt:"select_account",...config.extraAuthParams}).toString()}`}async function exchangeGoogleCode(code,config){let tokenRes=await fetch("https://oauth2.googleapis.com/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({code,client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri,grant_type:"authorization_code"}).toString()});if(!tokenRes.ok){let err=await tokenRes.text();throw Error(`Google token exchange failed: ${err}`)}let tokenData=await tokenRes.json(),tokens={accessToken:tokenData.access_token,refreshToken:tokenData.refresh_token,expiresAt:tokenData.expires_in?new Date(Date.now()+tokenData.expires_in*1000):void 0,scope:tokenData.scope},userRes=await fetch("https://www.googleapis.com/oauth2/v3/userinfo",{headers:{Authorization:`Bearer ${tokenData.access_token}`}});if(!userRes.ok)throw Error("Failed to fetch Google user info");let rawProfile=await userRes.json();return{profile:{providerAccountId:rawProfile.sub,email:rawProfile.email,emailVerified:rawProfile.email_verified===!0||rawProfile.email_verified==="true",name:rawProfile.name,avatarUrl:rawProfile.picture,rawProfile},tokens}}function buildMicrosoftUserInfoUrl(graphFields){if(!graphFields||graphFields.length===0)return"https://graph.microsoft.com/v1.0/me";let roots=graphFields.map((f)=>f.split(".")[0]).filter((f)=>Boolean(f));return`https://graph.microsoft.com/v1.0/me?$select=${[...new Set([...MICROSOFT_DEFAULT_FIELDS,...roots])].join(",")}`}function mergeMicrosoftProfile(graphProfile,idTokenPayload){if(!idTokenPayload)return graphProfile;return{...idTokenPayload,...graphProfile}}function getMicrosoftBaseUrl(config){return`https://login.microsoftonline.com/${config.tenantId??"common"}/oauth2/v2.0`}function decodeJwtPayload(jwt){if(!jwt)return null;let payloadSegment=jwt.split(".")[1];if(!payloadSegment)return null;try{let json=Buffer.from(payloadSegment,"base64url").toString("utf8");return JSON.parse(json)}catch{return null}}function resolveMicrosoftEmailVerified(config,idTokenPayload){let edov=idTokenPayload?.xms_edov;if(edov===!0||edov==="true")return!0;if(edov===!1||edov==="false")return!1;let tenant=(config.tenantId??"common").toLowerCase();return!MULTI_TENANT_AUTHORITIES.has(tenant)}function describeFetchError(err){if(err&&typeof err==="object"){let e=err,cause=e.cause,parts=[];if(e.message)parts.push(`message=${e.message}`);if(e.code)parts.push(`code=${e.code}`);if(cause?.code)parts.push(`cause.code=${cause.code}`);if(cause?.name)parts.push(`cause.name=${cause.name}`);if(cause?.message)parts.push(`cause.message=${cause.message}`);return parts.join(" | ")||String(err)}return String(err)}function buildMicrosoftAuthUrl(config,state){let configured=config.scopes??["openid","email","profile","User.Read"],scopes2=configured.includes("openid")?configured:["openid",...configured],params=new URLSearchParams({client_id:config.clientId,redirect_uri:config.redirectUri,response_type:"code",scope:scopes2.join(" "),state,response_mode:"query",prompt:"select_account",...config.extraAuthParams});return`${getMicrosoftBaseUrl(config)}/authorize?${params.toString()}`}async function exchangeMicrosoftCode(code,config,graphFields){let tokenUrl=`${getMicrosoftBaseUrl(config)}/token`,tokenRes;try{tokenRes=await fetch(tokenUrl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({code,client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri,grant_type:"authorization_code"}).toString()})}catch(err){throw Error(`Microsoft token endpoint fetch failed | url=${tokenUrl} | redirect_uri=${config.redirectUri} | ${describeFetchError(err)}`,{cause:err})}if(!tokenRes.ok){let body=await tokenRes.text().catch(()=>"<unable to read body>");throw Error(`Microsoft token exchange failed | status=${tokenRes.status} | url=${tokenUrl} | redirect_uri=${config.redirectUri} | body=${body}`)}let tokenData=await tokenRes.json();if(tokenData.error)throw Error(`Microsoft OAuth error: ${tokenData.error_description||tokenData.error} | url=${tokenUrl}`);let tokens={accessToken:tokenData.access_token,refreshToken:tokenData.refresh_token,expiresAt:tokenData.expires_in?new Date(Date.now()+tokenData.expires_in*1000):void 0,scope:tokenData.scope},selectedUrl=buildMicrosoftUserInfoUrl(graphFields),fetchProfile=async(url)=>{try{return await fetch(url,{headers:{Authorization:`Bearer ${tokenData.access_token}`}})}catch(err){throw Error(`Microsoft userinfo fetch failed | url=${url} | ${describeFetchError(err)}`,{cause:err})}},userRes=await fetchProfile(selectedUrl);if(userRes.status===400&&selectedUrl!=="https://graph.microsoft.com/v1.0/me"){let body=await userRes.text().catch(()=>"<unable to read body>");console.warn("[OAuth:microsoft] Graph rejected the $select projection \u2014 falling back to the default "+`property set so login can proceed. Mappings that need a selected property will be empty this round; check every configured claim name. | url=${selectedUrl} | body=${body}`),userRes=await fetchProfile("https://graph.microsoft.com/v1.0/me")}if(!userRes.ok){let body=await userRes.text().catch(()=>"<unable to read body>");throw Error(`Microsoft userinfo failed | status=${userRes.status} | url=${selectedUrl} | body=${body}`)}let rawProfile=await userRes.json(),idTokenPayload=decodeJwtPayload(tokenData.id_token);if(!idTokenPayload)console.warn('[OAuth:microsoft] No id_token in the token response; optional and directory-extension claims are unavailable. Check that the "openid" scope is granted.');return{profile:{providerAccountId:rawProfile.id,email:rawProfile.mail??rawProfile.userPrincipalName,emailVerified:resolveMicrosoftEmailVerified(config,idTokenPayload),name:rawProfile.displayName,avatarUrl:void 0,rawProfile:mergeMicrosoftProfile(rawProfile,idTokenPayload)},tokens}}var MICROSOFT_DEFAULT_FIELDS,MULTI_TENANT_AUTHORITIES;var init_microsoft=__esm(()=>{MICROSOFT_DEFAULT_FIELDS=["id","displayName","givenName","surname","mail","userPrincipalName","mobilePhone","businessPhones","jobTitle","officeLocation","preferredLanguage"];MULTI_TENANT_AUTHORITIES=new Set(["common","organizations","consumers"])});var exports_stateStore={};__export(exports_stateStore,{createRedisOAuthStateStore:()=>createRedisOAuthStateStore,InMemoryOAuthStateStore:()=>InMemoryOAuthStateStore});class InMemoryOAuthStateStore{store=new Map;cleanupInterval=null;constructor(cleanupEverySeconds){this.cleanupInterval=setInterval(()=>{let now=Date.now();for(let[k,v]of this.store.entries())if(v.expiresAt<now)this.store.delete(k)},cleanupEverySeconds*1000),this.cleanupInterval.unref?.()}async set(state,payload,ttlSeconds){this.store.set(state,{payload,expiresAt:Date.now()+ttlSeconds*1000})}async consume(state){let entry=this.store.get(state);if(!entry)return null;if(this.store.delete(state),entry.expiresAt<Date.now())return null;return entry.payload}stop(){if(this.cleanupInterval)clearInterval(this.cleanupInterval),this.cleanupInterval=null}}var key=(state)=>`nucleus:oauth:state:${state}`,createRedisOAuthStateStore=(redis)=>({async set(state,payload,ttlSeconds){await redis.create(key(state),payload,ttlSeconds)},async consume(state){let res=await redis.read(key(state));if(!res.success||!res.data)return null;return await redis.remove(key(state)),res.data}});import{randomBytes as randomBytes3}from"crypto";class OAuthService{config;stateStore;constructor(config,stateStore){this.config=config,this.stateStore=stateStore??new InMemoryOAuthStateStore(config.stateTtlSeconds??600)}stop(){this.stateStore.stop?.()}isProviderEnabled(provider){return!!(this.config.enabled&&this.config.providers[provider])}getEnabledProviders(){if(!this.config.enabled)return[];return Object.keys(this.config.providers)}async buildAuthorizationUrl(provider,linkUserId,redirectUrl){let providerConfig=this.config.providers[provider];if(!providerConfig)throw Error(`OAuth provider "${provider}" is not configured`);let statePayload={provider,linkUserId,redirectUrl,createdAt:Date.now()},state=randomBytes3(32).toString("hex"),ttlSeconds=this.config.stateTtlSeconds??600;switch(await this.stateStore.set(state,statePayload,ttlSeconds),provider){case"google":return buildGoogleAuthUrl(providerConfig,state);case"github":return buildGithubAuthUrl(providerConfig,state);case"microsoft":return buildMicrosoftAuthUrl(providerConfig,state);default:return buildGenericAuthUrl(providerConfig,state)}}consumeState(state){return this.stateStore.consume(state)}async exchangeCode(provider,code){let providerConfig=this.config.providers[provider];if(!providerConfig)throw Error(`OAuth provider "${provider}" is not configured`);switch(provider){case"google":return exchangeGoogleCode(code,providerConfig);case"github":return exchangeGithubCode(code,providerConfig);case"microsoft":return exchangeMicrosoftCode(code,providerConfig,this.graphFieldsForSync());default:return exchangeGenericCode(code,providerConfig)}}graphFieldsForSync(){return(this.config.profileSync?.mappings??[]).filter((m)=>m.source!=="token").map((m)=>m.claim)}get profileSync(){return this.config.profileSync}get allowAccountLinking(){return this.config.allowAccountLinking??!0}get autoCreateUser(){return this.config.autoCreateUser??!0}get successRedirectUrl(){return this.config.successRedirectUrl??"/"}get errorRedirectUrl(){return this.config.errorRedirectUrl??"/login"}get sendInviteOnCreate(){return this.config.sendInviteOnCreate??!1}get basePath(){return this.config.basePath??"/auth/oauth"}}var init_OAuthService=__esm(()=>{init_microsoft()});var init_OAuth=__esm(()=>{init_OAuthService()});import*as crypto5 from"crypto";var IYZICO_PATHS,generateAuthString=(apiKey,secretKey,path,payload,randomKey)=>{let compound=payload?randomKey+path+payload:randomKey+path,encryptedData=crypto5.createHmac("sha256",secretKey).update(compound).digest("hex"),authorizationString=`apiKey:${apiKey}&randomKey:${randomKey}&signature:${encryptedData}`;return`IYZWSv2 ${Buffer.from(authorizationString).toString("base64")}`},buildRequest=(config,path,payload)=>{let body=JSON.stringify(payload),randomKey=Date.now().toString(32),headers=new Headers;return headers.set("Content-Type","application/json"),headers.set("x-iyzi-rnd",randomKey),headers.set("Authorization",generateAuthString(config.apiKey,config.secretKey,path,body,randomKey)),{url:new URL(`${config.apiUrl}${path}`).toString(),headers,body}},iyzicoFetch=async(config,path,payload)=>{let{url,headers,body}=buildRequest(config,path,payload);try{let json=await(await fetch(url,{method:"POST",headers,body})).json(),isSuccess=json.status==="success";return{success:isSuccess,data:isSuccess?json:null,errorCode:json.errorCode??void 0,errorMessage:json.errorMessage??void 0,rawResponse:json}}catch(error){let message=error instanceof Error?error.message:"Unknown fetch error";return{success:!1,data:null,errorCode:"NETWORK_ERROR",errorMessage:message,rawResponse:{error:message}}}},iyzicoUnsupported=(verb)=>({success:!1,errorCode:"NOT_SUPPORTED",errorMessage:`${verb} is not supported by iyzico: it has no balance, payout or transfer API. Sub-merchants are settled by iyzico itself from the approved split; record platform-side payouts through the marketplace payout workflow.`}),createIyzicoProvider=(config)=>{let generateConversationId=()=>`nk-${Date.now().toString(16)}`;return{get name(){return"iyzico"},async initialize3DS(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId,price:request.price,paidPrice:request.paidPrice,currency:request.currency,installment:request.installment??1,paymentChannel:request.paymentChannel??"WEB",basketId:request.basketId??Date.now().toString(16),paymentGroup:request.paymentGroup??"PRODUCT",paymentCard:request.paymentCard,buyer:request.buyer,shippingAddress:request.shippingAddress,billingAddress:request.billingAddress,basketItems:request.basketItems,callbackUrl:request.callbackUrl},result=await iyzicoFetch(config,IYZICO_PATHS.INITIALIZE_3DS,payload);return{success:result.success,threeDSHtmlContent:result.data?.threeDSHtmlContent,paymentId:result.data?.paymentId,signature:result.data?.signature,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async complete3DS(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId,paymentId:request.paymentId},result=await iyzicoFetch(config,IYZICO_PATHS.COMPLETE_3DS,payload),respConversationId=result.data?.conversationId;if(request.conversationId&&respConversationId&&respConversationId!==request.conversationId)return{success:!1,paymentId:result.data?.paymentId,errorCode:"CONVERSATION_MISMATCH",errorMessage:"Payment does not belong to this transaction",rawResponse:result.rawResponse};return{success:result.success,paymentId:result.data?.paymentId,price:result.data?.price,paidPrice:result.data?.paidPrice,currency:result.data?.currency,installment:result.data?.installment,cardType:result.data?.cardType,cardAssociation:result.data?.cardAssociation,cardFamily:result.data?.cardFamily,binNumber:result.data?.binNumber,lastFourDigits:result.data?.lastFourDigits,basketId:result.data?.basketId,authCode:result.data?.authCode,signature:result.data?.signature,fraudStatus:result.data?.fraudStatus,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async getPaymentDetail(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId??generateConversationId(),paymentId:request.paymentId,paymentConversationId:request.paymentConversationId},result=await iyzicoFetch(config,IYZICO_PATHS.PAYMENT_DETAIL,payload);return{success:result.success,paymentId:result.data?.paymentId,price:result.data?.price,paidPrice:result.data?.paidPrice,currency:result.data?.currency,basketId:result.data?.basketId,cardType:result.data?.cardType,cardAssociation:result.data?.cardAssociation,lastFourDigits:result.data?.lastFourDigits,binNumber:result.data?.binNumber,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async queryBin(request){let payload={locale:request.locale??"tr",conversationId:generateConversationId(),binNumber:request.binNumber.slice(0,6)};if(request.price!==void 0)payload.price=request.price;let result=await iyzicoFetch(config,IYZICO_PATHS.BIN_CHECK,payload);return{success:result.success,binNumber:result.data?.binNumber,cardType:result.data?.cardType,cardAssociation:result.data?.cardAssociation,cardFamily:result.data?.cardFamily,bankName:result.data?.bankName,bankCode:result.data?.bankCode,commercial:result.data?.commercial,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async saveCard(request){let payload={locale:request.locale??"tr",conversationId:generateConversationId(),card:request.card,email:request.email,externalId:request.externalId},result=await iyzicoFetch(config,IYZICO_PATHS.CARD_SAVE,payload);return{success:result.success,cardUserKey:result.data?.cardUserKey,cardToken:result.data?.cardToken,cardAlias:result.data?.cardAlias,binNumber:result.data?.binNumber,lastFourDigits:result.data?.lastFourDigits,cardType:result.data?.cardType,cardAssociation:result.data?.cardAssociation,cardFamily:result.data?.cardFamily,cardBankName:result.data?.cardBankName,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async listCards(request){let payload={locale:request.locale??"tr",conversationId:generateConversationId(),cardUserKey:request.cardUserKey},result=await iyzicoFetch(config,IYZICO_PATHS.CARD_LIST,payload),cards=(result.data?.cardDetails??[]).map((cd)=>({cardToken:cd.cardToken,cardAlias:cd.cardAlias,binNumber:cd.binNumber,lastFourDigits:cd.lastFourDigits,cardType:cd.cardType,cardAssociation:cd.cardAssociation,cardFamily:cd.cardFamily,cardBankName:cd.cardBankName,cardBankCode:cd.cardBankCode}));return{success:result.success,cardUserKey:result.data?.cardUserKey,cards,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async deleteCard(request){let payload={locale:request.locale??"tr",conversationId:generateConversationId(),cardUserKey:request.cardUserKey,cardToken:request.cardToken},result=await iyzicoFetch(config,IYZICO_PATHS.CARD_DELETE,payload);return{success:result.success,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async refund(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId??generateConversationId(),paymentTransactionId:request.paymentTransactionId,price:request.price,currency:request.currency??"TRY"};if(request.ip)payload.ip=request.ip;let result=await iyzicoFetch(config,IYZICO_PATHS.REFUND,payload);return{success:result.success,paymentId:result.data?.paymentId,price:result.data?.price,currency:result.data?.currency,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},parseCallback(formData){return{success:formData.mdStatus==="1",paymentId:formData.paymentId??"",conversationId:formData.conversationId??"",mdStatus:formData.mdStatus??"",rawData:formData}},async registerSubMerchant(request){let typeMap={personal:"PERSONAL",private_company:"PRIVATE_COMPANY",limited_or_joint_stock_company:"LIMITED_OR_JOINT_STOCK_COMPANY"},payload={locale:request.locale??"tr",conversationId:request.conversationId??generateConversationId(),subMerchantExternalId:request.subMerchantExternalId,subMerchantType:typeMap[request.subMerchantType]??"PERSONAL",name:request.name,email:request.email,gsmNumber:request.gsmNumber,address:request.address,iban:request.iban,contactName:request.contactName,contactSurname:request.contactSurname,identityNumber:request.identityNumber,currency:request.currency??"TRY"};if(request.subMerchantType==="private_company")payload.taxOffice=request.taxOffice,payload.legalCompanyTitle=request.legalCompanyTitle;if(request.subMerchantType==="limited_or_joint_stock_company")payload.taxOffice=request.taxOffice,payload.taxNumber=request.taxNumber,payload.legalCompanyTitle=request.legalCompanyTitle;let result=await iyzicoFetch(config,IYZICO_PATHS.SUB_MERCHANT_CREATE,payload);return{success:result.success,subMerchantKey:result.data?.subMerchantKey,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async updateSubMerchant(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId??generateConversationId(),subMerchantKey:request.subMerchantKey};if(request.name)payload.name=request.name;if(request.email)payload.email=request.email;if(request.gsmNumber)payload.gsmNumber=request.gsmNumber;if(request.address)payload.address=request.address;if(request.iban)payload.iban=request.iban;if(request.contactName)payload.contactName=request.contactName;if(request.contactSurname)payload.contactSurname=request.contactSurname;if(request.identityNumber)payload.identityNumber=request.identityNumber;if(request.taxOffice)payload.taxOffice=request.taxOffice;if(request.taxNumber)payload.taxNumber=request.taxNumber;if(request.legalCompanyTitle)payload.legalCompanyTitle=request.legalCompanyTitle;if(request.currency)payload.currency=request.currency;let result=await iyzicoFetch(config,IYZICO_PATHS.SUB_MERCHANT_UPDATE,payload);return{success:result.success,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async getSubMerchant(request){let payload={locale:request.locale??"tr",conversationId:request.conversationId??generateConversationId(),subMerchantExternalId:request.subMerchantExternalId},result=await iyzicoFetch(config,IYZICO_PATHS.SUB_MERCHANT_DETAIL,payload);return{success:result.success,subMerchantKey:result.data?.subMerchantKey,name:result.data?.name,email:result.data?.email,gsmNumber:result.data?.gsmNumber,address:result.data?.address,iban:result.data?.iban,subMerchantType:result.data?.subMerchantType,currency:result.data?.currency,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async approveSplit(request){let payload={locale:request.locale??"tr",paymentTransactionId:request.paymentTransactionId},result=await iyzicoFetch(config,IYZICO_PATHS.APPROVAL,payload);return{success:result.success,paymentTransactionId:result.data?.paymentTransactionId,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async disapproveSplit(request){let payload={locale:request.locale??"tr",paymentTransactionId:request.paymentTransactionId},result=await iyzicoFetch(config,IYZICO_PATHS.DISAPPROVAL,payload);return{success:result.success,paymentTransactionId:result.data?.paymentTransactionId,errorCode:result.errorCode,errorMessage:result.errorMessage,rawResponse:result.rawResponse}},async createProduct(request){let localId=`iyzico-prod-${Date.now().toString(36)}`;return{success:!0,providerProductId:localId,name:request.name,rawResponse:{local:!0,id:localId}}},async updateProduct(request){return{success:!0,providerProductId:request.providerProductId,rawResponse:{local:!0,updated:request.providerProductId}}},async getProduct(request){return{success:!0,providerProductId:request.providerProductId,rawResponse:{local:!0,message:"Product data is stored locally for iyzico"}}},async listProducts(_request){return{success:!0,products:[],hasMore:!1,rawResponse:{local:!0,message:"Products are stored locally for iyzico"}}},async archiveProduct(request){return{success:!0,rawResponse:{local:!0,archived:request.providerProductId}}},async createPrice(request){let localId=`iyzico-price-${Date.now().toString(36)}`;return{success:!0,providerPriceId:localId,rawResponse:{local:!0,id:localId,product:request.providerProductId}}},async updatePrice(request){return{success:!0,providerPriceId:request.providerPriceId,rawResponse:{local:!0,updated:request.providerPriceId}}},async getPrice(request){return{success:!0,providerPriceId:request.providerPriceId,rawResponse:{local:!0,message:"Price data is stored locally for iyzico"}}},async listPrices(_request){return{success:!0,prices:[],hasMore:!1,rawResponse:{local:!0,message:"Prices are stored locally for iyzico"}}},async archivePrice(request){return{success:!0,rawResponse:{local:!0,archived:request.providerPriceId}}},async createCustomer(request){return{success:!0,providerCustomerId:`local_cus_${Date.now()}`,email:request.email,rawResponse:{local:!0,message:"Customer stored locally for iyzico"}}},async updateCustomer(request){return{success:!0,providerCustomerId:request.providerCustomerId,rawResponse:{local:!0,message:"Customer updated locally for iyzico"}}},async getCustomer(request){return{success:!0,providerCustomerId:request.providerCustomerId,rawResponse:{local:!0,message:"Customer data is stored locally for iyzico"}}},async listCustomers(_request){return{success:!0,customers:[],hasMore:!1,rawResponse:{local:!0,message:"Customers are stored locally for iyzico"}}},async deleteCustomer(_request){return{success:!0,rawResponse:{local:!0,message:"Customer deleted locally for iyzico"}}},async createSubscription(_request){return{success:!0,providerSubscriptionId:`local_sub_${Date.now()}`,status:"active",rawResponse:{local:!0,message:"Subscription stored locally for iyzico"}}},async updateSubscription(request){return{success:!0,providerSubscriptionId:request.providerSubscriptionId,status:"active",rawResponse:{local:!0,message:"Subscription updated locally for iyzico"}}},async cancelSubscription(request){return{success:!0,providerSubscriptionId:request.providerSubscriptionId,status:"canceled",rawResponse:{local:!0,message:"Subscription canceled locally for iyzico"}}},async getSubscription(request){return{success:!0,providerSubscriptionId:request.providerSubscriptionId,rawResponse:{local:!0,message:"Subscription data is stored locally for iyzico"}}},async listSubscriptions(_request){return{success:!0,subscriptions:[],hasMore:!1,rawResponse:{local:!0,message:"Subscriptions are stored locally for iyzico"}}},async reportUsage(request){return{success:!0,usageRecordId:`local_ur_${Date.now()}`,quantity:request.quantity,timestamp:request.timestamp??Math.floor(Date.now()/1000),rawResponse:{local:!0,message:"Usage recorded locally for iyzico"}}},async listUsageRecordSummaries(_request){return{success:!0,summaries:[],hasMore:!1,rawResponse:{local:!0,message:"Usage summaries are stored locally for iyzico"}}},async createInvoice(_request){return{success:!0,providerInvoiceId:`local_inv_${Date.now()}`,status:"draft",rawResponse:{local:!0,message:"Invoice stored locally for iyzico"}}},async finalizeInvoice(request){return{success:!0,providerInvoiceId:request.providerInvoiceId,status:"open",rawResponse:{local:!0,message:"Invoice finalized locally for iyzico"}}},async sendInvoice(request){return{success:!0,providerInvoiceId:request.providerInvoiceId,rawResponse:{local:!0,message:"Invoice send is a no-op for iyzico"}}},async voidInvoice(request){return{success:!0,providerInvoiceId:request.providerInvoiceId,rawResponse:{local:!0,message:"Invoice voided locally for iyzico"}}},async payInvoice(request){return{success:!0,providerInvoiceId:request.providerInvoiceId,status:"paid",rawResponse:{local:!0,message:"Invoice payment is a no-op for iyzico"}}},async getInvoice(request){return{success:!0,providerInvoiceId:request.providerInvoiceId,rawResponse:{local:!0,message:"Invoice data is stored locally for iyzico"}}},async listInvoices(_request){return{success:!0,invoices:[],hasMore:!1,rawResponse:{local:!0,message:"Invoices are stored locally for iyzico"}}},async getBalance(){return{...iyzicoUnsupported("Balance"),available:[],pending:[]}},async createPayout(_request){return iyzicoUnsupported("Creating a payout")},async getPayout(request){return{...iyzicoUnsupported("Reading a payout"),providerPayoutId:request.providerPayoutId}},async cancelPayout(request){return{...iyzicoUnsupported("Cancelling a payout"),providerPayoutId:request.providerPayoutId}},async listPayouts(_request){return{...iyzicoUnsupported("Listing payouts"),payouts:[],hasMore:!1}},async createTransfer(_request){return iyzicoUnsupported("Creating a transfer")},async listTransfers(_request){return{...iyzicoUnsupported("Listing transfers"),transfers:[],hasMore:!1}}}};var init_IyzicoProvider=__esm(()=>{IYZICO_PATHS={INITIALIZE_3DS:"/payment/3dsecure/initialize",COMPLETE_3DS:"/payment/3dsecure/auth",PAYMENT_AUTH:"/payment/auth",PAYMENT_DETAIL:"/payment/detail",BIN_CHECK:"/payment/bin/check",CARD_SAVE:"/cardstorage/card",CARD_LIST:"/cardstorage/cards",CARD_DELETE:"/cardstorage/card/delete",REFUND:"/payment/refund",SUB_MERCHANT_CREATE:"/onboarding/submerchant",SUB_MERCHANT_UPDATE:"/onboarding/submerchant",SUB_MERCHANT_DETAIL:"/onboarding/submerchant/detail",APPROVAL:"/payment/iyzipos/item/approve",DISAPPROVAL:"/payment/iyzipos/item/disapprove"}});var resolveMarketplaceConfig=(payment)=>{let m=payment?.marketplace;return{enabled:m?.enabled===!0,defaultSettlementDelayDays:m?.defaultSettlementDelayDays??7,newSellerDelayDays:m?.newSellerDelayDays??14,reserveRate:clampRate(m?.reserveRate??0),reserveReleaseDays:m?.reserveReleaseDays??0,minimumPayoutAmount:m?.minimumPayoutAmount??0,defaultCurrency:m?.defaultCurrency??payment?.defaultCurrency??"USD",autoReleaseReserves:m?.autoReleaseReserves===!0}},clampRate=(rate)=>{if(Number.isNaN(rate)||rate<0)return 0;if(rate>1)return 1;return rate},addDays=(base,days)=>new Date(base.getTime()+days*24*60*60*1000),computeSplitBreakdown=(input,config,policy,now=new Date)=>{let providerFee=Math.max(0,Math.round(input.providerFeeAmount??0)),platformFee=Math.max(0,Math.round(input.platformFeeAmount)),afterFees=Math.max(0,input.grossAmount-providerFee-platformFee),reserveRate=input.reserveRate??policy?.reserveRate??config.reserveRate,reserveAmount=Math.min(afterFees,Math.floor(afterFees*clampRate(reserveRate))),netPayable=afterFees-reserveAmount,delayDays=input.settlementDelayDays??(input.isNewSeller?policy?.newSellerDelayDays??config.newSellerDelayDays:policy?.defaultDelayDays??config.defaultSettlementDelayDays);return{grossAmount:input.grossAmount,providerFeeAmount:providerFee,platformFeeAmount:platformFee,reserveAmount,netPayableAmount:netPayable,availableForPayoutAt:addDays(now,Math.max(0,delayDays))}},str2=(v)=>v==null?"":String(v),strOrNull2=(v)=>v==null?null:String(v),num=(v)=>{let n=typeof v==="number"?v:Number(v);return Number.isFinite(n)?n:0},rowToSplit=(row)=>({id:str2(row.id),transactionId:strOrNull2(row.transactionId??row.transaction_id),recipientOwnerType:str2(row.recipientOwnerType??row.recipient_owner_type)||"seller",recipientOwnerId:str2(row.recipientOwnerId??row.recipient_owner_id),provider:str2(row.provider),grossAmount:num(row.grossAmount??row.gross_amount),providerFeeAmount:num(row.providerFeeAmount??row.provider_fee_amount),platformFeeAmount:num(row.platformFeeAmount??row.platform_fee_amount),reserveAmount:num(row.reserveAmount??row.reserve_amount),netPayableAmount:num(row.netPayableAmount??row.net_payable_amount),currency:str2(row.currency),availableForPayoutAt:strOrNull2(row.availableForPayoutAt??row.available_for_payout_at),status:str2(row.status)||"pending",sourceType:strOrNull2(row.sourceType??row.source_type),sourceId:strOrNull2(row.sourceId??row.source_id)}),rowToPayoutRequest=(row)=>({id:str2(row.id),recipientOwnerType:str2(row.recipientOwnerType??row.recipient_owner_type)||"seller",recipientOwnerId:str2(row.recipientOwnerId??row.recipient_owner_id),amount:num(row.amount),currency:str2(row.currency),status:str2(row.status)||"pending",provider:strOrNull2(row.provider),providerPayoutId:strOrNull2(row.providerPayoutId??row.provider_payout_id),providerTransferId:strOrNull2(row.providerTransferId??row.provider_transfer_id),destination:strOrNull2(row.destination),failureReason:strOrNull2(row.failureReason??row.failure_reason),requestedBy:strOrNull2(row.requestedBy??row.requested_by)}),rowToSettlementPolicy=(row)=>({id:str2(row.id),ownerType:str2(row.ownerType??row.owner_type),ownerId:str2(row.ownerId??row.owner_id),defaultDelayDays:num(row.defaultDelayDays??row.default_delay_days),newSellerDelayDays:num(row.newSellerDelayDays??row.new_seller_delay_days),reserveRate:num(row.reserveRate??row.reserve_rate),reserveDays:num(row.reserveDays??row.reserve_days),minimumPayoutAmount:num(row.minimumPayoutAmount??row.minimum_payout_amount),currency:strOrNull2(row.currency),earlyPayoutEnabled:Boolean(row.earlyPayoutEnabled??row.early_payout_enabled),status:str2(row.status)||"active"}),rowToDispute=(row)=>({id:str2(row.id),transactionId:strOrNull2(row.transactionId??row.transaction_id),provider:str2(row.provider),providerDisputeId:strOrNull2(row.providerDisputeId??row.provider_dispute_id),ownerType:strOrNull2(row.ownerType??row.owner_type),ownerId:strOrNull2(row.ownerId??row.owner_id),amount:row.amount==null?null:num(row.amount),currency:strOrNull2(row.currency),reason:strOrNull2(row.reason),status:str2(row.status)||"open",resolvedAt:strOrNull2(row.resolvedAt??row.resolved_at)});import{and as and7,eq as eq11,or as or2,sql as sql3}from"drizzle-orm";class MarketplacePayoutService{getDb;provider;logger;t;marketplace;constructor(cfg,marketplace){this.getDb=cfg.getDb,this.provider=cfg.provider,this.logger=cfg.logger.scoped("payment.payout"),this.t=cfg.tables,this.marketplace=marketplace}table(name){return this.t[name]}async requestPayout(input){let db=this.getDb();if(!db)throw Error("Marketplace payouts not ready");let recipientType=input.recipientOwnerType??"seller",currency=input.currency??this.marketplace.config.defaultCurrency;if(input.amount<=0)throw Error("Payout amount must be positive");let minimum=this.marketplace.config.minimumPayoutAmount;if(input.amount<minimum)throw Error(`Payout amount is below the minimum of ${minimum}`);let table=this.table("payoutRequests"),lockKey=`payout:${recipientType}:${input.recipientOwnerId}:${currency}`,insertRow=async(executor,locked)=>{let balance=await this.marketplace.getBalance(recipientType,input.recipientOwnerId,currency,locked);if(input.amount>balance.available)throw Error(`Insufficient available balance: requested ${input.amount}, available ${balance.available}`);let rows=await executor.insert(table).values({recipientOwnerType:recipientType,recipientOwnerId:input.recipientOwnerId,amount:input.amount,currency,status:"pending",destination:input.destination??null,requestedBy:input.requestedBy??null,metadata:JSON.stringify({})}).returning();return rowToPayoutRequest(rows[0])};if(typeof db.transaction==="function")return db.transaction(async(tx)=>{return await tx.execute(sql3`SELECT pg_advisory_xact_lock(hashtext(${lockKey}))`),insertRow(tx,tx)});return insertRow(db)}async listPayoutRequests(filter){let db=this.getDb();if(!db)return[];let table=this.table("payoutRequests"),where=filter.recipientOwnerId?eq11(col4(table,"recipientOwnerId"),filter.recipientOwnerId):void 0,mapped=(where?await db.select().from(table).where(where):await db.select().from(table)).map((r)=>rowToPayoutRequest(r));return filter.status?mapped.filter((p)=>p.status===filter.status):mapped}async getPayoutRequest(id){let db=this.getDb();if(!db)return null;let table=this.table("payoutRequests"),rows=await db.select().from(table).where(eq11(col4(table,"id"),id)).limit(1);return rows[0]?rowToPayoutRequest(rows[0]):null}async rejectPayout(id,deciderId){let db=this.getDb();if(!db)return null;let table=this.table("payoutRequests");return await db.update(table).set({status:"rejected",decidedBy:deciderId??null,decidedAt:new Date,updatedAt:new Date}).where(eq11(col4(table,"id"),id)),this.getPayoutRequest(id)}async reconcilePayoutByProvider(providerId,outcome,reason){let db=this.getDb();if(!db)return null;let table=this.table("payoutRequests"),row=(await db.select().from(table).where(or2(eq11(col4(table,"providerPayoutId"),providerId),eq11(col4(table,"providerTransferId"),providerId))).limit(1))[0];if(!row)return null;let previousStatus=String(row.status),nextStatus=outcome==="completed"?"completed":"failed";if(await db.update(table).set({status:nextStatus,failureReason:outcome==="failed"?reason??"Provider reported failure":null,completedAt:outcome==="completed"?new Date:row.completedAt??null,updatedAt:new Date}).where(eq11(col4(table,"id"),String(row.id))),outcome==="completed"&&previousStatus!=="completed"){if(!await this.marketplace.hasLedgerEntry({account:"seller_payable",direction:"debit",referenceType:"payout_request",referenceId:String(row.id)})){let amount=Number(row.amount),currency=String(row.currency),ownerType=String(row.recipientOwnerType??"seller"),ownerId=String(row.recipientOwnerId??""),bookkeep=async(exec)=>{await this.marketplace.recordLedgerEntry({account:"seller_payable",direction:"debit",amount,currency,entryType:"payout",ownerType,ownerId,referenceType:"payout_request",referenceId:String(row.id)},exec),await this.marketplace.recordLedgerEntry({account:"payout_completed",direction:"credit",amount,currency,entryType:"payout",ownerType,ownerId,referenceType:"payout_request",referenceId:String(row.id)},exec),await this.consumeSplits(ownerId,amount,currency,exec)};if(typeof db.transaction==="function")await db.transaction(async(tx)=>bookkeep(tx));else await bookkeep(db)}}if(outcome==="failed"&&previousStatus==="completed")await this.marketplace.recordLedgerEntry({account:"seller_payable",direction:"credit",amount:Number(row.amount),currency:String(row.currency),entryType:"adjustment",ownerType:String(row.recipientOwnerType??"seller"),ownerId:String(row.recipientOwnerId??""),referenceType:"payout_request",referenceId:String(row.id),description:`Payout reversal (${reason??"provider failure"})`});return this.getPayoutRequest(String(row.id))}async approvePayout(id,opts){let db=this.getDb();if(!db)return null;let request=await this.getPayoutRequest(id);if(!request)return null;if(request.status!=="pending")throw Error(`Payout is not pending (status: ${request.status})`);if(opts?.deciderId&&request.requestedBy&&opts.deciderId===request.requestedBy)throw Error("Approver cannot be the requester");let table=this.table("payoutRequests"),destination=opts?.destination??request.destination,lockKey=`payout:${request.recipientOwnerType}:${request.recipientOwnerId}:${request.currency}`,checkAndClaim=async(executor,locked)=>{let backing=(await this.marketplace.getBalance(request.recipientOwnerType,request.recipientOwnerId,request.currency,locked)).available+request.amount;if(backing<request.amount)throw Error(`Insufficient available balance: requested ${request.amount}, available ${backing}`);return executor.update(table).set({status:"processing",decidedBy:opts?.deciderId??null,decidedAt:new Date,updatedAt:new Date}).where(and7(eq11(col4(table,"id"),id),eq11(col4(table,"status"),"pending"))).returning()},claimed=typeof db.transaction==="function"?await db.transaction(async(tx)=>{return await tx.execute(sql3`SELECT pg_advisory_xact_lock(hashtext(${lockKey}))`),checkAndClaim(tx,tx)}):await checkAndClaim(db);if(!claimed||claimed.length===0)throw Error("Payout is no longer pending (already being processed)");let provider=this.provider;if(!provider)return this.logger.warn("No payment provider configured; payout left in processing",{id}),this.getPayoutRequest(id);let providerId,providerPayoutId,offProvider=!1;try{let result=destination?await provider.createTransfer({amount:request.amount,currency:request.currency,destination,transferGroup:`payout:${id}`,idempotencyKey:`payout:${id}`}):await provider.createPayout({amount:request.amount,currency:request.currency,idempotencyKey:`payout:${id}`});if(providerId="providerTransferId"in result?result.providerTransferId:void 0,providerPayoutId="providerPayoutId"in result?result.providerPayoutId:void 0,!result.success&&result.errorCode==="NOT_SUPPORTED")offProvider=!0,providerId=void 0,providerPayoutId=void 0,this.logger.warn("Provider has no payout API; settled on the platform ledger only",{id,provider:provider.name,reason:result.errorMessage});else if(!result.success)throw Error(result.errorMessage||"Provider payout failed")}catch(err){let reason=err instanceof Error?err.message:"Payout execution failed";return await db.update(table).set({status:"failed",failureReason:reason,updatedAt:new Date}).where(eq11(col4(table,"id"),id)),this.logger.error("Payout provider execution failed",{id,reason}),this.getPayoutRequest(id)}let settledBy=offProvider?`Settled off-provider: ${provider.name} has no payout API`:void 0,finalize=async(exec)=>{await exec.update(table).set({status:"completed",provider:provider.name,providerTransferId:providerId??null,providerPayoutId:providerPayoutId??null,completedAt:new Date,updatedAt:new Date}).where(eq11(col4(table,"id"),id)),await this.marketplace.recordLedgerEntry({account:"seller_payable",direction:"debit",amount:request.amount,currency:request.currency,entryType:"payout",ownerType:request.recipientOwnerType,ownerId:request.recipientOwnerId,referenceType:"payout_request",referenceId:id,description:settledBy},exec),await this.marketplace.recordLedgerEntry({account:"payout_completed",direction:"credit",amount:request.amount,currency:request.currency,entryType:"payout",ownerType:request.recipientOwnerType,ownerId:request.recipientOwnerId,referenceType:"payout_request",referenceId:id,description:settledBy},exec),await this.consumeSplits(request.recipientOwnerId,request.amount,request.currency,exec)};try{if(typeof db.transaction==="function")await db.transaction(async(tx)=>finalize(tx));else await finalize(db)}catch(err){let reason=err instanceof Error?err.message:"Bookkeeping failed after payout";try{await db.update(table).set({providerTransferId:providerId??null,providerPayoutId:providerPayoutId??null,failureReason:`RECONCILE_REQUIRED: ${reason}`,updatedAt:new Date}).where(eq11(col4(table,"id"),id))}catch{}this.logger.error("Payout bookkeeping failed AFTER funds moved \u2014 reconciliation required",{id,providerId,providerPayoutId,reason})}return this.getPayoutRequest(id)}async consumeSplits(ownerId,amount,currency,executor){let db=executor??this.getDb();if(!db)return;let table=this.table("splits"),splits=await this.marketplace.listSplits({recipientOwnerId:ownerId},executor),remaining=amount,now=Date.now();for(let s of splits){if(remaining<=0)break;if(s.currency!==currency)continue;if(s.status!=="pending"&&s.status!=="available")continue;if(!(s.availableForPayoutAt?new Date(s.availableForPayoutAt).getTime()<=now:!0))continue;if(s.netPayableAmount>remaining)continue;await db.update(table).set({status:"paid",updatedAt:new Date}).where(eq11(col4(table,"id"),s.id)),remaining-=s.netPayableAmount}}async openDispute(input){let db=this.getDb();if(!db)return null;let table=this.table("disputes"),rows=await db.insert(table).values({transactionId:input.transactionId??null,provider:input.provider,providerDisputeId:input.providerDisputeId??null,ownerType:input.ownerType??null,ownerId:input.ownerId??null,amount:input.amount??null,currency:input.currency??null,reason:input.reason??null,status:"open",evidenceDueAt:input.evidenceDueAt??null,metadata:JSON.stringify(input.metadata??{})}).returning(),dispute=rowToDispute(rows[0]);if(input.amount&&input.amount>0&&input.currency)await this.marketplace.recordLedgerEntry({account:"dispute_liability",direction:"credit",amount:input.amount,currency:input.currency,entryType:"dispute",ownerType:input.ownerType,ownerId:input.ownerId,referenceType:"dispute",referenceId:dispute.id});return dispute}async listDisputes(filter){let db=this.getDb();if(!db)return[];let table=this.table("disputes"),where=filter.ownerId?eq11(col4(table,"ownerId"),filter.ownerId):void 0,mapped=(where?await db.select().from(table).where(where):await db.select().from(table)).map((r)=>rowToDispute(r));return filter.status?mapped.filter((d)=>d.status===filter.status):mapped}async getDispute(id){let db=this.getDb();if(!db)return null;let table=this.table("disputes"),rows=await db.select().from(table).where(eq11(col4(table,"id"),id)).limit(1);return rows[0]?rowToDispute(rows[0]):null}async resolveDispute(id,outcome){let db=this.getDb();if(!db)return null;let table=this.table("disputes"),claimedRow=(await db.update(table).set({status:outcome,resolvedAt:new Date,updatedAt:new Date}).where(and7(eq11(col4(table,"id"),id),eq11(col4(table,"status"),"open"))).returning())[0];if(!claimedRow)return this.getDispute(id);let dispute=rowToDispute(claimedRow);if((outcome==="lost"||outcome==="accepted")&&dispute.amount&&dispute.currency)await this.marketplace.recordLedgerEntry({account:"seller_payable",direction:"debit",amount:dispute.amount,currency:dispute.currency,entryType:"dispute",ownerType:dispute.ownerType??void 0,ownerId:dispute.ownerId??void 0,referenceType:"dispute",referenceId:id});return this.getDispute(id)}async reconcileDisputeByProvider(providerDisputeId,status){let db=this.getDb();if(!db)return null;let table=this.table("disputes"),row=(await db.select().from(table).where(eq11(col4(table,"providerDisputeId"),providerDisputeId)).limit(1))[0];if(!row)return null;let dispute=rowToDispute(row);if(dispute.status!=="open")return dispute;let outcome=status==="won"||status==="warning_closed"?"won":status==="lost"?"lost":null;if(!outcome)return dispute;return this.resolveDispute(dispute.id,outcome)}}var col4=(table,name)=>table[name];var init_MarketplacePayoutService=()=>{};import{and as and8,eq as eq12}from"drizzle-orm";class MarketplaceService{getDb;config;logger;t;constructor(cfg){this.getDb=cfg.getDb,this.config=cfg.config,this.logger=cfg.logger.scoped("payment.marketplace"),this.t=cfg.tables}table(name){return this.t[name]}async recordLedgerEntry(entry,executor){let db=executor??this.getDb();if(!db)return;await db.insert(this.table("ledgerEntries")).values({account:entry.account,ownerType:entry.ownerType??null,ownerId:entry.ownerId??null,direction:entry.direction,amount:entry.amount,currency:entry.currency,entryType:entry.entryType,referenceType:entry.referenceType??null,referenceId:entry.referenceId??null,splitId:entry.splitId??null,description:entry.description??null,metadata:JSON.stringify({})})}async hasLedgerEntry(q){let db=this.getDb();if(!db)return!1;let table=this.table("ledgerEntries");return(await db.select({id:col5(table,"id")}).from(table).where(and8(eq12(col5(table,"account"),q.account),eq12(col5(table,"direction"),q.direction),eq12(col5(table,"referenceType"),q.referenceType),eq12(col5(table,"referenceId"),q.referenceId))).limit(1)).length>0}async getSettlementPolicy(ownerType,ownerId){let db=this.getDb();if(!db)return null;let table=this.table("settlementPolicies"),rows=await db.select().from(table).where(and8(eq12(col5(table,"ownerType"),ownerType),eq12(col5(table,"ownerId"),ownerId))).limit(1);return rows[0]?rowToSettlementPolicy(rows[0]):null}async upsertSettlementPolicy(input){let db=this.getDb();if(!db)return null;let table=this.table("settlementPolicies"),existing=await this.getSettlementPolicy(input.ownerType,input.ownerId),values={ownerType:input.ownerType,ownerId:input.ownerId,defaultDelayDays:input.defaultDelayDays??this.config.defaultSettlementDelayDays,newSellerDelayDays:input.newSellerDelayDays??this.config.newSellerDelayDays,reserveRate:String(input.reserveRate??this.config.reserveRate),reserveDays:input.reserveDays??this.config.reserveReleaseDays,minimumPayoutAmount:input.minimumPayoutAmount??this.config.minimumPayoutAmount,currency:input.currency??null,earlyPayoutEnabled:input.earlyPayoutEnabled??!1,updatedAt:new Date};if(existing)await db.update(table).set(values).where(eq12(col5(table,"id"),existing.id));else await db.insert(table).values(values);return this.getSettlementPolicy(input.ownerType,input.ownerId)}async recordSplit(input){let db=this.getDb();if(!db)return null;let recipientType=input.recipientOwnerType??"seller",currency=input.currency??this.config.defaultCurrency,now=new Date,policy=await this.getSettlementPolicy(recipientType,input.recipientOwnerId),breakdown=computeSplitBreakdown(input,this.config,policy,now),splitTable=this.table("splits"),rows=await db.insert(splitTable).values({transactionId:input.transactionId??null,recipientOwnerType:recipientType,recipientOwnerId:input.recipientOwnerId,provider:input.provider,grossAmount:breakdown.grossAmount,providerFeeAmount:breakdown.providerFeeAmount,platformFeeAmount:breakdown.platformFeeAmount,reserveAmount:breakdown.reserveAmount,netPayableAmount:breakdown.netPayableAmount,currency,availableForPayoutAt:breakdown.availableForPayoutAt,status:"pending",sourceType:input.sourceType??null,sourceId:input.sourceId??null,metadata:JSON.stringify(input.metadata??{})}).returning(),split=rowToSplit(rows[0]);if(await this.recordLedgerEntry({account:"seller_payable",direction:"credit",amount:breakdown.netPayableAmount,currency,entryType:"split",ownerType:recipientType,ownerId:input.recipientOwnerId,referenceType:"split",referenceId:split.id,splitId:split.id}),breakdown.platformFeeAmount>0)await this.recordLedgerEntry({account:"platform_revenue",direction:"credit",amount:breakdown.platformFeeAmount,currency,entryType:"split",referenceType:"split",referenceId:split.id,splitId:split.id});if(breakdown.providerFeeAmount>0)await this.recordLedgerEntry({account:"provider_fee",direction:"credit",amount:breakdown.providerFeeAmount,currency,entryType:"split",referenceType:"split",referenceId:split.id,splitId:split.id});if(breakdown.reserveAmount>0){let reserveDays=policy?.reserveDays??this.config.reserveReleaseDays;await db.insert(this.table("reserves")).values({ownerType:recipientType,ownerId:input.recipientOwnerId,splitId:split.id,amount:breakdown.reserveAmount,currency,reason:input.isNewSeller?"new_seller":"refund_window",status:"held",releaseAt:reserveDays>0?addDays(now,reserveDays):null,metadata:JSON.stringify({})}),await this.recordLedgerEntry({account:"reserve",direction:"credit",amount:breakdown.reserveAmount,currency,entryType:"reserve_hold",ownerType:recipientType,ownerId:input.recipientOwnerId,referenceType:"split",referenceId:split.id,splitId:split.id})}return this.logger.info("Recorded split",{splitId:split.id,gross:breakdown.grossAmount,netPayable:breakdown.netPayableAmount,reserve:breakdown.reserveAmount}),split}async listSplits(filter,executor){let db=executor??this.getDb();if(!db)return[];let table=this.table("splits"),where=filter.recipientOwnerId?eq12(col5(table,"recipientOwnerId"),filter.recipientOwnerId):void 0,mapped=(where?await db.select().from(table).where(where):await db.select().from(table)).map((r)=>rowToSplit(r));return filter.status?mapped.filter((s)=>s.status===filter.status):mapped}async releaseReserve(id){let db=this.getDb();if(!db)return!1;let table=this.table("reserves"),row=(await db.update(table).set({status:"released",releasedAt:new Date,updatedAt:new Date}).where(and8(eq12(col5(table,"id"),id),eq12(col5(table,"status"),"held"))).returning())[0];if(!row)return!1;let reserveOwnerType=String(row.ownerType??row.owner_type??""),reserveOwnerId=String(row.ownerId??row.owner_id??"");return await this.recordLedgerEntry({account:"reserve",direction:"debit",amount:Number(row.amount),currency:String(row.currency),entryType:"reserve_release",ownerType:reserveOwnerType,ownerId:reserveOwnerId,referenceType:"reserve",referenceId:id}),await this.recordLedgerEntry({account:"seller_payable",direction:"credit",amount:Number(row.amount),currency:String(row.currency),entryType:"reserve_release",ownerType:reserveOwnerType,ownerId:reserveOwnerId,referenceType:"reserve",referenceId:id}),!0}async autoReleaseDueReserves(ownerType,ownerId){if(!this.config.autoReleaseReserves)return;let db=this.getDb();if(!db)return;let table=this.table("reserves"),rows=await db.select().from(table).where(and8(eq12(col5(table,"ownerType"),ownerType),eq12(col5(table,"ownerId"),ownerId))),now=Date.now();for(let r of rows){let releaseAt=r.releaseAt??r.release_at;if(String(r.status)==="held"&&releaseAt&&new Date(String(releaseAt)).getTime()<=now)await this.releaseReserve(String(r.id))}}async getBalance(ownerType,ownerId,currency,executor){let cur=currency??this.config.defaultCurrency,db=executor??this.getDb(),base={ownerType,ownerId,currency:cur,available:0,pending:0,reserved:0,payoutInFlight:0};if(!db)return base;if(!executor)await this.autoReleaseDueReserves(ownerType,ownerId);let splits=await this.listSplits({recipientOwnerId:ownerId},executor),now=Date.now();for(let s of splits){if(s.currency!==cur)continue;if(s.status!=="pending"&&s.status!=="available")continue;if(!(s.availableForPayoutAt?new Date(s.availableForPayoutAt).getTime()<=now:!0))base.pending+=s.netPayableAmount}let ledgerTable=this.table("ledgerEntries"),ledgerRows=await db.select().from(ledgerTable).where(and8(eq12(col5(ledgerTable,"ownerType"),ownerType),eq12(col5(ledgerTable,"ownerId"),ownerId))),payableNet=0;for(let e of ledgerRows){if(String(e.account)!=="seller_payable"||String(e.currency)!==cur)continue;let amt=Number(e.amount);payableNet+=String(e.direction)==="credit"?amt:-amt}base.available=payableNet-base.pending;let reserveTable=this.table("reserves"),reserveRows=await db.select().from(reserveTable).where(and8(eq12(col5(reserveTable,"ownerType"),ownerType),eq12(col5(reserveTable,"ownerId"),ownerId)));for(let r of reserveRows)if(String(r.status)==="held"&&String(r.currency)===cur)base.reserved+=Number(r.amount);let payoutTable=this.table("payoutRequests"),payoutRows=await db.select().from(payoutTable).where(and8(eq12(col5(payoutTable,"recipientOwnerType"),ownerType),eq12(col5(payoutTable,"recipientOwnerId"),ownerId)));for(let p of payoutRows){let st=String(p.status);if((st==="pending"||st==="approved"||st==="processing")&&String(p.currency)===cur)base.payoutInFlight+=Number(p.amount),base.available-=Number(p.amount)}return base}}var col5=(table,name)=>table[name];var init_MarketplaceService=()=>{};var pick=(schemaTables,camel,snake)=>schemaTables[camel]??schemaTables[snake]??{},createMarketplaceServices=(params)=>{let config=resolveMarketplaceConfig(params.options.payment);if(!config.enabled)return null;let tables={splits:pick(params.schemaTables,"paymentSplits","payment_splits"),ledgerEntries:pick(params.schemaTables,"paymentLedgerEntries","payment_ledger_entries"),settlementPolicies:pick(params.schemaTables,"paymentSettlementPolicies","payment_settlement_policies"),reserves:pick(params.schemaTables,"paymentReserves","payment_reserves"),payoutRequests:pick(params.schemaTables,"paymentPayoutRequests","payment_payout_requests"),disputes:pick(params.schemaTables,"paymentDisputes","payment_disputes")},serviceConfig={getDb:params.getDb,provider:params.provider,config,logger:params.logger,tables},marketplaceService=new MarketplaceService(serviceConfig),payoutService=new MarketplacePayoutService(serviceConfig,marketplaceService);return{marketplaceService,payoutService}};var init_Marketplace=__esm(()=>{init_MarketplacePayoutService();init_MarketplaceService();init_MarketplacePayoutService();init_MarketplaceService()});var loadStripe=(config)=>{try{let StripeModule=__require("stripe");return new(StripeModule.default??StripeModule)(config.secretKey,{maxNetworkRetries:config.maxNetworkRetries??2,timeout:config.timeout??30000,...config.apiVersion?{apiVersion:config.apiVersion}:{}})}catch(error){return logger.scoped("payment.init").error("Failed to load stripe package. Install it: bun add stripe",error),null}},createStripeProvider=(config)=>{let stripe=loadStripe(config);if(!stripe)return null;let providerName="stripe";return{get name(){return providerName},async initialize3DS(request){try{let params={amount:Math.round(Number(request.paidPrice)*100),currency:request.currency.toLowerCase(),payment_method_types:["card"],description:`Order ${request.conversationId}`,metadata:{conversationId:request.conversationId,basketId:request.basketId??"",buyerEmail:request.buyer.email,buyerId:request.buyer.id}};if("cardToken"in request.paymentCard&&request.paymentCard.cardToken)params.payment_method=request.paymentCard.cardToken,params.confirm=!0,params.return_url=request.callbackUrl;let paymentIntent=await stripe.paymentIntents.create(params),clientSecret=paymentIntent.client_secret,piId=paymentIntent.id,status=paymentIntent.status;return{success:!0,paymentId:piId,threeDSHtmlContent:clientSecret,signature:status,rawResponse:paymentIntent}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Payment initialization failed",rawResponse:{error:err.message}}}},async complete3DS(request){try{let paymentIntent=await stripe.paymentIntents.retrieve(request.paymentId,{expand:["payment_method"]}),intentConversationId=String(paymentIntent.metadata?.conversationId??"");if(request.conversationId&&intentConversationId!==request.conversationId)return{success:!1,paymentId:paymentIntent.id,price:0,paidPrice:0,errorCode:"CONVERSATION_MISMATCH",errorMessage:"Payment does not belong to this transaction",rawResponse:paymentIntent};let status=paymentIntent.status,isSuccess=status==="succeeded",amountReceived=paymentIntent.amount_received??0,card=paymentIntent.payment_method?.card;return{success:isSuccess,paymentId:paymentIntent.id,price:amountReceived/100,paidPrice:amountReceived/100,currency:paymentIntent.currency?.toUpperCase(),cardType:card?.brand??void 0,cardAssociation:card?.brand??void 0,cardFamily:card?.funding??void 0,lastFourDigits:card?.last4??void 0,authCode:status,errorCode:isSuccess?void 0:"PAYMENT_NOT_SUCCEEDED",errorMessage:isSuccess?void 0:`Payment status: ${status}`,rawResponse:paymentIntent}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Payment completion failed",rawResponse:{error:err.message}}}},async getPaymentDetail(request){try{let paymentIntent=await stripe.paymentIntents.retrieve(request.paymentId,{expand:["payment_method"]}),amountReceived=paymentIntent.amount_received??paymentIntent.amount??0,card=paymentIntent.payment_method?.card;return{success:!0,paymentId:paymentIntent.id,price:amountReceived/100,paidPrice:amountReceived/100,currency:paymentIntent.currency?.toUpperCase(),cardType:card?.brand??void 0,cardAssociation:card?.brand??void 0,lastFourDigits:card?.last4??void 0,binNumber:void 0,rawResponse:paymentIntent}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Payment detail query failed",rawResponse:{error:err.message}}}},async queryBin(_request){return{success:!1,errorCode:"NOT_SUPPORTED",errorMessage:"BIN query is not supported by Stripe. Card info is available after payment method creation.",rawResponse:{}}},async saveCard(request){try{let customer=await stripe.customers.create({email:request.email,metadata:{externalId:request.externalId??""}}),paymentMethod=await stripe.paymentMethods.create({type:"card",card:{number:request.card.cardNumber,exp_month:Number(request.card.expireMonth),exp_year:Number(request.card.expireYear)}});await stripe.paymentMethods.attach(paymentMethod.id,{customer:customer.id});let card=paymentMethod.card;return{success:!0,cardUserKey:customer.id,cardToken:paymentMethod.id,cardAlias:request.card.cardAlias,binNumber:void 0,lastFourDigits:card?.last4??void 0,cardType:card?.brand??void 0,cardAssociation:card?.brand??void 0,cardFamily:card?.funding??void 0,cardBankName:void 0,rawResponse:{customer,paymentMethod}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to save card",rawResponse:{error:err.message}}}},async listCards(request){try{let response=await stripe.paymentMethods.list({customer:request.cardUserKey,type:"card"}),cards=response.data.map((pm)=>{let card=pm.card;return{cardToken:pm.id,cardAlias:pm.metadata?.alias??"",binNumber:"",lastFourDigits:card?.last4??"",cardType:card?.brand??"",cardAssociation:card?.brand??"",cardFamily:card?.funding??"",cardBankName:"",cardBankCode:0}});return{success:!0,cardUserKey:request.cardUserKey,cards,rawResponse:response}}catch(error){let err=error;return{success:!1,cards:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list cards",rawResponse:{error:err.message}}}},async deleteCard(request){try{return await stripe.paymentMethods.detach(request.cardToken),{success:!0,rawResponse:{detached:request.cardToken}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to delete card",rawResponse:{error:err.message}}}},async refund(request){try{let amountInCents=Math.round(Number(request.price)*100),refund=await stripe.refunds.create({payment_intent:request.paymentTransactionId,amount:amountInCents});return{success:refund.status==="succeeded",paymentId:refund.payment_intent,price:amountInCents/100,currency:refund.currency?.toUpperCase(),errorCode:refund.status!=="succeeded"?"REFUND_FAILED":void 0,errorMessage:refund.status!=="succeeded"?`Refund status: ${refund.status}`:void 0,rawResponse:refund}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Refund failed",rawResponse:{error:err.message}}}},parseCallback(formData){let paymentIntent=formData.payment_intent??formData.paymentId??"",redirectStatus=formData.redirect_status??"";return{success:redirectStatus==="succeeded",paymentId:paymentIntent,conversationId:formData.conversationId??"",mdStatus:redirectStatus,rawData:formData}},async registerSubMerchant(request){try{let account=await stripe.accounts.create({type:"express",country:request.currency==="TRY"?"TR":void 0,email:request.email,business_type:request.subMerchantType==="personal"?"individual":"company",metadata:{externalId:request.subMerchantExternalId,name:request.name,contactName:request.contactName??"",contactSurname:request.contactSurname??""},capabilities:{transfers:{requested:!0}}});return{success:!0,subMerchantKey:account.id,rawResponse:account}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create connected account",rawResponse:{error:err.message}}}},async updateSubMerchant(request){try{let params={metadata:{}},meta=params.metadata;if(request.name)meta.name=request.name;if(request.email)params.email=request.email;if(request.contactName)meta.contactName=request.contactName;if(request.contactSurname)meta.contactSurname=request.contactSurname;return await stripe.accounts.update(request.subMerchantKey,params),{success:!0,rawResponse:{updated:request.subMerchantKey}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to update connected account",rawResponse:{error:err.message}}}},async getSubMerchant(request){try{let account=await stripe.accounts.retrieve(request.subMerchantExternalId),meta=account.metadata??{};return{success:!0,subMerchantKey:account.id,name:meta.name,email:account.email,subMerchantType:account.business_type==="individual"?"personal":"company",currency:account.default_currency?.toUpperCase(),rawResponse:account}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to retrieve connected account",rawResponse:{error:err.message}}}},async approveSplit(request){return{success:!0,paymentTransactionId:request.paymentTransactionId,rawResponse:{message:"Stripe transfers are automatic; no manual approval needed."}}},async disapproveSplit(request){try{let reversal=await stripe.transfers.createReversal(request.paymentTransactionId);return{success:!0,paymentTransactionId:reversal.id,rawResponse:reversal}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to reverse transfer",rawResponse:{error:err.message}}}},async createProduct(request){try{let params={name:request.name};if(request.description)params.description=request.description;if(request.type)params.type=request.type;if(request.images)params.images=request.images;if(request.unitLabel)params.unit_label=request.unitLabel;if(request.url)params.url=request.url;if(request.metadata)params.metadata=request.metadata;let product=await stripe.products.create(params);return{success:!0,providerProductId:product.id,name:product.name,rawResponse:product}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create product",rawResponse:{error:err.message}}}},async updateProduct(request){try{let params={};if(request.name)params.name=request.name;if(request.description!==void 0)params.description=request.description;if(request.images)params.images=request.images;if(request.unitLabel!==void 0)params.unit_label=request.unitLabel;if(request.url!==void 0)params.url=request.url;if(request.active!==void 0)params.active=request.active;if(request.metadata)params.metadata=request.metadata;let product=await stripe.products.update(request.providerProductId,params);return{success:!0,providerProductId:product.id,rawResponse:product}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to update product",rawResponse:{error:err.message}}}},async getProduct(request){try{let product=await stripe.products.retrieve(request.providerProductId);return{success:!0,providerProductId:product.id,name:product.name,description:product.description,type:product.type,active:product.active,images:product.images,unitLabel:product.unit_label,url:product.url,metadata:product.metadata,rawResponse:product}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get product",rawResponse:{error:err.message}}}},async listProducts(request){try{let params={};if(request.active!==void 0)params.active=request.active;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.products.list(params);return{success:!0,products:response.data.map((p)=>({providerProductId:p.id,name:p.name,description:p.description,type:p.type,active:p.active,images:p.images,unitLabel:p.unit_label,metadata:p.metadata})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,products:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list products",rawResponse:{error:err.message}}}},async archiveProduct(request){try{return await stripe.products.update(request.providerProductId,{active:!1}),{success:!0,rawResponse:{archived:request.providerProductId}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to archive product",rawResponse:{error:err.message}}}},async createPrice(request){try{let params={product:request.providerProductId,currency:request.currency.toLowerCase(),unit_amount:request.unitAmount};if(request.nickname)params.nickname=request.nickname;if(request.billingScheme)params.billing_scheme=request.billingScheme;if(request.unitAmountDecimal)params.unit_amount_decimal=request.unitAmountDecimal;if(request.metadata)params.metadata=request.metadata;if(request.recurring)params.recurring={interval:request.recurring.interval,...request.recurring.intervalCount?{interval_count:request.recurring.intervalCount}:{},...request.recurring.usageType?{usage_type:request.recurring.usageType}:{},...request.recurring.aggregateUsage?{aggregate_usage:request.recurring.aggregateUsage}:{}};if(request.transformQuantity)params.transform_quantity={divide_by:request.transformQuantity.divideBy,round:request.transformQuantity.round};let price=await stripe.prices.create(params);return{success:!0,providerPriceId:price.id,rawResponse:price}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create price",rawResponse:{error:err.message}}}},async updatePrice(request){try{let params={};if(request.nickname!==void 0)params.nickname=request.nickname;if(request.active!==void 0)params.active=request.active;if(request.metadata)params.metadata=request.metadata;let price=await stripe.prices.update(request.providerPriceId,params);return{success:!0,providerPriceId:price.id,rawResponse:price}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to update price",rawResponse:{error:err.message}}}},async getPrice(request){try{let price=await stripe.prices.retrieve(request.providerPriceId),rec=price.recurring;return{success:!0,providerPriceId:price.id,providerProductId:price.product,currency:price.currency?.toUpperCase(),unitAmount:price.unit_amount,type:price.type,billingScheme:price.billing_scheme,nickname:price.nickname,active:price.active,recurring:rec?{interval:rec.interval,intervalCount:rec.interval_count??1,usageType:rec.usage_type}:void 0,metadata:price.metadata,rawResponse:price}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get price",rawResponse:{error:err.message}}}},async listPrices(request){try{let params={};if(request.providerProductId)params.product=request.providerProductId;if(request.active!==void 0)params.active=request.active;if(request.type)params.type=request.type;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.prices.list(params);return{success:!0,prices:response.data.map((p)=>{let rec=p.recurring;return{providerPriceId:p.id,providerProductId:p.product,currency:p.currency?.toUpperCase(),unitAmount:p.unit_amount,type:p.type,billingScheme:p.billing_scheme,nickname:p.nickname,active:p.active,recurring:rec?{interval:rec.interval,intervalCount:rec.interval_count??1,usageType:rec.usage_type}:void 0,metadata:p.metadata}}),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,prices:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list prices",rawResponse:{error:err.message}}}},async archivePrice(request){try{return await stripe.prices.update(request.providerPriceId,{active:!1}),{success:!0,rawResponse:{archived:request.providerPriceId}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to archive price",rawResponse:{error:err.message}}}},async createCustomer(request){try{let params={email:request.email};if(request.name)params.name=request.name;if(request.phone)params.phone=request.phone;if(request.description)params.description=request.description;if(request.metadata)params.metadata=request.metadata;if(request.address)params.address={line1:request.address.line1,line2:request.address.line2,city:request.address.city,state:request.address.state,postal_code:request.address.postalCode,country:request.address.country};let customer=await stripe.customers.create(params);if(request.taxId)try{let createTaxId=stripe.taxIds?.create;if(createTaxId)await createTaxId({customer:customer.id,type:request.taxId.type,value:request.taxId.value})}catch(_){}return{success:!0,providerCustomerId:customer.id,email:customer.email,rawResponse:customer}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create customer",rawResponse:{error:err.message}}}},async updateCustomer(request){try{let params={};if(request.email)params.email=request.email;if(request.name!==void 0)params.name=request.name;if(request.phone!==void 0)params.phone=request.phone;if(request.description!==void 0)params.description=request.description;if(request.metadata)params.metadata=request.metadata;if(request.address)params.address={line1:request.address.line1,line2:request.address.line2,city:request.address.city,state:request.address.state,postal_code:request.address.postalCode,country:request.address.country};let customer=await stripe.customers.update(request.providerCustomerId,params);return{success:!0,providerCustomerId:customer.id,rawResponse:customer}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to update customer",rawResponse:{error:err.message}}}},async getCustomer(request){try{let customer=await stripe.customers.retrieve(request.providerCustomerId);return{success:!0,providerCustomerId:customer.id,email:customer.email,name:customer.name,phone:customer.phone,description:customer.description,metadata:customer.metadata,rawResponse:customer}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get customer",rawResponse:{error:err.message}}}},async listCustomers(request){try{let params={};if(request.email)params.email=request.email;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.customers.list(params);return{success:!0,customers:response.data.map((c)=>({providerCustomerId:c.id,email:c.email,name:c.name,phone:c.phone})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,customers:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list customers",rawResponse:{error:err.message}}}},async deleteCustomer(request){try{return await stripe.customers.del(request.providerCustomerId),{success:!0,rawResponse:{deleted:request.providerCustomerId}}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to delete customer",rawResponse:{error:err.message}}}},async createSubscription(request){try{let params={customer:request.providerCustomerId,items:request.items.map((i)=>({price:i.providerPriceId,...i.quantity!==void 0?{quantity:i.quantity}:{}}))};if(request.trialPeriodDays)params.trial_period_days=request.trialPeriodDays;if(request.collectionMethod)params.collection_method=request.collectionMethod;if(request.daysUntilDue)params.days_until_due=request.daysUntilDue;if(request.cancelAtPeriodEnd!==void 0)params.cancel_at_period_end=request.cancelAtPeriodEnd;if(request.metadata)params.metadata=request.metadata;if(request.defaultPaymentMethod)params.default_payment_method=request.defaultPaymentMethod;params.payment_behavior="default_incomplete",params.payment_settings={save_default_payment_method:"on_subscription"},params.expand=["latest_invoice.payment_intent"];let sub=await stripe.subscriptions.create(params),pi=sub.latest_invoice?.payment_intent;return{success:!0,providerSubscriptionId:sub.id,status:sub.status,currentPeriodStart:sub.current_period_start?new Date(sub.current_period_start*1000).toISOString():void 0,currentPeriodEnd:sub.current_period_end?new Date(sub.current_period_end*1000).toISOString():void 0,clientSecret:pi?.client_secret,rawResponse:sub}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create subscription",rawResponse:{error:err.message}}}},async updateSubscription(request){try{let params={};if(request.items)params.items=request.items.map((i)=>({...i.id?{id:i.id}:{},price:i.providerPriceId,...i.quantity!==void 0?{quantity:i.quantity}:{}}));if(request.cancelAtPeriodEnd!==void 0)params.cancel_at_period_end=request.cancelAtPeriodEnd;if(request.trialEnd!==void 0)params.trial_end=request.trialEnd;if(request.metadata)params.metadata=request.metadata;if(request.collectionMethod)params.collection_method=request.collectionMethod;if(request.daysUntilDue)params.days_until_due=request.daysUntilDue;if(request.defaultPaymentMethod)params.default_payment_method=request.defaultPaymentMethod;if(request.prorationBehavior)params.proration_behavior=request.prorationBehavior;let sub=await stripe.subscriptions.update(request.providerSubscriptionId,params);return{success:!0,providerSubscriptionId:sub.id,status:sub.status,rawResponse:sub}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to update subscription",rawResponse:{error:err.message}}}},async cancelSubscription(request){try{if(request.cancelAtPeriodEnd){let sub2=await stripe.subscriptions.update(request.providerSubscriptionId,{cancel_at_period_end:!0});return{success:!0,providerSubscriptionId:sub2.id,status:sub2.status,rawResponse:sub2}}let params={};if(request.invoiceNow)params.invoice_now=request.invoiceNow;if(request.prorate)params.prorate=request.prorate;let sub=await stripe.subscriptions.cancel(request.providerSubscriptionId,params);return{success:!0,providerSubscriptionId:sub.id,status:sub.status,rawResponse:sub}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to cancel subscription",rawResponse:{error:err.message}}}},async getSubscription(request){try{let sub=await stripe.subscriptions.retrieve(request.providerSubscriptionId),itemsData=sub.items?.data??[];return{success:!0,providerSubscriptionId:sub.id,providerCustomerId:sub.customer,status:sub.status,currentPeriodStart:sub.current_period_start?new Date(sub.current_period_start*1000).toISOString():void 0,currentPeriodEnd:sub.current_period_end?new Date(sub.current_period_end*1000).toISOString():void 0,cancelAtPeriodEnd:sub.cancel_at_period_end,canceledAt:sub.canceled_at?new Date(sub.canceled_at*1000).toISOString():void 0,trialStart:sub.trial_start?new Date(sub.trial_start*1000).toISOString():void 0,trialEnd:sub.trial_end?new Date(sub.trial_end*1000).toISOString():void 0,items:itemsData.map((i)=>({id:i.id,providerPriceId:i.price?.id??i.price,quantity:i.quantity??1})),metadata:sub.metadata,rawResponse:sub}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get subscription",rawResponse:{error:err.message}}}},async listSubscriptions(request){try{let params={};if(request.providerCustomerId)params.customer=request.providerCustomerId;if(request.status)params.status=request.status;if(request.providerPriceId)params.price=request.providerPriceId;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.subscriptions.list(params);return{success:!0,subscriptions:response.data.map((s)=>({providerSubscriptionId:s.id,providerCustomerId:s.customer,status:s.status,currentPeriodStart:s.current_period_start?new Date(s.current_period_start*1000).toISOString():void 0,currentPeriodEnd:s.current_period_end?new Date(s.current_period_end*1000).toISOString():void 0,cancelAtPeriodEnd:s.cancel_at_period_end})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,subscriptions:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list subscriptions",rawResponse:{error:err.message}}}},async reportUsage(request){try{let params={quantity:request.quantity,action:request.action??"increment"};if(request.timestamp)params.timestamp=request.timestamp;let record=await stripe.subscriptionItems.usageRecords.create(request.subscriptionItemId,params);return{success:!0,usageRecordId:record.id,quantity:record.quantity,timestamp:record.timestamp,rawResponse:record}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to report usage",rawResponse:{error:err.message}}}},async listUsageRecordSummaries(request){try{let params={};if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.subscriptionItems.usageRecordSummaries.list(request.subscriptionItemId,params);return{success:!0,summaries:response.data.map((s)=>({id:s.id,totalUsage:s.total_usage,period:s.period,subscriptionItem:s.subscription_item})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,summaries:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list usage summaries",rawResponse:{error:err.message}}}},async createInvoice(request){try{let params={customer:request.providerCustomerId};if(request.collectionMethod)params.collection_method=request.collectionMethod;if(request.daysUntilDue)params.days_until_due=request.daysUntilDue;if(request.description)params.description=request.description;if(request.metadata)params.metadata=request.metadata;if(request.autoAdvance!==void 0)params.auto_advance=request.autoAdvance;let invoice=await stripe.invoices.create(params);if(request.items?.length)for(let item of request.items){let lineParams={customer:request.providerCustomerId,invoice:invoice.id};if(item.providerPriceId)lineParams.price=item.providerPriceId;if(item.quantity)lineParams.quantity=item.quantity;if(item.unitAmount!==void 0)lineParams.unit_amount=item.unitAmount;if(item.currency)lineParams.currency=item.currency;if(item.description)lineParams.description=item.description;await stripe.invoiceItems.create(lineParams)}return{success:!0,providerInvoiceId:invoice.id,status:invoice.status,hostedInvoiceUrl:invoice.hosted_invoice_url,invoicePdf:invoice.invoice_pdf,rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create invoice",rawResponse:{error:err.message}}}},async finalizeInvoice(request){try{let params={};if(request.autoAdvance!==void 0)params.auto_advance=request.autoAdvance;let invoice=await stripe.invoices.finalizeInvoice(request.providerInvoiceId,params);return{success:!0,providerInvoiceId:invoice.id,status:invoice.status,hostedInvoiceUrl:invoice.hosted_invoice_url,invoicePdf:invoice.invoice_pdf,rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to finalize invoice",rawResponse:{error:err.message}}}},async sendInvoice(request){try{let invoice=await stripe.invoices.sendInvoice(request.providerInvoiceId);return{success:!0,providerInvoiceId:invoice.id,rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to send invoice",rawResponse:{error:err.message}}}},async voidInvoice(request){try{let invoice=await stripe.invoices.voidInvoice(request.providerInvoiceId);return{success:!0,providerInvoiceId:invoice.id,rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to void invoice",rawResponse:{error:err.message}}}},async payInvoice(request){try{let params={};if(request.paymentMethod)params.payment_method=request.paymentMethod;let invoice=await stripe.invoices.pay(request.providerInvoiceId,params);return{success:!0,providerInvoiceId:invoice.id,status:invoice.status,amountPaid:invoice.amount_paid,amountDue:invoice.amount_due,rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to pay invoice",rawResponse:{error:err.message}}}},async getInvoice(request){try{let invoice=await stripe.invoices.retrieve(request.providerInvoiceId),linesData=invoice.lines?.data??[];return{success:!0,providerInvoiceId:invoice.id,providerCustomerId:invoice.customer,providerSubscriptionId:invoice.subscription,status:invoice.status,amountDue:invoice.amount_due,amountPaid:invoice.amount_paid,amountRemaining:invoice.amount_remaining,currency:invoice.currency?.toUpperCase(),description:invoice.description,hostedInvoiceUrl:invoice.hosted_invoice_url,invoicePdf:invoice.invoice_pdf,dueDate:invoice.due_date?new Date(invoice.due_date*1000).toISOString():void 0,periodStart:invoice.period_start?new Date(invoice.period_start*1000).toISOString():void 0,periodEnd:invoice.period_end?new Date(invoice.period_end*1000).toISOString():void 0,metadata:invoice.metadata,lines:linesData.map((l)=>({id:l.id,description:l.description,amount:l.amount,currency:l.currency?.toUpperCase(),quantity:l.quantity,providerPriceId:l.price?.id})),rawResponse:invoice}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get invoice",rawResponse:{error:err.message}}}},async listInvoices(request){try{let params={};if(request.providerCustomerId)params.customer=request.providerCustomerId;if(request.providerSubscriptionId)params.subscription=request.providerSubscriptionId;if(request.status)params.status=request.status;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.invoices.list(params);return{success:!0,invoices:response.data.map((inv)=>({providerInvoiceId:inv.id,providerCustomerId:inv.customer,status:inv.status,amountDue:inv.amount_due,amountPaid:inv.amount_paid,currency:inv.currency?.toUpperCase(),dueDate:inv.due_date?new Date(inv.due_date*1000).toISOString():void 0,hostedInvoiceUrl:inv.hosted_invoice_url})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,invoices:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list invoices",rawResponse:{error:err.message}}}},async getBalance(){try{let bal=await stripe.balance.retrieve(),mapAmounts=(arr)=>(arr??[]).map((a)=>({amount:a.amount,currency:a.currency?.toUpperCase()}));return{success:!0,available:mapAmounts(bal.available),pending:mapAmounts(bal.pending),connectReserved:mapAmounts(bal.connect_reserved),rawResponse:bal}}catch(error){let err=error;return{success:!1,available:[],pending:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get balance",rawResponse:{error:err.message}}}},async createPayout(request){try{let params={amount:request.amount,currency:request.currency.toLowerCase()};if(request.destination)params.destination=request.destination;if(request.description)params.description=request.description;if(request.metadata)params.metadata=request.metadata;if(request.method)params.method=request.method;if(request.sourceType)params.source_type=request.sourceType;if(request.statementDescriptor)params.statement_descriptor=request.statementDescriptor;let payout=await stripe.payouts.create(params,request.idempotencyKey?{idempotencyKey:request.idempotencyKey}:void 0);return{success:!0,providerPayoutId:payout.id,amount:payout.amount,currency:payout.currency?.toUpperCase(),status:payout.status,arrivalDate:payout.arrival_date?new Date(payout.arrival_date*1000).toISOString():void 0,method:payout.method,rawResponse:payout}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create payout",rawResponse:{error:err.message}}}},async getPayout(request){try{let payout=await stripe.payouts.retrieve(request.providerPayoutId);return{success:!0,providerPayoutId:payout.id,amount:payout.amount,currency:payout.currency?.toUpperCase(),status:payout.status,arrivalDate:payout.arrival_date?new Date(payout.arrival_date*1000).toISOString():void 0,method:payout.method,description:payout.description,destination:payout.destination,metadata:payout.metadata,failureCode:payout.failure_code,failureMessage:payout.failure_message,rawResponse:payout}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to get payout",rawResponse:{error:err.message}}}},async cancelPayout(request){try{let payout=await stripe.payouts.cancel(request.providerPayoutId);return{success:!0,providerPayoutId:payout.id,status:payout.status,rawResponse:payout}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to cancel payout",rawResponse:{error:err.message}}}},async listPayouts(request){try{let params={};if(request.status)params.status=request.status;if(request.destination)params.destination=request.destination;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;if(request.arrivalDateGte||request.arrivalDateLte){let arrival={};if(request.arrivalDateGte)arrival.gte=request.arrivalDateGte;if(request.arrivalDateLte)arrival.lte=request.arrivalDateLte;params.arrival_date=arrival}let response=await stripe.payouts.list(params);return{success:!0,payouts:response.data.map((p)=>({providerPayoutId:p.id,amount:p.amount,currency:p.currency?.toUpperCase(),status:p.status,arrivalDate:p.arrival_date?new Date(p.arrival_date*1000).toISOString():void 0,method:p.method,description:p.description})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,payouts:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list payouts",rawResponse:{error:err.message}}}},async createTransfer(request){try{let params={amount:request.amount,currency:request.currency.toLowerCase(),destination:request.destination};if(request.description)params.description=request.description;if(request.metadata)params.metadata=request.metadata;if(request.sourceTransaction)params.source_transaction=request.sourceTransaction;if(request.transferGroup)params.transfer_group=request.transferGroup;let transfer=await stripe.transfers.create(params,request.idempotencyKey?{idempotencyKey:request.idempotencyKey}:void 0);return{success:!0,providerTransferId:transfer.id,amount:transfer.amount,currency:transfer.currency?.toUpperCase(),destination:transfer.destination,rawResponse:transfer}}catch(error){let err=error;return{success:!1,errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to create transfer",rawResponse:{error:err.message}}}},async listTransfers(request){try{let params={};if(request.destination)params.destination=request.destination;if(request.transferGroup)params.transfer_group=request.transferGroup;if(request.limit)params.limit=request.limit;if(request.startingAfter)params.starting_after=request.startingAfter;let response=await stripe.transfers.list(params);return{success:!0,transfers:response.data.map((t)=>({providerTransferId:t.id,amount:t.amount,currency:t.currency?.toUpperCase(),destination:t.destination,description:t.description,created:t.created?new Date(t.created*1000).toISOString():void 0})),hasMore:response.has_more,rawResponse:response}}catch(error){let err=error;return{success:!1,transfers:[],errorCode:err.code??"STRIPE_ERROR",errorMessage:err.message??"Failed to list transfers",rawResponse:{error:err.message}}}}}},verifyStripeWebhook=async(rawBody,signature,webhookSecret)=>{try{let StripeModule=__require("stripe"),Stripe=StripeModule.default??StripeModule,stripe=new Stripe("sk_dummy_for_webhook_verification"),factory=Stripe,cryptoProvider=typeof factory.createSubtleCryptoProvider==="function"?factory.createSubtleCryptoProvider():typeof factory.createNodeCryptoProvider==="function"?factory.createNodeCryptoProvider():void 0;return{success:!0,event:await stripe.webhooks.constructEventAsync(rawBody,signature,webhookSecret,void 0,cryptoProvider)}}catch(error){return{success:!1,event:null,error:error instanceof Error?error.message:"Webhook verification failed"}}};var init_StripeProvider=__esm(()=>{init_Logger2()});var resolveEnvValue3=(value)=>{let envValue=process.env[value];if(envValue)return envValue;return value},createPaymentService=(config)=>{let paymentConfig=config.payment;if(!paymentConfig?.enabled)return null;let log=logger.scoped("payment.init"),providerName=paymentConfig.provider;if(!providerName)return log.warn("payment.enabled=true but no provider specified"),null;let providerConfig=null,provider=null,webhookSecret;switch(providerName){case"iyzico":{let iyzicoConf=paymentConfig.iyzico;if(!iyzicoConf)return log.warn("payment.provider=iyzico but no iyzico config block"),null;let apiUrl=resolveEnvValue3(iyzicoConf.apiUrl),apiKey=resolveEnvValue3(iyzicoConf.apiKey),secretKey=resolveEnvValue3(iyzicoConf.secretKey);if(!apiUrl||!apiKey||!secretKey)return log.warn("iyzico credentials missing \u2014 ensure env vars are set",{apiUrl:iyzicoConf.apiUrl,apiKeyEnv:iyzicoConf.apiKey,secretKeyEnv:iyzicoConf.secretKey}),null;providerConfig={name:"iyzico",apiUrl,apiKey,secretKey},provider=createIyzicoProvider(providerConfig);break}case"stripe":{let stripeConf=paymentConfig.stripe;if(!stripeConf)return log.warn("payment.provider=stripe but no stripe config block"),null;let secretKey=resolveEnvValue3(stripeConf.secretKey),whSecret=resolveEnvValue3(stripeConf.webhookSecret);if(!secretKey)return log.warn("Stripe secret key missing \u2014 ensure env var is set",{secretKeyEnv:stripeConf.secretKey}),null;if(!whSecret)log.warn("Stripe webhook secret missing \u2014 webhook verification will fail",{webhookSecretEnv:stripeConf.webhookSecret});webhookSecret=whSecret,provider=createStripeProvider({secretKey,webhookSecret:whSecret,apiVersion:stripeConf.apiVersion,maxNetworkRetries:stripeConf.maxNetworkRetries,timeout:stripeConf.timeout});break}default:return log.warn(`Unknown provider: ${providerName}`),null}if(!provider)return null;return log.info(`Initialized provider: ${providerName}`),{provider:providerName,providerInstance:provider,webhookSecret}};var init_PaymentService=__esm(()=>{init_Logger2();init_IyzicoProvider();init_StripeProvider()});var exports_Payment={};__export(exports_Payment,{verifyStripeWebhook:()=>verifyStripeWebhook,resolveMarketplaceConfig:()=>resolveMarketplaceConfig,createStripeProvider:()=>createStripeProvider,createPaymentService:()=>createPaymentService,createMarketplaceServices:()=>createMarketplaceServices,createIyzicoProvider:()=>createIyzicoProvider,computeSplitBreakdown:()=>computeSplitBreakdown,MarketplaceService:()=>MarketplaceService,MarketplacePayoutService:()=>MarketplacePayoutService});var init_Payment=__esm(()=>{init_IyzicoProvider();init_Marketplace();init_PaymentService();init_StripeProvider()});import{createHash as createHash2,randomUUID as randomUUID4}from"crypto";class RateLimiter{redis;logger;config;constructor(deps){this.redis=deps.redis,this.logger=deps.logger,this.config=this.mergeConfig(deps.config);for(let type of["login","register","passwordReset","magicLink"]){let declared=deps.config.authRoutes?.[type];if(declared?.max!==void 0&&declared.perAddressMax===void 0)this.logger.warn(`[RateLimit] authRoutes.${type}.max is set but perAddressMax is not. \`max\` is now ONE ACCOUNT's budget (${declared.max}); the limit for one ADDRESS across all accounts is \`perAddressMax\`, defaulting to ${this.config.authRoutes[type].perAddressMax}. Set it explicitly if that is not what you want.`)}if(deps.config.skipSuccessfulRequests)this.logger.warn("[RateLimit] `skipSuccessfulRequests` is accepted but NOT implemented: successful requests still count toward every limit. Size your limits as if it were false.")}mergeConfig(config){return{enabled:config.enabled??DEFAULT_CONFIG3.enabled,strategy:config.strategy??DEFAULT_CONFIG3.strategy,keyPrefix:config.keyPrefix??DEFAULT_CONFIG3.keyPrefix,failClosed:config.failClosed,authRoutes:{window:config.authRoutes?.window??DEFAULT_CONFIG3.authRoutes.window,max:config.authRoutes?.max??DEFAULT_CONFIG3.authRoutes.max,login:{window:config.authRoutes?.login?.window??DEFAULT_AUTH_LOGIN.window,max:config.authRoutes?.login?.max??DEFAULT_AUTH_LOGIN.max,perAddressMax:config.authRoutes?.login?.perAddressMax??DEFAULT_AUTH_LOGIN.perAddressMax,blockDuration:config.authRoutes?.login?.blockDuration??DEFAULT_AUTH_LOGIN.blockDuration},register:{window:config.authRoutes?.register?.window??DEFAULT_AUTH_REGISTER.window,max:config.authRoutes?.register?.max??DEFAULT_AUTH_REGISTER.max,perAddressMax:config.authRoutes?.register?.perAddressMax??DEFAULT_AUTH_REGISTER.perAddressMax,blockDuration:config.authRoutes?.register?.blockDuration??DEFAULT_AUTH_REGISTER.blockDuration},passwordReset:{window:config.authRoutes?.passwordReset?.window??DEFAULT_AUTH_PASSWORD_RESET.window,max:config.authRoutes?.passwordReset?.max??DEFAULT_AUTH_PASSWORD_RESET.max,perAddressMax:config.authRoutes?.passwordReset?.perAddressMax??DEFAULT_AUTH_PASSWORD_RESET.perAddressMax,blockDuration:config.authRoutes?.passwordReset?.blockDuration??DEFAULT_AUTH_PASSWORD_RESET.blockDuration},magicLink:{window:config.authRoutes?.magicLink?.window??DEFAULT_AUTH_MAGIC_LINK.window,max:config.authRoutes?.magicLink?.max??DEFAULT_AUTH_MAGIC_LINK.max,perAddressMax:config.authRoutes?.magicLink?.perAddressMax??DEFAULT_AUTH_MAGIC_LINK.perAddressMax,blockDuration:config.authRoutes?.magicLink?.blockDuration??DEFAULT_AUTH_MAGIC_LINK.blockDuration}},publicRoutes:{window:config.publicRoutes?.window??DEFAULT_CONFIG3.publicRoutes.window,max:config.publicRoutes?.max??DEFAULT_CONFIG3.publicRoutes.max},privateRoutes:{window:config.privateRoutes?.window??DEFAULT_CONFIG3.privateRoutes.window,max:config.privateRoutes?.max??DEFAULT_CONFIG3.privateRoutes.max},byIp:config.byIp??DEFAULT_CONFIG3.byIp,byUserId:config.byUserId??DEFAULT_CONFIG3.byUserId,byEndpoint:config.byEndpoint??DEFAULT_CONFIG3.byEndpoint,skipSuccessfulRequests:config.skipSuccessfulRequests??DEFAULT_CONFIG3.skipSuccessfulRequests,headers:{remaining:config.headers?.remaining??DEFAULT_CONFIG3.headers.remaining,reset:config.headers?.reset??DEFAULT_CONFIG3.headers.reset,limit:config.headers?.limit??DEFAULT_CONFIG3.headers.limit},whitelist:config.whitelist??DEFAULT_CONFIG3.whitelist,blacklist:config.blacklist??DEFAULT_CONFIG3.blacklist}}parseTimeToMs(time){let match=time.match(/^(\d+)(ms|s|m|h|d)$/);if(!match||!match[1]||!match[2])return 60000;let value=parseInt(match[1],10);switch(match[2]){case"ms":return value;case"s":return value*1000;case"m":return value*60*1000;case"h":return value*60*60*1000;case"d":return value*24*60*60*1000;default:return 60000}}buildKey(params){let parts=[this.config.keyPrefix,params.category];if(params.authType&¶ms.authType!=="other")parts.push(params.authType);if(params.identity)parts.push(`as:${hashIdentity(params.identity)}`);if(this.config.byIp&¶ms.ip)parts.push(`ip:${params.ip}`);if(this.config.byUserId&¶ms.userId)parts.push(`user:${params.userId}`);if(this.config.byEndpoint&¶ms.endpoint)parts.push(`ep:${params.endpoint.replace(/\//g,"_")}`);return parts.join(":")}getLimits(category,authType,perIdentity=!1){if(category==="auth"){if(authType&&authType!=="other"){let authConfig=this.config.authRoutes[authType];if(authConfig)return perIdentity?authConfig:{...authConfig,max:authConfig.perAddressMax}}return{window:this.config.authRoutes.window,max:this.config.authRoutes.max}}if(category==="public")return this.config.publicRoutes;return this.config.privateRoutes}isWhitelisted(ip){return this.config.whitelist.some((pattern)=>{if(pattern.includes("*"))return new RegExp(`^${pattern.replace(/\*/g,".*")}$`).test(ip);return pattern===ip})}isBlacklisted(ip){return this.config.blacklist.some((pattern)=>{if(pattern.includes("*"))return new RegExp(`^${pattern.replace(/\*/g,".*")}$`).test(ip);return pattern===ip})}async readRedis(key2){let result=await this.redis.read(key2);if(result.success)return result.data;throw new RateLimiterBackendError(result.error?String(result.error):"rate-limit backend read failed")}async check(params){if(!this.config.enabled)return{allowed:!0,remaining:-1,resetAt:0,limit:-1};if(this.isWhitelisted(params.ip))return{allowed:!0,remaining:-1,resetAt:0,limit:-1};if(this.isBlacklisted(params.ip))return this.logger.warn(`[RateLimit] Blacklisted IP: ${params.ip}`),{allowed:!1,remaining:0,resetAt:Date.now()+86400000,limit:0,retryAfter:86400};let key2=this.buildKey(params),useStrictMax=!!params.identity||params.identityResolved===!1,limits=this.getLimits(params.category,params.authType,useStrictMax),windowMs=this.parseTimeToMs(limits.window),blockKey=`${key2}:blocked`;try{let isBlocked=await this.readRedis(blockKey);if(isBlocked&&isBlocked.until>Date.now()){let retryAfter=Math.ceil((isBlocked.until-Date.now())/1000);return this.logger.warn(`[RateLimit] Blocked: ${key2}, retry after ${retryAfter}s`),{allowed:!1,remaining:0,resetAt:isBlocked.until,limit:limits.max,retryAfter}}if(this.config.strategy==="sliding-window")return await this.slidingWindowCheck(key2,limits.max,windowMs,limits.blockDuration);if(this.config.strategy==="fixed-window")return await this.fixedWindowCheck(key2,limits.max,windowMs,limits.blockDuration);return await this.tokenBucketCheck(key2,limits.max,windowMs)}catch(err){let failClosed=this.config.failClosed===!0||this.config.failClosed===void 0&¶ms.category==="auth";if(this.logger.error(`[RateLimit] Backend error for ${key2} \u2014 failing ${failClosed?"closed":"open"}`,{error:err instanceof Error?err.message:String(err),category:params.category}),failClosed)return{allowed:!1,remaining:0,resetAt:Date.now()+windowMs,limit:limits.max,retryAfter:Math.ceil(windowMs/1000)};return{allowed:!0,remaining:-1,resetAt:0,limit:limits.max}}}async slidingWindowCheck(key2,max,windowMs,blockDuration){let client=this.redis.getDirectClient?.();if(client)return this.slidingWindowAtomic(client,key2,max,windowMs,blockDuration);let now=Date.now(),windowStart=now-windowMs,dataKey=`${key2}:sw`,timestamps=(await this.readRedis(dataKey))?.timestamps||[];timestamps=timestamps.filter((ts)=>ts>windowStart);let count=timestamps.length,firstTimestamp=timestamps[0],resetAt=firstTimestamp!==void 0?firstTimestamp+windowMs:now+windowMs;if(count>=max){if(blockDuration){let blockMs=this.parseTimeToMs(blockDuration);await this.redis.create(`${key2}:blocked`,{until:now+blockMs},Math.ceil(blockMs/1000))}return{allowed:!1,remaining:0,resetAt,limit:max,retryAfter:Math.ceil((resetAt-now)/1000)}}return timestamps.push(now),await this.redis.create(dataKey,{timestamps},Math.ceil(windowMs/1000)+1),{allowed:!0,remaining:max-timestamps.length,resetAt,limit:max}}async slidingWindowAtomic(client,key2,max,windowMs,blockDuration){let now=Date.now(),member=`${now}-${randomUUID4()}`,res=await client.eval(SLIDING_WINDOW_LUA,1,`${key2}:swz`,String(now),String(windowMs),String(max),member),allowed=Number(res?.[0])===1,count=Number(res?.[1]??0),resetAt=now+windowMs;if(!allowed){if(blockDuration){let blockMs=this.parseTimeToMs(blockDuration);await this.redis.create(`${key2}:blocked`,{until:now+blockMs},Math.ceil(blockMs/1000))}return{allowed:!1,remaining:0,resetAt,limit:max,retryAfter:Math.ceil(windowMs/1000)}}return{allowed:!0,remaining:Math.max(0,max-count),resetAt,limit:max}}async fixedWindowAtomic(client,key2,max,windowMs,blockDuration){let now=Date.now(),windowId=Math.floor(now/windowMs),dataKey=`${key2}:fw:${windowId}`,resetAt=(windowId+1)*windowMs,count=await client.incr(dataKey);if(count===1)await client.pexpire(dataKey,windowMs+1000);if(count>max){if(blockDuration){let blockMs=this.parseTimeToMs(blockDuration);await this.redis.create(`${key2}:blocked`,{until:now+blockMs},Math.ceil(blockMs/1000))}return{allowed:!1,remaining:0,resetAt,limit:max,retryAfter:Math.ceil((resetAt-now)/1000)}}return{allowed:!0,remaining:Math.max(0,max-count),resetAt,limit:max}}async fixedWindowCheck(key2,max,windowMs,blockDuration){let client=this.redis.getDirectClient?.();if(client)return this.fixedWindowAtomic(client,key2,max,windowMs,blockDuration);let now=Date.now(),windowId=Math.floor(now/windowMs),dataKey=`${key2}:fw:${windowId}`,resetAt=(windowId+1)*windowMs,count=(await this.readRedis(dataKey))?.count||0;if(count>=max){if(blockDuration){let blockMs=this.parseTimeToMs(blockDuration);await this.redis.create(`${key2}:blocked`,{until:now+blockMs},Math.ceil(blockMs/1000))}return{allowed:!1,remaining:0,resetAt,limit:max,retryAfter:Math.ceil((resetAt-now)/1000)}}return await this.redis.create(dataKey,{count:count+1},Math.ceil(windowMs/1000)+1),{allowed:!0,remaining:max-(count+1),resetAt,limit:max}}async tokenBucketCheck(key2,max,windowMs){let now=Date.now(),refillRate=max/windowMs,dataKey=`${key2}:tb`,data=await this.readRedis(dataKey),tokens=data?.tokens??max,lastRefill=data?.lastRefill??now,refill=(now-lastRefill)*refillRate;if(tokens=Math.min(max,tokens+refill),tokens<1){let waitTime=Math.ceil((1-tokens)/refillRate);return{allowed:!1,remaining:0,resetAt:now+waitTime,limit:max,retryAfter:Math.ceil(waitTime/1000)}}return tokens-=1,await this.redis.create(dataKey,{tokens,lastRefill:now},Math.ceil(windowMs/1000)*2),{allowed:!0,remaining:Math.floor(tokens),resetAt:now+windowMs,limit:max}}getHeaders(result){let headers={};return headers[this.config.headers.remaining]=String(result.remaining),headers[this.config.headers.reset]=String(Math.ceil(result.resetAt/1000)),headers[this.config.headers.limit]=String(result.limit),headers}isEnabled(){return this.config.enabled}}var SLIDING_WINDOW_LUA=`
|
|
100
100
|
local now = tonumber(ARGV[1])
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.83",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|