nucleus-core-ts 0.10.21 → 0.10.22

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/server.js CHANGED
@@ -547,7 +547,12 @@ ${fetchWarnings.join(`
547
547
  `)}`:verdict.message,fetched:rootRecords.length,reported:reportedTotal2??0}});progress({phase:"mapping",total:rootRecords.length});let mapped3=buildMappedRecords(rootRecords.map((r)=>r.record),mapping.fieldMappings,mapping.dedupTargetField,rootRecords.map((r)=>r.parent));if(await refreshCancelFlag(),isCancelled())return finish({...empty,externalCalls,status:"cancelled",failure:{kind:"cancelled",message:"Cancelled before anything was written."}});let existing=await deps.repo.readExistingRows(mapping.targetEntity),referenceValues=await loadReferenceValues(mapping.references??[],(entity,column)=>deps.repo.readEntityColumn(entity,column)),references=await loadReferenceIds(mapping.references??[],mapping.targetEntity,(entity,column)=>deps.repo.readEntityPairsBy(entity,column)),referenceIds=references.ids,existingAccounts=mapping.accountRule?await deps.repo.readEntityIdsBy(mapping.accountRule.entity,mapping.accountRule.matchColumn):{},ops=planWrites({records:mapped3.records,existing,dedupTargetField:mapping.dedupTargetField,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,removalScope:removalScopeFor(mapping.removalScopeColumn,mapped3.records),originColumn:mapping.originColumn,originValue:mapping.originValue,requiredFields:deps.repo.requiredFields(mapping.targetEntity),uniqueFields:deps.repo.uniqueFields(mapping.targetEntity),createDefaults:context.createDefaults??{},accountRule:mapping.accountRule??null,existingAccounts,references:mapping.references??[],referenceValues,referenceIds,targetEntity:mapping.targetEntity});ops.issues.unshift(...mapped3.issues),ops.warnings.push(...ambiguityWarnings(references.ambiguous)),ops.warnings.unshift(...fetchWarnings);let plan=summarize(ops,{targetEntity:mapping.targetEntity,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,totalFetched:rootRecords.length,reportedTotal:reportedTotal2,existingCount:existing.length,dedupConfigured:Boolean(mapping.dedupTargetField)}),volume=checkPlanVolume({removing:ops.deletes.length+ops.deactivates.length,existing:existing.length,maxShare:mapping.maxRemovalShare??0});if(!volume.allowed)return finish({...empty,externalCalls,failure:{kind:volume.kind,message:volume.message,fetched:rootRecords.length,reported:reportedTotal2??0}});progress({phase:"writing",total:ops.creates.length+ops.updates.length});let applied=await applyWrites(deps.writer,{ops,targetEntity:mapping.targetEntity,userId:context.userId,account:mapping.accountRule&&context.accountPasswordHash?{idColumn:mapping.accountRule.idColumn,matchColumn:mapping.accountRule.matchColumn,matchFrom:mapping.accountRule.matchFrom,passwordColumn:mapping.accountRule.passwordColumn,passwordHash:context.accountPasswordHash}:null,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,isCancelled});if(!applied.ok){let failureIssues=applied.failure.kind==="validation_failed"?applied.failure.issues:[];return finish({...empty,plan,externalCalls,issues:[...ops.issues,...failureIssues],status:applied.failure.kind==="cancelled"?"cancelled":"failed",failure:applied.failure})}return preImagesStored=await deps.repo.savePreImages(runId,applied.preImages),deps.logger.info("[Integrations] Run finished",{runId,mappingId:mapping.id,targetEntity:mapping.targetEntity,created:applied.created,accountsCreated:applied.accountsCreated,referencesCreated:applied.referencesCreated,updated:applied.updated,deleted:applied.deleted,deactivated:applied.deactivated,externalCalls,cancelChecks:cancelChecked}),finish({...empty,status:"succeeded",plan,failure:null,created:applied.created,accountsCreated:applied.accountsCreated,referencesCreated:applied.referencesCreated,updated:applied.updated,deleted:applied.deleted,deactivated:applied.deactivated,externalCalls,issues:[...ops.issues,...applied.issues,...orphanIssues(applied)]})}function orphanIssues(applied){let note=orphanLinkNote({made:applied.linksMade,unresolved:applied.linksUnresolved});return note?[{sourceIndex:-1,kind:"parent_unresolved",message:note}]:[]}var RANGES=[[0,59],[0,23],[1,31],[1,12],[0,6]],NAMES=["minute","hour","day of month","month","day of week"];function expandField(raw,[min,max]){let values=new Set;for(let part of raw.split(",")){let[spec,stepText]=part.split("/"),step=stepText===void 0?1:Number(stepText);if(!Number.isInteger(step)||step<1)return null;let from,to;if(spec==="*")from=min,to=max;else if(spec?.includes("-")){let[a,b]=spec.split("-").map(Number);if(!Number.isInteger(a)||!Number.isInteger(b))return null;from=a,to=b}else{let one=Number(spec);if(!Number.isInteger(one))return null;from=one,to=stepText===void 0?one:max}if(from<min||to>max||from>to)return null;for(let v=from;v<=to;v+=step)values.add(v)}return values.size>0?values:null}function parseCron2(expression){let parts=expression.trim().split(/\s+/);if(parts.length!==5)return{ok:!1,error:`A schedule has five parts \u2014 minute hour day-of-month month day-of-week \u2014 and this has ${parts.length}. "0 2 * * *" is every night at 02:00.`};let sets2=[];for(let i=0;i<5;i++){let field=expandField(parts[i],RANGES[i]);if(!field)return{ok:!1,error:`The ${NAMES[i]} part ("${parts[i]}") is not something I can read.`};sets2.push(field)}return{ok:!0,fields:{minutes:sets2[0],hours:sets2[1],daysOfMonth:sets2[2],months:sets2[3],daysOfWeek:sets2[4],anyDayOfMonth:parts[2]==="*",anyDayOfWeek:parts[4]==="*"}}}function partsIn2(when,timeZone){let formatter=new Intl.DateTimeFormat("en-US",{timeZone,hour12:!1,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",weekday:"short"}),parts={};for(let part of formatter.formatToParts(when))parts[part.type]=part.value;let weekdays=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];return{year:Number(parts.year),month:Number(parts.month),day:Number(parts.day),hour:Number(parts.hour)%24,minute:Number(parts.minute),weekday:Math.max(0,weekdays.indexOf(parts.weekday??"Sun"))}}function matchesCron(fields,when,timeZone){let at=partsIn2(when,timeZone);if(!fields.minutes.has(at.minute))return!1;if(!fields.hours.has(at.hour))return!1;if(!fields.months.has(at.month))return!1;let dayOfMonthMatches=fields.daysOfMonth.has(at.day),dayOfWeekMatches=fields.daysOfWeek.has(at.weekday);if(fields.anyDayOfMonth&&fields.anyDayOfWeek)return!0;if(fields.anyDayOfMonth)return dayOfWeekMatches;if(fields.anyDayOfWeek)return dayOfMonthMatches;return dayOfMonthMatches||dayOfWeekMatches}var CATCH_UP_MINUTES=1500,MINUTE_MS=60000;function isDue(input){let parsed=parseCron2(input.expression);if(!parsed.ok)return{due:!1,error:parsed.error};let zone=input.timeZone?.trim()||"UTC",nowMinute=Math.floor(input.now.getTime()/MINUTE_MS),lookBack=0;if(input.lastRunAt){let lastMinute=Math.floor(input.lastRunAt.getTime()/MINUTE_MS);if(lastMinute>=nowMinute)return{due:!1};lookBack=Math.min(nowMinute-lastMinute-1,CATCH_UP_MINUTES)}try{for(let back=0;back<=lookBack;back++){let at=new Date((nowMinute-back)*MINUTE_MS);if(matchesCron(parsed.fields,at,zone))return{due:!0}}}catch{return{due:!1,error:`"${zone}" is not a timezone this system knows.`}}return{due:!1}}function describeCron(expression,timeZone){let parsed=parseCron2(expression);if(!parsed.ok)return parsed.error;let zone=timeZone?.trim()||"UTC",parts=expression.trim().split(/\s+/),[minute,hour,dayOfMonth,month,dayOfWeek]=parts,everyMinutes=minute?.startsWith("*/")?Number(minute.slice(2)):null;if(everyMinutes&&hour==="*"&&dayOfMonth==="*"&&month==="*"&&dayOfWeek==="*")return`Every ${everyMinutes} minutes.`;let time2=minute!==void 0&&hour!==void 0&&!minute.includes("*")&&!hour.includes("*")?`${hour.padStart(2,"0")}:${minute.padStart(2,"0")}`:null,DAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];if(time2&&dayOfWeek!=="*"&&dayOfMonth==="*")return`Every ${[...parsed.fields.daysOfWeek??[]].map((d)=>DAYS[d]).join(", ")} at ${time2} (${zone}).`;if(time2&&dayOfMonth!=="*")return`On day ${dayOfMonth} of the month at ${time2} (${zone}).`;if(time2)return`Every day at ${time2} (${zone}).`;return`On the schedule "${expression}" (${zone}).`}function nextFireTimes(input){let parsed=parseCron2(input.expression);if(!parsed.ok)return{times:[],error:parsed.error};let zone=input.timeZone?.trim()||"UTC",count2=Math.max(1,Math.min(input.count??5,50)),horizon=Math.max(1,Math.min(input.horizonDays??400,400)),times=[],startMinute=Math.floor(input.from.getTime()/MINUTE_MS)+1,limit=horizon*24*60;try{for(let offset=0;offset<limit&&times.length<count2;offset+=1){let when=new Date((startMinute+offset)*MINUTE_MS);if(matchesCron(parsed.fields,when,zone))times.push(when)}}catch{return{times:[],error:`"${zone}" is not a timezone this system knows.`}}return{times}}var MINUTE2=60000;function dueMappings(mappings,now){let due=[],problems=[];for(let mapping of mappings){if(!mapping.scheduleCron?.trim())continue;let answer=isDue({expression:mapping.scheduleCron,timeZone:mapping.scheduleTimezone,now,lastRunAt:mapping.lastRunAt??null});if(answer.error){problems.push({mapping,error:answer.error});continue}if(answer.due)due.push(mapping)}return{due,problems}}function startScheduler(deps){let now=deps.now??(()=>new Date),reported=new Set,stopped=!1,tick=async()=>{if(stopped)return;let mappings;try{mappings=await deps.listScheduled()}catch(error3){deps.logger.error("[Integrations] Could not read the schedules",{error:error3 instanceof Error?error3.message:String(error3)});return}let{due,problems}=dueMappings(mappings,now());for(let problem of problems){let token=`${problem.mapping.id}:${problem.error}`;if(reported.has(token))continue;reported.add(token),deps.logger.error(`[Integrations] "${problem.mapping.name}" has a schedule that cannot run: ${problem.error}`)}for(let mapping of due)try{if(await deps.startRun(mapping.id))deps.logger.info(`[Integrations] Scheduled import started: ${mapping.name}`)}catch(error3){deps.logger.error(`[Integrations] Scheduled import failed to start: ${mapping.name}`,{error:error3 instanceof Error?error3.message:String(error3)})}},timer=setInterval(()=>{tick()},MINUTE2);if(typeof timer==="object"&&timer&&"unref"in timer)timer.unref();return{stop:()=>{stopped=!0,clearInterval(timer)}}}var asObject=(value2)=>value2&&typeof value2==="object"&&!Array.isArray(value2)?value2:null,asArray=(value2)=>Array.isArray(value2)?value2:[],text=(value2)=>{if(value2==null)return null;let s=String(value2);return s===""?null:s};function buildTransferDocument(input){let{source,endpoints,mappings}=input,nameById=new Map;for(let endpoint of endpoints){let id=text(endpoint.id),name2=text(endpoint.name);if(id&&name2)nameById.set(id,name2)}return{version:1,exportedAt:input.exportedAt,secretsExcluded:!0,connection:{name:String(source.name??""),slug:String(source.slug??""),baseUrl:String(source.baseUrl??""),description:text(source.description),authType:String(source.authType??"none"),authHeaderName:text(source.authHeaderName),authQueryParam:text(source.authQueryParam),extraHeaders:asObject(source.extraHeaders),tokenUrl:text(source.tokenUrl),oauthGrantType:text(source.oauthGrantType),oauthClientId:text(source.oauthClientId),oauthUsername:text(source.oauthUsername),oauthScope:text(source.oauthScope),oauthClientAuth:text(source.oauthClientAuth),tlsVerify:source.tlsVerify!==!1,timeoutMs:typeof source.timeoutMs==="number"?source.timeoutMs:null,enabled:source.enabled!==!1},endpoints:endpoints.map((endpoint)=>({name:String(endpoint.name??""),tag:String(endpoint.tag??""),method:String(endpoint.method??"GET"),path:String(endpoint.path??""),description:text(endpoint.description),queryParams:asObject(endpoint.queryParams),requestBody:asObject(endpoint.requestBody),responseRootPath:text(endpoint.responseRootPath),treeChildrenPath:text(endpoint.treeChildrenPath),pagination:asObject(endpoint.pagination),paramBindings:asArray(endpoint.paramBindings).map((binding)=>{if(binding.from!=="endpoint")return{...binding};let{endpointId,...rest3}=binding;return{...rest3,endpointName:nameById.get(String(endpointId))??null}}),enabled:endpoint.enabled!==!1})),mappings:mappings.map((mapping)=>({name:String(mapping.name??""),endpointName:nameById.get(String(mapping.endpointId))??null,targetEntity:String(mapping.targetEntity??""),fieldMappings:asArray(mapping.fieldMappings),referenceChecks:asArray(mapping.referenceChecks),dedupSourceField:text(mapping.dedupSourceField),dedupTargetField:text(mapping.dedupTargetField),writeMode:String(mapping.writeMode??"upsert")??"upsert",missingRecordPolicy:String(mapping.missingRecordPolicy??"ignore")??"ignore",missingRecordColumn:text(mapping.missingRecordColumn),missingRecordValue:text(mapping.missingRecordValue),removalScopeColumn:text(mapping.removalScopeColumn),passwordTargetColumn:text(mapping.passwordTargetColumn),accountRule:mapping.accountRule??null,scheduleEnabled:mapping.scheduleEnabled===!0,scheduleCron:text(mapping.scheduleCron),scheduleTimezone:text(mapping.scheduleTimezone),enabled:mapping.enabled!==!1}))}}function validateTransferDocument(value2){let problems=[],doc=asObject(value2);if(!doc)return{ok:!1,document:null,problems:[{path:"",message:"This is not an integration export file."}]};if(typeof doc.version!=="number")problems.push({path:"version",message:"The file does not say which format it is in."});else if(doc.version>1)problems.push({path:"version",message:`This file was written by a newer version (${doc.version}); this one understands up to 1.`});let connection=asObject(doc.connection);if(!connection)problems.push({path:"connection",message:"The file has no connection."});else{if(!text(connection.name))problems.push({path:"connection.name",message:"Missing name."});if(!text(connection.slug))problems.push({path:"connection.slug",message:"Missing slug."});if(!text(connection.baseUrl))problems.push({path:"connection.baseUrl",message:"Missing base URL."})}let endpoints=asArray(doc.endpoints),endpointNames=new Set;return endpoints.forEach((endpoint,index)=>{let name2=text(endpoint.name);if(!name2){problems.push({path:`endpoints[${index}].name`,message:"Missing name."});return}if(endpointNames.has(name2))problems.push({path:`endpoints[${index}].name`,message:`Two endpoints are called "${name2}"; names are how the rest of the file refers to them.`});if(endpointNames.add(name2),!text(endpoint.path))problems.push({path:`endpoints[${index}].path`,message:"Missing path."});if(!text(endpoint.tag))problems.push({path:`endpoints[${index}].tag`,message:"Missing tag."})}),endpoints.forEach((endpoint,index)=>{asArray(endpoint.paramBindings).forEach((binding,bindingIndex)=>{if(binding.from!=="endpoint")return;let target2=text(binding.endpointName);if(!target2||!endpointNames.has(target2))problems.push({path:`endpoints[${index}].paramBindings[${bindingIndex}]`,message:`Takes its value from an endpoint called "${target2??"?"}", which is not in this file.`})})}),asArray(doc.mappings).forEach((mapping,index)=>{if(!text(mapping.name))problems.push({path:`mappings[${index}].name`,message:"Missing name."});if(!text(mapping.targetEntity))problems.push({path:`mappings[${index}].targetEntity`,message:"Missing target table."});let endpointName=text(mapping.endpointName);if(!endpointName||!endpointNames.has(endpointName))problems.push({path:`mappings[${index}].endpointName`,message:`Reads from an endpoint called "${endpointName??"?"}", which is not in this file.`})}),{ok:problems.length===0,document:problems.length===0?doc:null,problems}}init_crypto();init_routeCollector();init_utils5();init_utils6();init_godminGuard();init_esm3();init_utils5();async function testConnection(chain,tokenCache,deps={}){let{source,oauth}=chain,fetchImpl=deps.fetchImpl;if(oauth){tokenCache.invalidate(source.id);let token=await getAccessToken(oauth,tokenCache,{fetchImpl});return token.ok?{ok:!0,detail:`A token was issued (${token.accessToken.length} characters). The credentials and token address are valid.`}:{ok:!1,error:token.error}}let built=buildUrl(source.baseUrl,"");if(!built.ok)return{ok:!1,error:built.error};let{headers}=applyAuth(source,built.url),outcome=await fetchOnce(source,{method:"GET",url:built.url,headers},{fetchImpl});if(outcome.status===0)return{ok:!1,error:outcome.error};return{ok:outcome.status<500,detail:`The address answered: HTTP ${outcome.status}.`,error:outcome.status>=500?outcome.error:void 0}}var PREVIEW_LIMIT=1500;async function sampleEndpoint(source,endpoint,pathParams,tokenCache,oauth,deps={}){let fetchImpl=deps.fetchImpl,bearerToken=null;if(oauth){let token=await getAccessToken(oauth,tokenCache,{fetchImpl});if(!token.ok)return blank({error:token.error});bearerToken=token.accessToken}let values={...endpoint.queryParams??{},...pathParams??{}},resolved=resolvePath2(endpoint.path,values);if(resolved.missing.length>0)return blank({error:`This endpoint needs a value for ${resolved.missing.map((m)=>`{${m}}`).join(", ")}. Enter an example value to test it (e.g. ${resolved.missing[0]} = 12345).`});let query={};for(let[key2,value2]of Object.entries(values)){if(value2==null||resolved.used.has(key2))continue;query[key2]=value2}let built=buildUrl(source.baseUrl,resolved.path,query);if(!built.ok)return blank({error:built.error});let{headers}=applyAuth({...source,token:bearerToken??source.token},built.url);if(bearerToken)headers.Authorization=`Bearer ${bearerToken}`;let outcome=await fetchOnce(source,{method:endpoint.method,url:built.url,headers,body:endpoint.method.toUpperCase()==="GET"?void 0:endpoint.requestBody??{}},{fetchImpl}),bodyKeys=outcome.body&&typeof outcome.body==="object"&&!Array.isArray(outcome.body)?Object.keys(outcome.body).slice(0,40):[];if(!outcome.ok)return blank({status:outcome.status,error:outcome.error??`HTTP ${outcome.status}`,bodyKeys,bodyPreview:previewBody(outcome.body)});let childrenPath=treeChildrenPath(endpoint.treeChildrenPath),extracted=extractRecords(outcome.body,endpoint.responseRootPath),flattened=childrenPath?flattenTree(extracted,childrenPath):null;if(flattened?.truncated)return blank({status:outcome.status,error:"This source answered with a structure that nests into itself, so it cannot be walked. Check the field named as the children.",bodyKeys,bodyPreview:previewBody(outcome.body)});let records=flattened?flattened.records:extracted;return{ok:!0,status:outcome.status,error:records.length===0?bodyKeys.length>0?`No record array was found. The response's top-level keys are: ${bodyKeys.join(", ")} \u2014 set the response root path to whichever holds the list.`:"No record array was found in the response.":null,recordCount:records.length,sample:records.slice(0,5),fields:records.length>0?flattenKeys(records[0]):[],bodyKeys,bodyPreview:previewBody(outcome.body)}}function blank(over){return{ok:!1,status:0,error:null,recordCount:0,sample:[],fields:[],bodyKeys:[],bodyPreview:null,...over}}function previewBody(body){if(body==null)return null;try{let text2=typeof body==="string"?body:JSON.stringify(body);return text2.length>PREVIEW_LIMIT?`${text2.slice(0,PREVIEW_LIMIT)}\u2026`:text2}catch{return null}}async function previewRun(chain,tokenCache,lookups,deps={}){let fetchImpl=deps.fetchImpl,{mapping,source,oauth}=chain,bearerToken=null;if(oauth){let token=await getAccessToken(oauth,tokenCache,{fetchImpl});if(!token.ok)return{ok:!1,error:token.error};bearerToken=token.accessToken}let root=chain.endpoints.find((e)=>e.id===chain.rootEndpointId);if(!root)return{ok:!1,error:"The endpoint this mapping reads no longer exists."};let parents={},entityValues={},externalCalls=0;for(let binding of root.paramBindings??[]){if(binding.from==="endpoint"&&!parents[binding.endpointId]){let parentSpec=chain.endpoints.find((e)=>e.id===binding.endpointId);if(!parentSpec)continue;let collected2=await collectEndpoint(source,parentSpec,{},{fetchImpl,bearerToken,maxConcurrent:deps.maxConcurrent,maxRecords:deps.maxRecords});if(externalCalls+=collected2.callCount,!collected2.ok)return{ok:!1,error:collected2.error};parents[binding.endpointId]=collected2.records.map((r)=>r.record)}if(binding.from==="entity"){let key2=`${binding.entity}.${binding.field}`;if(!entityValues[key2])entityValues[key2]=await lookups.readEntityColumn(binding.entity,binding.field)}}let collected=await collectEndpoint(source,root,{parents,entityValues},{fetchImpl,bearerToken,maxConcurrent:deps.maxConcurrent,maxRecords:deps.maxRecords});if(externalCalls+=collected.callCount,!collected.ok)return{ok:!1,error:collected.error};let mapped3=buildMappedRecords(collected.records.map((r)=>r.record),mapping.fieldMappings,mapping.dedupTargetField,collected.records.map((r)=>r.parent)),existing=await lookups.readExistingRows(mapping.targetEntity),referenceValues=await loadReferenceValues(mapping.references??[],(entity,column)=>lookups.readEntityColumn(entity,column)),references=await loadReferenceIds(mapping.references??[],mapping.targetEntity,(entity,column)=>lookups.readEntityPairsBy(entity,column)),referenceIds=references.ids,existingAccounts=mapping.accountRule?await lookups.readEntityIdsBy(mapping.accountRule.entity,mapping.accountRule.matchColumn):{},ops=planWrites({records:mapped3.records,existing,dedupTargetField:mapping.dedupTargetField,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,requiredFields:lookups.requiredFields(mapping.targetEntity),uniqueFields:lookups.uniqueFields(mapping.targetEntity),createDefaults:mapping.passwordColumn?{[mapping.passwordColumn]:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"}:{},accountRule:mapping.accountRule??null,existingAccounts,references:mapping.references??[],referenceValues,referenceIds,targetEntity:mapping.targetEntity});ops.issues.unshift(...mapped3.issues),ops.warnings.push(...ambiguityWarnings(references.ambiguous));let invalid=lookups.validateRows?.(ops.creates)??[];for(let problem of invalid){let create4=ops.creates.find((c)=>c.sourceIndex===problem.sourceIndex);ops.issues.push({sourceIndex:problem.sourceIndex,kind:"invalid_value",message:problem.message,row:create4?.data})}if(invalid.length>0){let refused=new Set(invalid.map((problem)=>problem.sourceIndex));ops.creates=ops.creates.filter((create4)=>!refused.has(create4.sourceIndex))}return{ok:!0,externalCalls,plan:summarize(ops,{targetEntity:mapping.targetEntity,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,totalFetched:collected.records.length,reportedTotal:collected.reportedTotal,existingCount:existing.length,dedupConfigured:Boolean(mapping.dedupTargetField)})}}init_crypto();import{and as and20,eq as eq34,inArray as inArray14}from"drizzle-orm";var camel2=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase());function tableFor(deps,entity){return deps.schemaTables[camel2(entity)]??null}var col8=(table,field)=>table?.[field]??table?.[camel2(field)];function open(deps,envelope,where){if(typeof envelope!=="string"||envelope==="")return"";let key2=deps.getEncryptionKey();if(!key2)return deps.logger.error("[Integrations] A stored credential cannot be read: no encryption key",{column:where}),"";try{return decryptSecret(envelope,key2)}catch{return deps.logger.error("[Integrations] A stored credential could not be decrypted \u2014 the encryption key has changed since it was saved. Re-enter it in the panel.",{column:where}),""}}async function resolveChain(deps,mappingId){let mappings=tableFor(deps,"integration_mappings"),endpoints=tableFor(deps,"integration_endpoints"),sources=tableFor(deps,"integration_sources");if(!mappings||!endpoints||!sources)return{ok:!1,error:"The integration tables are not present in this schema."};let[mappingRow]=await deps.db.select().from(mappings).where(eq34(col8(mappings,"id"),mappingId)).limit(1);if(!mappingRow)return{ok:!1,error:"This mapping no longer exists."};let row=mappingRow,[endpointRow]=await deps.db.select().from(endpoints).where(eq34(col8(endpoints,"id"),String(row.endpointId))).limit(1);if(!endpointRow)return{ok:!1,error:"The endpoint this mapping reads no longer exists."};if(endpointRow.enabled===!1)return{ok:!1,error:"The call this mapping reads is switched off."};if(row.enabled===!1)return{ok:!1,error:"This mapping is switched off."};let sourceId=String(endpointRow.sourceId),siblingRows=await deps.db.select().from(endpoints).where(eq34(col8(endpoints,"sourceId"),sourceId)),[sourceRow]=await deps.db.select().from(sources).where(eq34(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return{ok:!1,error:"The source this mapping reads no longer exists."};let s=sourceRow;if(s.enabled===!1)return{ok:!1,error:"This source is switched off."};let authType=String(s.authType??"none"),source={id:String(s.id),baseUrl:String(s.baseUrl??""),authType,token:authType==="oauth2"?null:open(deps,s.authTokenEncrypted,"auth_token_encrypted"),authHeaderName:s.authHeaderName??null,authQueryParam:s.authQueryParam??null,extraHeaders:s.extraHeaders??null,tlsVerify:s.tlsVerify!==!1,timeoutMs:Number(s.timeoutMs??30000)},oauth=authType==="oauth2"?{sourceId:source.id,tokenUrl:String(s.tokenUrl??""),grantType:String(s.oauthGrantType??"password")==="client_credentials"?"client_credentials":"password",clientId:s.oauthClientId??null,clientSecret:open(deps,s.oauthClientSecretEncrypted,"oauth_client_secret_encrypted"),username:s.oauthUsername??null,password:open(deps,s.oauthPasswordEncrypted,"oauth_password_encrypted"),scope:s.oauthScope??null,clientAuth:String(s.oauthClientAuth??"basic")==="body"?"body":"basic",tlsVerify:source.tlsVerify,timeoutMs:source.timeoutMs}:null;return{ok:!0,chain:{mapping:toMappingSpec(row),endpoints:siblingRows.map((e)=>toEndpointSpec(e)),rootEndpointId:String(endpointRow.id),source,oauth}}}async function resolveEndpointContext(deps,by){let sources=tableFor(deps,"integration_sources"),endpoints=tableFor(deps,"integration_endpoints");if(!sources||!endpoints)return{ok:!1,error:"The integration tables are not present in this schema."};let endpointRow=null,sourceId=by.sourceId;if(by.endpointId){let[row]=await deps.db.select().from(endpoints).where(eq34(col8(endpoints,"id"),by.endpointId)).limit(1);if(!row)return{ok:!1,error:"This endpoint no longer exists."};endpointRow=row,sourceId=String(endpointRow.sourceId)}if(!sourceId)return{ok:!1,error:"No source was named."};let[sourceRow]=await deps.db.select().from(sources).where(eq34(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return{ok:!1,error:"This source no longer exists."};let{source,oauth}=toConnection(deps,sourceRow);return{ok:!0,context:{source,oauth,endpoint:endpointRow?toEndpointSpec(endpointRow):null}}}async function cacheEndpointSample(deps,endpointId,records){let endpoints=tableFor(deps,"integration_endpoints");if(!endpoints||records.length===0)return;try{await deps.db.update(endpoints).set({sampleResponse:{records:records.slice(0,3)},updatedAt:new Date}).where(eq34(col8(endpoints,"id"),endpointId))}catch(error3){deps.logger.warn("[Integrations] Could not cache the endpoint sample",{endpointId,error:error3 instanceof Error?error3.message:String(error3)})}}function toConnection(deps,s){let authType=String(s.authType??"none"),source={id:String(s.id),baseUrl:String(s.baseUrl??""),authType,token:authType==="oauth2"?null:open(deps,s.authTokenEncrypted,"auth_token_encrypted"),authHeaderName:s.authHeaderName??null,authQueryParam:s.authQueryParam??null,extraHeaders:s.extraHeaders??null,tlsVerify:s.tlsVerify!==!1,timeoutMs:Number(s.timeoutMs??30000)},oauth=authType==="oauth2"?{sourceId:source.id,tokenUrl:String(s.tokenUrl??""),grantType:String(s.oauthGrantType??"password")==="client_credentials"?"client_credentials":"password",clientId:s.oauthClientId??null,clientSecret:open(deps,s.oauthClientSecretEncrypted,"oauth_client_secret_encrypted"),username:s.oauthUsername??null,password:open(deps,s.oauthPasswordEncrypted,"oauth_password_encrypted"),scope:s.oauthScope??null,clientAuth:String(s.oauthClientAuth??"basic")==="body"?"body":"basic",tlsVerify:source.tlsVerify,timeoutMs:source.timeoutMs}:null;return{source,oauth}}function toMappingSpec(row){return{id:String(row.id),name:String(row.name??""),targetEntity:String(row.targetEntity??""),fieldMappings:Array.isArray(row.fieldMappings)?row.fieldMappings:[],references:Array.isArray(row.referenceChecks)?row.referenceChecks:[],dedupTargetField:row.dedupTargetField||null,writeMode:String(row.writeMode??"upsert")??"upsert",missingPolicy:String(row.missingRecordPolicy??"ignore")??"ignore",missingColumn:row.missingRecordColumn??null,removalScopeColumn:row.removalScopeColumn||null,missingValue:row.missingRecordValue??null,originColumn:row.originColumn??null,originValue:row.originValue??null,maxRemovalShare:Number(row.maxRemovalShare??0)||0,passwordColumn:row.passwordTargetColumn||null,accountRule:row.accountRule??null}}function toEndpointSpec(row){let pagination=row.pagination;return{id:String(row.id),method:String(row.method??"GET"),path:String(row.path??""),queryParams:row.queryParams??null,requestBody:row.requestBody??null,responseRootPath:row.responseRootPath||null,treeChildrenPath:row.treeChildrenPath||null,pagination:pagination&&typeof pagination==="object"&&"kind"in pagination?pagination:{kind:"none"},paramBindings:Array.isArray(row.paramBindings)?row.paramBindings:null}}async function readConnectionBundle(deps,sourceId){let sources=tableFor(deps,"integration_sources"),endpoints=tableFor(deps,"integration_endpoints"),mappings=tableFor(deps,"integration_mappings");if(!sources||!endpoints||!mappings)return null;let[sourceRow]=await deps.db.select().from(sources).where(eq34(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return null;let endpointRows=await deps.db.select().from(endpoints).where(eq34(col8(endpoints,"sourceId"),sourceId)),ids=new Set(endpointRows.map((row)=>String(row.id))),allMappings=await deps.db.select().from(mappings);return{source:sourceRow,endpoints:endpointRows,mappings:allMappings.filter((row)=>ids.has(String(row.endpointId)))}}async function applyTransferDocument(deps,document,userId){let sources=tableFor(deps,"integration_sources"),endpoints=tableFor(deps,"integration_endpoints"),mappings=tableFor(deps,"integration_mappings");if(!sources||!endpoints||!mappings)return{ok:!1,error:"The integration tables are not present in this schema."};return deps.db.transaction(async(tx)=>{let now=new Date,c=document.connection,connectionValues={name:c.name,slug:c.slug,baseUrl:c.baseUrl,description:c.description??null,authType:c.authType,authHeaderName:c.authHeaderName??null,authQueryParam:c.authQueryParam??null,extraHeaders:c.extraHeaders??null,tokenUrl:c.tokenUrl??null,oauthGrantType:c.oauthGrantType??null,oauthClientId:c.oauthClientId??null,oauthUsername:c.oauthUsername??null,oauthScope:c.oauthScope??null,oauthClientAuth:c.oauthClientAuth??null,tlsVerify:c.tlsVerify!==!1,timeoutMs:c.timeoutMs??null,enabled:c.enabled!==!1,updatedAt:now,updatedBy:userId},[existingSource]=await tx.select().from(sources).where(eq34(col8(sources,"slug"),c.slug)).limit(1),sourceId,created=!1;if(existingSource)sourceId=String(existingSource.id),await tx.update(sources).set(connectionValues).where(eq34(col8(sources,"id"),sourceId));else{let[row]=await tx.insert(sources).values({...connectionValues,createdAt:now,createdBy:userId}).returning();if(!row?.id)return{ok:!1,error:"The connection could not be created."};sourceId=String(row.id),created=!0}let existingEndpoints=await tx.select().from(endpoints).where(eq34(col8(endpoints,"sourceId"),sourceId)),endpointIdByName=new Map;for(let row of existingEndpoints)endpointIdByName.set(String(row.name),String(row.id));for(let endpoint of document.endpoints){let values={sourceId,name:endpoint.name,tag:endpoint.tag,method:endpoint.method,path:endpoint.path,description:endpoint.description??null,queryParams:endpoint.queryParams??null,requestBody:endpoint.requestBody??null,responseRootPath:endpoint.responseRootPath??null,treeChildrenPath:endpoint.treeChildrenPath??null,pagination:endpoint.pagination??null,enabled:endpoint.enabled!==!1,updatedAt:now,updatedBy:userId},existingId=endpointIdByName.get(endpoint.name);if(existingId)await tx.update(endpoints).set(values).where(eq34(col8(endpoints,"id"),existingId));else{let[row]=await tx.insert(endpoints).values({...values,paramBindings:[],createdAt:now,createdBy:userId}).returning();if(row?.id)endpointIdByName.set(endpoint.name,String(row.id))}}for(let endpoint of document.endpoints){let id=endpointIdByName.get(endpoint.name);if(!id)continue;let bindings=(endpoint.paramBindings??[]).map((binding)=>{if(binding.from!=="endpoint")return binding;let{endpointName,...rest3}=binding;return{...rest3,endpointId:endpointIdByName.get(String(endpointName))??""}});await tx.update(endpoints).set({paramBindings:bindings,updatedAt:now}).where(eq34(col8(endpoints,"id"),id))}let allMappings=await tx.select().from(mappings),ownIds=new Set(endpointIdByName.values()),mappingIdByName=new Map;for(let row of allMappings)if(ownIds.has(String(row.endpointId)))mappingIdByName.set(String(row.name),String(row.id));let mappingCount=0;for(let mapping of document.mappings){let endpointId=mapping.endpointName?endpointIdByName.get(mapping.endpointName):void 0;if(!endpointId)continue;let values={endpointId,name:mapping.name,targetEntity:mapping.targetEntity,fieldMappings:mapping.fieldMappings??[],referenceChecks:mapping.referenceChecks??[],dedupSourceField:mapping.dedupSourceField??null,dedupTargetField:mapping.dedupTargetField??null,writeMode:mapping.writeMode,missingRecordPolicy:mapping.missingRecordPolicy,missingRecordColumn:mapping.missingRecordColumn??null,missingRecordValue:mapping.missingRecordValue??null,removalScopeColumn:mapping.removalScopeColumn??null,passwordTargetColumn:mapping.passwordTargetColumn??null,accountRule:mapping.accountRule??null,scheduleEnabled:mapping.scheduleEnabled===!0,scheduleCron:mapping.scheduleCron??null,scheduleTimezone:mapping.scheduleTimezone??null,enabled:mapping.enabled!==!1,updatedAt:now,updatedBy:userId},existingId=mappingIdByName.get(mapping.name);if(existingId)await tx.update(mappings).set(values).where(eq34(col8(mappings,"id"),existingId));else await tx.insert(mappings).values({...values,createdAt:now,createdBy:userId});mappingCount++}return{ok:!0,sourceId,created,endpoints:endpointIdByName.size,mappings:mappingCount}})}async function readScheduledMappings(deps){let mappings=tableFor(deps,"integration_mappings");if(!mappings)return[];return(await deps.db.select().from(mappings)).filter((row)=>row.scheduleEnabled===!0&&row.enabled!==!1).filter((row)=>typeof row.scheduleCron==="string"&&row.scheduleCron.trim()!=="").map((row)=>({id:String(row.id),name:String(row.name??""),scheduleCron:String(row.scheduleCron),scheduleTimezone:row.scheduleTimezone??null,lastRunAt:row.lastRunAt?new Date(row.lastRunAt):null,needsPassword:Boolean(row.passwordTargetColumn)||row.accountRule!=null}))}function requiredFieldsOf(entities,targetEntity){return(entities.find((e)=>e.table_name===targetEntity)?.columns??[]).filter((c)=>c.notNull===!0&&c.default===void 0&&!c.defaultRaw&&!c.nullable).map((c)=>camel2(c.name))}function uniqueFieldsOf(entities,targetEntity){let entity=entities.find((e)=>e.table_name===targetEntity),columns=entity?.columns??[],fields=new Set;for(let column of columns)if(column.unique===!0||column.primaryKey===!0)fields.add(camel2(column.name));for(let index of entity?.indexes??[])if(index.unique===!0&&index.columns.length===1&&index.columns[0])fields.add(camel2(index.columns[0]));return fields.delete("id"),[...fields]}function createRunRepository(deps,entities){let runs=()=>tableFor(deps,"integration_runs"),runItems=()=>tableFor(deps,"integration_run_items");return{async openRun(input){let table=runs();if(!table)return{ok:!1,error:"integration_runs is missing from this schema."};try{let id=(await deps.db.insert(table).values({mappingId:input.mappingId,targetEntity:input.targetEntity,writeMode:input.writeMode,trigger:input.trigger,status:"running",startedAt:new Date,createdBy:input.userId??null}).returning())[0]?.id;return id?{ok:!0,runId:String(id)}:{ok:!1,error:"The run record was created but returned no identifier."}}catch(error3){return{ok:!1,error:error3 instanceof Error?error3.message:String(error3)}}},async closeRun(runId,patch){let table=runs();if(!table)return{ok:!1,error:"integration_runs is missing from this schema."};try{let mappingId=(await deps.db.update(table).set({...patch,updatedAt:new Date}).where(eq34(col8(table,"id"),runId)).returning())[0]?.mappingId,finishedAt=patch.finishedAt,mappings=tableFor(deps,"integration_mappings");if(mappings&&mappingId&&finishedAt)await deps.db.update(mappings).set({lastRunAt:finishedAt,updatedAt:new Date}).where(eq34(col8(mappings,"id"),String(mappingId)));return{ok:!0}}catch(error3){return deps.logger.error("[Integrations] Could not close the run record",{runId,error:error3 instanceof Error?error3.message:String(error3)}),{ok:!1,error:"Could not write the run record."}}},async savePreImages(runId,images){let table=runItems();if(!table)return!1;if(images.length===0)return!0;let CHUNK2=500;try{return await deps.db.transaction(async(tx)=>{for(let i=0;i<images.length;i+=CHUNK2)await tx.insert(table).values(images.slice(i,i+CHUNK2).map((image)=>({runId,targetEntity:image.targetEntity,rowId:image.rowId,action:image.action,before:image.before,after:image.after})))}),!0}catch(error3){return deps.logger.error("[Integrations] The run succeeded but its before-images could not be stored; it cannot be undone",{runId,error:error3 instanceof Error?error3.message:String(error3)}),!1}},async isCancelRequested(runId){let table=runs();if(!table)return!1;try{let[row]=await deps.db.select().from(table).where(eq34(col8(table,"id"),runId)).limit(1);return row?.cancelRequested===!0}catch{return!1}},async readExistingRows(targetEntity){let table=tableFor(deps,targetEntity);if(!table)return[];return(await deps.db.select().from(table)).map((row)=>({id:String(row.id),row}))},async readEntityColumn(entity,field){let table=tableFor(deps,entity),column=table?col8(table,field):void 0;if(!table||!column)return[];return(await deps.db.selectDistinct({v:column}).from(table)).map((r)=>r.v).filter((v)=>v!=null&&v!=="")},async readEntityIdsBy(entity,field){let table=tableFor(deps,entity),column=table?col8(table,field):void 0,idColumn=table?col8(table,"id"):void 0;if(!table||!column||!idColumn)return{};let rows=await deps.db.select({v:column,id:idColumn}).from(table),map3={};for(let row of rows){if(row.v==null||row.v===""||row.id==null)continue;let key2=String(row.v).trim();if(!map3[key2])map3[key2]=String(row.id)}return map3},async readEntityPairsBy(entity,field){let table=tableFor(deps,entity),column=table?col8(table,field):void 0,idColumn=table?col8(table,"id"):void 0;if(!table||!column||!idColumn)return[];return await deps.db.select({value:column,id:idColumn}).from(table)},requiredFields:(targetEntity)=>requiredFieldsOf(entities,targetEntity),uniqueFields:(targetEntity)=>uniqueFieldsOf(entities,targetEntity)}}async function readRowsById(deps,targetEntity,ids){let table=tableFor(deps,targetEntity),result=new Map;if(!table||ids.length===0)return result;let CHUNK2=1000;for(let i=0;i<ids.length;i+=CHUNK2){let rows=await deps.db.select().from(table).where(inArray14(col8(table,"id"),ids.slice(i,i+CHUNK2)));for(let row of rows)result.set(String(row.id),row)}return result}async function readPreImages(deps,runId){let table=tableFor(deps,"integration_run_items");if(!table)return[];return(await deps.db.select().from(table).where(eq34(col8(table,"runId"),runId))).map((row)=>({targetEntity:String(row.targetEntity),rowId:row.rowId?String(row.rowId):null,action:String(row.action),before:row.before??null,after:row.after??null}))}async function readRun(deps,runId){let table=tableFor(deps,"integration_runs");if(!table)return null;let[row]=await deps.db.select().from(table).where(eq34(col8(table,"id"),runId)).limit(1);return row??null}async function requestCancel(deps,runId){let table=tableFor(deps,"integration_runs");if(!table)return!1;return(await deps.db.update(table).set({cancelRequested:!0,updatedAt:new Date}).where(and20(eq34(col8(table,"id"),runId),eq34(col8(table,"status"),"running"))).returning()).length>0}var fail3=(set2,status,message)=>{return set2.status=status,{isSuccess:!1,message,data:null}};function createInspectRoutes(routeConfig,deps,tokenCache){let settings=routeConfig.resolvedOptions.integrations??{};return[defineRoute({method:"POST",path:"/sources/:id/test",detail:{tags:["Integrations"],summary:"Check a source is reachable and its credentials are accepted"},handler:async({params,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let context=await resolveEndpointContext(repoDeps,{sourceId:params.id});if(!context.ok)return fail3(set2,404,context.error);let result=await testConnection(context.context,tokenCache);return{isSuccess:result.ok,message:result.ok?result.detail??"Connected.":result.error??"Could not connect.",data:result}}}),defineRoute({method:"POST",path:"/endpoints/:id/sample",body:Type.Optional(Type.Object({pathParams:Type.Optional(Type.Record(Type.String(),Type.Unknown()))})),detail:{tags:["Integrations"],summary:"Call one endpoint once and report its real shape"},handler:async({params,body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let context=await resolveEndpointContext(repoDeps,{endpointId:params.id});if(!context.ok)return fail3(set2,404,context.error);let{source,oauth,endpoint}=context.context;if(!endpoint)return fail3(set2,404,"This endpoint no longer exists.");let result=await sampleEndpoint(source,endpoint,body?.pathParams,tokenCache,oauth);if(result.ok&&result.sample.length>0)await cacheEndpointSample(repoDeps,params.id,result.sample);return{isSuccess:result.ok,message:result.error??"Sampled.",data:result}}}),defineRoute({method:"POST",path:"/preview",body:Type.Object({mappingId:Type.String()}),detail:{tags:["Integrations"],summary:"Dry-run an import: counts, warnings and a sample, with no writes"},handler:async({body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail3(set2,404,chain.error);let repo=createRunRepository(repoDeps,repoDeps.entities),result=await previewRun(chain.chain,tokenCache,{readExistingRows:repo.readExistingRows,readEntityColumn:repo.readEntityColumn,requiredFields:repo.requiredFields,uniqueFields:repo.uniqueFields,readEntityIdsBy:repo.readEntityIdsBy,readEntityPairsBy:repo.readEntityPairsBy,validateRows:(rows)=>{let columns=repoDeps.entities.find((e)=>e.table_name===chain.chain.mapping.targetEntity)?.columns??[],problems=[];for(let row of rows){let result2=validatePayload(row.data,columns,!1);if(!result2.valid)problems.push({sourceIndex:row.sourceIndex,message:result2.errors.map((e)=>`${e.field}: ${e.message}`).join("; ")})}return problems}},{maxRecords:settings.maxRecordsPerRun,maxConcurrent:settings.maxConcurrentFanout});if(!result.ok)return fail3(set2,422,result.error);return{isSuccess:!0,message:"Nothing was written.",data:{plan:result.plan,externalCalls:result.externalCalls}}}}),defineRoute({method:"POST",path:"/schedule-check",body:Type.Object({mappingId:Type.String(),at:Type.Optional(Type.String())}),detail:{tags:["Integrations"],summary:"Simulate the schedule: the next firings, and whether a given moment is one",description:"Writes nothing and starts nothing. Answers the question a schedule cannot otherwise be asked until the night it runs: does this expression name the minute I meant, in the clock I meant."},handler:async({body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail3(set2,404,chain.error);let raw=chain.chain.mapping,expression=String(raw.scheduleCron??"").trim(),timeZone=String(raw.scheduleTimezone??"").trim()||"UTC";if(expression==="")return{isSuccess:!0,message:"Nothing was written.",data:{enabled:raw.scheduleEnabled===!0,expression:"",timeZone,times:[]}};let at=body.at?new Date(body.at):new Date;if(Number.isNaN(at.getTime()))return fail3(set2,400,"That is not a moment I can read.");let upcoming=nextFireTimes({expression,timeZone,from:at,count:5}),verdict=isDue({expression,timeZone,now:at,lastRunAt:null});return{isSuccess:!0,message:"Nothing was written.",data:{enabled:raw.scheduleEnabled===!0,expression,timeZone,describes:describeCron(expression,timeZone),at:at.toISOString(),wouldFireAtThatMoment:verdict.due,error:verdict.error??upcoming.error??null,times:upcoming.times.map((t)=>t.toISOString())}}}})]}var fail4=(set2,status,message)=>{return set2.status=status,{isSuccess:!1,message,data:null}};async function closeAbandonedRuns(repoDeps,mappingId,logger2){try{let runsTable=tableFor(repoDeps,"integration_runs");if(!runsTable)return;let{and:and21,eq:eq35,inArray:inArray15}=await import("drizzle-orm"),closed=await repoDeps.db.update(runsTable).set({status:"failed",finishedAt:new Date,errors:["This import stopped without finishing \u2014 the process running it went away (a restart, a deploy, or a crash). Whatever it had already written is still there; run it again, or check the target table."],updatedAt:new Date}).where(and21(eq35(runsTable.mappingId,mappingId),inArray15(runsTable.status,["running","queued"]))).returning();if(closed.length>0)logger2.warn(`[Integrations] ${closed.length} run(s) of this mapping were left open by a process that went away; marked as failed.`)}catch(error3){logger2.error("[Integrations] Could not close abandoned runs",error3)}}var tokenCache=new TokenCache;function createIntegrationRoutes(routeConfig){let{logger:logger2,basePath,db,schemaTables,resolvedOptions,getEncryptionKey,getLockStore,publish}=routeConfig,entities=resolveEntities(SYSTEM_TABLES,resolvedOptions.entities??[],{consumerMode:resolvedOptions.authentication?.mode==="consumer"}).entities,settings=resolvedOptions.integrations??{},topicPrefix=settings.realtimeTopicPrefix||"integrations",guard4=createGodminGuard({logger:logger2,db,schemaTables,area:"data integrations",allowedRoles:routeConfig.adminRoles??settings.adminRoles}),deps=()=>db?{db,schemaTables,entities,logger:logger2,getEncryptionKey}:null,inspectRoutes=createInspectRoutes(routeConfig,deps,tokenCache),writerFor=(repoDeps,targetEntity)=>{let table=tableFor(repoDeps,targetEntity);if(!table)return null;let entity=entities.find((e)=>e.table_name===targetEntity);return{db:repoDeps.db,drizzleTable:table,resolveCol:(field)=>table[field]??table[field.replace(/_([a-z])/g,(_m,l)=>l.toUpperCase())],columns:entity?.columns??[],logger:logger2,validatePayload,resolveEntity:(name2)=>{let refTable=tableFor(repoDeps,name2);if(!refTable)return null;let refEntity=entities.find((e)=>e.table_name===name2);return{table:refTable,columns:refEntity?.columns??[]}},accountClosure:accountClosureFor(targetEntity)}};function accountClosureFor(targetEntity){let link=accountLinkFor(routeConfig.resolvedOptions.authentication?.accountLinks,targetEntity);if(!link)return null;let usersTable=schemaTables.users;if(!usersTable)return null;let sessionsTable=schemaTables.userSessions??schemaTables.sessions;return{userColumn:link.userColumn,accountEntity:"users",usersTable,sessionsTable:sessionsTable??null}}let routePostRuns=defineRoute({method:"POST",path:"/runs",body:Type.Object({mappingId:Type.String(),confirm:Type.Optional(Type.Boolean()),sharedPassword:Type.Optional(Type.String()),referenceMode:Type.Optional(Type.Union([Type.Literal("create"),Type.Literal("skipRecord")]))}),detail:{tags:["Integrations"],summary:"Start an import; answers 202 with the run identifier"},handler:async({body,set:set2,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail4(set2,404,chain.error);let declared2=chain.chain.mapping,answer=body.referenceMode,mapping=answer?{...declared2,references:(declared2.references??[]).map((check)=>check.onMissing==="ignore"?check:{...check,onMissing:answer})}:declared2,wantsPassword=Boolean(mapping.passwordColumn||mapping.accountRule),createDefaults,accountPasswordHash=null;if(wantsPassword){let supplied=typeof body.sharedPassword==="string"?body.sharedPassword:"";if(supplied.length<8)return fail4(set2,422,mapping.accountRule?"This import creates a login for every new record, so it needs a password to create them with: send sharedPassword, at least 8 characters. It is used once, for this run, and is stored nowhere.":`This mapping writes a password into "${mapping.passwordColumn}", so it needs one to write: send sharedPassword, at least 8 characters. It is used for the accounts this run creates and is not stored anywhere.`);let hash=await hashPassword(supplied);if(accountPasswordHash=hash,mapping.passwordColumn)createDefaults={[mapping.passwordColumn]:hash}}if(isDestructive(mapping.writeMode,mapping.missingPolicy)&&body.confirm!==!0)return set2.status=428,{isSuccess:!1,message:mapping.writeMode==="replace"?"This mapping rebuilds the whole table. Send confirm: true to proceed.":mapping.missingPolicy==="deactivate"?"This mapping deactivates records the source no longer carries. Send confirm: true to proceed.":"This mapping deletes records the source no longer carries. Send confirm: true to proceed.",data:null};let lockStore=getLockStore();if(!lockStore)return fail4(set2,503,"The lock service is unavailable, so a second run of this mapping could not be prevented. Runs are refused until it is back.");let lock=await acquireRunLock(lockStore,mapping.id,{logger:logger2});if(!lock.ok){if(lock.reason==="unavailable")return fail4(set2,503,`The lock service is unavailable: ${lock.error}`);return set2.status=409,{isSuccess:!1,message:"This mapping is already importing. Wait for it to finish, or cancel it.",data:{heldSince:lock.heldSince}}}await closeAbandonedRuns(repoDeps,mapping.id,logger2);let writer=writerFor(repoDeps,mapping.targetEntity);if(!writer)return await lock.handle.release(),fail4(set2,422,`This mapping targets "${mapping.targetEntity}", which does not exist.`);let userId=request.headers.get("x-user-id")??"unknown",repo=createRunRepository(repoDeps,entities),announce=()=>{},runIdReady=new Promise((resolve2)=>{announce=resolve2}),started=executeRun({...chain.chain,mapping,userId,trigger:"manual",createDefaults,accountPasswordHash},{repo,writer,logger:logger2,tokenCache,publishProgress:(progress)=>publish?.(`${topicPrefix}.run`,progress),onRunOpened:(id)=>announce(id),maxRecordsPerRun:settings.maxRecordsPerRun,maxConcurrentFanout:settings.maxConcurrentFanout,retainPreImagesMs:parseDuration(settings.retainPreImagesFor)},lock.handle),runId=await Promise.race([runIdReady,started.then((outcome)=>outcome.runId)]);return started.catch(async(error3)=>{let reason=error3 instanceof Error?error3.message:String(error3);logger2.error("[Integrations] Run threw outside its own error handling",{mappingId:mapping.id,error:reason});let openedId=await runIdReady.catch(()=>null);if(openedId)await repo.closeRun(openedId,{status:"failed",finishedAt:new Date,errors:[reason]}).catch(()=>{})}).finally(()=>lock.handle.release()),set2.status=202,{isSuccess:!0,message:"The import has started.",data:{runId,mappingId:mapping.id,targetEntity:mapping.targetEntity}}}}),startScheduledRun=async(mappingId)=>{let repoDeps=deps();if(!repoDeps)return!1;let chain=await resolveChain(repoDeps,mappingId);if(!chain.ok)return logger2.error(`[Integrations] Scheduled import cannot run: ${chain.error}`),!1;let lockStore=getLockStore();if(!lockStore)return!1;let lock=await acquireRunLock(lockStore,mappingId,{logger:logger2});if(!lock.ok)return!1;let writer=writerFor(repoDeps,chain.chain.mapping.targetEntity);if(!writer)return await lock.handle.release(),logger2.error(`[Integrations] Scheduled import targets "${chain.chain.mapping.targetEntity}", which does not exist.`),!1;return executeRun({...chain.chain,userId:null,trigger:"schedule"},{repo:createRunRepository(repoDeps,entities),writer,logger:logger2,tokenCache,publishProgress:(progress)=>publish?.(`${topicPrefix}.run`,progress),maxRecordsPerRun:settings.maxRecordsPerRun,maxConcurrentFanout:settings.maxConcurrentFanout,retainPreImagesMs:parseDuration(settings.retainPreImagesFor)},lock.handle).then((outcome)=>{if(outcome.status!=="succeeded")logger2.error(`[Integrations] Scheduled import "${chain.chain.mapping.name}" ${outcome.status}: ${outcome.failure?.message??"no reason recorded"}`)}).catch((error3)=>{logger2.error("[Integrations] Scheduled import threw outside its own error handling",{mappingId,error:error3 instanceof Error?error3.message:String(error3)})}).finally(()=>lock.handle.release()),!0},unattendable=new Set;if(settings.schedulesEnabled!==!1)startScheduler({logger:logger2,listScheduled:async()=>{let repoDeps=deps();if(!repoDeps)return[];return(await readScheduledMappings(repoDeps)).filter((row)=>{if(!row.needsPassword)return!0;if(!unattendable.has(row.id))unattendable.add(row.id),logger2.error(`[Integrations] "${row.name}" is scheduled but creates logins, which need a password nobody can type at that hour. It will not run on its own \u2014 start it by hand, or turn the login off.`);return!1})},startRun:(mappingId)=>startScheduledRun(mappingId)});let routeGetSourcesIdExport=defineRoute({method:"GET",path:"/sources/:id/export",detail:{tags:["Integrations"],summary:"Export a connection, its calls and its mappings \u2014 without any secret"},handler:async({params,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let bundle=await readConnectionBundle(repoDeps,params.id);if(!bundle)return fail4(set2,404,"This connection no longer exists.");let document=buildTransferDocument({...bundle,exportedAt:new Date().toISOString()});return{isSuccess:!0,message:`${document.endpoints.length} call(s) and ${document.mappings.length} mapping(s). Credentials are NOT included \u2014 they are asked for again on the other side.`,data:document}}}),routePostImport=defineRoute({method:"POST",path:"/import",body:Type.Object({document:Type.Unknown()}),detail:{tags:["Integrations"],summary:"Bring in an exported connection; re-running it updates rather than duplicates"},handler:async({body,set:set2,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let checked=validateTransferDocument(body?.document);if(!checked.ok||!checked.document)return set2.status=422,{isSuccess:!1,message:"This file cannot be imported as it stands.",data:{problems:checked.problems}};let userId=request.headers.get("x-user-id")??"unknown",result=await applyTransferDocument(repoDeps,checked.document,userId);if(!result.ok)return fail4(set2,422,result.error);return{isSuccess:!0,message:`Connection "${checked.document.connection.name}" is here: ${result.endpoints} call(s) and ${result.mappings} mapping(s). Its credentials are not \u2014 enter them before running anything.`,data:result}}}),routePostRunsIdCancel=defineRoute({method:"POST",path:"/runs/:id/cancel",detail:{tags:["Integrations"],summary:"Ask a running import to stop"},handler:async({params,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");if(!await requestCancel(repoDeps,params.id))return fail4(set2,409,"This run is not running, so there is nothing to cancel.");return{isSuccess:!0,message:"Cancellation requested. The import stops at its next checkpoint and anything it had written is rolled back.",data:{runId:params.id}}}}),routePostRunsIdRevert=defineRoute({method:"POST",path:"/runs/:id/revert",body:Type.Optional(Type.Object({force:Type.Optional(Type.Boolean())})),detail:{tags:["Integrations"],summary:"Undo a run that succeeded"},handler:async({params,body,set:set2,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let run=await readRun(repoDeps,params.id);if(!run)return fail4(set2,404,"This run no longer exists.");if(run.status!=="succeeded")return fail4(set2,409,`Only a run that finished successfully can be undone; this one is "${String(run.status)}".`);let expiry=run.preImagesExpireAt?new Date(String(run.preImagesExpireAt)):null;if(expiry&&expiry.getTime()<Date.now())return fail4(set2,410,"This run is too old to undo: the records of what it replaced have been cleared.");let targetEntity=String(run.targetEntity??""),preImages=await readPreImages(repoDeps,params.id);if(preImages.length===0)return fail4(set2,410,"There is no record of what this run replaced, so it cannot be undone.");let groups=groupPreImagesByEntity(preImages).sort((a,b)=>a.targetEntity===targetEntity?-1:b.targetEntity===targetEntity?1:0),planned=[];for(let group of groups){let current=await readRowsById(repoDeps,group.targetEntity,touchedRowIds(group.preImages));planned.push({targetEntity:group.targetEntity,ops:planRevert({preImages:group.preImages,current,force:body?.force===!0})})}let ops={deletes:planned.flatMap((p)=>p.ops.deletes),restores:planned.flatMap((p)=>p.ops.restores),reinserts:planned.flatMap((p)=>p.ops.reinserts),conflicts:planned.flatMap((p)=>p.ops.conflicts)};if(ops.deletes.length===0&&ops.restores.length===0&&ops.reinserts.length===0&&ops.conflicts.length>0)return set2.status=409,{isSuccess:!1,message:"Every record this run touched has been edited since. Undoing would discard those edits; send force: true to overwrite them anyway.",data:{conflicts:ops.conflicts}};let userId=request.headers.get("x-user-id")??"unknown";for(let group of planned)if(!writerFor(repoDeps,group.targetEntity))return fail4(set2,422,`"${group.targetEntity}" no longer exists.`);let undone={deleted:0,restored:0,reinserted:0},failure=null;try{await repoDeps.db.transaction(async(tx)=>{for(let group of planned){let groupWriter=writerFor(repoDeps,group.targetEntity),result2=await applyRevert(groupWriter,group.ops,{targetEntity:group.targetEntity,userId,tx});if(!result2.ok)throw failure=result2.error??"The undo could not be completed.",Error(failure);undone={deleted:undone.deleted+(result2.deleted??0),restored:undone.restored+(result2.restored??0),reinserted:undone.reinserted+(result2.reinserted??0)}}})}catch(error3){let message=failure??(error3 instanceof Error?error3.message:"The undo could not be completed.");return fail4(set2,500,`${message} Nothing was undone \u2014 every table was rolled back together.`)}let result={ok:!0,...undone,conflicts:ops.conflicts.length},finished=ops.conflicts.length===0,runsTable=tableFor(repoDeps,"integration_runs");if(runsTable&&finished){let{eq:eq35}=await import("drizzle-orm");await repoDeps.db.update(runsTable).set({status:"reverted",revertedAt:new Date,revertedBy:userId}).where(eq35(runsTable.id,params.id))}return{isSuccess:!0,message:finished?"The import was undone.":`Undone except for ${ops.conflicts.length} record(s) edited since the import. They were left as they are \u2014 send force: true to overwrite them too.`,data:{deleted:result.deleted,restored:result.restored,reinserted:result.reinserted,conflicts:ops.conflicts}}}}),routePutSourcesIdCredentials=defineRoute({method:"PUT",path:"/sources/:id/credentials",body:Type.Object({token:Type.Optional(Type.String()),oauthClientSecret:Type.Optional(Type.String()),oauthPassword:Type.Optional(Type.String())}),detail:{tags:["Integrations"],summary:"Store a source credential; only a masked preview is returned"},handler:async({params,body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let key2=getEncryptionKey();if(!key2)return fail4(set2,503,"No encryption key is configured, so a credential stored now could never be read back.");let table=tableFor(repoDeps,"integration_sources");if(!table)return fail4(set2,503,"The integration tables are not present in this schema.");let patch={},hints={},apply=(value2,column,hintColumn,name2)=>{if(value2===void 0)return;let trimmed=value2.trim();if(trimmed===""){patch[column]=null,patch[hintColumn]=null,hints[name2]=null;return}patch[column]=encryptSecret(trimmed,key2),patch[hintColumn]=previewOf(trimmed),hints[name2]=previewOf(trimmed)};if(apply(body.token,"authTokenEncrypted","authTokenHint","token"),apply(body.oauthClientSecret,"oauthClientSecretEncrypted","oauthClientSecretHint","clientSecret"),apply(body.oauthPassword,"oauthPasswordEncrypted","oauthPasswordHint","password"),Object.keys(patch).length===0)return{isSuccess:!0,message:"Nothing to change.",data:{hints}};let{eq:eq35}=await import("drizzle-orm");return await repoDeps.db.update(table).set({...patch,updatedAt:new Date}).where(eq35(table.id,params.id)),tokenCache.invalidate(params.id),{isSuccess:!0,message:"Saved.",data:{hints}}}});logger2.info(`[Integrations] Routes enabled at ${basePath}`);let declared=[routePostRuns,routeGetSourcesIdExport,routePostImport,routePostRunsIdCancel,routePostRunsIdRevert,routePutSourcesIdCredentials,...inspectRoutes];return collectRoutes([{prefix:basePath,guards:[guard4],routes:declared}])}function parseDuration(value2){if(!value2)return;let match=value2.trim().match(/^(\d+)\s*([smhd])$/i);if(!match)return;let amount=Number(match[1]),unit=(match[2]??"d").toLowerCase();return amount*(unit==="s"?1000:unit==="m"?60000:unit==="h"?3600000:86400000)}init_esm3();init_routeCollector();init_adminGuard();var STREAM_HEADERS={"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache, no-transform",Connection:"keep-alive"},textEncoder=new TextEncoder,encodeEvent=(event,data)=>{let payload=typeof data==="string"?data:JSON.stringify(data);return textEncoder.encode(`event: ${event}
548
548
  data: ${payload}
549
549
 
550
- `)};function parseTimeToMs2(time2){let match=time2.match(/^(\d+)(ms|s|m|h)$/);if(!match?.[1]||!match[2])return 5000;let value2=parseInt(match[1],10);switch(match[2]){case"ms":return value2;case"s":return value2*1000;case"m":return value2*60*1000;case"h":return value2*60*60*1000;default:return 5000}}function createMonitoringRoutes(config){let{getService,logger:logger2,endpoints,db,schemaTables}=config,snapshotOrNull=async()=>await getService()?.getLatestSnapshot()??null,alertsOrEmpty=()=>getService()?.getActiveAlerts()??[],guard4=createPrivilegedGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"monitoring",{allowRoles:endpoints.allowRoles}),mount=(routes2)=>collectRoutes([{prefix:endpoints.basePath,guards:[guard4],routes:routes2}]);if(!endpoints.enabled)return mount([]);logger2.info(`[Monitoring] Routes enabled at ${endpoints.basePath} (stream: ${endpoints.stream.enabled}, snapshot: ${endpoints.snapshot.enabled}, history: ${endpoints.history.enabled}, alerts: ${endpoints.alerts.enabled})`);let routes=[];if(endpoints.stream.enabled)routes.push(defineRoute({method:"GET",path:endpoints.stream.path,handler:({request})=>{let intervalMs=parseTimeToMs2(endpoints.stream.interval),abortSignal=request.signal;logger2.info("[Monitoring] SSE stream connected");let cleanup,stream=new ReadableStream({start(controller){let closed=!1,send=(event,data)=>{if(closed)return;controller.enqueue(encodeEvent(event,data))},cleanupHandler=()=>{if(closed)return;closed=!0,clearInterval(timer),abortSignal?.removeEventListener("abort",cleanupHandler),logger2.info("[Monitoring] SSE stream disconnected");try{controller.close()}catch{}};cleanup=cleanupHandler;let tick=async()=>{if(closed)return;let snapshot=await snapshotOrNull();if(snapshot)send("snapshot",snapshot);let alerts=alertsOrEmpty();if(alerts.length>0)send("alerts",alerts)},timer=setInterval(tick,intervalMs);abortSignal?.addEventListener("abort",cleanupHandler),send("open",{interval:intervalMs}),tick()},cancel(){cleanup?.()}});return new Response(stream,{headers:STREAM_HEADERS})},detail:{tags:["Monitoring"],summary:"Stream real-time monitoring data",description:"Server-Sent Events stream for real-time monitoring metrics"}}));if(endpoints.snapshot.enabled)routes.push(defineRoute({method:"GET",path:endpoints.snapshot.path,handler:async()=>{let snapshot=await snapshotOrNull();if(!snapshot)return{isSuccess:!0,message:"No monitoring data available yet",data:null};return{isSuccess:!0,message:"Current monitoring snapshot",data:snapshot}},detail:{tags:["Monitoring"],summary:"Get current monitoring snapshot",description:"Returns the latest monitoring metrics snapshot"}}));if(endpoints.history.enabled)routes.push(defineRoute({method:"GET",path:endpoints.history.path,handler:async({query})=>{let minutes=Math.min(query.minutes?parseInt(String(query.minutes),10):60,endpoints.history.maxMinutes),history=await getService()?.getHistory(minutes)??[];return{isSuccess:!0,message:`Monitoring history for last ${minutes} minutes`,data:{minutes,count:history.length,snapshots:history}}},query:Type.Object({minutes:Type.Optional(Type.Number())}),detail:{tags:["Monitoring"],summary:"Get monitoring history",description:"Returns historical monitoring data for the specified time range"}}));if(endpoints.alerts.enabled)routes.push(defineRoute({method:"GET",path:endpoints.alerts.path,handler:()=>{let alerts=alertsOrEmpty();return{isSuccess:!0,message:"Active alerts",data:{count:alerts.length,alerts}}},detail:{tags:["Monitoring"],summary:"Get active alerts",description:"Returns all currently active monitoring alerts"}})),routes.push(defineRoute({method:"POST",path:`${endpoints.alerts.path}/:alertId/acknowledge`,handler:({params,set:set2})=>{let service=getService();if(!service)return set2.status=503,{isSuccess:!1,message:"Monitoring service is not available",data:null};if(!service.acknowledgeAlert(params.alertId))return set2.status=404,{isSuccess:!1,message:"Alert not found",data:null};return{isSuccess:!0,message:"Alert acknowledged",data:{alertId:params.alertId}}},params:Type.Object({alertId:Type.String()}),detail:{tags:["Monitoring"],summary:"Acknowledge an alert",description:"Mark an alert as acknowledged"}}));return mount(routes)}init_esm3();init_routeCollector();init_adminGuard();function createLiveMonitoringRoutes(config){let{getService,getLifecycleRecorder,logger:logger2,basePath,streamInterval,db,schemaTables}=config,guard4=createGodminGuard2({db:db??null,schemaTables:schemaTables??{},logger:logger2},"live monitoring"),requireService=()=>{let svc=getService();if(!svc)throw Error("Live monitoring service not initialized");return svc};logger2.info(`[LiveMonitoring] Routes enabled at ${basePath} (stream interval: ${streamInterval}ms)`);let routes=[defineRoute({method:"GET",path:"/health",handler:()=>{let svc=getService();return{status:svc?"ok":"initializing",timestamp:Date.now(),monitoring:svc?.isEnabled()??!1}},detail:{tags:["Live Monitoring"],summary:"Health check for live monitoring"}}),defineRoute({method:"GET",path:"/settings",handler:()=>{return requireService().getSettings()},detail:{tags:["Live Monitoring"],summary:"Get live monitoring settings"}}),defineRoute({method:"PATCH",path:"/settings",handler:({body})=>{return requireService().changeSettings(body)},body:Type.Partial(Type.Object({logMemory:Type.Boolean(),logCpu:Type.Boolean(),logDapr:Type.Boolean(),logWebSocket:Type.Boolean(),memoryLogInterval:Type.Number(),cpuLogInterval:Type.Number(),memoryLogLimit:Type.Number(),cpuLogLimit:Type.Number(),daprLogLimit:Type.Number(),wsLogLimit:Type.Number(),requestLogLimit:Type.Number()})),detail:{tags:["Live Monitoring"],summary:"Change live monitoring settings"}}),defineRoute({method:"GET",path:"/incidents",handler:async({query})=>{let raw=Number(query?.limit),limit=Number.isFinite(raw)&&raw>0?Math.min(raw,50):20,recorder=getLifecycleRecorder?.();if(!recorder)return{available:!1,incidents:[]};return{available:!0,incidents:await recorder.incidents(limit)}},query:Type.Optional(Type.Object({limit:Type.Optional(Type.String())})),detail:{tags:["Live Monitoring"],summary:"Why previous runs of this process ended"}}),defineRoute({method:"GET",path:"/realtime",handler:({query})=>{let raw=Number(query?.windowMinutes),minutes=Number.isFinite(raw)&&raw>0?Math.min(raw,1440):void 0;return requireService().getRealtimeHealth(minutes?minutes*60000:void 0)},query:Type.Optional(Type.Object({windowMinutes:Type.Optional(Type.String())})),detail:{tags:["Live Monitoring"],summary:"Realtime (WebSocket) health digest"}}),defineRoute({method:"GET",path:"/logs",handler:()=>{return requireService().getLogs()},detail:{tags:["Live Monitoring"],summary:"Get all live monitoring logs"}}),defineRoute({method:"GET",path:"/logs/stream",handler:({request})=>{let abortSignal=request.signal,cleanup,svc=requireService(),snapshot=svc.getSnapshot(),lastTimestamps={memory:snapshot.memory.length?snapshot.memory[snapshot.memory.length-1]?.timestamp??0:0,cpu:snapshot.cpu.length?snapshot.cpu[snapshot.cpu.length-1]?.timestamp??0:0,request:snapshot.requests.length?snapshot.requests[snapshot.requests.length-1]?.timestamp??0:0,dapr:snapshot.dapr.length?snapshot.dapr[snapshot.dapr.length-1]?.timestamp??0:0,ws:snapshot.ws.length?snapshot.ws[snapshot.ws.length-1]?.timestamp??0:0},stream=new ReadableStream({start(controller){let closed=!1;controller.enqueue(encodeEvent("snapshot",snapshot));let interval=setInterval(()=>{if(closed)return;let update=svc.getUpdatesSince(lastTimestamps);if(!update){controller.enqueue(encodeEvent("heartbeat",{timestamp:Date.now()}));return}if(update.memory.length)lastTimestamps.memory=update.memory[update.memory.length-1]?.timestamp??lastTimestamps.memory;if(update.cpu.length)lastTimestamps.cpu=update.cpu[update.cpu.length-1]?.timestamp??lastTimestamps.cpu;if(update.requests.length)lastTimestamps.request=update.requests[update.requests.length-1]?.timestamp??lastTimestamps.request;if(update.dapr.length)lastTimestamps.dapr=update.dapr[update.dapr.length-1]?.timestamp??lastTimestamps.dapr;if(update.ws.length)lastTimestamps.ws=update.ws[update.ws.length-1]?.timestamp??lastTimestamps.ws;controller.enqueue(encodeEvent("update",update))},streamInterval),cleanupHandler=()=>{if(closed)return;closed=!0,clearInterval(interval),abortSignal?.removeEventListener("abort",cleanupHandler);try{controller.close()}catch{}};cleanup=cleanupHandler,abortSignal?.addEventListener("abort",cleanupHandler)},cancel(){cleanup?.()}});return new Response(stream,{headers:STREAM_HEADERS})},detail:{tags:["Live Monitoring"],summary:"Stream real-time live monitoring data via SSE"}})];return collectRoutes([{prefix:basePath,guards:[guard4],routes}])}init_esm3();init_routeCollector();init_adminGuard();var LOG_LEVELS=["debug","info","warn","error","fatal"],SOURCES=["logger","stdout","stderr"];function parseLevels(raw){if(!raw)return;let parsed=raw.split(",").map((value2)=>value2.trim().toLowerCase()).filter((value2)=>LOG_LEVELS.includes(value2));return parsed.length>0?parsed:void 0}function parseSource(raw){if(!raw)return;let value2=raw.trim().toLowerCase();return SOURCES.includes(value2)?value2:void 0}function parseMinLevel(raw){if(!raw)return;let value2=raw.trim().toLowerCase();return LOG_LEVELS.includes(value2)?value2:void 0}function parseCursor(raw){if(raw===void 0||raw===null||raw==="")return;let value2=Number(raw);return Number.isFinite(value2)&&value2>=0?value2:void 0}function buildQuery(query){return{levels:parseLevels(query.levels),minLevel:parseMinLevel(query.minLevel),scope:query.scope?.trim()||void 0,search:query.search||void 0,source:parseSource(query.source),after:parseCursor(query.after),before:parseCursor(query.before),limit:parseCursor(query.limit)}}var STREAM_KEEPALIVE_MS=25000;function createServerLogRoutes(config){let{buffer,logger:logger2,basePath,path:path7,allowRoles,streamEnabled=!0,clearEnabled=!0,db,schemaTables}=config,guard4=createPrivilegedGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"server logs",{allowRoles}),routes=[defineRoute({method:"GET",path:path7,handler:({query})=>{return{isSuccess:!0,message:"Server logs",data:{...buffer.query(buildQuery(query)),scopes:buffer.scopes(),levelCounts:buffer.levelCounts()}}},query:Type.Object({levels:Type.Optional(Type.String()),minLevel:Type.Optional(Type.String()),scope:Type.Optional(Type.String()),search:Type.Optional(Type.String()),source:Type.Optional(Type.String()),after:Type.Optional(Type.Number()),before:Type.Optional(Type.Number()),limit:Type.Optional(Type.Number())}),detail:{tags:["Monitoring"],summary:"Read buffered server logs",description:"Filtered, cursor-paged view of the in-memory server log buffer. Use `after` to tail forward and `before` to page backwards."}})];if(streamEnabled)routes.push(defineRoute({method:"GET",path:`${path7}/stream`,handler:({request,query})=>{let tailQuery=buildQuery(query),{after,before:_before,limit:_limit,...filter}=tailQuery,abortSignal=request.signal,cleanup,stream=new ReadableStream({start(controller){let closed=!1,backlog=buffer.query({...filter,...after!==void 0?{after}:{},limit:200});controller.enqueue(encodeEvent("backlog",backlog));let unsubscribe=buffer.subscribe((record3)=>{if(closed)return;if(!buffer.query({...filter,after:record3.seq-1,limit:1}).records.some((r)=>r.seq===record3.seq))return;try{controller.enqueue(encodeEvent("logs",[record3]))}catch{cleanup?.()}}),heartbeat=setInterval(()=>{if(closed)return;try{controller.enqueue(encodeEvent("ping",{timestamp:Date.now()}))}catch{cleanup?.()}},STREAM_KEEPALIVE_MS),cleanupHandler=()=>{if(closed)return;closed=!0,clearInterval(heartbeat),unsubscribe(),abortSignal?.removeEventListener("abort",cleanupHandler);try{controller.close()}catch{}};cleanup=cleanupHandler,abortSignal?.addEventListener("abort",cleanupHandler)},cancel(){cleanup?.()}});return new Response(stream,{headers:STREAM_HEADERS})},query:Type.Object({levels:Type.Optional(Type.String()),minLevel:Type.Optional(Type.String()),scope:Type.Optional(Type.String()),search:Type.Optional(Type.String()),source:Type.Optional(Type.String()),after:Type.Optional(Type.Number())}),detail:{tags:["Monitoring"],summary:"Tail server logs",description:"Server-Sent Events tail of the server log buffer, filtered server-side."}}));if(clearEnabled)routes.push(defineRoute({method:"DELETE",path:path7,handler:()=>{return buffer.clear(),logger2.info("[ServerLogs] Buffer cleared from the admin panel"),{isSuccess:!0,message:"Server log buffer cleared",data:null}},detail:{tags:["Monitoring"],summary:"Clear the server log buffer"}}));return logger2.info(`[ServerLogs] Routes enabled at ${basePath}${path7} (capacity: ${buffer.getCapacity()}, stream: ${streamEnabled})`),collectRoutes([{prefix:basePath,guards:[guard4],routes}])}init_esm3();init_Notification();init_routeCollector();init_adminGuard();var CHANNELS=["portal","email","telegram","webhook"];function createNotificationRoutes(routeConfig){let{db,schemaTables,config,logger:logger2,tenantRegistry,getEmailService}=routeConfig,basePath=routeConfig.basePath||config.endpoints?.basePath||"/notifications",buildService=(tables)=>new NotificationService({db,schemaTables:tables,config,logger:logger2,getEmailService}),defaultService=buildService(schemaTables),serviceFor=(request)=>{if(!tenantRegistry)return defaultService;let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return defaultService;let ctx=tenantRegistry.getSchemaContext(schemaName);return ctx?buildService(ctx.schemaTables):defaultService},allowedSendRoles=new Set((config.endpoints?.sendRoles??[]).map((role)=>role.toLowerCase())),canSend=async(request)=>{if(allowedSendRoles.size>0){if(decodeHeaderList(request.headers.get("x-user-roles")).some((role)=>allowedSendRoles.has(role.toLowerCase())))return!0}return isGodminRequest(request,{db,schemaTables,logger:logger2})},requireUser=(request,set2)=>{let userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,null;return userId},declared=[defineRoute({method:"GET",path:"/",handler:async({request,query,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};return{success:!0,data:await serviceFor(request).getNotifications(userId,{limit:query.limit?Number(query.limit):void 0,offset:query.offset?Number(query.offset):void 0,type:query.type||void 0})}},query:Type.Object({limit:Type.Optional(Type.String()),offset:Type.Optional(Type.String()),type:Type.Optional(Type.String())}),detail:{tags:["Notifications"],summary:"List the caller\u2019s notifications"}}),defineRoute({method:"GET",path:"/unseen-count",handler:async({request,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};return{success:!0,data:{count:await serviceFor(request).getUnseenCount(userId)}}},detail:{tags:["Notifications"],summary:"Unseen notification count"}}),defineRoute({method:"POST",path:"/:notification_id/seen",handler:async({params,request,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};if(!await serviceFor(request).markAsSeen(params.notification_id,userId))return set2.status=404,{success:!1,message:"Notification not found"};return{success:!0,message:"Marked as seen"}},params:Type.Object({notification_id:Type.String()}),detail:{tags:["Notifications"],summary:"Mark one notification as seen"}}),defineRoute({method:"POST",path:"/seen-all",handler:async({request,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};return{success:!0,data:{count:await serviceFor(request).markAllAsSeen(userId)}}},detail:{tags:["Notifications"],summary:"Mark every notification as seen"}}),defineRoute({method:"POST",path:"/send",handler:async({body,request,set:set2})=>{if(!requireUser(request,set2))return{success:!1,message:"Authentication required"};if(!await canSend(request))return set2.status=403,{success:!1,message:"Sending notifications requires godmin or an allowed role"};let recipients=body.userIds??(body.userId?[body.userId]:[]);if(recipients.length===0)return set2.status=400,{success:!1,message:"userId or userIds is required"};let channels=body.channels??[config.defaultChannel??"portal"],service=serviceFor(request),failed=(await Promise.allSettled(recipients.map((user_id)=>service.send({user_id,title:body.title,body:body.body,entity_name:body.entityName,entity_id:body.entityId,type:body.type??"custom",source:body.source,channels})))).map((result,index)=>{let userId=recipients[index];if(result.status==="rejected"){let reason=result.reason;return{userId,sent:!1,reason:reason instanceof Error?reason.message:String(reason)}}if(result.value.delivered.length>0)return{userId,sent:!0,reason:""};return{userId,sent:!1,reason:result.value.undelivered.map((one)=>`${one.channel}: ${one.reason}`).join("; ")||"no channel was requested"}}).filter((one)=>!one.sent),sentCount=recipients.length-failed.length,reasons=[...new Set(failed.map((one)=>one.reason))].join("; ");if(sentCount===0)set2.status=503;return{success:failed.length===0,message:failed.length===0?`Sent to ${recipients.length} recipient(s)`:`Sent to ${sentCount}, failed for ${failed.length}: ${reasons}`,data:{sent:sentCount,failed:failed.map((one)=>one.userId)}}},body:Type.Object({userId:Type.Optional(Type.String()),userIds:Type.Optional(Type.Array(Type.String())),title:Type.String(),body:Type.Optional(Type.String()),entityName:Type.Optional(Type.String()),entityId:Type.Optional(Type.String()),type:Type.Optional(Type.Union([Type.Literal("verification"),Type.Literal("system"),Type.Literal("custom")])),source:Type.Optional(Type.String()),channels:Type.Optional(Type.Array(Type.Union(CHANNELS.map((c)=>Type.Literal(c)))))}),detail:{tags:["Notifications"],summary:"Send a notification to one or more users",description:"Privileged. Delivers over the requested channels (portal, email, telegram, webhook). Email uses the configured provider \u2014 credentials resolve through the secrets store, so a rotation applies immediately."}}),defineRoute({method:"POST",path:"/email",handler:async({body,request,set:set2})=>{if(!requireUser(request,set2))return{success:!1,message:"Authentication required"};if(!await canSend(request))return set2.status=403,{success:!1,message:"Sending email requires godmin or an allowed role"};let emailService=getEmailService?.()??null;if(!emailService?.isAvailable())return set2.status=503,{success:!1,message:"No email provider is configured or available"};let result=await emailService.sendEmail({to:body.to,subject:body.subject,html:body.html,text:body.text,replyTo:body.replyTo});if(!result.success)return set2.status=502,{success:!1,message:result.error??"Email delivery failed"};return logger2.info("[Notification] Transactional email sent",{recipients:Array.isArray(body.to)?body.to.length:1,subject:body.subject}),{success:!0,message:"Email sent",data:null}},body:Type.Object({to:Type.Union([Type.String(),Type.Array(Type.String())]),subject:Type.String(),html:Type.Optional(Type.String()),text:Type.Optional(Type.String()),replyTo:Type.Optional(Type.String())}),detail:{tags:["Notifications"],summary:"Send a transactional email to explicit recipients",description:"Backend-to-backend send through the configured provider \u2014 an app never needs an email SDK or the provider credentials of its own. Privileged, same gate as /send."}})];return{routes:collectRoutes([{prefix:basePath,routes:declared}]),notificationService:defaultService}}init_routeCollector();init_routeRecording();init_websocket();init_ack_manager();import{timingSafeEqual as timingSafeEqual3}from"crypto";init_ack_manager();var clients=new Map,presenceBroadcastDebounce=new Map,DEFAULT_MAX_TOPICS_PER_CLIENT=512;function createClientManager(config){let{redis,logger:logger2}=config,ackTtl=config.ack.ttlSeconds,ackEnabled=config.ack.enabled,maxRetries=config.ack.maxRetries,presenceEnabled=config.presence.enabled,presenceDebounceMs=config.presence.debounceMs,maxClientsPerUser=config.maxClientsPerUser,maxTopicsPerClient=config.maxTopicsPerClient??DEFAULT_MAX_TOPICS_PER_CLIENT,MAX_TOPIC_LENGTH=256;function generateClientId(){return`client_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}function capTopics(topics){let accepted=[],dropped=[],seen=new Set;for(let t of topics){if(typeof t!=="string")continue;let trimmed=t.length>256?t.slice(0,256):t;if(seen.has(trimmed))continue;if(seen.add(trimmed),accepted.length>=maxTopicsPerClient){dropped.push(trimmed);continue}accepted.push(trimmed)}return{accepted,dropped}}function reportDropped(context,clientId,userId,dropped){if(dropped.length===0)return;logger2.warn("[PubSub] Topic subscription capped \u2014 events for these will NOT be delivered",{context,clientId,userId,cap:maxTopicsPerClient,droppedCount:dropped.length,dropped})}function registerClient(clientId,ws,userId,topics){let existingClients=getClientsByUser(userId);if(existingClients.length>=maxClientsPerUser){let oldestClientId=existingClients[0];if(oldestClientId)logger2.info("[PubSub] Max clients reached, closing oldest",{userId,oldestClientId}),unregisterClient(oldestClientId)}let{accepted,dropped}=capTopics(topics);return clients.set(clientId,{ws,userId,subscribedTopics:new Set(accepted),connectedAt:Date.now(),pendingAcks:new Map}),logger2.info("[PubSub] Client registered",{clientId,userId,topics:accepted,requestedCount:topics.length,subscribedCount:accepted.length,totalClients:clients.size}),reportDropped("registerClient",clientId,userId,dropped),{accepted,dropped}}function unregisterClient(clientId){let client=clients.get(clientId);if(!client)return;let userId=client.userId;if(clients.delete(clientId),logger2.info("[PubSub] Client unregistered",{clientId,userId,totalClients:clients.size}),presenceEnabled&&userId){if(getClientsByUser(userId).length===0)broadcastPresenceToOthers(userId,"offline")}}function updateClientTopics(clientId,topics){let client=clients.get(clientId);if(!client)return{accepted:[],dropped:[]};let{accepted,dropped}=capTopics(topics);return client.subscribedTopics=new Set(accepted),reportDropped("updateClientTopics",clientId,client.userId,dropped),{accepted,dropped}}function getClientsByUser(userId){let result=[];for(let[clientId,client]of clients)if(client.userId===userId)result.push(clientId);return result}function getConnectedUserIds(){let userIds=new Set;for(let[,client]of clients)if(client.userId)userIds.add(client.userId);return userIds}function getClientCount(){return clients.size}function sendToClient(clientId,message){let client=clients.get(clientId);if(!client)return!1;try{return client.ws.send(JSON.stringify(message)),!0}catch{return clients.delete(clientId),!1}}function broadcastEvent(topic,event){let messageId=generateMessageId(),timestamp=Date.now(),payload=JSON.stringify({type:"event",messageId:ackEnabled?messageId:void 0,topic,data:event,timestamp}),sentCount=0;for(let[clientId,client]of clients)if(client.subscribedTopics.has("*")||client.subscribedTopics.has(topic))try{if(client.ws.send(payload),sentCount++,ackEnabled){if(client.pendingAcks.set(messageId,{sentAt:timestamp,retryCount:0}),recordSent(),client.userId)storePendingMessage(redis,{messageId,topic,clientId,userId:client.userId,data:event,sentAt:timestamp,retryCount:0,maxRetries},ackTtl).catch(()=>{})}}catch{clients.delete(clientId)}logger2.info("[PubSub] Broadcasted event",{topic,messageId,sentCount})}function broadcastToUser(userId,topic,event){let messageId=generateMessageId(),timestamp=Date.now(),payload=JSON.stringify({type:"event",messageId:ackEnabled?messageId:void 0,topic,data:event,timestamp}),sentCount=0,sentToAnyClient=!1;for(let[clientId,client]of clients){if(client.userId!==userId)continue;if(!client.subscribedTopics.has("*")&&!client.subscribedTopics.has(topic))continue;try{if(client.ws.send(payload),sentCount++,sentToAnyClient=!0,ackEnabled)client.pendingAcks.set(messageId,{sentAt:timestamp,retryCount:0}),recordSent()}catch{clients.delete(clientId)}}if(ackEnabled)storePendingMessage(redis,{messageId,topic,clientId:sentToAnyClient?"delivered":"pending",userId,data:event,sentAt:timestamp,retryCount:0,maxRetries},ackTtl).catch(()=>{});logger2.info("[PubSub] Broadcasted to user",{userId,topic,messageId,sentCount,storedForOffline:!sentToAnyClient})}function broadcastToUserNoAck(userId,topic,event){let timestamp=Date.now(),payload=JSON.stringify({type:"event",topic,data:event,timestamp});for(let[clientId,client]of clients){if(client.userId!==userId)continue;if(!client.subscribedTopics.has("*")&&!client.subscribedTopics.has(topic))continue;try{client.ws.send(payload)}catch{clients.delete(clientId)}}}function broadcastPresenceToOthers(userId,status){let debounceKey=`${userId}:${status}`,now=Date.now(),lastBroadcast=presenceBroadcastDebounce.get(debounceKey);if(lastBroadcast&&now-lastBroadcast<presenceDebounceMs)return;if(presenceBroadcastDebounce.set(debounceKey,now),presenceBroadcastDebounce.size>1000){let cutoff=now-presenceDebounceMs*2;for(let[key2,timestamp]of presenceBroadcastDebounce)if(timestamp<cutoff)presenceBroadcastDebounce.delete(key2)}let connectedUserIds=getConnectedUserIds();for(let targetUserId of connectedUserIds)if(targetUserId!==userId)broadcastToUserNoAck(targetUserId,"user-presence",{type:"user-presence",data:{userId,status}})}async function handleClientAck(clientId,messageId){if(!ackEnabled)return!0;let client=clients.get(clientId);if(!client)return!1;if(client.pendingAcks.get(messageId))client.pendingAcks.delete(messageId);if(client.userId)return acknowledgeMessage(redis,client.userId,messageId,ackTtl);return!0}async function deliverPendingMessages(userId,clientId){if(!ackEnabled)return 0;let client=clients.get(clientId);if(!client||client.userId!==userId)return 0;let pendingMessages=await getPendingMessagesForUser(redis,userId);if(pendingMessages.length===0)return 0;logger2.info("[PubSub] Delivering pending messages",{userId,count:pendingMessages.length});let deliveredCount=0;for(let message of pendingMessages){if(!client.subscribedTopics.has("*")&&!client.subscribedTopics.has(message.topic))continue;let payload=JSON.stringify({type:"event",messageId:message.messageId,topic:message.topic,data:message.data,timestamp:message.sentAt,isRedelivery:!0});try{client.ws.send(payload),client.pendingAcks.set(message.messageId,{sentAt:Date.now(),retryCount:message.retryCount+1}),await incrementRetryCount(redis,userId,message.messageId,ackTtl,maxRetries),deliveredCount++}catch{break}}return deliveredCount}return{generateClientId,registerClient,unregisterClient,updateClientTopics,getClientsByUser,getConnectedUserIds,getClientCount,sendToClient,broadcastEvent,broadcastToUser,broadcastToUserNoAck,broadcastPresenceToOthers,handleClientAck,deliverPendingMessages,getPendingMessageCount:(userId)=>getPendingMessageCount(redis,userId)}}var OPAQUE_RUN=/[A-Za-z0-9_-]{24,}(?:\.[A-Za-z0-9_-]{8,}){0,2}/g;function previewOf2(frame){return frame.slice(0,120).replace(OPAQUE_RUN,(match)=>`[redacted ${match.length} chars]`)}function classifyClientFrame(message){if(typeof message==="string"){if(message.trim()==="")return{kind:"ignore"};let parsed;try{parsed=JSON.parse(message)}catch{return{kind:"malformed",preview:previewOf2(message)}}return asMessage(parsed,message.length)}if(message&&typeof message==="object")return asMessage(message,approximateSize(message));return{kind:"ignore"}}function approximateSize(value2){if(ArrayBuffer.isView(value2))return value2.byteLength;if(value2 instanceof ArrayBuffer)return value2.byteLength;try{return JSON.stringify(value2)?.length??0}catch{return 0}}function asMessage(value2,size){if(!value2||typeof value2!=="object"||Array.isArray(value2))return{kind:"ignore"};let record3=value2,type4=record3.type;if(typeof type4!=="string"||type4==="")return{kind:"ignore"};return{kind:"message",parsed:record3,type:type4,size}}function tokensMatch(a,b){let ba=Buffer.from(a),bb=Buffer.from(b);if(ba.length!==bb.length)return!1;return timingSafeEqual3(ba,bb)}var HEARTBEAT_WS_TYPES=new Set(["ping","pong","ack"]);function buildHandshakeHeaders(rawHeaders,queryToken){let headers=new Headers;if(rawHeaders&&typeof rawHeaders==="object"){for(let[key2,value2]of Object.entries(rawHeaders))if(typeof value2==="string")headers.set(key2,value2)}if(queryToken)headers.set("authorization",`Bearer ${queryToken}`),headers.delete("cookie");return headers}function createPubSubRoutes(config){let{logger:logger2,getLiveMonitoringService}=config,clientManager=createClientManager(config);if(config.ack.enabled)initAckCleanup();let cleanupTimer=null,daprApiToken=config.daprApiToken||process.env.APP_API_TOKEN||process.env.DAPR_API_TOKEN,warnedNoDaprToken=!1;recordBootStep(()=>{if(config.cleanupIntervalMs>0)cleanupTimer=setInterval(()=>{logger2.info("[PubSub] Periodic cleanup running",{totalClients:clientManager.getClientCount()})},config.cleanupIntervalMs);logger2.info("[PubSub] Routes initialized",{basePath:config.basePath,wsPath:config.wsPath,ackEnabled:config.ack.enabled,presenceEnabled:config.presence.enabled})}),recordShutdownStep(()=>{if(cleanupTimer)clearInterval(cleanupTimer),cleanupTimer=null});let routePostTopic=defineRoute({method:"POST",path:"/:topic",handler:async({params,body,request,set:set2})=>{let topic=params.topic;if(daprApiToken){let presented=request.headers.get("dapr-api-token")??"";if(!tokensMatch(presented,daprApiToken))return logger2.warn("[PubSub] Rejected Dapr event: missing/invalid dapr-api-token",{topic}),set2.status=403,{status:"DROP"}}else if(!warnedNoDaprToken)warnedNoDaprToken=!0,logger2.warn("[PubSub] Dapr subscription endpoint is UNAUTHENTICATED \u2014 set config.pubsub.daprApiToken or the APP_API_TOKEN/DAPR_API_TOKEN env so only the Dapr sidecar can inject events");let eventData;try{if(!body){let text2=await request.text();eventData=JSON.parse(text2)}else eventData=body}catch{return set2.status=200,{status:"DROP"}}logger2.info("[PubSub] Dapr event received",{topic,eventId:eventData.id,source:eventData.source});let userId=eventData.data?.user_id;if(userId)clientManager.broadcastToUser(userId,topic,eventData);else clientManager.broadcastEvent(topic,eventData);return getLiveMonitoringService?.()?.recordDaprEvent("pubsub_subscribe",{topic,success:!0,metadata:{eventId:eventData.id,source:eventData.source,userId:userId||null}}),set2.status=200,{status:"SUCCESS"}}}),routes=collectRoutes([{prefix:config.basePath,routes:[routePostTopic]}]),socketRoute=defineWebSocketRoute({path:config.wsPath,idleTimeout:config.wsIdleTimeout,async open(ws){let data=ws.data,query=data.query,userId;if(config.authenticate){let handshakeHeaders=buildHandshakeHeaders(data.headers,query.token),authResult=await config.authenticate(handshakeHeaders);if(!authResult||!("userId"in authResult)){let reason=authResult?.reason??"unspecified";ws.send(JSON.stringify({type:"error",error:"Unauthorized: a valid session is required for this connection",reason,timestamp:Date.now()})),ws.close(4001,`Unauthorized: ${reason}`),getLiveMonitoringService?.()?.recordWsEvent("error",{success:!1,error:`Unauthorized WebSocket handshake: ${reason}`,metadata:{reason}});return}userId=authResult.userId}else userId=query.userId;if(!userId){ws.send(JSON.stringify({type:"error",error:"userId is required for WebSocket connection",timestamp:Date.now()})),ws.close(4001,"userId is required"),getLiveMonitoringService?.()?.recordWsEvent("error",{success:!1,error:"userId is required"});return}let clientId=clientManager.generateClientId(),topics=query.topics?.split(",").map((t)=>t.trim())||["*"];ws.data.clientId=clientId,ws.data.openedAt=Date.now();let registration=clientManager.registerClient(clientId,ws,userId,topics);if(ws.send(JSON.stringify({type:"connected",clientId,subscribedTopics:registration.accepted,...registration.dropped.length>0?{droppedTopics:registration.dropped,topicCap:config.maxTopicsPerClient}:{},timestamp:Date.now()})),getLiveMonitoringService?.()?.recordWsEvent("connection",{clientId,userId,topic:topics.join(","),success:!0}),config.presence.enabled)clientManager.broadcastPresenceToOthers(userId,"online");let connectedUserId=userId;if(config.ack.enabled&&connectedUserId)clientManager.getPendingMessageCount(connectedUserId).then((count2)=>{if(count2>0)logger2.info("[PubSub] Delivering pending messages",{userId:connectedUserId,count:count2}),clientManager.deliverPendingMessages(connectedUserId,clientId).catch(()=>{})}).catch(()=>{})},message(ws,message){let clientId=ws.data.clientId,frame=classifyClientFrame(message);if(frame.kind==="ignore")return;if(frame.kind==="malformed"){logger2.debug("[PubSub] Failed to parse client message",{clientId,frame:frame.preview});return}let{parsed}=frame;try{if(!HEARTBEAT_WS_TYPES.has(frame.type))getLiveMonitoringService?.()?.recordWsEvent("message",{clientId,messageType:frame.type,success:!0,dataSize:frame.size});switch(frame.type){case"subscribe":if(Array.isArray(parsed.topics)){let update=clientManager.updateClientTopics(clientId,parsed.topics);clientManager.sendToClient(clientId,{type:"subscribed",topics:update.accepted,...update.dropped.length>0?{droppedTopics:update.dropped,topicCap:config.maxTopicsPerClient}:{},timestamp:Date.now()})}break;case"unsubscribe":logger2.info("[PubSub] Unsubscribe request",{clientId,topics:parsed.topics});break;case"ping":clientManager.sendToClient(clientId,{type:"pong",timestamp:Date.now()});break;case"ack":if(parsed.messageId)clientManager.handleClientAck(clientId,parsed.messageId).catch(()=>{});break}}catch(error3){logger2.error("[PubSub] Client message handler failed",error3,{clientId,messageType:frame.type})}},close(ws,code,reason){let state=ws.data,clientId=state.clientId,openedAt=state.openedAt,livedMs=typeof openedAt==="number"?Date.now()-openedAt:null;if(!clientId)return;clientManager.unregisterClient(clientId),getLiveMonitoringService?.()?.recordWsEvent("close",{clientId,success:!0,metadata:{code,reason:reason||"",livedMs,shortLived:livedMs!==null&&livedMs<60000}}),logger2.info("[PubSub] Client disconnected",{clientId,code,reason,livedMs})}});return{routes,socket:socketRoute,clientManager}}init_JWT();init_utils5();init_sessionBinding();function classifyRefusal(error3){if(!error3)return"unspecified";switch(error3){case"Token expired":return"expired";case"Invalid signature":return"invalid-signature";case"Invalid token format: expected 3 parts":case"Invalid token format: missing parts":case"Invalid header: failed to decode":case"Invalid header: typ must be JWT":case"Invalid payload: failed to decode":return"malformed";case"Invalid issuer":case"Invalid audience":return"wrong-audience";case"Token issued in the future":return"not-yet-valid";default:if(error3.startsWith("Unsupported algorithm:"))return"malformed";return"unspecified"}}function createHandshakeAuthenticator(deps){return async(headers)=>{let wsTokens=parseTokenValuesFromHeaders(headers,deps.tokenNames);if(!wsTokens.access_token)return deps.logger.debug("[PubSub] WS handshake rejected: no access token presented"),{reason:"no-token"};let jwtResult=verifyJWT(wsTokens.access_token,deps.accessTokenSecret,{issuer:deps.issuer,audience:deps.audience});if(!jwtResult.valid){let reason=classifyRefusal(jwtResult.error);return deps.logger.warn("[PubSub] WS handshake rejected: JWT verification failed",{reason:jwtResult.error,classified:reason}),{reason}}let sub=jwtResult.payload?.sub;if(typeof sub!=="string"||sub.length===0)return{reason:"token-without-subject"};if(!deps.requireLiveSession)return{userId:sub};let boundSessionId=typeof jwtResult.payload.sessionId==="string"?jwtResult.payload.sessionId:"";if(!boundSessionId)return deps.logger.warn("[PubSub] WS handshake rejected: token carries no session",{userId:sub}),{reason:"session-unbound"};let tenant=typeof jwtResult.payload.tenant==="string"?jwtResult.payload.tenant:void 0,sessionsTable=deps.resolveSessionsTable?.(tenant)??null,session;try{session=await refreshSessionIsAlive({sessionId:boundSessionId,db:deps.db,sessionsTable})}catch(error3){return deps.logger.warn("[PubSub] WS handshake rejected: session store unreadable",{userId:sub,sessionId:boundSessionId,error:error3 instanceof Error?error3.message:String(error3)}),{reason:"session-unreadable"}}if(!session.alive)return deps.logger.warn("[PubSub] WS handshake rejected: session is no longer active",{userId:sub,sessionId:boundSessionId,reason:session.reason}),{reason:"session-revoked"};if(session.userId&&session.userId!==sub)return deps.logger.warn("[PubSub] WS handshake rejected: token subject is not the session owner",{userId:sub,sessionId:boundSessionId,sessionOwner:session.userId}),{reason:"session-subject-mismatch"};return{userId:sub}}}init_esm3();init_Secrets();var timed=async(fn)=>{let started=performance.now();return{result:await fn(),durationMs:Math.round(performance.now()-started)}},postForm=async(url,body)=>{let response=await fetch(url,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams(body).toString(),signal:AbortSignal.timeout(12000)}),text2=await response.text(),json={};try{json=JSON.parse(text2)}catch{json=Object.fromEntries(new URLSearchParams(text2))}return{status:response.status,json,text:text2}},describeError=(error3)=>{if(error3 instanceof Error){if(error3.name==="TimeoutError")return"Provider did not respond within 12s";return error3.message}return String(error3)},MICROSOFT_TOKEN_URL=(tenant)=>`https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,DEFAULT_TOKEN_URLS={google:"https://oauth2.googleapis.com/token",github:"https://github.com/login/oauth/access_token"},describeRedirectUri=(value2)=>{let trimmed=value2?.trim();if(!trimmed)return"The reply URL (Redirect URI) is empty \u2014 sign-in cannot complete";if(/^[A-Z][A-Z0-9_]{2,}$/.test(trimmed))return`The reply URL looks like an environment variable name ("${trimmed}") and that variable is not set`;let parsed;try{parsed=new URL(trimmed)}catch{return`The reply URL is not a valid URL: "${trimmed}"`}if(parsed.protocol!=="https:"&&parsed.hostname!=="localhost")return`The reply URL must be https (it is "${parsed.protocol}//")`;return null};async function testOAuthProvider(provider,config){if(!config.clientId||!config.clientSecret)return{ok:!1,status:"failed",message:"Client ID and Client Secret must both be filled in",durationMs:0};let redirectProblem=describeRedirectUri(config.redirectUri);if(redirectProblem)return{ok:!1,status:"failed",message:redirectProblem,durationMs:0};try{if(provider==="microsoft"){let tenant=config.tenantId||"common";if(tenant==="common"||tenant==="organizations"||tenant==="consumers")return await probeWithBogusCode(MICROSOFT_TOKEN_URL(tenant),config);let{result,durationMs}=await timed(()=>postForm(MICROSOFT_TOKEN_URL(tenant),{grant_type:"client_credentials",client_id:config.clientId,client_secret:config.clientSecret,scope:"https://graph.microsoft.com/.default"}));if(result.status===200&&result.json.access_token)return{ok:!0,status:"ok",message:`Entra credentials verified (tenant ${tenant})`,durationMs};let code=String(result.json.error??result.status);return{ok:!1,status:"failed",message:String(result.json.error_description??result.text).slice(0,400),code,durationMs}}let tokenUrl=config.tokenUrl||DEFAULT_TOKEN_URLS[provider];if(!tokenUrl)return{ok:!1,status:"unknown",message:`No token URL is known for "${provider}" \u2014 enter a tokenUrl`,durationMs:0};return await probeWithBogusCode(tokenUrl,config)}catch(error3){return{ok:!1,status:"failed",message:describeError(error3),durationMs:0}}}async function probeWithBogusCode(tokenUrl,config){let{result,durationMs}=await timed(()=>postForm(tokenUrl,{grant_type:"authorization_code",code:"nucleus-credential-probe",client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri||"https://localhost/callback"})),code=String(result.json.error??""),description=String(result.json.error_description??result.text).slice(0,400);if(code==="invalid_grant"||code==="bad_verification_code")return{ok:!0,status:"ok",message:"The provider accepted these credentials",code,durationMs};if(code==="invalid_client"||code==="unauthorized_client"||result.status===401)return{ok:!1,status:"failed",message:description||"The provider refused this Client ID/Secret pair",code:code||"invalid_client",durationMs};if(description.includes("AADSTS7000215")||description.includes("AADSTS700016"))return{ok:!1,status:"failed",message:description,code,durationMs};return{ok:!1,status:"unknown",message:description||"The provider returned an unexpected answer",code:code||String(result.status),durationMs}}async function testStorageProvider(provider){if(!provider)return{ok:!1,status:"failed",message:"No storage provider is configured",durationMs:0};try{let{result,durationMs}=await timed(()=>provider.isAvailable());return result?{ok:!0,status:"ok",message:`Connection verified: ${provider.describe}`,durationMs}:{ok:!1,status:"failed",message:`Unreachable: ${provider.describe} \u2014 check the server, the share name and the credentials`,durationMs}}catch(error3){return{ok:!1,status:"failed",message:describeError(error3),durationMs:0}}}async function testEmailProvider(service,recipient){if(!service)return{ok:!1,status:"failed",message:"No email provider is configured",durationMs:0};if(!service.isAvailable())return{ok:!1,status:"failed",message:"The provider could not be started with these credentials",durationMs:0};if(!recipient)return{ok:!0,status:"ok",message:"The provider started \u2014 enter a recipient address to send for real",durationMs:0};try{let{result,durationMs}=await timed(()=>service.sendEmail({to:recipient,subject:"Nucleus connection test",html:"<p>This email was sent by a connection test run from the admin panel.</p>"}));return result.success?{ok:!0,status:"ok",message:`Test email sent to ${recipient}`,durationMs}:{ok:!1,status:"failed",message:result.error??"Sending failed",durationMs}}catch(error3){return{ok:!1,status:"failed",message:describeError(error3),durationMs:0}}}init_routeCollector();init_godminGuard();var SECRET_KINDS=["text","password","json","connection_string","pem","boolean","list"];var SCOPE_PATTERN=/^[A-Za-z0-9_.-]{1,200}$/,KEY_PATTERN=/^[A-Za-z0-9_-]{1,120}$/,fail5=(set2,status,message)=>{return set2.status=status,{isSuccess:!1,message,data:null}};function createSecretsRoutes(routeConfig){let{logger:logger2,basePath,db,schemaTables,getService,resolvedOptions,getEmailService,getStorageProvider:getStorageProvider2}=routeConfig,guard4=createGodminGuard({logger:logger2,db,schemaTables,area:"secrets management",allowedRoles:routeConfig.adminRoles}),requireService=(set2)=>{let service=getService();if(!service)return set2.status=503,null;return service},actorOf=(request)=>request.headers.get("x-user-id")??void 0,declared=[defineRoute({method:"GET",path:"/slots",handler:async({set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled \u2014 set secrets.enabled and provide an encryption key",data:null};let slots=await service.describe(resolvedOptions),groups=[...new Set(slots.map((s)=>s.group))].sort();return{isSuccess:!0,message:"Credential slots",data:{slots,groups,counts:{total:slots.length,fromDb:slots.filter((s)=>s.source==="db").length,fromEnv:slots.filter((s)=>s.source==="env").length,missing:slots.filter((s)=>s.source==="missing").length,missingRequired:slots.filter((s)=>s.source==="missing"&&s.required).length}}}},detail:{tags:["Secrets"],summary:"List credential slots discovered from the running config",description:"Slots are derived by walking the config tree, so a new integration appears without a code change. Values are never returned \u2014 only a masked preview and the resolution source."}}),defineRoute({method:"GET",path:"/",handler:async({set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};return{isSuccess:!0,message:"Stored credentials",data:{secrets:await service.list()}}},detail:{tags:["Secrets"],summary:"List stored credentials",description:"Metadata only \u2014 the encrypted value is never returned."}}),defineRoute({method:"PUT",path:"/:scope/:key",handler:async({params,body,set:set2,request})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let{scope,key:key2}=params;if(!SCOPE_PATTERN.test(scope))return fail5(set2,400,"Invalid scope");if(!KEY_PATTERN.test(key2))return fail5(set2,400,"Invalid key");let value2=body.value;if(typeof value2!=="string"||value2.length===0)return fail5(set2,400,"value is required");if(value2.length>65536)return fail5(set2,400,"value exceeds 64KB");if(body.kind==="json")try{JSON.parse(value2)}catch{return fail5(set2,400,"value is not valid JSON")}let saved=await service.set({scope,key:key2,value:value2,kind:body.kind,description:body.description,actorId:actorOf(request)});if(!saved)return fail5(set2,500,"Could not store the credential");return{isSuccess:!0,message:"Credential stored",data:{secret:saved}}},params:Type.Object({scope:Type.String(),key:Type.String()}),body:Type.Object({value:Type.String(),kind:Type.Optional(Type.Union(SECRET_KINDS.map((kind)=>Type.Literal(kind)))),description:Type.Optional(Type.String())}),detail:{tags:["Secrets"],summary:"Set or rotate a credential",description:"Stores the value encrypted (AES-256-GCM). Rotating retains the previous value for a one-step rollback. Takes effect within seconds on every pod \u2014 no restart."}}),defineRoute({method:"POST",path:"/:scope/:key/rollback",handler:async({params,set:set2,request})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let restored=await service.rollback(params.scope,params.key,actorOf(request));if(!restored)return fail5(set2,404,"No previous value to roll back to");return{isSuccess:!0,message:"Previous credential restored",data:{secret:restored}}},params:Type.Object({scope:Type.String(),key:Type.String()}),detail:{tags:["Secrets"],summary:"Roll back to the value replaced by the last rotation"}}),defineRoute({method:"DELETE",path:"/:scope/:key",handler:async({params,set:set2,request})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};if(!await service.remove(params.scope,params.key,actorOf(request)))return fail5(set2,404,"Credential not found");return{isSuccess:!0,message:"Credential removed \u2014 the config falls back to env or literal",data:null}},params:Type.Object({scope:Type.String(),key:Type.String()}),detail:{tags:["Secrets"],summary:"Delete a stored credential",description:"Resolution falls back to the env var or literal declared in config."}}),defineRoute({method:"POST",path:"/test",handler:async({body,set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let{target:target2}=body,result;if(target2==="email")result=await testEmailProvider(getEmailService?.()??null,body.recipient);else if(target2==="oauth"){let provider=body.provider;if(!provider)return fail5(set2,400,"provider is required for an oauth test");let scope=`authentication.oauth.providers.${provider}`,providerConfig=(resolvedOptions.authentication?.oauth?.providers??{})[provider]??{},declaredString=(key2)=>{let raw=providerConfig[key2];return typeof raw==="string"?raw:void 0};result=await testOAuthProvider(provider,{clientId:service.value(scope,"clientId",declaredString("clientId")),clientSecret:service.value(scope,"clientSecret",declaredString("clientSecret")),tenantId:service.value(scope,"tenantId",declaredString("tenantId")),tokenUrl:declaredString("tokenUrl"),redirectUri:service.value(scope,"redirectUri",declaredString("redirectUri"))})}else if(target2==="storage")result=await testStorageProvider(getStorageProvider2?.()??null);else return fail5(set2,400,`Unsupported test target "${target2}"`);return logger2.info("[Secrets] Connection test",{target:target2,provider:body.provider,status:result.status,durationMs:result.durationMs}),{isSuccess:result.ok,message:result.message,data:{result}}},body:Type.Object({target:Type.Union([Type.Literal("oauth"),Type.Literal("email"),Type.Literal("storage")]),provider:Type.Optional(Type.String()),recipient:Type.Optional(Type.String())}),detail:{tags:["Secrets"],summary:"Test a credential against the real provider",description:"Backend-to-backend only. OAuth uses client_credentials where the provider supports it and a client-authentication probe otherwise; email optionally sends a real test message."}}),defineRoute({method:"POST",path:"/reencrypt",handler:async({body,set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};if(!body.newMasterKey||body.newMasterKey.length<16)return fail5(set2,400,"newMasterKey must be at least 16 characters");let result=await service.reencryptAll(body.newMasterKey),uncovered="Credentials stored outside this store \u2014 an integration connection\u2019s token, client secret and service password \u2014 are NOT re-encrypted by this. Re-enter them from the integrations panel after the new key is deployed.";return{isSuccess:result.failed.length===0,message:result.failed.length===0?`Re-encrypted ${result.migrated} credentials \u2014 deploy the new key before the next restart. ${uncovered}`:`Nothing was re-encrypted: ${result.failed.length} credential(s) could not be read with the current key, so none were rewritten.`,data:{...result,uncovered}}},body:Type.Object({newMasterKey:Type.String()}),detail:{tags:["Secrets"],summary:"Re-encrypt every credential under a new master key",description:"Rotates the key protecting the store. The new key must be deployed to the environment before the next restart, or nothing will decrypt."}}),defineRoute({method:"GET",path:"/discover",handler:()=>({isSuccess:!0,message:"Slots discovered from the running config",data:{slots:discoverSlots(resolvedOptions).map(({configValue:_configValue,...slot})=>slot)}}),detail:{tags:["Secrets"],summary:"Show which credential slots the config declares",description:"Diagnostics \u2014 resolution state is not consulted and no value is read."}}),defineRoute({method:"POST",path:"/storage/migrate",handler:async(ctx)=>{let body=ctx.body,build=routeConfig.buildStorageProvider;if(!build)return fail5(ctx.set,503,"Storage is not configured");let destination=build(body.to),source=build(body.to==="smb"?"local":"smb");if(!source||!destination)return fail5(ctx.set,503,"Storage is not configured");if(!await destination.isAvailable())return fail5(ctx.set,422,`The destination store is unreachable (${destination.describe}) \u2014 test the connection first`);if(body.dryRun){let preview=await migrateStorage({from:source,to:Object.assign(Object.create(Object.getPrototypeOf(destination)),destination,{exists:async()=>!1,write:async()=>{},ensureDir:async()=>{}}),maxFiles:1e5},{logger:logger2});return{isSuccess:!0,message:`${preview.copied} file${preview.copied===1?"":"s"} would be copied`,data:{preview}}}let report=await migrateStorage({from:source,to:destination},{logger:logger2}),clean=report.failed===0&&!report.truncated;return logger2.warn("[Storage] Migration run from the admin panel",{to:body.to,copied:report.copied,skipped:report.skipped,failed:report.failed,truncated:report.truncated}),{isSuccess:clean,message:clean?`${report.copied} file${report.copied===1?"":"s"} copied, ${report.skipped} already present`:`${report.copied} copied, ${report.failed} failed${report.truncated?", list truncated":""}`,data:{report}}},body:Type.Object({to:Type.Union([Type.Literal("local"),Type.Literal("smb")]),dryRun:Type.Optional(Type.Boolean())}),detail:{tags:["Secrets"],summary:"Copy every stored file to the other backend",description:"Copy-only: the source is never deleted, and a file already on the target is skipped so a re-run resumes. Run BEFORE switching storage.provider."}})];return collectRoutes([{prefix:basePath,guards:[guard4],routes:declared}])}function readAttemptedIdentity(parsedBody,rawBodyText,contentType){let fromJson=parsedBody.email;if(typeof fromJson==="string"&&fromJson.trim())return fromJson;if(!rawBodyText)return;if(!(contentType||"").toLowerCase().includes("application/x-www-form-urlencoded"))return;try{let value2=new URLSearchParams(rawBodyText).get("email");return value2?.trim()?value2:void 0}catch{return}}var snakeToCamel4=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase());function warnOnEntityBasePathCollision(feature,basePath,entities,logger2){let segment=basePath.replace(/^\/+|\/+$/g,"");if(!segment||segment.includes("/"))return!1;let clash=(entities??[]).find((entity)=>snakeToCamel4(entity.table_name)===segment);if(!clash)return!1;return logger2.error(`[${feature}] basePath "${basePath}" is also the generated entity route for "${clash.table_name}". Both are registered and one will shadow the other. Move the feature (e.g. "/nucleus${basePath}") or rename the entity.`),!0}function requiredBodyCeiling(input){if(!input.storageEnabled)return null;let perFile=input.maxFileSizeBytes;if(!perFile||!Number.isFinite(perFile)||perFile<=0)return null;let files=Math.max(1,Math.floor(input.maxFiles??1));return perFile*files+8388608}function bodyCeilingComplaint(input){if(input.required===null)return null;if((input.configured??134217728)>=input.required)return null;let mb2=(bytes)=>`${Math.round(bytes/1048576)} MB`,source=input.configured==null?`the host passed no \`serve.maxRequestBodySize\`, so Bun's default of ${mb2(134217728)} applies`:`the host passed ${mb2(input.configured)}`;return`Uploads are configured up to ${mb2(input.required)}, but ${source}. `+"Anything larger is refused by the server before this application sees it \u2014 "+`the upload appears to run and then fails with nothing to show. Fix: raise \`storage.maxFileSizeBytes\` / \`security.maxRequestBodyBytes\` in the config, or pass \`maxRequestBodyBytes\` to \`createNucleusServer\`. The server needs ${input.required} bytes of headroom.`}function bunBodyCeilingFor(input){let wanted=Math.max(input.storageRequired??0,input.guardCeiling??0);return wanted>134217728?wanted:void 0}var APP_BASELINE_BYTES=268435456,COPIES_IN_FLIGHT=2;function memoryCeilingComplaint(input){if(input.required===null||input.cgroupLimit===null||input.cgroupLimit<=0)return null;let needed=input.required*COPIES_IN_FLIGHT+APP_BASELINE_BYTES;if(input.cgroupLimit>=needed)return null;let mb2=(bytes)=>`${Math.round(bytes/1048576)} MB`,affordable=Math.max(0,Math.floor((input.cgroupLimit-APP_BASELINE_BYTES)/COPIES_IN_FLIGHT));return`Uploads are configured up to ${mb2(input.required)}, but this container is capped at ${mb2(input.cgroupLimit)} and an upload is held in memory twice on top of the ~${mb2(APP_BASELINE_BYTES)} `+`the app already holds \u2014 it needs ~${mb2(needed)}. The kernel will kill the process mid-upload. Fix: raise the container's memory limit to at least ${mb2(needed)}, or lower storage.maxFileSizeBytes to about ${mb2(affordable)}.`}var ipMatches=(ip,entry)=>entry.includes("/")?isIpInCidr(ip,entry):ip===entry;function resolveClientIp(socketIp,forwardedFor,trustedProxies=[]){let peer=socketIp?.trim()||"",chain=(forwardedFor||"").split(",").map((s)=>s.trim()).filter(Boolean);if(!peer)return chain[0]||"unknown";if(trustedProxies.length===0)return peer;let trusted=(ip)=>trustedProxies.some((p)=>ipMatches(ip,p));if(!trusted(peer))return peer;for(let i=chain.length-1;i>=0;i--){let hop=chain[i];if(hop&&!trusted(hop))return hop}return chain[0]||peer}var isNonDiscriminatingPeer=(ip)=>ip==="::1"||ip==="unknown"||/^(::ffff:)?127\./.test(ip);function createPeerDiversityWatcher(sampleSize=200){let seen=new Set,seenCount=0,settled=!1;return{observe(ip){if(settled)return null;if(seenCount++,seen.add(ip),seen.size>1)return settled=!0,null;if(seenCount>=sampleSize)return settled=!0,ip;return null}}}var DEFAULTS={cookieName:"csrf_token",headerName:"x-csrf-token"},resolveCsrfConfig=(raw)=>{if(raw===!0)return{enabled:!0,...DEFAULTS};if(!raw)return{enabled:!1,...DEFAULTS};return{enabled:raw.enabled!==!1,cookieName:raw.cookieName||DEFAULTS.cookieName,headerName:(raw.headerName||DEFAULTS.headerName).toLowerCase()}},parseCookies=(cookieHeader)=>{let out={};if(!cookieHeader)return out;for(let part of cookieHeader.split(";")){let trimmed=part.trim(),eq36=trimmed.indexOf("=");if(eq36>0)out[trimmed.slice(0,eq36)]=trimmed.slice(eq36+1)}return out},SAFE_METHODS=new Set(["GET","HEAD","OPTIONS"]),isStateChanging=(method)=>!SAFE_METHODS.has(method.toUpperCase()),requiresCsrf=(params)=>{if(!isStateChanging(params.method))return!1;if(params.hasAuthorizationHeader)return!1;return params.authCookieNames.some((n)=>!!params.cookies[n])},csrfTokenMatches=(csrfCookie,csrfHeader)=>{if(!csrfCookie||!csrfHeader)return!1;return csrfCookie===csrfHeader};var SENSITIVE_HEADER_NAMES=new Set(["authorization","proxy-authorization","cookie","set-cookie","x-access-token","x-refresh-token","x-session-id","x-user-id","x-user-roles","x-user-claims","x-user-email","x-api-key","x-api-key-id","x-api-key-owner-type","x-auth-type"]);function redactSensitiveHeaders(headers){let out={};return headers.forEach((value2,key2)=>{out[key2]=SENSITIVE_HEADER_NAMES.has(key2.toLowerCase())?"[REDACTED]":value2}),out}init_sensitiveColumns();var TENANT_CLAIM="tenant",verifyTenantBinding=(tokenTenant,resolvedSchema,mode="lenient")=>{if(mode==="off")return{ok:!0};if(!resolvedSchema)return{ok:!0};let claim=typeof tokenTenant==="string"&&tokenTenant.length>0?tokenTenant:void 0;if(!claim)return mode==="strict"?{ok:!1,reason:"token is not bound to a tenant"}:{ok:!0};return claim===resolvedSchema?{ok:!0}:{ok:!1,reason:"token tenant does not match the requested tenant"}};init_storage2();init_routeMount();init_esm3();init_routeCollector();import{eq as eq36}from"drizzle-orm";var RESERVED_SUBDOMAINS=["www","api","app","admin","dashboard","mail","ftp","cdn","static","assets","docs","help","support","status","blog","dev","staging","test","demo","localhost"],DEFAULT_SELF_SIGNUP_LIMITS={maxSignupsPerIpPerWindow:3,ipWindowMs:3600000,maxTenantsPerEmail:1};var col9=(table,name2)=>table[name2];function createCheckSubdomainRoute(config){let declared=[defineRoute({method:"GET",path:"/tenants/check-subdomain/:subdomain",handler:async(ctx)=>{let{subdomain}=ctx.params,normalized=subdomain.toLowerCase().trim();if(!/^[a-z][a-z0-9-]*$/.test(normalized)||normalized.length<2||normalized.length>63)return ctx.set.status=400,{isSuccess:!1,message:"Invalid subdomain format. Must start with a letter, contain only lowercase letters, numbers, and hyphens, and be 2-63 characters.",data:{available:!1,subdomain:normalized}};if(RESERVED_SUBDOMAINS.includes(normalized))return{isSuccess:!0,message:"Subdomain is reserved",data:{available:!1,subdomain:normalized}};let tenantRegistry=config.getTenantRegistry(),db=config.getDb();if(tenantRegistry){if(tenantRegistry.getActiveTenants().find((t)=>t.subdomain===normalized))return{isSuccess:!0,message:"Subdomain is already taken",data:{available:!1,subdomain:normalized}}}if(db&&tenantRegistry){let tenantsTable=tenantRegistry.getMainContext().schemaTables.tenants;if(tenantsTable)try{if((await db.select().from(tenantsTable).where(eq36(col9(tenantsTable,"subdomain"),normalized)).limit(1)).length>0)return{isSuccess:!0,message:"Subdomain is already taken",data:{available:!1,subdomain:normalized}}}catch{}}return{isSuccess:!0,message:"Subdomain is available",data:{available:!0,subdomain:normalized}}},params:Type.Object({subdomain:Type.String({minLength:2,maxLength:63})}),detail:{tags:["Tenants"],summary:"Check subdomain availability",description:"Public endpoint to check if a subdomain is available for registration. No authentication required."}})];return collectRoutes([{routes:declared}])}init_esm3();init_Logger2();init_routeCollector();import{eq as eq37}from"drizzle-orm";var col10=(table,name2)=>table[name2];function createManageRoutes(config){let log3=config.logger.scoped(TENANT_SUSPEND),declared=[defineRoute({method:"GET",path:"/tenants",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available",data:null};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required",data:null};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required",data:null};let allTenants=[...tenantRegistry.getActiveTenants(),...Array.from(getAllTenants(tenantRegistry)).filter((t)=>t.status!=="active")];return{success:!0,message:`Found ${allTenants.length} tenants`,data:{items:allTenants.map((t)=>({id:t.id,subdomain:t.subdomain,schemaName:t.schemaName,companyId:t.companyId,companyName:t.companyName,godAdminEmail:t.godAdminEmail,status:t.status,plan:t.plan,domain:t.domain,maxUsers:t.maxUsers,provisionedAt:t.provisionedAt,suspendedAt:t.suspendedAt})),totalCount:allTenants.length}}},detail:{tags:["Tenants"],summary:"List tenants",description:"List all tenants in the platform. Requires godmin privileges."}}),defineRoute({method:"GET",path:"/tenants/:id",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available",data:null};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required",data:null};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required",data:null};let{id}=ctx.params,tenant=tenantRegistry.getTenantById(id);if(!tenant)return ctx.set.status=404,{success:!1,message:"Tenant not found",data:null};let features=tenantRegistry.getTenantFeatures(id);return{success:!0,message:"Tenant found",data:{...tenant,features:features.map((f)=>({featureName:f.featureName,enabled:f.enabled}))}}},detail:{tags:["Tenants"],summary:"Get tenant details",description:"Get detailed information about a specific tenant. Requires godmin privileges."}}),defineRoute({method:"POST",path:"/tenants/:id/suspend",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required"};let{id}=ctx.params,body=ctx.body,tenant=tenantRegistry.getTenantById(id);if(!tenant)return ctx.set.status=404,{success:!1,message:"Tenant not found"};if(tenant.status==="suspended")return ctx.set.status=400,{success:!1,message:"Tenant is already suspended"};let mainContext=tenantRegistry.getMainContext(),tenantsTable=mainContext.schemaTables.tenants;if(!tenantsTable)return ctx.set.status=503,{success:!1,message:"Tenants table not available"};let now=new Date;await db.update(tenantsTable).set({status:"suspended",suspendedAt:now,suspendedReason:body.reason||"Suspended by godmin",updatedAt:now}).where(eq37(col10(tenantsTable,"id"),id));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId:id,eventType:"suspended",eventData:JSON.stringify({reason:body.reason||"Suspended by godmin"}),performedBy:requestingUserId,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",createdAt:now,updatedAt:now})}catch{log3.warn("Failed to record suspend event")}return await tenantRegistry.refreshTenant(id),log3.info("Tenant suspended",{tenantId:id,reason:body.reason}),{success:!0,message:`Tenant "${tenant.subdomain}" suspended`}},body:Type.Object({reason:Type.Optional(Type.String())}),detail:{tags:["Tenants"],summary:"Suspend tenant",description:"Suspend a tenant, preventing all access. Requires godmin privileges."}}),defineRoute({method:"POST",path:"/tenants/:id/reactivate",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required"};let{id}=ctx.params,tenant=tenantRegistry.getTenantById(id);if(!tenant)return ctx.set.status=404,{success:!1,message:"Tenant not found"};if(tenant.status!=="suspended")return ctx.set.status=400,{success:!1,message:`Tenant is not suspended (current: ${tenant.status})`};let mainContext=tenantRegistry.getMainContext(),tenantsTable=mainContext.schemaTables.tenants;if(!tenantsTable)return ctx.set.status=503,{success:!1,message:"Tenants table not available"};let now=new Date;await db.update(tenantsTable).set({status:"active",suspendedAt:null,suspendedReason:null,updatedAt:now}).where(eq37(col10(tenantsTable,"id"),id));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId:id,eventType:"reactivated",eventData:JSON.stringify({previousReason:tenant.suspendedReason}),performedBy:requestingUserId,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",createdAt:now,updatedAt:now})}catch{log3.warn("Failed to record reactivate event")}return await tenantRegistry.refreshTenant(id),log3.info("Tenant reactivated",{tenantId:id}),{success:!0,message:`Tenant "${tenant.subdomain}" reactivated`}},detail:{tags:["Tenants"],summary:"Reactivate tenant",description:"Reactivate a suspended tenant. Requires godmin privileges."}})];return collectRoutes([{routes:declared}])}var verifyGodmin=async(db,tenantRegistry,userId)=>{let usersTable=tenantRegistry.getMainContext().schemaTables.users;if(!usersTable)return!1;return(await db.select().from(usersTable).where(eq37(col10(usersTable,"id"),userId)).limit(1))[0]?.isGod===!0},getAllTenants=(registry3)=>{let schemaNames=registry3.getAllSchemaNames(),tenants=[];for(let name2 of schemaNames){let ctx=registry3.getSchemaContext(name2);if(ctx?.tenant)tenants.push(ctx.tenant)}return tenants};init_esm3();init_Logger2();init_routeCollector();init_utils6();import{eq as eq38}from"drizzle-orm";var col11=(table,name2)=>table[name2],ProvisionBodySchema=Type.Object({subdomain:Type.String({minLength:2,maxLength:63,pattern:"^[a-z][a-z0-9-]*$"}),companyId:Type.String({format:"uuid"}),companyName:Type.Optional(Type.String()),godAdminEmail:Type.String({format:"email"}),plan:Type.Optional(Type.String()),domain:Type.Optional(Type.String())});function createProvisionRoute(config){let log3=config.logger.scoped(TENANT_PROVISION),declared=[defineRoute({method:"POST",path:"/tenants/provision",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant provisioning not available"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let mainContext=tenantRegistry.getMainContext(),usersTable=mainContext.schemaTables.users;if(!usersTable)return ctx.set.status=503,{success:!1,message:"Users table not available"};if(!(await db.select().from(usersTable).where(eq38(col11(usersTable,"id"),requestingUserId)).limit(1))[0]?.isGod)return ctx.set.status=403,{success:!1,message:"Godmin privileges required"};let body=ctx.body,tenantSchemaName=`tenant_${body.subdomain.replace(/-/g,"_")}`;if(tenantRegistry.getActiveTenants().find((t)=>t.subdomain===body.subdomain))return ctx.set.status=409,{success:!1,message:`Subdomain "${body.subdomain}" is already taken`};let tenantsTable=mainContext.schemaTables.tenants;if(!tenantsTable)return ctx.set.status=503,{success:!1,message:"Tenants table not available in main schema"};let tenantId=crypto.randomUUID(),now=new Date;try{await db.insert(tenantsTable).values({id:tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName,companyId:body.companyId,companyName:body.companyName||null,godAdminEmail:body.godAdminEmail,status:"provisioning",plan:body.plan||null,domain:body.domain||null,createdAt:now,updatedAt:now}),log3.info("Tenant record created",{tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName})}catch(err){let msg=err instanceof Error?err.message:String(err);return log3.error("Failed to insert tenant record",{error:msg}),ctx.set.status=500,{success:!1,message:`Failed to create tenant record: ${msg}`}}try{let tenantRecord={id:tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName,companyId:body.companyId,companyName:body.companyName||null,godAdminEmail:body.godAdminEmail,status:"provisioning",plan:body.plan||null,domain:body.domain||null,settings:{},trustedSources:[],maxUsers:null,provisionedAt:null,suspendedAt:null,suspendedReason:null},tenantContext=await tenantRegistry.provisionTenant(tenantRecord);log3.info("Schema provisioned",{tenantId,schemaName:tenantSchemaName,tableCount:Object.keys(tenantContext.schemaTables).length});let tenantUsersTable=tenantContext.schemaTables.users;if(tenantUsersTable){let godminId=crypto.randomUUID(),tempPassword=crypto.randomUUID().slice(0,16),hashedPassword=await hashPassword(tempPassword);await db.insert(tenantUsersTable).values({id:godminId,email:body.godAdminEmail,password:hashedPassword,isGod:!0,isActive:!0,createdAt:now,updatedAt:now}),log3.info("Godmin user seeded",{tenantId,godminEmail:body.godAdminEmail,godminId})}await db.update(tenantsTable).set({status:"active",provisionedAt:now,updatedAt:now}).where(eq38(col11(tenantsTable,"id"),tenantId));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId,eventType:"provisioned",eventData:JSON.stringify({schemaName:tenantSchemaName,godAdminEmail:body.godAdminEmail,plan:body.plan||null}),performedBy:requestingUserId,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",createdAt:now,updatedAt:now})}catch{log3.warn("Failed to record tenant_event, continuing")}return await tenantRegistry.refreshTenant(tenantId),log3.info("Provisioning complete",{tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName}),await config.logger.audit({entityName:"tenants",entityId:tenantId,operation:"PROVISION",userId:requestingUserId,summary:`Tenant "${body.subdomain}" provisioned by godmin`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""}),{success:!0,message:"Tenant provisioned successfully",data:{tenantId,schemaName:tenantSchemaName,subdomain:body.subdomain,godAdminEmail:body.godAdminEmail,status:"active"}}}catch(err){let msg=err instanceof Error?err.message:String(err);log3.error("Provisioning failed",{error:msg,tenantId});try{await db.update(tenantsTable).set({status:"provisioning",updatedAt:now}).where(eq38(col11(tenantsTable,"id"),tenantId))}catch{}return ctx.set.status=500,{success:!1,message:`Provisioning failed: ${msg}`}}},body:ProvisionBodySchema,detail:{tags:["Tenants"],summary:"Provision new tenant",description:"Create a new tenant with its own schema, tables, and godmin user. Requires godmin privileges."}})];return collectRoutes([{routes:declared}])}init_esm3();init_Authorization();init_Logger2();init_routeCollector();init_utils6();import{eq as eq39}from"drizzle-orm";var col12=(table,name2)=>table[name2],ipRateMap=new Map,lastCleanup=Date.now();function cleanupRateMap(windowMs){let now=Date.now();if(now-lastCleanup<600000)return;lastCleanup=now;for(let[key2,entry]of ipRateMap.entries())if(now-entry.windowStart>windowMs)ipRateMap.delete(key2)}function isIpRateLimited(ip,maxPerWindow,windowMs){let now=Date.now(),entry=ipRateMap.get(ip);if(!entry||now-entry.windowStart>windowMs)return ipRateMap.set(ip,{count:1,windowStart:now}),!1;return entry.count++,entry.count>maxPerWindow}var SelfSignupBodySchema=Type.Object({email:Type.String({format:"email"}),password:Type.String({minLength:8}),subdomain:Type.String({minLength:2,maxLength:63,pattern:"^[a-z][a-z0-9-]*$"}),plan:Type.Optional(Type.String()),companyName:Type.Optional(Type.String())});function createSelfSignupRoute(config){let log3=config.logger.scoped(TENANT_SELF_SIGNUP),limits={...DEFAULT_SELF_SIGNUP_LIMITS,...config.selfSignupLimits},declared=[defineRoute({method:"POST",path:"/tenants/self-signup",handler:async(ctx)=>{cleanupRateMap(limits.ipWindowMs);let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant provisioning not available"};let ip=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown";if(isIpRateLimited(ip,limits.maxSignupsPerIpPerWindow,limits.ipWindowMs))return log3.warn("Self-signup rate limited",{ip}),ctx.set.status=429,{success:!1,message:"Too many signup attempts. Please try again later."};let body=ctx.body,email=body.email.toLowerCase().trim(),subdomain=body.subdomain.toLowerCase().trim();if(RESERVED_SUBDOMAINS.includes(subdomain))return ctx.set.status=400,{success:!1,message:"This subdomain is reserved."};let mainContext=tenantRegistry.getMainContext(),usersTable=mainContext.schemaTables.users,tenantsTable=mainContext.schemaTables.tenants;if(!usersTable||!tenantsTable)return ctx.set.status=503,{success:!1,message:"Required tables not available."};if(tenantRegistry.getActiveTenants().find((t)=>t.subdomain===subdomain))return ctx.set.status=409,{success:!1,message:"This subdomain is already taken."};try{if((await db.select().from(tenantsTable).where(eq39(col12(tenantsTable,"subdomain"),subdomain)).limit(1)).length>0)return ctx.set.status=409,{success:!1,message:"This subdomain is already taken."}}catch{}try{if((await db.select().from(tenantsTable).where(eq39(col12(tenantsTable,"godAdminEmail"),email))).length>=limits.maxTenantsPerEmail)return ctx.set.status=409,{success:!1,message:`Tenant limit reached. Maximum ${limits.maxTenantsPerEmail} tenant(s) per account.`}}catch{}let userId,hashedPassword=await hashPassword(body.password),now=new Date,existingUsers=await db.select().from(usersTable).where(eq39(col12(usersTable,"email"),email)).limit(1);if(existingUsers.length>0)userId=existingUsers[0].id,log3.info("Self-signup: existing user resolved",{userId,email});else{userId=crypto.randomUUID();try{await db.insert(usersTable).values({id:userId,email,password:hashedPassword,isActive:!0,createdAt:now,updatedAt:now}),log3.info("Self-signup: user created",{userId,email})}catch(err){let msg=err instanceof Error?err.message:String(err);return log3.error("Self-signup: user creation failed",{error:msg}),ctx.set.status=500,{success:!1,message:"Failed to create account."}}}let tenantId=crypto.randomUUID(),tenantSchemaName=`tenant_${subdomain.replace(/-/g,"_")}`;try{await db.insert(tenantsTable).values({id:tenantId,subdomain,schemaName:tenantSchemaName,companyId:userId,companyName:body.companyName||subdomain,godAdminEmail:email,status:"provisioning",plan:body.plan||null,createdAt:now,updatedAt:now}),log3.info("Tenant record created",{tenantId,subdomain})}catch(err){let msg=err instanceof Error?err.message:String(err);return log3.error("Tenant record creation failed",{error:msg}),ctx.set.status=500,{success:!1,message:"Failed to create tenant."}}try{let tenantRecord={id:tenantId,subdomain,schemaName:tenantSchemaName,companyId:userId,companyName:body.companyName||subdomain,godAdminEmail:email,status:"provisioning",plan:body.plan||null,domain:null,settings:{},trustedSources:[],maxUsers:null,provisionedAt:null,suspendedAt:null,suspendedReason:null},tenantContext=await tenantRegistry.provisionTenant(tenantRecord);log3.info("Schema provisioned",{tenantId,schemaName:tenantSchemaName,tableCount:Object.keys(tenantContext.schemaTables).length});let tenantUsersTable=tenantContext.schemaTables.users;if(tenantUsersTable)await db.insert(tenantUsersTable).values({id:userId,email,password:hashedPassword,isGod:!0,isActive:!0,createdAt:now,updatedAt:now}),log3.info("Godmin seeded in tenant schema",{tenantId,email,userId});let tenantRolesTable=tenantContext.schemaTables.roles,tenantUserRolesTable=tenantContext.schemaTables.userRoles;if(tenantRolesTable&&tenantUserRolesTable)try{let godminRoles=await db.select().from(tenantRolesTable).where(eq39(tenantRolesTable.name,GODMIN_ROLE_NAME)).limit(1),godminRoleId;if(godminRoles.length>0)godminRoleId=godminRoles[0].id;else{let[newRole]=await db.insert(tenantRolesTable).values({name:GODMIN_ROLE_NAME,description:"God mode administrator - bypasses all authorization checks"}).returning();godminRoleId=newRole.id,log3.info("Created godmin role in tenant schema",{tenantId,roleId:godminRoleId})}if(godminRoleId)await db.insert(tenantUserRolesTable).values({userId,roleId:godminRoleId}),log3.info("Assigned godmin role to signup user",{tenantId,userId,roleId:godminRoleId})}catch(roleErr){log3.warn("Failed to assign godmin role to signup user",{tenantId,userId,error:roleErr instanceof Error?roleErr.message:String(roleErr)})}await db.update(tenantsTable).set({status:"active",provisionedAt:now,updatedAt:now}).where(eq39(col12(tenantsTable,"id"),tenantId));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId,eventType:"self_signup",eventData:JSON.stringify({email,subdomain,plan:body.plan||null}),performedBy:userId,ipAddress:ip,createdAt:now,updatedAt:now})}catch{}return await tenantRegistry.refreshTenant(tenantId),log3.info("Self-signup complete",{tenantId,subdomain,email}),await config.logger.audit({entityName:"tenants",entityId:tenantId,operation:"SELF_SIGNUP",userId,summary:`Self-signup: tenant "${subdomain}" provisioned by ${email}`,ipAddress:ip,userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""}),{success:!0,message:"Tenant created successfully",data:{tenantId,subdomain,schemaName:tenantSchemaName,userId,email,status:"active"}}}catch(err){let msg=err instanceof Error?err.message:String(err);log3.error("Self-signup provisioning failed",{error:msg,tenantId});try{await db.update(tenantsTable).set({status:"provisioning",updatedAt:now}).where(eq39(col12(tenantsTable,"id"),tenantId))}catch{}return ctx.set.status=500,{success:!1,message:"Provisioning failed. Please try again."}}},body:SelfSignupBodySchema,detail:{tags:["Tenants"],summary:"Self-service tenant signup",description:"Public endpoint for self-service signup. Creates user account, provisions tenant schema, and seeds godmin. Rate-limited by IP with configurable max tenants per email."}})];return collectRoutes([{routes:declared}])}function createTenantRoutes(app,config){return useRoutes(app,createCheckSubdomainRoute(config)),useRoutes(app,createProvisionRoute(config)),useRoutes(app,createSelfSignupRoute(config)),useRoutes(app,createManageRoutes(config)),app}init_esm3();init_Notification();init_Verification();init_routeCollector();init_adminGuard();function createVerificationRoutes(routeConfig){let{db,schemaTables,config,logger:logger2,tenantRegistry}=routeConfig,defaultVerificationService=new VerificationService({db,schemaTables,config,logger:logger2}),defaultNotificationService=new NotificationService({db,schemaTables,config:routeConfig.notificationConfig||{enabled:!1},logger:logger2,getEmailService:routeConfig.getEmailService}),wireNotificationHandler=(verSvc,notifSvc)=>{verSvc.setNotificationHandler(async(params)=>{if(!routeConfig.notificationConfig?.enabled)return;await notifSvc.triggerNotifications({trigger:params.trigger,flow_id:params.flow_id,entity_name:params.entity_name,entity_id:params.entity_id,node_id:params.node_id,verifier_id:params.verifier_id,decision:params.decision,context:params.context})})};wireNotificationHandler(defaultVerificationService,defaultNotificationService);let getServicesForRequest=(request)=>{if(!tenantRegistry)return{verificationService:defaultVerificationService,notificationService:defaultNotificationService};let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return{verificationService:defaultVerificationService,notificationService:defaultNotificationService};let ctx=tenantRegistry.getSchemaContext(schemaName);if(!ctx)return{verificationService:defaultVerificationService,notificationService:defaultNotificationService};let reqVerSvc=new VerificationService({db,schemaTables:ctx.schemaTables,config,logger:logger2}),reqNotifSvc=new NotificationService({db,schemaTables:ctx.schemaTables,config:routeConfig.notificationConfig||{enabled:!1},logger:logger2,getEmailService:routeConfig.getEmailService});return wireNotificationHandler(reqVerSvc,reqNotifSvc),{verificationService:reqVerSvc,notificationService:reqNotifSvc}},basePath=config.endpoints?.basePath||"/verifications",flowBasePath=config.flowEndpoints?.basePath||"/verification-flows",requireUser=({request,set:set2})=>{if(!request.headers.get("x-user-id"))return set2.status=401,{success:!1,message:"Authentication required"}},verificationDeclared=[defineRoute({method:"GET",path:"/status/:entity_name/:entity_id",handler:async({params,request,set:set2})=>{let{verificationService}=getServicesForRequest(request),status=await verificationService.getStatus(params.entity_name,params.entity_id),startedBy=status.instance?.started_by;if(!hasGodminRole(request)&&startedBy!=null&&String(startedBy)!==String(request.headers.get("x-user-id")))return set2.status=403,{success:!1,message:"Forbidden"};return{success:!0,data:status}},params:Type.Object({entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"POST",path:"/:entity_name/:entity_id/decide",handler:async({params,body,request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");if(!userId)return{success:!1,message:"User ID required",status:401};return await verificationService.decide({entity_name:params.entity_name,entity_id:params.entity_id,user_id:userId,decision:body.decision,reason:body.reason,signature_id:body.signature_id,diff:body.diff})},params:Type.Object({entity_name:Type.String(),entity_id:Type.String()}),body:Type.Object({decision:Type.Union([Type.Literal("approved"),Type.Literal("rejected")]),reason:Type.Optional(Type.String()),signature_id:Type.Optional(Type.String()),diff:Type.Optional(Type.Record(Type.String(),Type.Unknown()))})}),defineRoute({method:"GET",path:"/pending",handler:async({request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");if(!userId)return{success:!1,message:"User ID required",status:401};return{success:!0,data:await verificationService.getPending(userId)}}}),defineRoute({method:"GET",path:"/history/:entity_name/:entity_id",handler:async({params,request,set:set2})=>{let{verificationService}=getServicesForRequest(request),status=await verificationService.getStatus(params.entity_name,params.entity_id),startedBy=status.instance?.started_by;if(!hasGodminRole(request)&&startedBy!=null&&String(startedBy)!==String(request.headers.get("x-user-id")))return set2.status=403,{success:!1,message:"Forbidden"};return{success:!0,data:{verifications:status.verifications,current_step:status.current_step,total_steps:status.total_steps,is_completed:status.is_completed,is_rejected:status.is_rejected}}},params:Type.Object({entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"POST",path:"/start",handler:async({body,request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");return await verificationService.startFlow({flow_id:body.flow_id,entity_name:body.entity_name,entity_id:body.entity_id,started_by:userId||void 0})},body:Type.Object({flow_id:Type.String(),entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"POST",path:"/start-for-entity",handler:async({body,request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");return await verificationService.startFlowForEntity({entity_name:body.entity_name,entity_id:body.entity_id,started_by:userId||void 0})},body:Type.Object({entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"GET",path:"/statuses/:entity_name",handler:async({params,query,request,set:set2})=>{if(!hasGodminRole(request))return set2.status=403,{success:!1,message:"Forbidden: operator privileges required"};let{verificationService}=getServicesForRequest(request);return{success:!0,data:await verificationService.listEntityStatuses({entity_name:params.entity_name,status:query.status||void 0,page:query.page?Number(query.page):void 0,limit:query.limit?Number(query.limit):void 0})}},params:Type.Object({entity_name:Type.String()}),query:Type.Object({status:Type.Optional(Type.String()),page:Type.Optional(Type.String()),limit:Type.Optional(Type.String())})})],groups=[{prefix:basePath,guards:[requireUser],routes:verificationDeclared}];if(logger2.info(`[Verification] Routes registered at ${basePath}`),config.flowEndpoints?.enabled!==!1){let flowDeclared=[defineRoute({method:"GET",path:"/",handler:async({query,request})=>{let{verificationService:vs}=getServicesForRequest(request);return{success:!0,data:await vs.listFlows(query.entity_name||void 0)}},query:Type.Object({entity_name:Type.Optional(Type.String())})}),defineRoute({method:"GET",path:"/:flow_id",handler:async({params,request,set:set2})=>{let{verificationService:vs}=getServicesForRequest(request),result=await vs.getFlow(params.flow_id);if(!result)return set2.status=404,{success:!1,message:"Flow not found"};return{success:!0,data:result}},params:Type.Object({flow_id:Type.String()})}),defineRoute({method:"POST",path:"/",handler:async({body,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.saveFlow(body)},body:Type.Object({flow_id:Type.String(),entity_name:Type.String(),name:Type.String(),description:Type.Optional(Type.String()),trigger_on:Type.Union([Type.Literal("create"),Type.Literal("update"),Type.Literal("delete"),Type.Literal("manual")]),trigger_fields:Type.Optional(Type.Array(Type.String())),is_draft:Type.Boolean(),viewport:Type.Optional(Type.Object({x:Type.Number(),y:Type.Number(),zoom:Type.Number()})),graph:Type.Object({steps:Type.Array(Type.Any()),edges:Type.Array(Type.Any()),verifier_configs:Type.Array(Type.Any()),notification_rules:Type.Array(Type.Any()),notification_recipients:Type.Array(Type.Any()),notification_channels:Type.Array(Type.Any())})})}),defineRoute({method:"POST",path:"/:flow_id/publish",handler:async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.publishFlow(params.flow_id)},params:Type.Object({flow_id:Type.String()})}),defineRoute({method:"DELETE",path:"/:flow_id",handler:async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.deleteFlow(params.flow_id)},params:Type.Object({flow_id:Type.String()})})];groups.push({prefix:flowBasePath,guards:[createGodminGuard2({db,schemaTables,logger:logger2},"verification flow management")],routes:flowDeclared}),logger2.info(`[Verification] Flow routes registered at ${flowBasePath}`)}return{routes:collectRoutes(groups),verificationService:defaultVerificationService,notificationService:defaultNotificationService}}function createNucleusRuntime(deps){return{sendEmail:async(payload)=>{let mailer=deps.getEmailService();if(!mailer?.isAvailable())return{success:!1,error:"No email provider is configured or available"};return mailer.sendEmail(payload)},emailProvider:()=>deps.activeEmailProvider(),isEmailAvailable:()=>!!deps.getEmailService()?.isAvailable(),emailSenderAddress:()=>deps.getEmailService()?.getSenderAddress?.()??""}}init_utils5();function asText(value2){return typeof value2==="string"&&value2.trim()?value2.trim():void 0}function fieldOf(path7){let text2=asText(path7);if(!text2)return;return text2.replace(/^\//,"").replace(/\//g,".")||void 0}function fromValidator(error3){let candidate=error3;if(typeof candidate?.validator?.Errors!=="function")return;try{let fields=[],reason;for(let item of candidate.validator.Errors(candidate.value)){let field=fieldOf(item?.path);if(field&&!fields.includes(field))fields.push(field);if(reason??=asText(item?.message),fields.length>=4)break}return fields.length||reason?{fields,reason}:void 0}catch{return}}function compose(where,reason){if(where&&reason)return`Invalid request: ${where} \u2014 ${reason}`;if(where)return`Invalid request: ${where}`;if(reason)return`Invalid request: ${reason}`;return"Invalid request"}function fromMessage(raw){let text2=asText(raw);if(!text2)return"Invalid request";let payload;try{payload=JSON.parse(text2)}catch{return}if(!payload||typeof payload!=="object"||payload.type!=="validation")return;let on=asText(payload.on),property=fieldOf(payload.property),reason=asText(payload.summary)??asText(payload.message),where=property&&property!=="root"?`${on?`${on}.`:""}${property}`:on?`the request ${on}`:void 0;return compose(where,reason)}function describeValidationError(error3){if(typeof error3!=="string"&&error3){let detail=fromValidator(error3);if(detail){let on=asText(error3.type),where=detail.fields.length?detail.fields.map((field)=>on?`${on}.${field}`:field).join(", "):on?`the request ${on}`:void 0;return compose(where,detail.reason)}return fromMessage(error3.message)}return fromMessage(error3)}function statusOfError(code,error3){if(typeof code==="number")return code;let carried=error3?.status;if(typeof carried==="number"&&carried>=100&&carried<=599)return carried;return 500}init_auth();init_backup();init_payment();init_marketplace();init_storage2();var mergeEntitiesByName=(entities)=>{let entityMap=new Map;for(let entity of entities){let existing=entityMap.get(entity.table_name),mergedColumns=entity.columns??existing?.columns;entityMap.set(entity.table_name,{...existing||{},...entity,columns:mergedColumns})}return Array.from(entityMap.values())},effectiveConfigEntities=(configEntities)=>{if(!configEntities.some((e)=>e.extends))return configEntities;let{entities:resolved}=resolveEntities(SYSTEM_TABLES,configEntities),byName=new Map(resolved.map((t)=>[t.table_name,t]));return configEntities.map((e)=>e.extends?byName.get(e.table_name)??e:e)},normalizeSystemTable=(table)=>({table_name:table.table_name,excluded_methods:table.excluded_methods?[...table.excluded_methods]:void 0,columns:table.columns?table.columns.map((column)=>({name:column.name,type:column.type})):void 0}),extractSchemaTableEntities=(schemaTables)=>{let entities=[];for(let[_key,tableValue]of Object.entries(schemaTables)){if(!tableValue||typeof tableValue!=="object")continue;let tableObj=tableValue,underscoreMeta=tableObj._;if(underscoreMeta?.name){entities.push({table_name:underscoreMeta.name});continue}let symbols3=Object.getOwnPropertySymbols(tableObj);for(let sym of symbols3){let symValue=tableObj[sym];if(symValue&&typeof symValue==="object"){let symMeta=symValue;if(symMeta.name&&typeof symMeta.name==="string"){entities.push({table_name:symMeta.name});break}}}}return entities},lastMountedGroups=[];function getMountedRuntime2(){return getMountedRuntime()}function getResolvedOptions2(){return getResolvedOptions()}function getMountedRouteGroups(){return lastMountedGroups}var lastMountedSocket=null;function getMountedSocket(){return lastMountedSocket}var lastStaticAssets=null;function getMountedStaticAssets(){return lastStaticAssets}var lastBootSteps=null,lastShutdownSteps=null,lastAuthMiddleware=null,lastErrorHandler=null,lastAfterHandler=null;function getAfterHandler(){return lastAfterHandler}function getErrorHandler(){if(!lastErrorHandler)throw Error("getErrorHandler() before NucleusElysiaPlugin() \u2014 nothing has been built yet");return lastErrorHandler}function getAuthMiddleware(){if(!lastAuthMiddleware)throw Error("getAuthMiddleware() before NucleusElysiaPlugin() \u2014 nothing has been built yet");return lastAuthMiddleware}function getBootSteps(){if(!lastBootSteps)throw Error("getBootSteps() before NucleusElysiaPlugin() \u2014 nothing has been built yet");return lastBootSteps}function getShutdownSteps(){return lastShutdownSteps??[]}function resolveOverrides(groups,logger2){let claim=new Map,dropped=[],kept=[];for(let index=groups.length-1;index>=0;index--){let group=groups[index];if(!group)continue;let routes=[];for(let position=group.routes.length-1;position>=0;position--){let route=group.routes[position];if(!route)continue;let declared=route.path==="/"?"":route.path,key2=`${route.method} ${`${group.prefix??""}${declared}`||"/"}`,winner=claim.get(key2);if(winner){let summary=route.detail?.summary??"(no summary)";dropped.push(`${key2} \u2014 kept "${winner.summary}", dropped "${summary}"`);continue}claim.set(key2,{summary:route.detail?.summary??"(no summary)"}),routes.unshift(route)}if(routes.length>0)kept.unshift({...group,routes})}if(dropped.length>0)logger2.warn(`[Routes] ${dropped.length} route(s) replaced by a later declaration of the same path`,{replaced:dropped});return kept}async function buildNucleus(config){beginRouteRecording();let plugin=collectRoutes(),routeGetHealth=defineRoute({method:"GET",path:"/health",handler:()=>({status:"ok",timestamp:Date.now()})});if(mountRoutes(plugin,[{routes:[routeGetHealth]}]),config.staticAssets!==!1){let path8=__require("path"),fs3=__require("fs"),assetsPath;if(typeof config.staticAssets==="string")assetsPath=config.staticAssets;else{let localPath=path8.join(process.cwd(),"public"),resolvedPkgPath="";for(let pkgName of["nucleus-core-ts","nucleus-core"])try{let pkgJson=__require.resolve(`${pkgName}/package.json`),candidate=path8.join(path8.dirname(pkgJson),"public");if(fs3.existsSync(candidate)){resolvedPkgPath=candidate;break}}catch{}if(resolvedPkgPath)assetsPath=resolvedPkgPath;else if(fs3.existsSync(localPath))assetsPath=localPath;else assetsPath=localPath}recordStaticAssets(assetsPath)}let publicRoutes=[],resolvedOptions,configDir=process.cwd(),configFilePath=null;if(typeof config.options==="string"){let fs3=__require("fs"),path8=__require("path"),configPath=path8.isAbsolute(config.options)?config.options:path8.resolve(process.cwd(),config.options);configDir=path8.dirname(configPath),configFilePath=configPath;let configContent=fs3.readFileSync(configPath,"utf-8");resolvedOptions=JSON.parse(configContent)}else resolvedOptions=config.options;if(resolvedOptions.email?.gmail?.json_file_path){let path8=__require("path"),gmailPath=resolvedOptions.email.gmail.json_file_path;if(!path8.isAbsolute(gmailPath))resolvedOptions.email.gmail.json_file_path=path8.resolve(configDir,gmailPath)}recordResolvedOptions(resolvedOptions);let{authentication,audit,entities,database}=resolvedOptions,isDev=resolvedOptions.mode==="development",loggingConfig=resolvedOptions.logging,logger2=new Logger({service:resolvedOptions.appId||"nucleus",level:loggingConfig?.level||(isDev?"debug":"info"),prettyPrint:loggingConfig?.prettyPrint??isDev,colorize:loggingConfig?.colorize??isDev,includeCallerInfo:loggingConfig?.includeCallerInfo??isDev,redactKeys:loggingConfig?.redactKeys||[],auditEnabled:audit?.enabled??!1,...audit?.suppressReasons!==void 0?{auditSuppressReasons:audit.suppressReasons}:{},...audit?.minSeverity!==void 0?{auditMinSeverity:audit.minSeverity}:{},enabledScopes:loggingConfig?.scopes||["*"]});Logger.getInstance().configure({service:resolvedOptions.appId||"nucleus",level:loggingConfig?.level||(isDev?"debug":"info"),prettyPrint:loggingConfig?.prettyPrint??isDev,colorize:loggingConfig?.colorize??isDev,includeCallerInfo:loggingConfig?.includeCallerInfo??isDev,redactKeys:loggingConfig?.redactKeys||[],enabledScopes:loggingConfig?.scopes||["*"]});let serverLogsConfig=resolvedOptions.monitoring?.serverLogs,serverLogBuffer=serverLogsConfig?.enabled===!0?getServerLogBuffer({capacity:serverLogsConfig.capacity??2000,maxLineLength:serverLogsConfig.maxLineLength}):null;if(serverLogBuffer){let transport=new ServerLogTransport(serverLogBuffer);if(logger2.addTransport(transport),Logger.getInstance().addTransport(transport),serverLogsConfig?.captureStdout!==!1)attachStreamCapture(serverLogBuffer);logger2.info(`[ServerLogs] Buffer active (capacity: ${serverLogBuffer.getCapacity()}, stdout capture: ${serverLogsConfig?.captureStdout!==!1})`)}{let{entities:resolvedForBoot,errors:extendErrors,warnings:extendWarnings}=resolveEntities(SYSTEM_TABLES,entities??[],{consumerMode:authentication?.mode==="consumer"});for(let warning of extendWarnings)logger2.warn(warning);let syncErrors=[],syncMappings=resolvedOptions.authentication?.oauth?.profileSync?.mappings??[];if(syncMappings.length>0){let columnsFor=(name2)=>{return(resolvedForBoot.find((t)=>t.table_name===name2)?.columns??[]).map((c)=>c.name)};syncErrors.push(...validateProfileSyncTargets(syncMappings,{users:columnsFor("users"),profiles:columnsFor("profiles")}))}if(extendErrors.length>0||syncErrors.length>0){let detail=[...extendErrors.map((e)=>` ${e.code}: ${e.message}`),...syncErrors.map((m)=>` PROFILE_SYNC_TARGET: ${m}`)].join(`
550
+ `)};function parseTimeToMs2(time2){let match=time2.match(/^(\d+)(ms|s|m|h)$/);if(!match?.[1]||!match[2])return 5000;let value2=parseInt(match[1],10);switch(match[2]){case"ms":return value2;case"s":return value2*1000;case"m":return value2*60*1000;case"h":return value2*60*60*1000;default:return 5000}}function createMonitoringRoutes(config){let{getService,logger:logger2,endpoints,db,schemaTables}=config,snapshotOrNull=async()=>await getService()?.getLatestSnapshot()??null,alertsOrEmpty=()=>getService()?.getActiveAlerts()??[],guard4=createPrivilegedGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"monitoring",{allowRoles:endpoints.allowRoles}),mount=(routes2)=>collectRoutes([{prefix:endpoints.basePath,guards:[guard4],routes:routes2}]);if(!endpoints.enabled)return mount([]);logger2.info(`[Monitoring] Routes enabled at ${endpoints.basePath} (stream: ${endpoints.stream.enabled}, snapshot: ${endpoints.snapshot.enabled}, history: ${endpoints.history.enabled}, alerts: ${endpoints.alerts.enabled})`);let routes=[];if(endpoints.stream.enabled)routes.push(defineRoute({method:"GET",path:endpoints.stream.path,handler:({request})=>{let intervalMs=parseTimeToMs2(endpoints.stream.interval),abortSignal=request.signal;logger2.info("[Monitoring] SSE stream connected");let cleanup,stream=new ReadableStream({start(controller){let closed=!1,send=(event,data)=>{if(closed)return;controller.enqueue(encodeEvent(event,data))},cleanupHandler=()=>{if(closed)return;closed=!0,clearInterval(timer),abortSignal?.removeEventListener("abort",cleanupHandler),logger2.info("[Monitoring] SSE stream disconnected");try{controller.close()}catch{}};cleanup=cleanupHandler;let tick=async()=>{if(closed)return;let snapshot=await snapshotOrNull();if(snapshot)send("snapshot",snapshot);let alerts=alertsOrEmpty();if(alerts.length>0)send("alerts",alerts)},timer=setInterval(tick,intervalMs);abortSignal?.addEventListener("abort",cleanupHandler),send("open",{interval:intervalMs}),tick()},cancel(){cleanup?.()}});return new Response(stream,{headers:STREAM_HEADERS})},detail:{tags:["Monitoring"],summary:"Stream real-time monitoring data",description:"Server-Sent Events stream for real-time monitoring metrics"}}));if(endpoints.snapshot.enabled)routes.push(defineRoute({method:"GET",path:endpoints.snapshot.path,handler:async()=>{let snapshot=await snapshotOrNull();if(!snapshot)return{isSuccess:!0,message:"No monitoring data available yet",data:null};return{isSuccess:!0,message:"Current monitoring snapshot",data:snapshot}},detail:{tags:["Monitoring"],summary:"Get current monitoring snapshot",description:"Returns the latest monitoring metrics snapshot"}}));if(endpoints.history.enabled)routes.push(defineRoute({method:"GET",path:endpoints.history.path,handler:async({query})=>{let minutes=Math.min(query.minutes?parseInt(String(query.minutes),10):60,endpoints.history.maxMinutes),history=await getService()?.getHistory(minutes)??[];return{isSuccess:!0,message:`Monitoring history for last ${minutes} minutes`,data:{minutes,count:history.length,snapshots:history}}},query:Type.Object({minutes:Type.Optional(Type.Number())}),detail:{tags:["Monitoring"],summary:"Get monitoring history",description:"Returns historical monitoring data for the specified time range"}}));if(endpoints.alerts.enabled)routes.push(defineRoute({method:"GET",path:endpoints.alerts.path,handler:()=>{let alerts=alertsOrEmpty();return{isSuccess:!0,message:"Active alerts",data:{count:alerts.length,alerts}}},detail:{tags:["Monitoring"],summary:"Get active alerts",description:"Returns all currently active monitoring alerts"}})),routes.push(defineRoute({method:"POST",path:`${endpoints.alerts.path}/:alertId/acknowledge`,handler:({params,set:set2})=>{let service=getService();if(!service)return set2.status=503,{isSuccess:!1,message:"Monitoring service is not available",data:null};if(!service.acknowledgeAlert(params.alertId))return set2.status=404,{isSuccess:!1,message:"Alert not found",data:null};return{isSuccess:!0,message:"Alert acknowledged",data:{alertId:params.alertId}}},params:Type.Object({alertId:Type.String()}),detail:{tags:["Monitoring"],summary:"Acknowledge an alert",description:"Mark an alert as acknowledged"}}));return mount(routes)}init_esm3();init_routeCollector();init_adminGuard();function createLiveMonitoringRoutes(config){let{getService,getLifecycleRecorder,logger:logger2,basePath,streamInterval,db,schemaTables}=config,guard4=createGodminGuard2({db:db??null,schemaTables:schemaTables??{},logger:logger2},"live monitoring"),requireService=()=>{let svc=getService();if(!svc)throw Error("Live monitoring service not initialized");return svc};logger2.info(`[LiveMonitoring] Routes enabled at ${basePath} (stream interval: ${streamInterval}ms)`);let routes=[defineRoute({method:"GET",path:"/health",handler:()=>{let svc=getService();return{status:svc?"ok":"initializing",timestamp:Date.now(),monitoring:svc?.isEnabled()??!1}},detail:{tags:["Live Monitoring"],summary:"Health check for live monitoring"}}),defineRoute({method:"GET",path:"/settings",handler:()=>{return requireService().getSettings()},detail:{tags:["Live Monitoring"],summary:"Get live monitoring settings"}}),defineRoute({method:"PATCH",path:"/settings",handler:({body})=>{return requireService().changeSettings(body)},body:Type.Partial(Type.Object({logMemory:Type.Boolean(),logCpu:Type.Boolean(),logDapr:Type.Boolean(),logWebSocket:Type.Boolean(),memoryLogInterval:Type.Number(),cpuLogInterval:Type.Number(),memoryLogLimit:Type.Number(),cpuLogLimit:Type.Number(),daprLogLimit:Type.Number(),wsLogLimit:Type.Number(),requestLogLimit:Type.Number()})),detail:{tags:["Live Monitoring"],summary:"Change live monitoring settings"}}),defineRoute({method:"GET",path:"/incidents",handler:async({query})=>{let raw=Number(query?.limit),limit=Number.isFinite(raw)&&raw>0?Math.min(raw,50):20,recorder=getLifecycleRecorder?.();if(!recorder)return{available:!1,incidents:[]};return{available:!0,incidents:await recorder.incidents(limit)}},query:Type.Optional(Type.Object({limit:Type.Optional(Type.String())})),detail:{tags:["Live Monitoring"],summary:"Why previous runs of this process ended"}}),defineRoute({method:"GET",path:"/realtime",handler:({query})=>{let raw=Number(query?.windowMinutes),minutes=Number.isFinite(raw)&&raw>0?Math.min(raw,1440):void 0;return requireService().getRealtimeHealth(minutes?minutes*60000:void 0)},query:Type.Optional(Type.Object({windowMinutes:Type.Optional(Type.String())})),detail:{tags:["Live Monitoring"],summary:"Realtime (WebSocket) health digest"}}),defineRoute({method:"GET",path:"/logs",handler:()=>{return requireService().getLogs()},detail:{tags:["Live Monitoring"],summary:"Get all live monitoring logs"}}),defineRoute({method:"GET",path:"/logs/stream",handler:({request})=>{let abortSignal=request.signal,cleanup,svc=requireService(),snapshot=svc.getSnapshot(),lastTimestamps={memory:snapshot.memory.length?snapshot.memory[snapshot.memory.length-1]?.timestamp??0:0,cpu:snapshot.cpu.length?snapshot.cpu[snapshot.cpu.length-1]?.timestamp??0:0,request:snapshot.requests.length?snapshot.requests[snapshot.requests.length-1]?.timestamp??0:0,dapr:snapshot.dapr.length?snapshot.dapr[snapshot.dapr.length-1]?.timestamp??0:0,ws:snapshot.ws.length?snapshot.ws[snapshot.ws.length-1]?.timestamp??0:0},stream=new ReadableStream({start(controller){let closed=!1;controller.enqueue(encodeEvent("snapshot",snapshot));let interval=setInterval(()=>{if(closed)return;let update=svc.getUpdatesSince(lastTimestamps);if(!update){controller.enqueue(encodeEvent("heartbeat",{timestamp:Date.now()}));return}if(update.memory.length)lastTimestamps.memory=update.memory[update.memory.length-1]?.timestamp??lastTimestamps.memory;if(update.cpu.length)lastTimestamps.cpu=update.cpu[update.cpu.length-1]?.timestamp??lastTimestamps.cpu;if(update.requests.length)lastTimestamps.request=update.requests[update.requests.length-1]?.timestamp??lastTimestamps.request;if(update.dapr.length)lastTimestamps.dapr=update.dapr[update.dapr.length-1]?.timestamp??lastTimestamps.dapr;if(update.ws.length)lastTimestamps.ws=update.ws[update.ws.length-1]?.timestamp??lastTimestamps.ws;controller.enqueue(encodeEvent("update",update))},streamInterval),cleanupHandler=()=>{if(closed)return;closed=!0,clearInterval(interval),abortSignal?.removeEventListener("abort",cleanupHandler);try{controller.close()}catch{}};cleanup=cleanupHandler,abortSignal?.addEventListener("abort",cleanupHandler)},cancel(){cleanup?.()}});return new Response(stream,{headers:STREAM_HEADERS})},detail:{tags:["Live Monitoring"],summary:"Stream real-time live monitoring data via SSE"}})];return collectRoutes([{prefix:basePath,guards:[guard4],routes}])}init_esm3();init_routeCollector();init_adminGuard();var LOG_LEVELS=["debug","info","warn","error","fatal"],SOURCES=["logger","stdout","stderr"];function parseLevels(raw){if(!raw)return;let parsed=raw.split(",").map((value2)=>value2.trim().toLowerCase()).filter((value2)=>LOG_LEVELS.includes(value2));return parsed.length>0?parsed:void 0}function parseSource(raw){if(!raw)return;let value2=raw.trim().toLowerCase();return SOURCES.includes(value2)?value2:void 0}function parseMinLevel(raw){if(!raw)return;let value2=raw.trim().toLowerCase();return LOG_LEVELS.includes(value2)?value2:void 0}function parseCursor(raw){if(raw===void 0||raw===null||raw==="")return;let value2=Number(raw);return Number.isFinite(value2)&&value2>=0?value2:void 0}function buildQuery(query){return{levels:parseLevels(query.levels),minLevel:parseMinLevel(query.minLevel),scope:query.scope?.trim()||void 0,search:query.search||void 0,source:parseSource(query.source),after:parseCursor(query.after),before:parseCursor(query.before),limit:parseCursor(query.limit)}}var STREAM_KEEPALIVE_MS=25000;function createServerLogRoutes(config){let{buffer,logger:logger2,basePath,path:path7,allowRoles,streamEnabled=!0,clearEnabled=!0,db,schemaTables}=config,guard4=createPrivilegedGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"server logs",{allowRoles}),routes=[defineRoute({method:"GET",path:path7,handler:({query})=>{return{isSuccess:!0,message:"Server logs",data:{...buffer.query(buildQuery(query)),scopes:buffer.scopes(),levelCounts:buffer.levelCounts()}}},query:Type.Object({levels:Type.Optional(Type.String()),minLevel:Type.Optional(Type.String()),scope:Type.Optional(Type.String()),search:Type.Optional(Type.String()),source:Type.Optional(Type.String()),after:Type.Optional(Type.Number()),before:Type.Optional(Type.Number()),limit:Type.Optional(Type.Number())}),detail:{tags:["Monitoring"],summary:"Read buffered server logs",description:"Filtered, cursor-paged view of the in-memory server log buffer. Use `after` to tail forward and `before` to page backwards."}})];if(streamEnabled)routes.push(defineRoute({method:"GET",path:`${path7}/stream`,handler:({request,query})=>{let tailQuery=buildQuery(query),{after,before:_before,limit:_limit,...filter}=tailQuery,abortSignal=request.signal,cleanup,stream=new ReadableStream({start(controller){let closed=!1,backlog=buffer.query({...filter,...after!==void 0?{after}:{},limit:200});controller.enqueue(encodeEvent("backlog",backlog));let unsubscribe=buffer.subscribe((record3)=>{if(closed)return;if(!buffer.query({...filter,after:record3.seq-1,limit:1}).records.some((r)=>r.seq===record3.seq))return;try{controller.enqueue(encodeEvent("logs",[record3]))}catch{cleanup?.()}}),heartbeat=setInterval(()=>{if(closed)return;try{controller.enqueue(encodeEvent("ping",{timestamp:Date.now()}))}catch{cleanup?.()}},STREAM_KEEPALIVE_MS),cleanupHandler=()=>{if(closed)return;closed=!0,clearInterval(heartbeat),unsubscribe(),abortSignal?.removeEventListener("abort",cleanupHandler);try{controller.close()}catch{}};cleanup=cleanupHandler,abortSignal?.addEventListener("abort",cleanupHandler)},cancel(){cleanup?.()}});return new Response(stream,{headers:STREAM_HEADERS})},query:Type.Object({levels:Type.Optional(Type.String()),minLevel:Type.Optional(Type.String()),scope:Type.Optional(Type.String()),search:Type.Optional(Type.String()),source:Type.Optional(Type.String()),after:Type.Optional(Type.Number())}),detail:{tags:["Monitoring"],summary:"Tail server logs",description:"Server-Sent Events tail of the server log buffer, filtered server-side."}}));if(clearEnabled)routes.push(defineRoute({method:"DELETE",path:path7,handler:()=>{return buffer.clear(),logger2.info("[ServerLogs] Buffer cleared from the admin panel"),{isSuccess:!0,message:"Server log buffer cleared",data:null}},detail:{tags:["Monitoring"],summary:"Clear the server log buffer"}}));return logger2.info(`[ServerLogs] Routes enabled at ${basePath}${path7} (capacity: ${buffer.getCapacity()}, stream: ${streamEnabled})`),collectRoutes([{prefix:basePath,guards:[guard4],routes}])}init_esm3();init_Notification();init_routeCollector();init_adminGuard();var CHANNELS=["portal","email","telegram","webhook"];function createNotificationRoutes(routeConfig){let{db,schemaTables,config,logger:logger2,tenantRegistry,getEmailService}=routeConfig,basePath=routeConfig.basePath||config.endpoints?.basePath||"/notifications",buildService=(tables)=>new NotificationService({db,schemaTables:tables,config,logger:logger2,getEmailService}),defaultService=buildService(schemaTables),serviceFor=(request)=>{if(!tenantRegistry)return defaultService;let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return defaultService;let ctx=tenantRegistry.getSchemaContext(schemaName);return ctx?buildService(ctx.schemaTables):defaultService},allowedSendRoles=new Set((config.endpoints?.sendRoles??[]).map((role)=>role.toLowerCase())),canSend=async(request)=>{if(allowedSendRoles.size>0){if(decodeHeaderList(request.headers.get("x-user-roles")).some((role)=>allowedSendRoles.has(role.toLowerCase())))return!0}return isGodminRequest(request,{db,schemaTables,logger:logger2})},requireUser=(request,set2)=>{let userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,null;return userId},declared=[defineRoute({method:"GET",path:"/",handler:async({request,query,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};return{success:!0,data:await serviceFor(request).getNotifications(userId,{limit:query.limit?Number(query.limit):void 0,offset:query.offset?Number(query.offset):void 0,type:query.type||void 0})}},query:Type.Object({limit:Type.Optional(Type.String()),offset:Type.Optional(Type.String()),type:Type.Optional(Type.String())}),detail:{tags:["Notifications"],summary:"List the caller\u2019s notifications"}}),defineRoute({method:"GET",path:"/unseen-count",handler:async({request,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};return{success:!0,data:{count:await serviceFor(request).getUnseenCount(userId)}}},detail:{tags:["Notifications"],summary:"Unseen notification count"}}),defineRoute({method:"POST",path:"/:notification_id/seen",handler:async({params,request,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};if(!await serviceFor(request).markAsSeen(params.notification_id,userId))return set2.status=404,{success:!1,message:"Notification not found"};return{success:!0,message:"Marked as seen"}},params:Type.Object({notification_id:Type.String()}),detail:{tags:["Notifications"],summary:"Mark one notification as seen"}}),defineRoute({method:"POST",path:"/seen-all",handler:async({request,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};return{success:!0,data:{count:await serviceFor(request).markAllAsSeen(userId)}}},detail:{tags:["Notifications"],summary:"Mark every notification as seen"}}),defineRoute({method:"POST",path:"/send",handler:async({body,request,set:set2})=>{if(!requireUser(request,set2))return{success:!1,message:"Authentication required"};if(!await canSend(request))return set2.status=403,{success:!1,message:"Sending notifications requires godmin or an allowed role"};let recipients=body.userIds??(body.userId?[body.userId]:[]);if(recipients.length===0)return set2.status=400,{success:!1,message:"userId or userIds is required"};let channels=body.channels??[config.defaultChannel??"portal"],service=serviceFor(request),failed=(await Promise.allSettled(recipients.map((user_id)=>service.send({user_id,title:body.title,body:body.body,entity_name:body.entityName,entity_id:body.entityId,type:body.type??"custom",source:body.source,channels})))).map((result,index)=>{let userId=recipients[index];if(result.status==="rejected"){let reason=result.reason;return{userId,sent:!1,reason:reason instanceof Error?reason.message:String(reason)}}if(result.value.delivered.length>0)return{userId,sent:!0,reason:""};return{userId,sent:!1,reason:result.value.undelivered.map((one)=>`${one.channel}: ${one.reason}`).join("; ")||"no channel was requested"}}).filter((one)=>!one.sent),sentCount=recipients.length-failed.length,reasons=[...new Set(failed.map((one)=>one.reason))].join("; ");if(sentCount===0)set2.status=503;return{success:failed.length===0,message:failed.length===0?`Sent to ${recipients.length} recipient(s)`:`Sent to ${sentCount}, failed for ${failed.length}: ${reasons}`,data:{sent:sentCount,failed:failed.map((one)=>one.userId)}}},body:Type.Object({userId:Type.Optional(Type.String()),userIds:Type.Optional(Type.Array(Type.String())),title:Type.String(),body:Type.Optional(Type.String()),entityName:Type.Optional(Type.String()),entityId:Type.Optional(Type.String()),type:Type.Optional(Type.Union([Type.Literal("verification"),Type.Literal("system"),Type.Literal("custom")])),source:Type.Optional(Type.String()),channels:Type.Optional(Type.Array(Type.Union(CHANNELS.map((c)=>Type.Literal(c)))))}),detail:{tags:["Notifications"],summary:"Send a notification to one or more users",description:"Privileged. Delivers over the requested channels (portal, email, telegram, webhook). Email uses the configured provider \u2014 credentials resolve through the secrets store, so a rotation applies immediately."}}),defineRoute({method:"POST",path:"/email",handler:async({body,request,set:set2})=>{if(!requireUser(request,set2))return{success:!1,message:"Authentication required"};if(!await canSend(request))return set2.status=403,{success:!1,message:"Sending email requires godmin or an allowed role"};let emailService=getEmailService?.()??null;if(!emailService?.isAvailable())return set2.status=503,{success:!1,message:"No email provider is configured or available"};let result=await emailService.sendEmail({to:body.to,subject:body.subject,html:body.html,text:body.text,replyTo:body.replyTo});if(!result.success)return set2.status=502,{success:!1,message:result.error??"Email delivery failed"};return logger2.info("[Notification] Transactional email sent",{recipients:Array.isArray(body.to)?body.to.length:1,subject:body.subject}),{success:!0,message:"Email sent",data:null}},body:Type.Object({to:Type.Union([Type.String(),Type.Array(Type.String())]),subject:Type.String(),html:Type.Optional(Type.String()),text:Type.Optional(Type.String()),replyTo:Type.Optional(Type.String())}),detail:{tags:["Notifications"],summary:"Send a transactional email to explicit recipients",description:"Backend-to-backend send through the configured provider \u2014 an app never needs an email SDK or the provider credentials of its own. Privileged, same gate as /send."}})];return{routes:collectRoutes([{prefix:basePath,routes:declared}]),notificationService:defaultService}}init_routeCollector();init_routeRecording();init_websocket();init_ack_manager();import{timingSafeEqual as timingSafeEqual3}from"crypto";init_ack_manager();var clients=new Map,presenceBroadcastDebounce=new Map,DEFAULT_MAX_TOPICS_PER_CLIENT=512;function createClientManager(config){let{redis,logger:logger2}=config,ackTtl=config.ack.ttlSeconds,ackEnabled=config.ack.enabled,maxRetries=config.ack.maxRetries,presenceEnabled=config.presence.enabled,presenceDebounceMs=config.presence.debounceMs,maxClientsPerUser=config.maxClientsPerUser,maxTopicsPerClient=config.maxTopicsPerClient??DEFAULT_MAX_TOPICS_PER_CLIENT,MAX_TOPIC_LENGTH=256;function generateClientId(){return`client_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}function capTopics(topics){let accepted=[],dropped=[],seen=new Set;for(let t of topics){if(typeof t!=="string")continue;let trimmed=t.length>256?t.slice(0,256):t;if(seen.has(trimmed))continue;if(seen.add(trimmed),accepted.length>=maxTopicsPerClient){dropped.push(trimmed);continue}accepted.push(trimmed)}return{accepted,dropped}}function reportDropped(context,clientId,userId,dropped){if(dropped.length===0)return;logger2.warn("[PubSub] Topic subscription capped \u2014 events for these will NOT be delivered",{context,clientId,userId,cap:maxTopicsPerClient,droppedCount:dropped.length,dropped})}function registerClient(clientId,ws,userId,topics){let existingClients=getClientsByUser(userId);if(existingClients.length>=maxClientsPerUser){let oldestClientId=existingClients[0];if(oldestClientId)logger2.info("[PubSub] Max clients reached, closing oldest",{userId,oldestClientId}),unregisterClient(oldestClientId)}let{accepted,dropped}=capTopics(topics);return clients.set(clientId,{ws,userId,subscribedTopics:new Set(accepted),connectedAt:Date.now(),pendingAcks:new Map}),logger2.info("[PubSub] Client registered",{clientId,userId,topics:accepted,requestedCount:topics.length,subscribedCount:accepted.length,totalClients:clients.size}),reportDropped("registerClient",clientId,userId,dropped),{accepted,dropped}}function unregisterClient(clientId){let client=clients.get(clientId);if(!client)return;let userId=client.userId;if(clients.delete(clientId),logger2.info("[PubSub] Client unregistered",{clientId,userId,totalClients:clients.size}),presenceEnabled&&userId){if(getClientsByUser(userId).length===0)broadcastPresenceToOthers(userId,"offline")}}function updateClientTopics(clientId,topics){let client=clients.get(clientId);if(!client)return{accepted:[],dropped:[]};let{accepted,dropped}=capTopics(topics);return client.subscribedTopics=new Set(accepted),reportDropped("updateClientTopics",clientId,client.userId,dropped),{accepted,dropped}}function getClientsByUser(userId){let result=[];for(let[clientId,client]of clients)if(client.userId===userId)result.push(clientId);return result}function getConnectedUserIds(){let userIds=new Set;for(let[,client]of clients)if(client.userId)userIds.add(client.userId);return userIds}function getClientCount(){return clients.size}function sendToClient(clientId,message){let client=clients.get(clientId);if(!client)return!1;try{return client.ws.send(JSON.stringify(message)),!0}catch{return clients.delete(clientId),!1}}function broadcastEvent(topic,event){let messageId=generateMessageId(),timestamp=Date.now(),payload=JSON.stringify({type:"event",messageId:ackEnabled?messageId:void 0,topic,data:event,timestamp}),sentCount=0;for(let[clientId,client]of clients)if(client.subscribedTopics.has("*")||client.subscribedTopics.has(topic))try{if(client.ws.send(payload),sentCount++,ackEnabled){if(client.pendingAcks.set(messageId,{sentAt:timestamp,retryCount:0}),recordSent(),client.userId)storePendingMessage(redis,{messageId,topic,clientId,userId:client.userId,data:event,sentAt:timestamp,retryCount:0,maxRetries},ackTtl).catch(()=>{})}}catch{clients.delete(clientId)}logger2.info("[PubSub] Broadcasted event",{topic,messageId,sentCount})}function broadcastToUser(userId,topic,event){let messageId=generateMessageId(),timestamp=Date.now(),payload=JSON.stringify({type:"event",messageId:ackEnabled?messageId:void 0,topic,data:event,timestamp}),sentCount=0,sentToAnyClient=!1;for(let[clientId,client]of clients){if(client.userId!==userId)continue;if(!client.subscribedTopics.has("*")&&!client.subscribedTopics.has(topic))continue;try{if(client.ws.send(payload),sentCount++,sentToAnyClient=!0,ackEnabled)client.pendingAcks.set(messageId,{sentAt:timestamp,retryCount:0}),recordSent()}catch{clients.delete(clientId)}}if(ackEnabled)storePendingMessage(redis,{messageId,topic,clientId:sentToAnyClient?"delivered":"pending",userId,data:event,sentAt:timestamp,retryCount:0,maxRetries},ackTtl).catch(()=>{});logger2.info("[PubSub] Broadcasted to user",{userId,topic,messageId,sentCount,storedForOffline:!sentToAnyClient})}function broadcastToUserNoAck(userId,topic,event){let timestamp=Date.now(),payload=JSON.stringify({type:"event",topic,data:event,timestamp});for(let[clientId,client]of clients){if(client.userId!==userId)continue;if(!client.subscribedTopics.has("*")&&!client.subscribedTopics.has(topic))continue;try{client.ws.send(payload)}catch{clients.delete(clientId)}}}function broadcastPresenceToOthers(userId,status){let debounceKey=`${userId}:${status}`,now=Date.now(),lastBroadcast=presenceBroadcastDebounce.get(debounceKey);if(lastBroadcast&&now-lastBroadcast<presenceDebounceMs)return;if(presenceBroadcastDebounce.set(debounceKey,now),presenceBroadcastDebounce.size>1000){let cutoff=now-presenceDebounceMs*2;for(let[key2,timestamp]of presenceBroadcastDebounce)if(timestamp<cutoff)presenceBroadcastDebounce.delete(key2)}let connectedUserIds=getConnectedUserIds();for(let targetUserId of connectedUserIds)if(targetUserId!==userId)broadcastToUserNoAck(targetUserId,"user-presence",{type:"user-presence",data:{userId,status}})}async function handleClientAck(clientId,messageId){if(!ackEnabled)return!0;let client=clients.get(clientId);if(!client)return!1;if(client.pendingAcks.get(messageId))client.pendingAcks.delete(messageId);if(client.userId)return acknowledgeMessage(redis,client.userId,messageId,ackTtl);return!0}async function deliverPendingMessages(userId,clientId){if(!ackEnabled)return 0;let client=clients.get(clientId);if(!client||client.userId!==userId)return 0;let pendingMessages=await getPendingMessagesForUser(redis,userId);if(pendingMessages.length===0)return 0;logger2.info("[PubSub] Delivering pending messages",{userId,count:pendingMessages.length});let deliveredCount=0;for(let message of pendingMessages){if(!client.subscribedTopics.has("*")&&!client.subscribedTopics.has(message.topic))continue;let payload=JSON.stringify({type:"event",messageId:message.messageId,topic:message.topic,data:message.data,timestamp:message.sentAt,isRedelivery:!0});try{client.ws.send(payload),client.pendingAcks.set(message.messageId,{sentAt:Date.now(),retryCount:message.retryCount+1}),await incrementRetryCount(redis,userId,message.messageId,ackTtl,maxRetries),deliveredCount++}catch{break}}return deliveredCount}return{generateClientId,registerClient,unregisterClient,updateClientTopics,getClientsByUser,getConnectedUserIds,getClientCount,sendToClient,broadcastEvent,broadcastToUser,broadcastToUserNoAck,broadcastPresenceToOthers,handleClientAck,deliverPendingMessages,getPendingMessageCount:(userId)=>getPendingMessageCount(redis,userId)}}var OPAQUE_RUN=/[A-Za-z0-9_-]{24,}(?:\.[A-Za-z0-9_-]{8,}){0,2}/g;function previewOf2(frame){return frame.slice(0,120).replace(OPAQUE_RUN,(match)=>`[redacted ${match.length} chars]`)}function classifyClientFrame(message){if(typeof message==="string"){if(message.trim()==="")return{kind:"ignore"};let parsed;try{parsed=JSON.parse(message)}catch{return{kind:"malformed",preview:previewOf2(message)}}return asMessage(parsed,message.length)}if(message&&typeof message==="object")return asMessage(message,approximateSize(message));return{kind:"ignore"}}function approximateSize(value2){if(ArrayBuffer.isView(value2))return value2.byteLength;if(value2 instanceof ArrayBuffer)return value2.byteLength;try{return JSON.stringify(value2)?.length??0}catch{return 0}}function asMessage(value2,size){if(!value2||typeof value2!=="object"||Array.isArray(value2))return{kind:"ignore"};let record3=value2,type4=record3.type;if(typeof type4!=="string"||type4==="")return{kind:"ignore"};return{kind:"message",parsed:record3,type:type4,size}}function tokensMatch(a,b){let ba=Buffer.from(a),bb=Buffer.from(b);if(ba.length!==bb.length)return!1;return timingSafeEqual3(ba,bb)}var HEARTBEAT_WS_TYPES=new Set(["ping","pong","ack"]);function buildHandshakeHeaders(rawHeaders,queryToken){let headers=new Headers;if(rawHeaders&&typeof rawHeaders==="object"){for(let[key2,value2]of Object.entries(rawHeaders))if(typeof value2==="string")headers.set(key2,value2)}if(queryToken)headers.set("authorization",`Bearer ${queryToken}`),headers.delete("cookie");return headers}function createPubSubRoutes(config){let{logger:logger2,getLiveMonitoringService}=config,clientManager=createClientManager(config);if(config.ack.enabled)initAckCleanup();let cleanupTimer=null,daprApiToken=config.daprApiToken||process.env.APP_API_TOKEN||process.env.DAPR_API_TOKEN,warnedNoDaprToken=!1;recordBootStep(()=>{if(config.cleanupIntervalMs>0)cleanupTimer=setInterval(()=>{logger2.info("[PubSub] Periodic cleanup running",{totalClients:clientManager.getClientCount()})},config.cleanupIntervalMs);logger2.info("[PubSub] Routes initialized",{basePath:config.basePath,wsPath:config.wsPath,ackEnabled:config.ack.enabled,presenceEnabled:config.presence.enabled})}),recordShutdownStep(()=>{if(cleanupTimer)clearInterval(cleanupTimer),cleanupTimer=null});let routePostTopic=defineRoute({method:"POST",path:"/:topic",handler:async({params,body,request,set:set2})=>{let topic=params.topic;if(daprApiToken){let presented=request.headers.get("dapr-api-token")??"";if(!tokensMatch(presented,daprApiToken))return logger2.warn("[PubSub] Rejected Dapr event: missing/invalid dapr-api-token",{topic}),set2.status=403,{status:"DROP"}}else if(!warnedNoDaprToken)warnedNoDaprToken=!0,logger2.warn("[PubSub] Dapr subscription endpoint is UNAUTHENTICATED \u2014 set config.pubsub.daprApiToken or the APP_API_TOKEN/DAPR_API_TOKEN env so only the Dapr sidecar can inject events");let eventData;try{if(!body){let text2=await request.text();eventData=JSON.parse(text2)}else eventData=body}catch{return set2.status=200,{status:"DROP"}}logger2.info("[PubSub] Dapr event received",{topic,eventId:eventData.id,source:eventData.source});let userId=eventData.data?.user_id;if(userId)clientManager.broadcastToUser(userId,topic,eventData);else clientManager.broadcastEvent(topic,eventData);return getLiveMonitoringService?.()?.recordDaprEvent("pubsub_subscribe",{topic,success:!0,metadata:{eventId:eventData.id,source:eventData.source,userId:userId||null}}),set2.status=200,{status:"SUCCESS"}}}),routes=collectRoutes([{prefix:config.basePath,routes:[routePostTopic]}]),socketRoute=defineWebSocketRoute({path:config.wsPath,idleTimeout:config.wsIdleTimeout,async open(ws){let data=ws.data,query=data.query,userId;if(config.authenticate){let handshakeHeaders=buildHandshakeHeaders(data.headers,query.token),authResult=await config.authenticate(handshakeHeaders);if(!authResult||!("userId"in authResult)){let reason=authResult?.reason??"unspecified";ws.send(JSON.stringify({type:"error",error:"Unauthorized: a valid session is required for this connection",reason,timestamp:Date.now()})),ws.close(4001,`Unauthorized: ${reason}`),getLiveMonitoringService?.()?.recordWsEvent("error",{success:!1,error:`Unauthorized WebSocket handshake: ${reason}`,metadata:{reason}});return}userId=authResult.userId}else userId=query.userId;if(!userId){ws.send(JSON.stringify({type:"error",error:"userId is required for WebSocket connection",timestamp:Date.now()})),ws.close(4001,"userId is required"),getLiveMonitoringService?.()?.recordWsEvent("error",{success:!1,error:"userId is required"});return}let clientId=clientManager.generateClientId(),topics=query.topics?.split(",").map((t)=>t.trim())||["*"];ws.data.clientId=clientId,ws.data.openedAt=Date.now();let registration=clientManager.registerClient(clientId,ws,userId,topics);if(ws.send(JSON.stringify({type:"connected",clientId,subscribedTopics:registration.accepted,...registration.dropped.length>0?{droppedTopics:registration.dropped,topicCap:config.maxTopicsPerClient}:{},timestamp:Date.now()})),getLiveMonitoringService?.()?.recordWsEvent("connection",{clientId,userId,topic:topics.join(","),success:!0}),config.presence.enabled)clientManager.broadcastPresenceToOthers(userId,"online");let connectedUserId=userId;if(config.ack.enabled&&connectedUserId)clientManager.getPendingMessageCount(connectedUserId).then((count2)=>{if(count2>0)logger2.info("[PubSub] Delivering pending messages",{userId:connectedUserId,count:count2}),clientManager.deliverPendingMessages(connectedUserId,clientId).catch(()=>{})}).catch(()=>{})},message(ws,message){let clientId=ws.data.clientId,frame=classifyClientFrame(message);if(frame.kind==="ignore")return;if(frame.kind==="malformed"){logger2.debug("[PubSub] Failed to parse client message",{clientId,frame:frame.preview});return}let{parsed}=frame;try{if(!HEARTBEAT_WS_TYPES.has(frame.type))getLiveMonitoringService?.()?.recordWsEvent("message",{clientId,messageType:frame.type,success:!0,dataSize:frame.size});switch(frame.type){case"subscribe":if(Array.isArray(parsed.topics)){let update=clientManager.updateClientTopics(clientId,parsed.topics);clientManager.sendToClient(clientId,{type:"subscribed",topics:update.accepted,...update.dropped.length>0?{droppedTopics:update.dropped,topicCap:config.maxTopicsPerClient}:{},timestamp:Date.now()})}break;case"unsubscribe":logger2.info("[PubSub] Unsubscribe request",{clientId,topics:parsed.topics});break;case"ping":clientManager.sendToClient(clientId,{type:"pong",timestamp:Date.now()});break;case"ack":if(parsed.messageId)clientManager.handleClientAck(clientId,parsed.messageId).catch(()=>{});break}}catch(error3){logger2.error("[PubSub] Client message handler failed",error3,{clientId,messageType:frame.type})}},close(ws,code,reason){let state=ws.data,clientId=state.clientId,openedAt=state.openedAt,livedMs=typeof openedAt==="number"?Date.now()-openedAt:null;if(!clientId)return;clientManager.unregisterClient(clientId),getLiveMonitoringService?.()?.recordWsEvent("close",{clientId,success:!0,metadata:{code,reason:reason||"",livedMs,shortLived:livedMs!==null&&livedMs<60000}}),logger2.info("[PubSub] Client disconnected",{clientId,code,reason,livedMs})}});return{routes,socket:socketRoute,clientManager}}init_JWT();init_utils5();init_sessionBinding();function classifyRefusal(error3){if(!error3)return"unspecified";switch(error3){case"Token expired":return"expired";case"Invalid signature":return"invalid-signature";case"Invalid token format: expected 3 parts":case"Invalid token format: missing parts":case"Invalid header: failed to decode":case"Invalid header: typ must be JWT":case"Invalid payload: failed to decode":return"malformed";case"Invalid issuer":case"Invalid audience":return"wrong-audience";case"Token issued in the future":return"not-yet-valid";default:if(error3.startsWith("Unsupported algorithm:"))return"malformed";return"unspecified"}}function createHandshakeAuthenticator(deps){return async(headers)=>{let wsTokens=parseTokenValuesFromHeaders(headers,deps.tokenNames);if(!wsTokens.access_token)return deps.logger.debug("[PubSub] WS handshake rejected: no access token presented"),{reason:"no-token"};let jwtResult=verifyJWT(wsTokens.access_token,deps.accessTokenSecret,{issuer:deps.issuer,audience:deps.audience});if(!jwtResult.valid){let reason=classifyRefusal(jwtResult.error);return deps.logger.warn("[PubSub] WS handshake rejected: JWT verification failed",{reason:jwtResult.error,classified:reason}),{reason}}let sub=jwtResult.payload?.sub;if(typeof sub!=="string"||sub.length===0)return{reason:"token-without-subject"};if(!deps.requireLiveSession)return{userId:sub};let boundSessionId=typeof jwtResult.payload.sessionId==="string"?jwtResult.payload.sessionId:"";if(!boundSessionId)return deps.logger.warn("[PubSub] WS handshake rejected: token carries no session",{userId:sub}),{reason:"session-unbound"};let tenant=typeof jwtResult.payload.tenant==="string"?jwtResult.payload.tenant:void 0,sessionsTable=deps.resolveSessionsTable?.(tenant)??null,session;try{session=await refreshSessionIsAlive({sessionId:boundSessionId,db:deps.db,sessionsTable})}catch(error3){return deps.logger.warn("[PubSub] WS handshake rejected: session store unreadable",{userId:sub,sessionId:boundSessionId,error:error3 instanceof Error?error3.message:String(error3)}),{reason:"session-unreadable"}}if(!session.alive)return deps.logger.warn("[PubSub] WS handshake rejected: session is no longer active",{userId:sub,sessionId:boundSessionId,reason:session.reason}),{reason:"session-revoked"};if(session.userId&&session.userId!==sub)return deps.logger.warn("[PubSub] WS handshake rejected: token subject is not the session owner",{userId:sub,sessionId:boundSessionId,sessionOwner:session.userId}),{reason:"session-subject-mismatch"};return{userId:sub}}}init_esm3();init_Secrets();var timed=async(fn)=>{let started=performance.now();return{result:await fn(),durationMs:Math.round(performance.now()-started)}},postForm=async(url,body)=>{let response=await fetch(url,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams(body).toString(),signal:AbortSignal.timeout(12000)}),text2=await response.text(),json={};try{json=JSON.parse(text2)}catch{json=Object.fromEntries(new URLSearchParams(text2))}return{status:response.status,json,text:text2}},describeError=(error3)=>{if(error3 instanceof Error){if(error3.name==="TimeoutError")return"Provider did not respond within 12s";return error3.message}return String(error3)},MICROSOFT_TOKEN_URL=(tenant)=>`https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,DEFAULT_TOKEN_URLS={google:"https://oauth2.googleapis.com/token",github:"https://github.com/login/oauth/access_token"},describeRedirectUri=(value2)=>{let trimmed=value2?.trim();if(!trimmed)return"The reply URL (Redirect URI) is empty \u2014 sign-in cannot complete";if(/^[A-Z][A-Z0-9_]{2,}$/.test(trimmed))return`The reply URL looks like an environment variable name ("${trimmed}") and that variable is not set`;let parsed;try{parsed=new URL(trimmed)}catch{return`The reply URL is not a valid URL: "${trimmed}"`}if(parsed.protocol!=="https:"&&parsed.hostname!=="localhost")return`The reply URL must be https (it is "${parsed.protocol}//")`;return null};async function testOAuthProvider(provider,config){if(!config.clientId||!config.clientSecret)return{ok:!1,status:"failed",message:"Client ID and Client Secret must both be filled in",durationMs:0};let redirectProblem=describeRedirectUri(config.redirectUri);if(redirectProblem)return{ok:!1,status:"failed",message:redirectProblem,durationMs:0};try{if(provider==="microsoft"){let tenant=config.tenantId||"common";if(tenant==="common"||tenant==="organizations"||tenant==="consumers")return await probeWithBogusCode(MICROSOFT_TOKEN_URL(tenant),config);let{result,durationMs}=await timed(()=>postForm(MICROSOFT_TOKEN_URL(tenant),{grant_type:"client_credentials",client_id:config.clientId,client_secret:config.clientSecret,scope:"https://graph.microsoft.com/.default"}));if(result.status===200&&result.json.access_token)return{ok:!0,status:"ok",message:`Entra credentials verified (tenant ${tenant})`,durationMs};let code=String(result.json.error??result.status);return{ok:!1,status:"failed",message:String(result.json.error_description??result.text).slice(0,400),code,durationMs}}let tokenUrl=config.tokenUrl||DEFAULT_TOKEN_URLS[provider];if(!tokenUrl)return{ok:!1,status:"unknown",message:`No token URL is known for "${provider}" \u2014 enter a tokenUrl`,durationMs:0};return await probeWithBogusCode(tokenUrl,config)}catch(error3){return{ok:!1,status:"failed",message:describeError(error3),durationMs:0}}}async function probeWithBogusCode(tokenUrl,config){let{result,durationMs}=await timed(()=>postForm(tokenUrl,{grant_type:"authorization_code",code:"nucleus-credential-probe",client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri||"https://localhost/callback"})),code=String(result.json.error??""),description=String(result.json.error_description??result.text).slice(0,400);if(code==="invalid_grant"||code==="bad_verification_code")return{ok:!0,status:"ok",message:"The provider accepted these credentials",code,durationMs};if(code==="invalid_client"||code==="unauthorized_client"||result.status===401)return{ok:!1,status:"failed",message:description||"The provider refused this Client ID/Secret pair",code:code||"invalid_client",durationMs};if(description.includes("AADSTS7000215")||description.includes("AADSTS700016"))return{ok:!1,status:"failed",message:description,code,durationMs};return{ok:!1,status:"unknown",message:description||"The provider returned an unexpected answer",code:code||String(result.status),durationMs}}async function testStorageProvider(provider){if(!provider)return{ok:!1,status:"failed",message:"No storage provider is configured",durationMs:0};try{let{result,durationMs}=await timed(()=>provider.isAvailable());return result?{ok:!0,status:"ok",message:`Connection verified: ${provider.describe}`,durationMs}:{ok:!1,status:"failed",message:`Unreachable: ${provider.describe} \u2014 check the server, the share name and the credentials`,durationMs}}catch(error3){return{ok:!1,status:"failed",message:describeError(error3),durationMs:0}}}async function testEmailProvider(service,recipient){if(!service)return{ok:!1,status:"failed",message:"No email provider is configured",durationMs:0};if(!service.isAvailable())return{ok:!1,status:"failed",message:"The provider could not be started with these credentials",durationMs:0};if(!recipient)return{ok:!0,status:"ok",message:"The provider started \u2014 enter a recipient address to send for real",durationMs:0};try{let{result,durationMs}=await timed(()=>service.sendEmail({to:recipient,subject:"Nucleus connection test",html:"<p>This email was sent by a connection test run from the admin panel.</p>"}));return result.success?{ok:!0,status:"ok",message:`Test email sent to ${recipient}`,durationMs}:{ok:!1,status:"failed",message:result.error??"Sending failed",durationMs}}catch(error3){return{ok:!1,status:"failed",message:describeError(error3),durationMs:0}}}init_routeCollector();init_godminGuard();var SECRET_KINDS=["text","password","json","connection_string","pem","boolean","list"];var SCOPE_PATTERN=/^[A-Za-z0-9_.-]{1,200}$/,KEY_PATTERN=/^[A-Za-z0-9_-]{1,120}$/,fail5=(set2,status,message)=>{return set2.status=status,{isSuccess:!1,message,data:null}};function createSecretsRoutes(routeConfig){let{logger:logger2,basePath,db,schemaTables,getService,resolvedOptions,getEmailService,getStorageProvider:getStorageProvider2}=routeConfig,guard4=createGodminGuard({logger:logger2,db,schemaTables,area:"secrets management",allowedRoles:routeConfig.adminRoles}),requireService=(set2)=>{let service=getService();if(!service)return set2.status=503,null;return service},actorOf=(request)=>request.headers.get("x-user-id")??void 0,declared=[defineRoute({method:"GET",path:"/slots",handler:async({set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled \u2014 set secrets.enabled and provide an encryption key",data:null};let slots=await service.describe(resolvedOptions),groups=[...new Set(slots.map((s)=>s.group))].sort();return{isSuccess:!0,message:"Credential slots",data:{slots,groups,counts:{total:slots.length,fromDb:slots.filter((s)=>s.source==="db").length,fromEnv:slots.filter((s)=>s.source==="env").length,missing:slots.filter((s)=>s.source==="missing").length,missingRequired:slots.filter((s)=>s.source==="missing"&&s.required).length}}}},detail:{tags:["Secrets"],summary:"List credential slots discovered from the running config",description:"Slots are derived by walking the config tree, so a new integration appears without a code change. Values are never returned \u2014 only a masked preview and the resolution source."}}),defineRoute({method:"GET",path:"/",handler:async({set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};return{isSuccess:!0,message:"Stored credentials",data:{secrets:await service.list()}}},detail:{tags:["Secrets"],summary:"List stored credentials",description:"Metadata only \u2014 the encrypted value is never returned."}}),defineRoute({method:"PUT",path:"/:scope/:key",handler:async({params,body,set:set2,request})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let{scope,key:key2}=params;if(!SCOPE_PATTERN.test(scope))return fail5(set2,400,"Invalid scope");if(!KEY_PATTERN.test(key2))return fail5(set2,400,"Invalid key");let value2=body.value;if(typeof value2!=="string"||value2.length===0)return fail5(set2,400,"value is required");if(value2.length>65536)return fail5(set2,400,"value exceeds 64KB");if(body.kind==="json")try{JSON.parse(value2)}catch{return fail5(set2,400,"value is not valid JSON")}let saved=await service.set({scope,key:key2,value:value2,kind:body.kind,description:body.description,actorId:actorOf(request)});if(!saved)return fail5(set2,500,"Could not store the credential");return{isSuccess:!0,message:"Credential stored",data:{secret:saved}}},params:Type.Object({scope:Type.String(),key:Type.String()}),body:Type.Object({value:Type.String(),kind:Type.Optional(Type.Union(SECRET_KINDS.map((kind)=>Type.Literal(kind)))),description:Type.Optional(Type.String())}),detail:{tags:["Secrets"],summary:"Set or rotate a credential",description:"Stores the value encrypted (AES-256-GCM). Rotating retains the previous value for a one-step rollback. Takes effect within seconds on every pod \u2014 no restart."}}),defineRoute({method:"POST",path:"/:scope/:key/rollback",handler:async({params,set:set2,request})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let restored=await service.rollback(params.scope,params.key,actorOf(request));if(!restored)return fail5(set2,404,"No previous value to roll back to");return{isSuccess:!0,message:"Previous credential restored",data:{secret:restored}}},params:Type.Object({scope:Type.String(),key:Type.String()}),detail:{tags:["Secrets"],summary:"Roll back to the value replaced by the last rotation"}}),defineRoute({method:"DELETE",path:"/:scope/:key",handler:async({params,set:set2,request})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};if(!await service.remove(params.scope,params.key,actorOf(request)))return fail5(set2,404,"Credential not found");return{isSuccess:!0,message:"Credential removed \u2014 the config falls back to env or literal",data:null}},params:Type.Object({scope:Type.String(),key:Type.String()}),detail:{tags:["Secrets"],summary:"Delete a stored credential",description:"Resolution falls back to the env var or literal declared in config."}}),defineRoute({method:"POST",path:"/test",handler:async({body,set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let{target:target2}=body,result;if(target2==="email")result=await testEmailProvider(getEmailService?.()??null,body.recipient);else if(target2==="oauth"){let provider=body.provider;if(!provider)return fail5(set2,400,"provider is required for an oauth test");let scope=`authentication.oauth.providers.${provider}`,providerConfig=(resolvedOptions.authentication?.oauth?.providers??{})[provider]??{},declaredString=(key2)=>{let raw=providerConfig[key2];return typeof raw==="string"?raw:void 0};result=await testOAuthProvider(provider,{clientId:service.value(scope,"clientId",declaredString("clientId")),clientSecret:service.value(scope,"clientSecret",declaredString("clientSecret")),tenantId:service.value(scope,"tenantId",declaredString("tenantId")),tokenUrl:declaredString("tokenUrl"),redirectUri:service.value(scope,"redirectUri",declaredString("redirectUri"))})}else if(target2==="storage")result=await testStorageProvider(getStorageProvider2?.()??null);else return fail5(set2,400,`Unsupported test target "${target2}"`);return logger2.info("[Secrets] Connection test",{target:target2,provider:body.provider,status:result.status,durationMs:result.durationMs}),{isSuccess:result.ok,message:result.message,data:{result}}},body:Type.Object({target:Type.Union([Type.Literal("oauth"),Type.Literal("email"),Type.Literal("storage")]),provider:Type.Optional(Type.String()),recipient:Type.Optional(Type.String())}),detail:{tags:["Secrets"],summary:"Test a credential against the real provider",description:"Backend-to-backend only. OAuth uses client_credentials where the provider supports it and a client-authentication probe otherwise; email optionally sends a real test message."}}),defineRoute({method:"POST",path:"/reencrypt",handler:async({body,set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};if(!body.newMasterKey||body.newMasterKey.length<16)return fail5(set2,400,"newMasterKey must be at least 16 characters");let result=await service.reencryptAll(body.newMasterKey),uncovered="Credentials stored outside this store \u2014 an integration connection\u2019s token, client secret and service password \u2014 are NOT re-encrypted by this. Re-enter them from the integrations panel after the new key is deployed.";return{isSuccess:result.failed.length===0,message:result.failed.length===0?`Re-encrypted ${result.migrated} credentials \u2014 deploy the new key before the next restart. ${uncovered}`:`Nothing was re-encrypted: ${result.failed.length} credential(s) could not be read with the current key, so none were rewritten.`,data:{...result,uncovered}}},body:Type.Object({newMasterKey:Type.String()}),detail:{tags:["Secrets"],summary:"Re-encrypt every credential under a new master key",description:"Rotates the key protecting the store. The new key must be deployed to the environment before the next restart, or nothing will decrypt."}}),defineRoute({method:"GET",path:"/discover",handler:()=>({isSuccess:!0,message:"Slots discovered from the running config",data:{slots:discoverSlots(resolvedOptions).map(({configValue:_configValue,...slot})=>slot)}}),detail:{tags:["Secrets"],summary:"Show which credential slots the config declares",description:"Diagnostics \u2014 resolution state is not consulted and no value is read."}}),defineRoute({method:"POST",path:"/storage/migrate",handler:async(ctx)=>{let body=ctx.body,build=routeConfig.buildStorageProvider;if(!build)return fail5(ctx.set,503,"Storage is not configured");let destination=build(body.to),source=build(body.to==="smb"?"local":"smb");if(!source||!destination)return fail5(ctx.set,503,"Storage is not configured");if(!await destination.isAvailable())return fail5(ctx.set,422,`The destination store is unreachable (${destination.describe}) \u2014 test the connection first`);if(body.dryRun){let preview=await migrateStorage({from:source,to:Object.assign(Object.create(Object.getPrototypeOf(destination)),destination,{exists:async()=>!1,write:async()=>{},ensureDir:async()=>{}}),maxFiles:1e5},{logger:logger2});return{isSuccess:!0,message:`${preview.copied} file${preview.copied===1?"":"s"} would be copied`,data:{preview}}}let report=await migrateStorage({from:source,to:destination},{logger:logger2}),clean=report.failed===0&&!report.truncated;return logger2.warn("[Storage] Migration run from the admin panel",{to:body.to,copied:report.copied,skipped:report.skipped,failed:report.failed,truncated:report.truncated}),{isSuccess:clean,message:clean?`${report.copied} file${report.copied===1?"":"s"} copied, ${report.skipped} already present`:`${report.copied} copied, ${report.failed} failed${report.truncated?", list truncated":""}`,data:{report}}},body:Type.Object({to:Type.Union([Type.Literal("local"),Type.Literal("smb")]),dryRun:Type.Optional(Type.Boolean())}),detail:{tags:["Secrets"],summary:"Copy every stored file to the other backend",description:"Copy-only: the source is never deleted, and a file already on the target is skipped so a re-run resumes. Run BEFORE switching storage.provider."}})];return collectRoutes([{prefix:basePath,guards:[guard4],routes:declared}])}function readAttemptedIdentity(parsedBody,rawBodyText,contentType){let fromJson=parsedBody.email;if(typeof fromJson==="string"&&fromJson.trim())return fromJson;if(!rawBodyText)return;let type4=(contentType||"").toLowerCase();if(type4.includes("application/x-www-form-urlencoded"))try{let value2=new URLSearchParams(rawBodyText).get("email");return value2?.trim()?value2:void 0}catch{return}if(type4.includes("multipart/form-data")&&rawBodyText.length<=MAX_SCANNED_BODY)return readMultipartField(rawBodyText,"email");return}var MAX_SCANNED_BODY=65536;function readMultipartField(body,field){let marker=`name="${field}"`,at=body.indexOf(marker);if(at<0)return;let separator=body.indexOf(`\r
551
+ \r
552
+ `,at),start=separator>=0?separator+4:body.indexOf(`
553
+
554
+ `,at)+2;if(start<=1)return;let end=body.indexOf(`\r
555
+ `,start),value2=(end>=0?body.slice(start,end):body.slice(start)).trim();return value2?value2:void 0}var snakeToCamel4=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase());function warnOnEntityBasePathCollision(feature,basePath,entities,logger2){let segment=basePath.replace(/^\/+|\/+$/g,"");if(!segment||segment.includes("/"))return!1;let clash=(entities??[]).find((entity)=>snakeToCamel4(entity.table_name)===segment);if(!clash)return!1;return logger2.error(`[${feature}] basePath "${basePath}" is also the generated entity route for "${clash.table_name}". Both are registered and one will shadow the other. Move the feature (e.g. "/nucleus${basePath}") or rename the entity.`),!0}function requiredBodyCeiling(input){if(!input.storageEnabled)return null;let perFile=input.maxFileSizeBytes;if(!perFile||!Number.isFinite(perFile)||perFile<=0)return null;let files=Math.max(1,Math.floor(input.maxFiles??1));return perFile*files+8388608}function bodyCeilingComplaint(input){if(input.required===null)return null;if((input.configured??134217728)>=input.required)return null;let mb2=(bytes)=>`${Math.round(bytes/1048576)} MB`,source=input.configured==null?`the host passed no \`serve.maxRequestBodySize\`, so Bun's default of ${mb2(134217728)} applies`:`the host passed ${mb2(input.configured)}`;return`Uploads are configured up to ${mb2(input.required)}, but ${source}. `+"Anything larger is refused by the server before this application sees it \u2014 "+`the upload appears to run and then fails with nothing to show. Fix: raise \`storage.maxFileSizeBytes\` / \`security.maxRequestBodyBytes\` in the config, or pass \`maxRequestBodyBytes\` to \`createNucleusServer\`. The server needs ${input.required} bytes of headroom.`}function bunBodyCeilingFor(input){let wanted=Math.max(input.storageRequired??0,input.guardCeiling??0);return wanted>134217728?wanted:void 0}var APP_BASELINE_BYTES=268435456,COPIES_IN_FLIGHT=2;function memoryCeilingComplaint(input){if(input.required===null||input.cgroupLimit===null||input.cgroupLimit<=0)return null;let needed=input.required*COPIES_IN_FLIGHT+APP_BASELINE_BYTES;if(input.cgroupLimit>=needed)return null;let mb2=(bytes)=>`${Math.round(bytes/1048576)} MB`,affordable=Math.max(0,Math.floor((input.cgroupLimit-APP_BASELINE_BYTES)/COPIES_IN_FLIGHT));return`Uploads are configured up to ${mb2(input.required)}, but this container is capped at ${mb2(input.cgroupLimit)} and an upload is held in memory twice on top of the ~${mb2(APP_BASELINE_BYTES)} `+`the app already holds \u2014 it needs ~${mb2(needed)}. The kernel will kill the process mid-upload. Fix: raise the container's memory limit to at least ${mb2(needed)}, or lower storage.maxFileSizeBytes to about ${mb2(affordable)}.`}var ipMatches=(ip,entry)=>entry.includes("/")?isIpInCidr(ip,entry):ip===entry;function resolveClientIp(socketIp,forwardedFor,trustedProxies=[]){let peer=socketIp?.trim()||"",chain=(forwardedFor||"").split(",").map((s)=>s.trim()).filter(Boolean);if(!peer)return chain[0]||"unknown";if(trustedProxies.length===0)return peer;let trusted=(ip)=>trustedProxies.some((p)=>ipMatches(ip,p));if(!trusted(peer))return peer;for(let i=chain.length-1;i>=0;i--){let hop=chain[i];if(hop&&!trusted(hop))return hop}return chain[0]||peer}var isNonDiscriminatingPeer=(ip)=>ip==="::1"||ip==="unknown"||/^(::ffff:)?127\./.test(ip);function createPeerDiversityWatcher(sampleSize=200){let seen=new Set,seenCount=0,settled=!1;return{observe(ip){if(settled)return null;if(seenCount++,seen.add(ip),seen.size>1)return settled=!0,null;if(seenCount>=sampleSize)return settled=!0,ip;return null}}}var DEFAULTS={cookieName:"csrf_token",headerName:"x-csrf-token"},resolveCsrfConfig=(raw)=>{if(raw===!0)return{enabled:!0,...DEFAULTS};if(!raw)return{enabled:!1,...DEFAULTS};return{enabled:raw.enabled!==!1,cookieName:raw.cookieName||DEFAULTS.cookieName,headerName:(raw.headerName||DEFAULTS.headerName).toLowerCase()}},parseCookies=(cookieHeader)=>{let out={};if(!cookieHeader)return out;for(let part of cookieHeader.split(";")){let trimmed=part.trim(),eq36=trimmed.indexOf("=");if(eq36>0)out[trimmed.slice(0,eq36)]=trimmed.slice(eq36+1)}return out},SAFE_METHODS=new Set(["GET","HEAD","OPTIONS"]),isStateChanging=(method)=>!SAFE_METHODS.has(method.toUpperCase()),requiresCsrf=(params)=>{if(!isStateChanging(params.method))return!1;if(params.hasAuthorizationHeader)return!1;return params.authCookieNames.some((n)=>!!params.cookies[n])},csrfTokenMatches=(csrfCookie,csrfHeader)=>{if(!csrfCookie||!csrfHeader)return!1;return csrfCookie===csrfHeader};var SENSITIVE_HEADER_NAMES=new Set(["authorization","proxy-authorization","cookie","set-cookie","x-access-token","x-refresh-token","x-session-id","x-user-id","x-user-roles","x-user-claims","x-user-email","x-api-key","x-api-key-id","x-api-key-owner-type","x-auth-type"]);function redactSensitiveHeaders(headers){let out={};return headers.forEach((value2,key2)=>{out[key2]=SENSITIVE_HEADER_NAMES.has(key2.toLowerCase())?"[REDACTED]":value2}),out}init_sensitiveColumns();var TENANT_CLAIM="tenant",verifyTenantBinding=(tokenTenant,resolvedSchema,mode="lenient")=>{if(mode==="off")return{ok:!0};if(!resolvedSchema)return{ok:!0};let claim=typeof tokenTenant==="string"&&tokenTenant.length>0?tokenTenant:void 0;if(!claim)return mode==="strict"?{ok:!1,reason:"token is not bound to a tenant"}:{ok:!0};return claim===resolvedSchema?{ok:!0}:{ok:!1,reason:"token tenant does not match the requested tenant"}};init_storage2();init_routeMount();init_esm3();init_routeCollector();import{eq as eq36}from"drizzle-orm";var RESERVED_SUBDOMAINS=["www","api","app","admin","dashboard","mail","ftp","cdn","static","assets","docs","help","support","status","blog","dev","staging","test","demo","localhost"],DEFAULT_SELF_SIGNUP_LIMITS={maxSignupsPerIpPerWindow:3,ipWindowMs:3600000,maxTenantsPerEmail:1};var col9=(table,name2)=>table[name2];function createCheckSubdomainRoute(config){let declared=[defineRoute({method:"GET",path:"/tenants/check-subdomain/:subdomain",handler:async(ctx)=>{let{subdomain}=ctx.params,normalized=subdomain.toLowerCase().trim();if(!/^[a-z][a-z0-9-]*$/.test(normalized)||normalized.length<2||normalized.length>63)return ctx.set.status=400,{isSuccess:!1,message:"Invalid subdomain format. Must start with a letter, contain only lowercase letters, numbers, and hyphens, and be 2-63 characters.",data:{available:!1,subdomain:normalized}};if(RESERVED_SUBDOMAINS.includes(normalized))return{isSuccess:!0,message:"Subdomain is reserved",data:{available:!1,subdomain:normalized}};let tenantRegistry=config.getTenantRegistry(),db=config.getDb();if(tenantRegistry){if(tenantRegistry.getActiveTenants().find((t)=>t.subdomain===normalized))return{isSuccess:!0,message:"Subdomain is already taken",data:{available:!1,subdomain:normalized}}}if(db&&tenantRegistry){let tenantsTable=tenantRegistry.getMainContext().schemaTables.tenants;if(tenantsTable)try{if((await db.select().from(tenantsTable).where(eq36(col9(tenantsTable,"subdomain"),normalized)).limit(1)).length>0)return{isSuccess:!0,message:"Subdomain is already taken",data:{available:!1,subdomain:normalized}}}catch{}}return{isSuccess:!0,message:"Subdomain is available",data:{available:!0,subdomain:normalized}}},params:Type.Object({subdomain:Type.String({minLength:2,maxLength:63})}),detail:{tags:["Tenants"],summary:"Check subdomain availability",description:"Public endpoint to check if a subdomain is available for registration. No authentication required."}})];return collectRoutes([{routes:declared}])}init_esm3();init_Logger2();init_routeCollector();import{eq as eq37}from"drizzle-orm";var col10=(table,name2)=>table[name2];function createManageRoutes(config){let log3=config.logger.scoped(TENANT_SUSPEND),declared=[defineRoute({method:"GET",path:"/tenants",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available",data:null};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required",data:null};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required",data:null};let allTenants=[...tenantRegistry.getActiveTenants(),...Array.from(getAllTenants(tenantRegistry)).filter((t)=>t.status!=="active")];return{success:!0,message:`Found ${allTenants.length} tenants`,data:{items:allTenants.map((t)=>({id:t.id,subdomain:t.subdomain,schemaName:t.schemaName,companyId:t.companyId,companyName:t.companyName,godAdminEmail:t.godAdminEmail,status:t.status,plan:t.plan,domain:t.domain,maxUsers:t.maxUsers,provisionedAt:t.provisionedAt,suspendedAt:t.suspendedAt})),totalCount:allTenants.length}}},detail:{tags:["Tenants"],summary:"List tenants",description:"List all tenants in the platform. Requires godmin privileges."}}),defineRoute({method:"GET",path:"/tenants/:id",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available",data:null};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required",data:null};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required",data:null};let{id}=ctx.params,tenant=tenantRegistry.getTenantById(id);if(!tenant)return ctx.set.status=404,{success:!1,message:"Tenant not found",data:null};let features=tenantRegistry.getTenantFeatures(id);return{success:!0,message:"Tenant found",data:{...tenant,features:features.map((f)=>({featureName:f.featureName,enabled:f.enabled}))}}},detail:{tags:["Tenants"],summary:"Get tenant details",description:"Get detailed information about a specific tenant. Requires godmin privileges."}}),defineRoute({method:"POST",path:"/tenants/:id/suspend",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required"};let{id}=ctx.params,body=ctx.body,tenant=tenantRegistry.getTenantById(id);if(!tenant)return ctx.set.status=404,{success:!1,message:"Tenant not found"};if(tenant.status==="suspended")return ctx.set.status=400,{success:!1,message:"Tenant is already suspended"};let mainContext=tenantRegistry.getMainContext(),tenantsTable=mainContext.schemaTables.tenants;if(!tenantsTable)return ctx.set.status=503,{success:!1,message:"Tenants table not available"};let now=new Date;await db.update(tenantsTable).set({status:"suspended",suspendedAt:now,suspendedReason:body.reason||"Suspended by godmin",updatedAt:now}).where(eq37(col10(tenantsTable,"id"),id));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId:id,eventType:"suspended",eventData:JSON.stringify({reason:body.reason||"Suspended by godmin"}),performedBy:requestingUserId,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",createdAt:now,updatedAt:now})}catch{log3.warn("Failed to record suspend event")}return await tenantRegistry.refreshTenant(id),log3.info("Tenant suspended",{tenantId:id,reason:body.reason}),{success:!0,message:`Tenant "${tenant.subdomain}" suspended`}},body:Type.Object({reason:Type.Optional(Type.String())}),detail:{tags:["Tenants"],summary:"Suspend tenant",description:"Suspend a tenant, preventing all access. Requires godmin privileges."}}),defineRoute({method:"POST",path:"/tenants/:id/reactivate",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required"};let{id}=ctx.params,tenant=tenantRegistry.getTenantById(id);if(!tenant)return ctx.set.status=404,{success:!1,message:"Tenant not found"};if(tenant.status!=="suspended")return ctx.set.status=400,{success:!1,message:`Tenant is not suspended (current: ${tenant.status})`};let mainContext=tenantRegistry.getMainContext(),tenantsTable=mainContext.schemaTables.tenants;if(!tenantsTable)return ctx.set.status=503,{success:!1,message:"Tenants table not available"};let now=new Date;await db.update(tenantsTable).set({status:"active",suspendedAt:null,suspendedReason:null,updatedAt:now}).where(eq37(col10(tenantsTable,"id"),id));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId:id,eventType:"reactivated",eventData:JSON.stringify({previousReason:tenant.suspendedReason}),performedBy:requestingUserId,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",createdAt:now,updatedAt:now})}catch{log3.warn("Failed to record reactivate event")}return await tenantRegistry.refreshTenant(id),log3.info("Tenant reactivated",{tenantId:id}),{success:!0,message:`Tenant "${tenant.subdomain}" reactivated`}},detail:{tags:["Tenants"],summary:"Reactivate tenant",description:"Reactivate a suspended tenant. Requires godmin privileges."}})];return collectRoutes([{routes:declared}])}var verifyGodmin=async(db,tenantRegistry,userId)=>{let usersTable=tenantRegistry.getMainContext().schemaTables.users;if(!usersTable)return!1;return(await db.select().from(usersTable).where(eq37(col10(usersTable,"id"),userId)).limit(1))[0]?.isGod===!0},getAllTenants=(registry3)=>{let schemaNames=registry3.getAllSchemaNames(),tenants=[];for(let name2 of schemaNames){let ctx=registry3.getSchemaContext(name2);if(ctx?.tenant)tenants.push(ctx.tenant)}return tenants};init_esm3();init_Logger2();init_routeCollector();init_utils6();import{eq as eq38}from"drizzle-orm";var col11=(table,name2)=>table[name2],ProvisionBodySchema=Type.Object({subdomain:Type.String({minLength:2,maxLength:63,pattern:"^[a-z][a-z0-9-]*$"}),companyId:Type.String({format:"uuid"}),companyName:Type.Optional(Type.String()),godAdminEmail:Type.String({format:"email"}),plan:Type.Optional(Type.String()),domain:Type.Optional(Type.String())});function createProvisionRoute(config){let log3=config.logger.scoped(TENANT_PROVISION),declared=[defineRoute({method:"POST",path:"/tenants/provision",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant provisioning not available"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let mainContext=tenantRegistry.getMainContext(),usersTable=mainContext.schemaTables.users;if(!usersTable)return ctx.set.status=503,{success:!1,message:"Users table not available"};if(!(await db.select().from(usersTable).where(eq38(col11(usersTable,"id"),requestingUserId)).limit(1))[0]?.isGod)return ctx.set.status=403,{success:!1,message:"Godmin privileges required"};let body=ctx.body,tenantSchemaName=`tenant_${body.subdomain.replace(/-/g,"_")}`;if(tenantRegistry.getActiveTenants().find((t)=>t.subdomain===body.subdomain))return ctx.set.status=409,{success:!1,message:`Subdomain "${body.subdomain}" is already taken`};let tenantsTable=mainContext.schemaTables.tenants;if(!tenantsTable)return ctx.set.status=503,{success:!1,message:"Tenants table not available in main schema"};let tenantId=crypto.randomUUID(),now=new Date;try{await db.insert(tenantsTable).values({id:tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName,companyId:body.companyId,companyName:body.companyName||null,godAdminEmail:body.godAdminEmail,status:"provisioning",plan:body.plan||null,domain:body.domain||null,createdAt:now,updatedAt:now}),log3.info("Tenant record created",{tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName})}catch(err){let msg=err instanceof Error?err.message:String(err);return log3.error("Failed to insert tenant record",{error:msg}),ctx.set.status=500,{success:!1,message:`Failed to create tenant record: ${msg}`}}try{let tenantRecord={id:tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName,companyId:body.companyId,companyName:body.companyName||null,godAdminEmail:body.godAdminEmail,status:"provisioning",plan:body.plan||null,domain:body.domain||null,settings:{},trustedSources:[],maxUsers:null,provisionedAt:null,suspendedAt:null,suspendedReason:null},tenantContext=await tenantRegistry.provisionTenant(tenantRecord);log3.info("Schema provisioned",{tenantId,schemaName:tenantSchemaName,tableCount:Object.keys(tenantContext.schemaTables).length});let tenantUsersTable=tenantContext.schemaTables.users;if(tenantUsersTable){let godminId=crypto.randomUUID(),tempPassword=crypto.randomUUID().slice(0,16),hashedPassword=await hashPassword(tempPassword);await db.insert(tenantUsersTable).values({id:godminId,email:body.godAdminEmail,password:hashedPassword,isGod:!0,isActive:!0,createdAt:now,updatedAt:now}),log3.info("Godmin user seeded",{tenantId,godminEmail:body.godAdminEmail,godminId})}await db.update(tenantsTable).set({status:"active",provisionedAt:now,updatedAt:now}).where(eq38(col11(tenantsTable,"id"),tenantId));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId,eventType:"provisioned",eventData:JSON.stringify({schemaName:tenantSchemaName,godAdminEmail:body.godAdminEmail,plan:body.plan||null}),performedBy:requestingUserId,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",createdAt:now,updatedAt:now})}catch{log3.warn("Failed to record tenant_event, continuing")}return await tenantRegistry.refreshTenant(tenantId),log3.info("Provisioning complete",{tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName}),await config.logger.audit({entityName:"tenants",entityId:tenantId,operation:"PROVISION",userId:requestingUserId,summary:`Tenant "${body.subdomain}" provisioned by godmin`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""}),{success:!0,message:"Tenant provisioned successfully",data:{tenantId,schemaName:tenantSchemaName,subdomain:body.subdomain,godAdminEmail:body.godAdminEmail,status:"active"}}}catch(err){let msg=err instanceof Error?err.message:String(err);log3.error("Provisioning failed",{error:msg,tenantId});try{await db.update(tenantsTable).set({status:"provisioning",updatedAt:now}).where(eq38(col11(tenantsTable,"id"),tenantId))}catch{}return ctx.set.status=500,{success:!1,message:`Provisioning failed: ${msg}`}}},body:ProvisionBodySchema,detail:{tags:["Tenants"],summary:"Provision new tenant",description:"Create a new tenant with its own schema, tables, and godmin user. Requires godmin privileges."}})];return collectRoutes([{routes:declared}])}init_esm3();init_Authorization();init_Logger2();init_routeCollector();init_utils6();import{eq as eq39}from"drizzle-orm";var col12=(table,name2)=>table[name2],ipRateMap=new Map,lastCleanup=Date.now();function cleanupRateMap(windowMs){let now=Date.now();if(now-lastCleanup<600000)return;lastCleanup=now;for(let[key2,entry]of ipRateMap.entries())if(now-entry.windowStart>windowMs)ipRateMap.delete(key2)}function isIpRateLimited(ip,maxPerWindow,windowMs){let now=Date.now(),entry=ipRateMap.get(ip);if(!entry||now-entry.windowStart>windowMs)return ipRateMap.set(ip,{count:1,windowStart:now}),!1;return entry.count++,entry.count>maxPerWindow}var SelfSignupBodySchema=Type.Object({email:Type.String({format:"email"}),password:Type.String({minLength:8}),subdomain:Type.String({minLength:2,maxLength:63,pattern:"^[a-z][a-z0-9-]*$"}),plan:Type.Optional(Type.String()),companyName:Type.Optional(Type.String())});function createSelfSignupRoute(config){let log3=config.logger.scoped(TENANT_SELF_SIGNUP),limits={...DEFAULT_SELF_SIGNUP_LIMITS,...config.selfSignupLimits},declared=[defineRoute({method:"POST",path:"/tenants/self-signup",handler:async(ctx)=>{cleanupRateMap(limits.ipWindowMs);let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant provisioning not available"};let ip=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown";if(isIpRateLimited(ip,limits.maxSignupsPerIpPerWindow,limits.ipWindowMs))return log3.warn("Self-signup rate limited",{ip}),ctx.set.status=429,{success:!1,message:"Too many signup attempts. Please try again later."};let body=ctx.body,email=body.email.toLowerCase().trim(),subdomain=body.subdomain.toLowerCase().trim();if(RESERVED_SUBDOMAINS.includes(subdomain))return ctx.set.status=400,{success:!1,message:"This subdomain is reserved."};let mainContext=tenantRegistry.getMainContext(),usersTable=mainContext.schemaTables.users,tenantsTable=mainContext.schemaTables.tenants;if(!usersTable||!tenantsTable)return ctx.set.status=503,{success:!1,message:"Required tables not available."};if(tenantRegistry.getActiveTenants().find((t)=>t.subdomain===subdomain))return ctx.set.status=409,{success:!1,message:"This subdomain is already taken."};try{if((await db.select().from(tenantsTable).where(eq39(col12(tenantsTable,"subdomain"),subdomain)).limit(1)).length>0)return ctx.set.status=409,{success:!1,message:"This subdomain is already taken."}}catch{}try{if((await db.select().from(tenantsTable).where(eq39(col12(tenantsTable,"godAdminEmail"),email))).length>=limits.maxTenantsPerEmail)return ctx.set.status=409,{success:!1,message:`Tenant limit reached. Maximum ${limits.maxTenantsPerEmail} tenant(s) per account.`}}catch{}let userId,hashedPassword=await hashPassword(body.password),now=new Date,existingUsers=await db.select().from(usersTable).where(eq39(col12(usersTable,"email"),email)).limit(1);if(existingUsers.length>0)userId=existingUsers[0].id,log3.info("Self-signup: existing user resolved",{userId,email});else{userId=crypto.randomUUID();try{await db.insert(usersTable).values({id:userId,email,password:hashedPassword,isActive:!0,createdAt:now,updatedAt:now}),log3.info("Self-signup: user created",{userId,email})}catch(err){let msg=err instanceof Error?err.message:String(err);return log3.error("Self-signup: user creation failed",{error:msg}),ctx.set.status=500,{success:!1,message:"Failed to create account."}}}let tenantId=crypto.randomUUID(),tenantSchemaName=`tenant_${subdomain.replace(/-/g,"_")}`;try{await db.insert(tenantsTable).values({id:tenantId,subdomain,schemaName:tenantSchemaName,companyId:userId,companyName:body.companyName||subdomain,godAdminEmail:email,status:"provisioning",plan:body.plan||null,createdAt:now,updatedAt:now}),log3.info("Tenant record created",{tenantId,subdomain})}catch(err){let msg=err instanceof Error?err.message:String(err);return log3.error("Tenant record creation failed",{error:msg}),ctx.set.status=500,{success:!1,message:"Failed to create tenant."}}try{let tenantRecord={id:tenantId,subdomain,schemaName:tenantSchemaName,companyId:userId,companyName:body.companyName||subdomain,godAdminEmail:email,status:"provisioning",plan:body.plan||null,domain:null,settings:{},trustedSources:[],maxUsers:null,provisionedAt:null,suspendedAt:null,suspendedReason:null},tenantContext=await tenantRegistry.provisionTenant(tenantRecord);log3.info("Schema provisioned",{tenantId,schemaName:tenantSchemaName,tableCount:Object.keys(tenantContext.schemaTables).length});let tenantUsersTable=tenantContext.schemaTables.users;if(tenantUsersTable)await db.insert(tenantUsersTable).values({id:userId,email,password:hashedPassword,isGod:!0,isActive:!0,createdAt:now,updatedAt:now}),log3.info("Godmin seeded in tenant schema",{tenantId,email,userId});let tenantRolesTable=tenantContext.schemaTables.roles,tenantUserRolesTable=tenantContext.schemaTables.userRoles;if(tenantRolesTable&&tenantUserRolesTable)try{let godminRoles=await db.select().from(tenantRolesTable).where(eq39(tenantRolesTable.name,GODMIN_ROLE_NAME)).limit(1),godminRoleId;if(godminRoles.length>0)godminRoleId=godminRoles[0].id;else{let[newRole]=await db.insert(tenantRolesTable).values({name:GODMIN_ROLE_NAME,description:"God mode administrator - bypasses all authorization checks"}).returning();godminRoleId=newRole.id,log3.info("Created godmin role in tenant schema",{tenantId,roleId:godminRoleId})}if(godminRoleId)await db.insert(tenantUserRolesTable).values({userId,roleId:godminRoleId}),log3.info("Assigned godmin role to signup user",{tenantId,userId,roleId:godminRoleId})}catch(roleErr){log3.warn("Failed to assign godmin role to signup user",{tenantId,userId,error:roleErr instanceof Error?roleErr.message:String(roleErr)})}await db.update(tenantsTable).set({status:"active",provisionedAt:now,updatedAt:now}).where(eq39(col12(tenantsTable,"id"),tenantId));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId,eventType:"self_signup",eventData:JSON.stringify({email,subdomain,plan:body.plan||null}),performedBy:userId,ipAddress:ip,createdAt:now,updatedAt:now})}catch{}return await tenantRegistry.refreshTenant(tenantId),log3.info("Self-signup complete",{tenantId,subdomain,email}),await config.logger.audit({entityName:"tenants",entityId:tenantId,operation:"SELF_SIGNUP",userId,summary:`Self-signup: tenant "${subdomain}" provisioned by ${email}`,ipAddress:ip,userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""}),{success:!0,message:"Tenant created successfully",data:{tenantId,subdomain,schemaName:tenantSchemaName,userId,email,status:"active"}}}catch(err){let msg=err instanceof Error?err.message:String(err);log3.error("Self-signup provisioning failed",{error:msg,tenantId});try{await db.update(tenantsTable).set({status:"provisioning",updatedAt:now}).where(eq39(col12(tenantsTable,"id"),tenantId))}catch{}return ctx.set.status=500,{success:!1,message:"Provisioning failed. Please try again."}}},body:SelfSignupBodySchema,detail:{tags:["Tenants"],summary:"Self-service tenant signup",description:"Public endpoint for self-service signup. Creates user account, provisions tenant schema, and seeds godmin. Rate-limited by IP with configurable max tenants per email."}})];return collectRoutes([{routes:declared}])}function createTenantRoutes(app,config){return useRoutes(app,createCheckSubdomainRoute(config)),useRoutes(app,createProvisionRoute(config)),useRoutes(app,createSelfSignupRoute(config)),useRoutes(app,createManageRoutes(config)),app}init_esm3();init_Notification();init_Verification();init_routeCollector();init_adminGuard();function createVerificationRoutes(routeConfig){let{db,schemaTables,config,logger:logger2,tenantRegistry}=routeConfig,defaultVerificationService=new VerificationService({db,schemaTables,config,logger:logger2}),defaultNotificationService=new NotificationService({db,schemaTables,config:routeConfig.notificationConfig||{enabled:!1},logger:logger2,getEmailService:routeConfig.getEmailService}),wireNotificationHandler=(verSvc,notifSvc)=>{verSvc.setNotificationHandler(async(params)=>{if(!routeConfig.notificationConfig?.enabled)return;await notifSvc.triggerNotifications({trigger:params.trigger,flow_id:params.flow_id,entity_name:params.entity_name,entity_id:params.entity_id,node_id:params.node_id,verifier_id:params.verifier_id,decision:params.decision,context:params.context})})};wireNotificationHandler(defaultVerificationService,defaultNotificationService);let getServicesForRequest=(request)=>{if(!tenantRegistry)return{verificationService:defaultVerificationService,notificationService:defaultNotificationService};let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return{verificationService:defaultVerificationService,notificationService:defaultNotificationService};let ctx=tenantRegistry.getSchemaContext(schemaName);if(!ctx)return{verificationService:defaultVerificationService,notificationService:defaultNotificationService};let reqVerSvc=new VerificationService({db,schemaTables:ctx.schemaTables,config,logger:logger2}),reqNotifSvc=new NotificationService({db,schemaTables:ctx.schemaTables,config:routeConfig.notificationConfig||{enabled:!1},logger:logger2,getEmailService:routeConfig.getEmailService});return wireNotificationHandler(reqVerSvc,reqNotifSvc),{verificationService:reqVerSvc,notificationService:reqNotifSvc}},basePath=config.endpoints?.basePath||"/verifications",flowBasePath=config.flowEndpoints?.basePath||"/verification-flows",requireUser=({request,set:set2})=>{if(!request.headers.get("x-user-id"))return set2.status=401,{success:!1,message:"Authentication required"}},verificationDeclared=[defineRoute({method:"GET",path:"/status/:entity_name/:entity_id",handler:async({params,request,set:set2})=>{let{verificationService}=getServicesForRequest(request),status=await verificationService.getStatus(params.entity_name,params.entity_id),startedBy=status.instance?.started_by;if(!hasGodminRole(request)&&startedBy!=null&&String(startedBy)!==String(request.headers.get("x-user-id")))return set2.status=403,{success:!1,message:"Forbidden"};return{success:!0,data:status}},params:Type.Object({entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"POST",path:"/:entity_name/:entity_id/decide",handler:async({params,body,request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");if(!userId)return{success:!1,message:"User ID required",status:401};return await verificationService.decide({entity_name:params.entity_name,entity_id:params.entity_id,user_id:userId,decision:body.decision,reason:body.reason,signature_id:body.signature_id,diff:body.diff})},params:Type.Object({entity_name:Type.String(),entity_id:Type.String()}),body:Type.Object({decision:Type.Union([Type.Literal("approved"),Type.Literal("rejected")]),reason:Type.Optional(Type.String()),signature_id:Type.Optional(Type.String()),diff:Type.Optional(Type.Record(Type.String(),Type.Unknown()))})}),defineRoute({method:"GET",path:"/pending",handler:async({request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");if(!userId)return{success:!1,message:"User ID required",status:401};return{success:!0,data:await verificationService.getPending(userId)}}}),defineRoute({method:"GET",path:"/history/:entity_name/:entity_id",handler:async({params,request,set:set2})=>{let{verificationService}=getServicesForRequest(request),status=await verificationService.getStatus(params.entity_name,params.entity_id),startedBy=status.instance?.started_by;if(!hasGodminRole(request)&&startedBy!=null&&String(startedBy)!==String(request.headers.get("x-user-id")))return set2.status=403,{success:!1,message:"Forbidden"};return{success:!0,data:{verifications:status.verifications,current_step:status.current_step,total_steps:status.total_steps,is_completed:status.is_completed,is_rejected:status.is_rejected}}},params:Type.Object({entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"POST",path:"/start",handler:async({body,request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");return await verificationService.startFlow({flow_id:body.flow_id,entity_name:body.entity_name,entity_id:body.entity_id,started_by:userId||void 0})},body:Type.Object({flow_id:Type.String(),entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"POST",path:"/start-for-entity",handler:async({body,request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");return await verificationService.startFlowForEntity({entity_name:body.entity_name,entity_id:body.entity_id,started_by:userId||void 0})},body:Type.Object({entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"GET",path:"/statuses/:entity_name",handler:async({params,query,request,set:set2})=>{if(!hasGodminRole(request))return set2.status=403,{success:!1,message:"Forbidden: operator privileges required"};let{verificationService}=getServicesForRequest(request);return{success:!0,data:await verificationService.listEntityStatuses({entity_name:params.entity_name,status:query.status||void 0,page:query.page?Number(query.page):void 0,limit:query.limit?Number(query.limit):void 0})}},params:Type.Object({entity_name:Type.String()}),query:Type.Object({status:Type.Optional(Type.String()),page:Type.Optional(Type.String()),limit:Type.Optional(Type.String())})})],groups=[{prefix:basePath,guards:[requireUser],routes:verificationDeclared}];if(logger2.info(`[Verification] Routes registered at ${basePath}`),config.flowEndpoints?.enabled!==!1){let flowDeclared=[defineRoute({method:"GET",path:"/",handler:async({query,request})=>{let{verificationService:vs}=getServicesForRequest(request);return{success:!0,data:await vs.listFlows(query.entity_name||void 0)}},query:Type.Object({entity_name:Type.Optional(Type.String())})}),defineRoute({method:"GET",path:"/:flow_id",handler:async({params,request,set:set2})=>{let{verificationService:vs}=getServicesForRequest(request),result=await vs.getFlow(params.flow_id);if(!result)return set2.status=404,{success:!1,message:"Flow not found"};return{success:!0,data:result}},params:Type.Object({flow_id:Type.String()})}),defineRoute({method:"POST",path:"/",handler:async({body,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.saveFlow(body)},body:Type.Object({flow_id:Type.String(),entity_name:Type.String(),name:Type.String(),description:Type.Optional(Type.String()),trigger_on:Type.Union([Type.Literal("create"),Type.Literal("update"),Type.Literal("delete"),Type.Literal("manual")]),trigger_fields:Type.Optional(Type.Array(Type.String())),is_draft:Type.Boolean(),viewport:Type.Optional(Type.Object({x:Type.Number(),y:Type.Number(),zoom:Type.Number()})),graph:Type.Object({steps:Type.Array(Type.Any()),edges:Type.Array(Type.Any()),verifier_configs:Type.Array(Type.Any()),notification_rules:Type.Array(Type.Any()),notification_recipients:Type.Array(Type.Any()),notification_channels:Type.Array(Type.Any())})})}),defineRoute({method:"POST",path:"/:flow_id/publish",handler:async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.publishFlow(params.flow_id)},params:Type.Object({flow_id:Type.String()})}),defineRoute({method:"DELETE",path:"/:flow_id",handler:async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.deleteFlow(params.flow_id)},params:Type.Object({flow_id:Type.String()})})];groups.push({prefix:flowBasePath,guards:[createGodminGuard2({db,schemaTables,logger:logger2},"verification flow management")],routes:flowDeclared}),logger2.info(`[Verification] Flow routes registered at ${flowBasePath}`)}return{routes:collectRoutes(groups),verificationService:defaultVerificationService,notificationService:defaultNotificationService}}function createNucleusRuntime(deps){return{sendEmail:async(payload)=>{let mailer=deps.getEmailService();if(!mailer?.isAvailable())return{success:!1,error:"No email provider is configured or available"};return mailer.sendEmail(payload)},emailProvider:()=>deps.activeEmailProvider(),isEmailAvailable:()=>!!deps.getEmailService()?.isAvailable(),emailSenderAddress:()=>deps.getEmailService()?.getSenderAddress?.()??""}}init_utils5();function asText(value2){return typeof value2==="string"&&value2.trim()?value2.trim():void 0}function fieldOf(path7){let text2=asText(path7);if(!text2)return;return text2.replace(/^\//,"").replace(/\//g,".")||void 0}function fromValidator(error3){let candidate=error3;if(typeof candidate?.validator?.Errors!=="function")return;try{let fields=[],reason;for(let item of candidate.validator.Errors(candidate.value)){let field=fieldOf(item?.path);if(field&&!fields.includes(field))fields.push(field);if(reason??=asText(item?.message),fields.length>=4)break}return fields.length||reason?{fields,reason}:void 0}catch{return}}function compose(where,reason){if(where&&reason)return`Invalid request: ${where} \u2014 ${reason}`;if(where)return`Invalid request: ${where}`;if(reason)return`Invalid request: ${reason}`;return"Invalid request"}function fromMessage(raw){let text2=asText(raw);if(!text2)return"Invalid request";let payload;try{payload=JSON.parse(text2)}catch{return}if(!payload||typeof payload!=="object"||payload.type!=="validation")return;let on=asText(payload.on),property=fieldOf(payload.property),reason=asText(payload.summary)??asText(payload.message),where=property&&property!=="root"?`${on?`${on}.`:""}${property}`:on?`the request ${on}`:void 0;return compose(where,reason)}function describeValidationError(error3){if(typeof error3!=="string"&&error3){let detail=fromValidator(error3);if(detail){let on=asText(error3.type),where=detail.fields.length?detail.fields.map((field)=>on?`${on}.${field}`:field).join(", "):on?`the request ${on}`:void 0;return compose(where,detail.reason)}return fromMessage(error3.message)}return fromMessage(error3)}function statusOfError(code,error3){if(typeof code==="number")return code;let carried=error3?.status;if(typeof carried==="number"&&carried>=100&&carried<=599)return carried;return 500}init_auth();init_backup();init_payment();init_marketplace();init_storage2();var mergeEntitiesByName=(entities)=>{let entityMap=new Map;for(let entity of entities){let existing=entityMap.get(entity.table_name),mergedColumns=entity.columns??existing?.columns;entityMap.set(entity.table_name,{...existing||{},...entity,columns:mergedColumns})}return Array.from(entityMap.values())},effectiveConfigEntities=(configEntities)=>{if(!configEntities.some((e)=>e.extends))return configEntities;let{entities:resolved}=resolveEntities(SYSTEM_TABLES,configEntities),byName=new Map(resolved.map((t)=>[t.table_name,t]));return configEntities.map((e)=>e.extends?byName.get(e.table_name)??e:e)},normalizeSystemTable=(table)=>({table_name:table.table_name,excluded_methods:table.excluded_methods?[...table.excluded_methods]:void 0,columns:table.columns?table.columns.map((column)=>({name:column.name,type:column.type})):void 0}),extractSchemaTableEntities=(schemaTables)=>{let entities=[];for(let[_key,tableValue]of Object.entries(schemaTables)){if(!tableValue||typeof tableValue!=="object")continue;let tableObj=tableValue,underscoreMeta=tableObj._;if(underscoreMeta?.name){entities.push({table_name:underscoreMeta.name});continue}let symbols3=Object.getOwnPropertySymbols(tableObj);for(let sym of symbols3){let symValue=tableObj[sym];if(symValue&&typeof symValue==="object"){let symMeta=symValue;if(symMeta.name&&typeof symMeta.name==="string"){entities.push({table_name:symMeta.name});break}}}}return entities},lastMountedGroups=[];function getMountedRuntime2(){return getMountedRuntime()}function getResolvedOptions2(){return getResolvedOptions()}function getMountedRouteGroups(){return lastMountedGroups}var lastMountedSocket=null;function getMountedSocket(){return lastMountedSocket}var lastStaticAssets=null;function getMountedStaticAssets(){return lastStaticAssets}var lastBootSteps=null,lastShutdownSteps=null,lastAuthMiddleware=null,lastErrorHandler=null,lastAfterHandler=null;function getAfterHandler(){return lastAfterHandler}function getErrorHandler(){if(!lastErrorHandler)throw Error("getErrorHandler() before NucleusElysiaPlugin() \u2014 nothing has been built yet");return lastErrorHandler}function getAuthMiddleware(){if(!lastAuthMiddleware)throw Error("getAuthMiddleware() before NucleusElysiaPlugin() \u2014 nothing has been built yet");return lastAuthMiddleware}function getBootSteps(){if(!lastBootSteps)throw Error("getBootSteps() before NucleusElysiaPlugin() \u2014 nothing has been built yet");return lastBootSteps}function getShutdownSteps(){return lastShutdownSteps??[]}function resolveOverrides(groups,logger2){let claim=new Map,dropped=[],kept=[];for(let index=groups.length-1;index>=0;index--){let group=groups[index];if(!group)continue;let routes=[];for(let position=group.routes.length-1;position>=0;position--){let route=group.routes[position];if(!route)continue;let declared=route.path==="/"?"":route.path,key2=`${route.method} ${`${group.prefix??""}${declared}`||"/"}`,winner=claim.get(key2);if(winner){let summary=route.detail?.summary??"(no summary)";dropped.push(`${key2} \u2014 kept "${winner.summary}", dropped "${summary}"`);continue}claim.set(key2,{summary:route.detail?.summary??"(no summary)"}),routes.unshift(route)}if(routes.length>0)kept.unshift({...group,routes})}if(dropped.length>0)logger2.warn(`[Routes] ${dropped.length} route(s) replaced by a later declaration of the same path`,{replaced:dropped});return kept}async function buildNucleus(config){beginRouteRecording();let plugin=collectRoutes(),routeGetHealth=defineRoute({method:"GET",path:"/health",handler:()=>({status:"ok",timestamp:Date.now()})});if(mountRoutes(plugin,[{routes:[routeGetHealth]}]),config.staticAssets!==!1){let path8=__require("path"),fs3=__require("fs"),assetsPath;if(typeof config.staticAssets==="string")assetsPath=config.staticAssets;else{let localPath=path8.join(process.cwd(),"public"),resolvedPkgPath="";for(let pkgName of["nucleus-core-ts","nucleus-core"])try{let pkgJson=__require.resolve(`${pkgName}/package.json`),candidate=path8.join(path8.dirname(pkgJson),"public");if(fs3.existsSync(candidate)){resolvedPkgPath=candidate;break}}catch{}if(resolvedPkgPath)assetsPath=resolvedPkgPath;else if(fs3.existsSync(localPath))assetsPath=localPath;else assetsPath=localPath}recordStaticAssets(assetsPath)}let publicRoutes=[],resolvedOptions,configDir=process.cwd(),configFilePath=null;if(typeof config.options==="string"){let fs3=__require("fs"),path8=__require("path"),configPath=path8.isAbsolute(config.options)?config.options:path8.resolve(process.cwd(),config.options);configDir=path8.dirname(configPath),configFilePath=configPath;let configContent=fs3.readFileSync(configPath,"utf-8");resolvedOptions=JSON.parse(configContent)}else resolvedOptions=config.options;if(resolvedOptions.email?.gmail?.json_file_path){let path8=__require("path"),gmailPath=resolvedOptions.email.gmail.json_file_path;if(!path8.isAbsolute(gmailPath))resolvedOptions.email.gmail.json_file_path=path8.resolve(configDir,gmailPath)}recordResolvedOptions(resolvedOptions);let{authentication,audit,entities,database}=resolvedOptions,isDev=resolvedOptions.mode==="development",loggingConfig=resolvedOptions.logging,logger2=new Logger({service:resolvedOptions.appId||"nucleus",level:loggingConfig?.level||(isDev?"debug":"info"),prettyPrint:loggingConfig?.prettyPrint??isDev,colorize:loggingConfig?.colorize??isDev,includeCallerInfo:loggingConfig?.includeCallerInfo??isDev,redactKeys:loggingConfig?.redactKeys||[],auditEnabled:audit?.enabled??!1,...audit?.suppressReasons!==void 0?{auditSuppressReasons:audit.suppressReasons}:{},...audit?.minSeverity!==void 0?{auditMinSeverity:audit.minSeverity}:{},enabledScopes:loggingConfig?.scopes||["*"]});Logger.getInstance().configure({service:resolvedOptions.appId||"nucleus",level:loggingConfig?.level||(isDev?"debug":"info"),prettyPrint:loggingConfig?.prettyPrint??isDev,colorize:loggingConfig?.colorize??isDev,includeCallerInfo:loggingConfig?.includeCallerInfo??isDev,redactKeys:loggingConfig?.redactKeys||[],enabledScopes:loggingConfig?.scopes||["*"]});let serverLogsConfig=resolvedOptions.monitoring?.serverLogs,serverLogBuffer=serverLogsConfig?.enabled===!0?getServerLogBuffer({capacity:serverLogsConfig.capacity??2000,maxLineLength:serverLogsConfig.maxLineLength}):null;if(serverLogBuffer){let transport=new ServerLogTransport(serverLogBuffer);if(logger2.addTransport(transport),Logger.getInstance().addTransport(transport),serverLogsConfig?.captureStdout!==!1)attachStreamCapture(serverLogBuffer);logger2.info(`[ServerLogs] Buffer active (capacity: ${serverLogBuffer.getCapacity()}, stdout capture: ${serverLogsConfig?.captureStdout!==!1})`)}{let{entities:resolvedForBoot,errors:extendErrors,warnings:extendWarnings}=resolveEntities(SYSTEM_TABLES,entities??[],{consumerMode:authentication?.mode==="consumer"});for(let warning of extendWarnings)logger2.warn(warning);let syncErrors=[],syncMappings=resolvedOptions.authentication?.oauth?.profileSync?.mappings??[];if(syncMappings.length>0){let columnsFor=(name2)=>{return(resolvedForBoot.find((t)=>t.table_name===name2)?.columns??[]).map((c)=>c.name)};syncErrors.push(...validateProfileSyncTargets(syncMappings,{users:columnsFor("users"),profiles:columnsFor("profiles")}))}if(extendErrors.length>0||syncErrors.length>0){let detail=[...extendErrors.map((e)=>` ${e.code}: ${e.message}`),...syncErrors.map((m)=>` PROFILE_SYNC_TARGET: ${m}`)].join(`
551
556
  `);throw Error(`Invalid Nucleus config:
552
557
  ${detail}`)}let sensitiveNames=resolvedForBoot.flatMap((e)=>e.columns??[]).filter((c)=>c.sensitive===!0).map((c)=>c.name);{registerSensitiveColumns(sensitiveNames);let redactUnion=[...loggingConfig?.redactKeys||[],...sensitiveColumnNames()];if(logger2.configure({redactKeys:redactUnion}),Logger.getInstance().configure({redactKeys:redactUnion}),sensitiveNames.length>0)logger2.info("[Schema] Sensitive columns registered",{columns:sensitiveNames})}}let requestLogConfig={enabled:loggingConfig?.requests?.enabled!==!1,logArrival:loggingConfig?.requests?.logArrival===!0,includeQuery:loggingConfig?.requests?.includeQuery!==!1,slowThresholdMs:loggingConfig?.requests?.slowThresholdMs??3000,excludePaths:loggingConfig?.requests?.excludePaths??["/health"]},isRequestLogExcluded=(pathname)=>requestLogConfig.excludePaths.some((p)=>p.endsWith("*")?pathname.startsWith(p.slice(0,-1)):pathname===p),envValidation=validateEnvVariables(resolvedOptions);if(!envValidation.valid){for(let error3 of envValidation.errors)logger2.error(`[CONFIG] ${error3.message}`,{field:error3.field,envName:error3.envName});throw Error("Nucleus configuration error: Missing required environment variables. Check logs for details.")}let{resolved:envResolved}=envValidation;{let secretEntries=[["accessToken",envResolved.accessTokenSecret],["refreshToken",envResolved.refreshTokenSecret],["sessionToken",envResolved.sessionTokenSecret]];for(let[name2,value2]of secretEntries)if(value2&&value2.length<32)logger2.warn(`[Security] authentication.${name2}.secret is only ${value2.length} chars \u2014 use at least ${"32"} random bytes.`);let present=secretEntries.map(([,v])=>v).filter((v)=>!!v);if(present.length>1&&new Set(present).size<present.length)logger2.warn("[Security] Two or more of the access/refresh/session token secrets are identical \u2014 use a distinct secret for each.")}let tokenNames={access_token:authentication?.accessToken?.name||"access_token",refresh_token:authentication?.refreshToken?.name||"refresh_token",session_token:authentication?.sessionToken?.name||"session_token"},csrfConfig=resolveCsrfConfig(authentication?.csrf),csrfAuthCookieNames=[tokenNames.session_token,tokenNames.access_token],targetSchemaName=database?.sharedSchema||database?.schemas?.[0]||"main",targetSchema=pgSchema2(targetSchemaName);if(envResolved.databaseUrl)await ensureDatabaseExists(envResolved.databaseUrl,logger2,envResolved.databaseAuthMode);let db=null,dbPool=null,dbAuthMode=envResolved.databaseAuthMode||"password",backgroundIntervals=[],trackInterval=(timer)=>{timer.unref?.(),backgroundIntervals.push(timer)};if(envResolved.databaseUrl){let{Pool:Pool2}=await import("pg"),poolOptions=resolveDbPoolConfig(database?.pool);if(dbAuthMode==="password")dbPool=new Pool2({connectionString:envResolved.databaseUrl,...poolOptions});else{let{getPostgresToken:getPostgresToken2}=await Promise.resolve().then(() => (init_Azure(),exports_Azure));await getPostgresToken2(),dbPool=new Pool2({connectionString:envResolved.databaseUrl,password:getPostgresToken2,ssl:{rejectUnauthorized:!0},...poolOptions}),logger2.info(`[Database] Using Entra ID auth mode: ${dbAuthMode}`)}db=drizzle2(dbPool),dbPool.on("error",(err)=>{logger2.warn("[Database] idle pool client error",{error:err instanceof Error?err.message:String(err)})})}let isMultiTenant=database?.isMultiTenant===!0,tenantRegistry=null,schemaTables={},schemaRelations={},claimsCache=null;if(config.schema){let schemasPath=__require("path").resolve(process.cwd(),config.schema),schemas=__require(schemasPath);schemaTables=schemas.createAllTablesForSchema?schemas.createAllTablesForSchema(targetSchema):{}}if(config.relations){let relationsPath=__require("path").resolve(process.cwd(),config.relations);schemaRelations=__require(relationsPath)}let secretsService=null;if(resolvedOptions.secrets?.enabled){let masterKey=resolveMasterKey(resolvedOptions.secrets.encryptionKey);if(!db)logger2.warn("[Secrets] secrets.enabled=true but no database is configured \u2014 disabled");else if(!masterKey)logger2.error("[Secrets] secrets.enabled=true but no encryption key resolved \u2014 set secrets.encryptionKey or NUCLEUS_SECRETS_KEY. Credential storage is disabled; env and literal values still work.");else if(!schemaTables[SECRETS_TABLE])logger2.error(`[Secrets] secrets.enabled=true but the "${SECRETS_TABLE}" table is missing from the generated schema \u2014 re-run schema generation. Credential storage is disabled.`);else{secretsService=new SecretsService({store:new SecretsStore({db,resolveTable:(tableName)=>schemaTables[tableName],masterKey}),logger:logger2,refreshIntervalMs:resolvedOptions.secrets.refreshIntervalMs,getRedis:()=>getRedisManager()}),logger2.info("[Secrets] Credential store enabled");try{if(await secretsService.refresh(),secretsService.isReady())logger2.info("[Secrets] Credentials preloaded for boot-time resolution",{storedCredentials:secretsService.size()})}catch(error3){logger2.warn("[Secrets] Preload failed \u2014 env and literal values still resolve",{error:error3 instanceof Error?error3.message:String(error3)})}}}let resolveCredential=(scope,key2,configValue)=>{if(secretsService)return secretsService.value(scope,key2,configValue);if(!configValue)return;let fromEnv=process.env[configValue];if(fromEnv)return fromEnv;return/^[A-Z][A-Z0-9_]{2,}$/.test(configValue)?void 0:configValue},resolveRedirectUrl=(value2)=>{if(!value2)return;let fromEnv=process.env[value2];if(fromEnv)return fromEnv;return/^[A-Z][A-Z0-9_]{2,}$/.test(value2)?void 0:value2},withResolvedRedirects=(node,keys)=>{if(!node)return node;let resolved={...node};for(let key2 of keys)if(typeof node[key2]==="string")resolved[key2]=resolveRedirectUrl(node[key2]);return resolved},trustedAppOrigins=()=>{let configured=normalizeTrustedOrigins(resolvedOptions.authentication?.trustedAppOrigins);if(!secretsService)return configured;let resolved=secretsService.listValue("authentication","trustedAppOrigins",configured);return resolved.length>0?resolved:configured},liveOAuthProviders=(resolvedProviders)=>{if(!secretsService)return resolvedProviders;let declared=resolvedOptions.authentication?.oauth?.providers??{},live={};for(let[providerName,providerConfig]of Object.entries(resolvedProviders)){let declaredConfig=declared[providerName]??{},entry={...providerConfig};for(let key2 of["clientId","clientSecret","tenantId","redirectUri"]){let declaredValue=declaredConfig[key2],bootValue=providerConfig[key2];Object.defineProperty(entry,key2,{get:()=>{let resolved=secretsService?.value(`authentication.oauth.providers.${providerName}`,key2,typeof declaredValue==="string"?declaredValue:void 0);if(resolved!==void 0)return resolved;return looksLikeEnvName(typeof bootValue==="string"?bootValue:void 0)?void 0:bootValue},enumerable:!0,configurable:!0})}live[providerName]=entry}return live},storageProvider=null;if(resolvedOptions.storage?.enabled){let storageBase=resolvedOptions.storage.basePath||"./uploads";storageProvider=createStorageProvider({provider:resolvedOptions.storage.provider,basePath:storageBase,smb:resolvedOptions.storage.smb,resolveCredential},{logger:logger2}),setStorageProvider(storageProvider)}let buildStorageProvider=(kind)=>{if(!resolvedOptions.storage?.enabled)return null;return createStorageProvider({provider:kind,basePath:resolvedOptions.storage.basePath||"./uploads",smb:resolvedOptions.storage.smb,resolveCredential:(scope,key2,configValue)=>scope==="storage"&&key2==="provider"?kind:resolveCredential(scope,key2,configValue)},{logger:logger2})};recordSwagger(config.swagger);let systemTables=config.systemTables||[];publicRoutes=buildPublicRoutes(resolvedOptions,systemTables,"",targetSchemaName),logger2.info(`[AUTH] Built ${publicRoutes.length} public routes`);let rateLimiter=null,warnedSharedRateLimitBucket=!1,peerDiversity=createPeerDiversityWatcher(),monitoringService=null,liveMonitoringService=null,lifecycleRecorder=null,LIFECYCLE_BEAT_MS=1e4,activeEmailProvider=()=>{let declared=resolveCredential("email","provider",resolvedOptions.email?.provider);if(declared==="azure"||declared==="gmail"||declared==="smtp")return emailProviderOn(declared)||resolvedOptions.email?.provider===declared?declared:null;if(emailProviderOn("azure"))return"azure";if(emailProviderOn("gmail"))return"gmail";if(emailProviderOn("smtp"))return"smtp";return null},emailProviderOn=(provider)=>{let configured=resolvedOptions.email?.[provider]?.enabled===!0;return secretsService?secretsService.flag(`email.${provider}`,"enabled",configured):configured},azureEmailCredentials=()=>{let azureConfig=resolvedOptions.email?.azure;return{enabled:!0,connectionString:resolveCredential("email.azure","connection_string",azureConfig?.connection_string)??"",senderAddress:resolveCredential("email.azure","sender_address",azureConfig?.sender_address)??"",fromName:resolveCredential("email.azure","from_name",azureConfig?.from_name)}},gmailCredentials=()=>{let gmailConfig=resolvedOptions.email?.gmail;return{enabled:!0,jsonFilePath:gmailConfig?.json_file_path,serviceAccountJson:resolveCredential("email.gmail","service_account_json",gmailConfig?.service_account_json),fromEmail:resolveCredential("email.gmail","from_email",gmailConfig?.from_email)??"",fromName:resolveCredential("email.gmail","from_name",gmailConfig?.from_name)}},smtpCredentials=()=>{let smtpConfig=resolvedOptions.email?.smtp,port=resolveCredential("email.smtp","port",smtpConfig?.port===void 0?void 0:String(smtpConfig.port)),secure=resolveCredential("email.smtp","secure",smtpConfig?.secure===void 0?void 0:String(smtpConfig.secure));return{enabled:!0,host:resolveCredential("email.smtp","host",smtpConfig?.host)??"",port:port?Number(port):void 0,secure:secure===void 0?void 0:String(secure)==="true",user:resolveCredential("email.smtp","user",smtpConfig?.user)??"",password:resolveCredential("email.smtp","password",smtpConfig?.password)??"",senderAddress:resolveCredential("email.smtp","sender_address",smtpConfig?.sender_address)??"",fromName:resolveCredential("email.smtp","from_name",smtpConfig?.from_name)}},azureService=null,gmailService=null,smtpService=null,getEmailService=()=>{let provider=activeEmailProvider();if(!provider)return null;if(provider==="azure"){if(!azureService)azureService=new AzureEmailService(azureEmailCredentials(),logger2),logger2.info("[AzureEmailService] Initialized",{available:azureService.isAvailable()});else azureService.reconfigure(azureEmailCredentials());return azureService}if(provider==="smtp"){if(!smtpService)smtpService=new SmtpEmailService(smtpCredentials(),logger2),logger2.info("[SmtpEmailService] Initialized",{available:smtpService.isAvailable()});else smtpService.applyConfig(smtpCredentials());return smtpService}if(!gmailService)gmailService=new GmailService(gmailCredentials(),logger2),logger2.info("[GmailService] Initialized",{available:gmailService.isAvailable()});else gmailService.reconfigure(gmailCredentials());return gmailService},nucleusRuntime=createNucleusRuntime({getEmailService,activeEmailProvider});if(recordRuntime(nucleusRuntime),resolvedOptions.liveMonitoring?.enabled){let liveBasePath=resolvedOptions.liveMonitoring.basePath||"/monitoring",liveStreamInterval=resolvedOptions.liveMonitoring.streamInterval||150;useRoutes(plugin,createLiveMonitoringRoutes({getService:()=>liveMonitoringService,getLifecycleRecorder:()=>lifecycleRecorder,logger:logger2,basePath:liveBasePath,streamInterval:liveStreamInterval,db,schemaTables}))}if(resolvedOptions.monitoring?.enabled&&resolvedOptions.monitoring.endpoints?.enabled){let monitoringEndpoints=resolvedOptions.monitoring.endpoints;useRoutes(plugin,createMonitoringRoutes({getService:()=>monitoringService,logger:logger2,endpoints:{enabled:!0,basePath:monitoringEndpoints.basePath||"/monitoring",allowRoles:monitoringEndpoints.allowRoles,stream:{enabled:monitoringEndpoints.stream?.enabled!==!1,path:monitoringEndpoints.stream?.path||"/stream",interval:monitoringEndpoints.stream?.interval||"5s"},snapshot:{enabled:monitoringEndpoints.snapshot?.enabled!==!1,path:monitoringEndpoints.snapshot?.path||"/snapshot"},history:{enabled:monitoringEndpoints.history?.enabled!==!1,path:monitoringEndpoints.history?.path||"/history",maxMinutes:monitoringEndpoints.history?.maxMinutes||60},alerts:{enabled:monitoringEndpoints.alerts?.enabled!==!1,path:monitoringEndpoints.alerts?.path||"/alerts"}},db,schemaTables}))}if(serverLogBuffer)useRoutes(plugin,createServerLogRoutes({buffer:serverLogBuffer,logger:logger2,basePath:serverLogsConfig?.basePath||resolvedOptions.monitoring?.endpoints?.basePath||"/monitoring",path:serverLogsConfig?.path||"/server-logs",allowRoles:serverLogsConfig?.allowRoles,streamEnabled:serverLogsConfig?.stream!==!1,clearEnabled:serverLogsConfig?.allowClear!==!1,db,schemaTables}));let bootStep=async(name2,run)=>{try{return await run()}catch(error3){logger2.error(`[Boot] Step "${name2}" failed \u2014 continuing in a degraded state rather than crashing`,{error:error3 instanceof Error?error3.message:String(error3)});return}},inboundGuard=createInboundGuard({maxRequestBodyBytes:resolvedOptions.security?.maxRequestBodyBytes});recordBootStep(async()=>{await bootStep("redis",()=>initiateRedisManager(resolvedOptions));let redis=getRedisManager();if(redis&&resolvedOptions.configManagement?.enabled)try{let{loadOverridesFromRedis:loadOverridesFromRedis2}=await Promise.resolve().then(() => (init_helpers4(),exports_helpers)),redisAdapter={read:async(key2)=>{let r=await redis.read(key2);return{success:r.success,data:r.success?r.data:null}}},{applied,ignored}=await loadOverridesFromRedis2(redisAdapter,resolvedOptions.appId||"nucleus",resolvedOptions);if(applied.length>0)logger2.info(`[ConfigManagement] Loaded ${applied.length} override(s) from Redis`,{sections:applied});if(ignored.length>0)logger2.warn(`[ConfigManagement] Ignored ${ignored.length} Redis override(s) naming no config section`,{sections:ignored})}catch(err){logger2.warn("[ConfigManagement] Failed to load overrides from Redis",{error:err instanceof Error?err.message:String(err)})}if(redis&&resolvedOptions.rateLimit?.enabled!==!1)rateLimiter=new RateLimiter({redis,logger:logger2,config:resolvedOptions.rateLimit||{}}),logger2.info(`[RateLimit] Enabled with strategy: ${resolvedOptions.rateLimit?.strategy||"sliding-window"}`);{let channels=resolvedOptions.notification?.channels;if(channels?.telegram?.enabled&&(!channels.telegram.botToken||!channels.telegram.chatId))logger2.warn("[Notification] telegram channel is enabled but botToken/chatId is missing \u2014 telegram deliveries will be skipped");if(channels?.webhook?.enabled&&!channels.webhook.url)logger2.warn("[Notification] webhook channel is enabled but url is missing \u2014 webhook deliveries will be skipped")}if(redis&&resolvedOptions.monitoring?.enabled){let monitoringDb=db,monitoringMetricsTable=schemaTables.monitoringMetrics,monitoringPersistenceEnabled=resolvedOptions.monitoring.persistence?.enabled!==!1,monitoringRetentionDays=resolvedOptions.monitoring.persistence?.retentionDays??30,lastMetricsCleanupAt=0,monitoringDbQuery=monitoringDb?async(sqlText)=>{return(await monitoringDb.execute(sql14.raw(sqlText))).rows}:void 0,monitoringLoadPeaks=monitoringDb&&monitoringPersistenceEnabled&&monitoringMetricsTable?async()=>{let t=monitoringMetricsTable;return(await monitoringDb.select({metricName:t.metricName,value:t.value,recordedAt:t.recordedAt,tags:t.tags}).from(t).where(eq68(t.metricType,"peak")).orderBy(desc7(t.value))).map((r)=>({metricName:String(r.metricName),value:Number(r.value??0),at:r.recordedAt?new Date(r.recordedAt).getTime():0,context:r.tags??void 0}))}:void 0;if(monitoringPersistenceEnabled&&monitoringDb&&!monitoringMetricsTable)logger2.warn("[Monitoring] monitoring.persistence is enabled but the monitoring_metrics table is missing from the generated schema \u2014 re-run nucleus-generate to add it");let monitoringFlushToDb=monitoringDb&&monitoringPersistenceEnabled&&monitoringMetricsTable?async(metrics)=>{if(metrics.length===0)return;let metricsTable=monitoringMetricsTable;await monitoringDb.insert(metricsTable).values(metrics.map((m)=>({metricType:m.metricType,metricName:m.metricName,value:m.value,tags:m.tags??null,recordedAt:new Date(m.timestamp)})));let nowMs=Date.now();if(nowMs-lastMetricsCleanupAt>86400000){lastMetricsCleanupAt=nowMs;let cutoff=new Date(nowMs-monitoringRetentionDays*24*60*60*1000);await monitoringDb.delete(metricsTable).where(and28(lt3(metricsTable.recordedAt,cutoff),ne4(metricsTable.metricType,"peak")))}}:void 0;monitoringService=new MonitoringService({redis,logger:logger2,getEmailService,config:resolvedOptions.monitoring,appId:resolvedOptions.appId,dbQuery:monitoringDbQuery,flushToDb:monitoringFlushToDb,loadPeaks:monitoringLoadPeaks}),monitoringService.start(),logger2.info("[Monitoring] Service started")}if(resolvedOptions.liveMonitoring?.enabled)liveMonitoringService=new LiveMonitoringService(resolvedOptions.liveMonitoring),liveMonitoringService.start(),logger2.info("[LiveMonitoring] Service started");let lifecycleRedis=getRedisManager()?.getDirectClient?.();if(lifecycleRedis){lifecycleRecorder=new LifecycleRecorder({backend:redisLifecycleBackend(lifecycleRedis),labels:{...process.env.HOSTNAME?{host:process.env.HOSTNAME}:{},...process.env.APP_VERSION?{version:process.env.APP_VERSION}:{}},logger:logger2}),lifecycleRecorder.claim();let beat=setInterval(()=>{(async()=>{let memory=(await monitoringService?.getLatestSnapshot?.())?.system?.memory;await lifecycleRecorder?.touch(memory?{rss:memory.rss??0,heapUsed:memory.heapUsed,external:memory.external??0,total:memory.total,usagePercent:memory.usagePercent,scope:memory.scope}:void 0)})()},LIFECYCLE_BEAT_MS);backgroundIntervals.push(beat),process.on("uncaughtException",(error3)=>{lifecycleRecorder?.recordFatal(error3,"uncaughtException"),logger2.error("[Lifecycle] Uncaught exception",error3)}),process.on("unhandledRejection",(reason)=>{lifecycleRecorder?.recordFatal(reason,"unhandledRejection"),logger2.error("[Lifecycle] Unhandled rejection",reason)})}let isConsumerModeOnStart=authentication?.mode==="consumer",consumerAllowedTableKeys=isConsumerModeOnStart&&entities?new Set(entities.map((e)=>e.table_name.replace(/_([a-z])/g,(_,c)=>c.toUpperCase()))):null;if(consumerAllowedTableKeys){let opts=resolvedOptions,verificationEnabled=opts.verification?.enabled===!0,notificationEnabled=opts.notification?.enabled===!0,auditEnabled=opts.audit?.enabled===!0,chatEnabled=opts.chat?.enabled===!0,storageEnabled=opts.storage?.enabled===!0,paymentEnabled=opts.payment?.enabled===!0,backupEnabled=opts.backup?.enabled===!0,monitoringPersistenceOn=resolvedOptions.monitoring?.enabled===!0&&resolvedOptions.monitoring?.persistence?.enabled!==!1,featureEnabled={verification:verificationEnabled,notification:notificationEnabled,audit:auditEnabled,chat:chatEnabled,storage:storageEnabled,payment:paymentEnabled,backup:backupEnabled,monitoring:monitoringPersistenceOn,secrets:resolvedOptions.secrets?.enabled===!0,domains:resolvedOptions.domains?.enabled===!0,"multi-tenant":isMultiTenant};for(let sysTable of SYSTEM_TABLES)if(sysTable.feature_set.some((f)=>{if(f==="authentication"||f==="authorization")return!1;if(!(f in featureEnabled))return logger2.warn(`[Schema] Consumer mode has no rule for feature "${f}" (table "${sysTable.table_name}") \u2014 `+"its table will NOT be synced. Add it to featureEnabled in Nucleus/index.ts."),!1;return featureEnabled[f]===!0})){let camelKey=sysTable.table_name.replace(/_([a-z])/g,(_,c)=>c.toUpperCase());consumerAllowedTableKeys.add(camelKey)}}if(db&&config.schema){let schemas=await import(__require("path").resolve(process.cwd(),config.schema)),auditLogsTable=schemaTables.auditLogs||schemas.auditLogs;if(audit?.enabled&&auditLogsTable)logger2.addAuditTransport(new DatabaseAuditTransport({db,table:auditLogsTable,enabled:!0,dedup:audit.dedup}));let{ensureSchemaExists:ensureSchemaExists2,applySchemaPush:applySchemaPush2}=await Promise.resolve().then(() => (init_schema(),exports_schema)),{reconcileMissingColumns:reconcileMissingColumns2,expectedFromTables:expectedFromTables2,readActualSchema:readActualSchema2,undeclaredTables:undeclaredTables2}=await Promise.resolve().then(() => (init_schemaReconcile(),exports_schemaReconcile));try{logger2.info(`Syncing schema to database (target: ${targetSchemaName})...`),await ensureSchemaExists2(db,targetSchemaName);let filteredTables={};try{filteredTables=Object.fromEntries(Object.entries(schemaTables).filter(([key2,v])=>{if(v===void 0||v===null)return!1;if(consumerAllowedTableKeys&&!consumerAllowedTableKeys.has(key2))return!1;if(typeof v==="object"&&v!==null)return Object.getOwnPropertySymbols(v).length>0||v._!==void 0;return!1}));let tableNames=Object.keys(filteredTables);if(logger2.info("[Schema] Tables to sync:",{tables:tableNames,count:tableNames.length,mode:isConsumerModeOnStart?"consumer":"full"}),!isConsumerModeOnStart){let usersTableDef=filteredTables.users;if(usersTableDef){let columnSymbols=Object.getOwnPropertyNames(usersTableDef).filter((k)=>!k.startsWith("_"));logger2.info("[Schema] Users table columns:",{columns:columnSymbols})}}let realExit=process.exit,push;try{process.exit=(code)=>{throw Error(`drizzle-kit called process.exit(${code??0}) during pushSchema \u2014 `+"suppressed so the app can keep booting")};let excluded=await(async()=>{try{let actual=await readActualSchema2((statement)=>db.execute(sql14.raw(statement)),targetSchemaName);return undeclaredTables2(expectedFromTables2(filteredTables),actual)}catch{return[]}})();if(excluded.length>0)logger2.info(`[Schema] Excluding tables the model does not declare, so the push is not asked to resolve a rename it cannot: ${excluded.join(", ")}`);let timeoutMs=database?.schemaPushTimeoutMs??120000;push=await withDeadline(pushSchema({schema:targetSchema,...filteredTables},db,[targetSchemaName],excluded.length>0?excluded.map((name2)=>`!${name2}`):void 0),timeoutMs,`pushSchema did not finish within ${timeoutMs}ms. drizzle-kit most likely hit its `+"interactive rename prompt, which cannot be answered without a TTY \u2014 it appears "+"when a push adds a new table while another table exists in the database but not in the schema. Drop the stale table, or split the change across two deploys.")}finally{process.exit=realExit}if(await applySchemaPush2(push,{schemaName:targetSchemaName,allowDataLoss:database?.allowDataLoss===!0,logger:logger2,execute:(statement)=>db.execute(sql14.raw(statement))}))logger2.info("[Schema] pushSchema completed successfully")}catch(pushError){let msg=pushError instanceof Error?pushError.message:String(pushError),repair=await reconcileMissingColumns2({db,schema:targetSchemaName,tables:filteredTables,logger:logger2,schemaObject:targetSchema});if(!repair||repair.applied>0||repair.plan.createdTables.length>0||repair.plan.missingTables.length>0||repair.plan.unsafe.length>0)logger2.error(`[Schema] pushSchema applied NOTHING: ${msg}`);else logger2.info(`[Schema] pushSchema applied nothing, and nothing was needed \u2014 the database already matches the model. (${msg})`)}logger2.info("[Schema] Schema sync completed",{schema:targetSchemaName})}catch(error3){logger2.error("[Schema] Schema sync failed",error3,{schema:targetSchemaName})}try{let{relaxSessionExpiryIfNever:relaxSessionExpiryIfNever2}=await Promise.resolve().then(() => (init_relaxSessionExpiry(),exports_relaxSessionExpiry)),{sql:sql15}=await import("drizzle-orm");await relaxSessionExpiryIfNever2({db,sql:sql15,schemaName:targetSchemaName,lifetimeSeconds:lifetimeSeconds(authentication?.sessionToken?.expiresIn,2592000),logger:logger2})}catch(error3){logger2.warn("[Schema] Session expiry relaxation skipped",{error:String(error3)})}if(secretsService)try{await secretsService.start(),logger2.info("[Secrets] Credential store ready",{storedCredentials:secretsService.size()})}catch(error3){logger2.error("[Secrets] Credential store failed to start \u2014 env and literal values still resolve",{error:error3 instanceof Error?error3.message:String(error3)})}if(logger2.info("[Database] Connection established"),await bootStep("multi-tenant schemas",async()=>{if(isMultiTenant&&db&&config.schema){let schemasForTenant={};try{let schemaPath=__require("path").resolve(process.cwd(),config.schema);logger2.info("[MultiTenant] Loading schema for tenant registry",{schemaPath}),schemasForTenant=await import(schemaPath),logger2.info("[MultiTenant] Schema loaded",{keys:Object.keys(schemasForTenant).slice(0,10),hasCreateAll:!!schemasForTenant.createAllTablesForSchema})}catch(err){let msg=err instanceof Error?err.message:String(err);logger2.error("[MultiTenant] Failed to load schema for tenant registry",{error:msg})}let createAllFn=schemasForTenant.createAllTablesForSchema;if(createAllFn){let idpUrl=isConsumerModeOnStart?authentication?.idpUrl?String(process.env[authentication.idpUrl]||authentication.idpUrl):void 0:void 0;if(tenantRegistry=new TenantRegistry({db,logger:logger2,mainSchemaName:targetSchemaName,mainSchemaTables:schemaTables,mainSchemaRelations:schemaRelations,createAllTablesForSchema:createAllFn,createAllRelationsForSchema:schemasForTenant.createAllRelationsForSchema,appId:resolvedOptions.appId,authMode:authentication?.mode,tenantResolution:database?.tenantResolution||"both",tenantHeader:database?.tenantHeader||"x-tenant-id",redisCacheTtlSeconds:300,defaultTrustedSources:database?.defaultTrustedSources,idpUrl,allowDataLoss:database?.allowDataLoss===!0,onTenantProvisioned:resolvedOptions.authorization?.enabled&&!isConsumerModeOnStart?async(context)=>{let authConfig={...DEFAULT_AUTHORIZATION_CONFIG,...resolvedOptions.authorization};if(authConfig.autoSeedClaims&&db){let claimEntities=mergeEntitiesByName([...extractSchemaTableEntities(context.schemaTables),...SYSTEM_TABLES.map((table)=>normalizeSystemTable(table)),...effectiveConfigEntities(resolvedOptions.entities||[]),...config.systemTables||[],...resolvedOptions.authorization?.externalEntities||[]]);await seedClaims(db,context.schemaTables,context.schemaRelations,claimEntities,authConfig,logger2)}let seedConfig=resolvedOptions.authorization?.seed;if(seedConfig&&db){let{runSeed:runSeed2}=await Promise.resolve().then(() => (init_SeedRunner(),exports_SeedRunner));await runSeed2(db,context.schemaTables,seedConfig,logger2)}logger2.info(`[Authorization] New tenant schema seeded: ${context.schemaName}`)}:void 0}),isConsumerModeOnStart&&idpUrl){await tenantRegistry.initializeFromIdp(),logger2.info("[MultiTenant] Consumer mode: tenants fetched from IDP");let tenantSyncTimer=setInterval(()=>{tenantRegistry?.syncFromIdp().then((result)=>{if(result.added.length>0||result.removed.length>0)logger2.info(`[MultiTenant] Tenant sync: +${result.added.length} / -${result.removed.length} (total ${result.total})`)}).catch((err)=>{let msg=err instanceof Error?err.message:String(err);logger2.warn(`[MultiTenant] Tenant sync failed: ${msg}`)})},60000);trackInterval(tenantSyncTimer)}else await tenantRegistry.initialize();for(let schemaName of tenantRegistry.getAllSchemaNames()){if(schemaName===targetSchemaName)continue;let ctx=tenantRegistry.getSchemaContext(schemaName);if(ctx){await ensureSchemaExists2(db,schemaName);try{let tenantFilteredTables=Object.fromEntries(Object.entries(ctx.schemaTables).filter(([key2,v])=>{if(v===void 0||v===null)return!1;if(consumerAllowedTableKeys&&!consumerAllowedTableKeys.has(key2))return!1;if(typeof v==="object"&&v!==null)return Object.getOwnPropertySymbols(v).length>0||v._!==void 0;return!1})),tenantSchema=pgSchema2(schemaName),tenantPush=await pushSchema({schema:tenantSchema,...tenantFilteredTables},db,[schemaName]);if(await applySchemaPush2(tenantPush,{schemaName,allowDataLoss:database?.allowDataLoss===!0,logger:logger2,execute:(statement)=>db.execute(sql14.raw(statement))}))logger2.info(`[Schema] Tenant schema synced: ${schemaName}`)}catch(tenantPushError){let msg=tenantPushError instanceof Error?tenantPushError.message:String(tenantPushError);logger2.warn(`[Schema] Tenant schema sync warning for ${schemaName}: ${msg}`)}}}logger2.info(`[MultiTenant] Registry initialized with ${tenantRegistry.getAllSchemaNames().length} schemas`),logger2.info("[MultiTenant] Tenant registry ready, routes were pre-registered")}}}),resolvedOptions.authorization?.enabled&&!isConsumerModeOnStart){let authConfig={...DEFAULT_AUTHORIZATION_CONFIG,...resolvedOptions.authorization,godminEmail:resolveConfigCredential(resolvedOptions.authorization?.godminEmail),godminPassword:resolveConfigCredential(resolvedOptions.authorization?.godminPassword)};if(authConfig.autoSeedClaims){let schemaEntities=extractSchemaTableEntities(schemaTables),systemEntities=SYSTEM_TABLES.map((table)=>normalizeSystemTable(table)),configEntities=effectiveConfigEntities(resolvedOptions.entities||[]),externalEntities=resolvedOptions.authorization?.externalEntities||[],claimEntities=mergeEntitiesByName([...schemaEntities,...configEntities,...systemEntities,...config.systemTables||[],...externalEntities]);logger2.info("[Authorization] Seeding claims...",{schemaEntities:schemaEntities.length,systemEntities:systemEntities.length,configEntities:configEntities.length,externalEntities:externalEntities.length,totalEntities:claimEntities.length}),await seedClaims(db,schemaTables,schemaRelations,claimEntities,authConfig,logger2)}let discoveryConfig=resolvedOptions.authorization?.endpointDiscovery;if(discoveryConfig?.enabled&&(discoveryConfig.runOnBoot??!0)&&db)try{let{runEndpointDiscovery:runEndpointDiscovery2}=await Promise.resolve().then(() => (init_seed(),exports_seed)),discoveryResult=await runEndpointDiscovery2(db,schemaTables,discoveryConfig,logger2);logger2.info("[Authorization] Endpoint discovery completed",{services:discoveryResult.services})}catch(discoveryErr){logger2.error("[Authorization] Endpoint discovery failed",{error:discoveryErr instanceof Error?discoveryErr.message:String(discoveryErr)})}if(authConfig.godminEmail&&authConfig.godminPassword)logger2.info("[Authorization] Setting up godmin..."),await setupGodmin(db,schemaTables,authConfig,logger2);let seedConfig=resolvedOptions.authorization?.seed;if(seedConfig){let{runSeed:runSeed2}=await Promise.resolve().then(() => (init_SeedRunner(),exports_SeedRunner));logger2.info("[Authorization] Running custom seed...");let seedResult=await bootStep("authorization.seed",()=>runSeed2(db,schemaTables,seedConfig,logger2));if(seedResult)logger2.info("[Authorization] Custom seed completed",{rolesCreated:seedResult.rolesCreated,rolesExisting:seedResult.rolesExisting,claimsCreated:seedResult.claimsCreated,claimsExisting:seedResult.claimsExisting,assignmentsCreated:seedResult.assignmentsCreated,assignmentsExisting:seedResult.assignmentsExisting,assignmentsUpdated:seedResult.assignmentsUpdated})}let claimGuardsCfg=resolvedOptions.authorization?.claimGuards;if(claimGuardsCfg?.length){let{seedClaimGuards:seedClaimGuards2}=await Promise.resolve().then(() => (init_ClaimGuard(),exports_ClaimGuard));await bootStep("authorization.claimGuards",()=>seedClaimGuards2(db,schemaTables,claimGuardsCfg,logger2))}let jwtClaimsMode=resolvedOptions.authorization?.jwtClaimsMode||"embed",claimsCacheRedis=getRedisManager();if(jwtClaimsMode==="resolve"&&db&&claimsCacheRedis){let{ClaimsCache:ClaimsCache3}=await Promise.resolve().then(() => (init_ClaimsCache(),exports_ClaimsCache));claimsCache=new ClaimsCache3({prefix:resolvedOptions.authorization?.claimsCachePrefix||"nucleus:claims",redis:{get:async(key2)=>{let r=await claimsCacheRedis.read(key2);return r.success?r.data:null},set:async(key2,value2)=>{await claimsCacheRedis.create(key2,value2)},delete:async(key2)=>{await claimsCacheRedis.remove(key2)}},db,schemaTables,logger:logger2});let cacheInstance=claimsCache,cacheResult=await bootStep("authorization.claimsCache",()=>cacheInstance.buildCache());if(cacheResult)logger2.info("[Authorization] Claims cache built (resolve mode)",{version:cacheResult.version,roleCount:cacheResult.roleCount,totalMappings:cacheResult.totalMappings})}else if(jwtClaimsMode==="resolve"&&!claimsCacheRedis)logger2.warn("[Authorization] jwtClaimsMode=resolve requires Redis. Falling back to embed mode.");if(logger2.info("[Authorization] Enabled"),isMultiTenant&&tenantRegistry){let tenantSchemas=tenantRegistry.getAllSchemaNames().filter((name2)=>name2!==targetSchemaName);for(let tenantSchemaName of tenantSchemas){let tenantCtx=tenantRegistry.getSchemaContext(tenantSchemaName);if(!tenantCtx)continue;try{if(authConfig.autoSeedClaims){let tenantSchemaEntities=extractSchemaTableEntities(tenantCtx.schemaTables),tenantClaimEntities=mergeEntitiesByName([...tenantSchemaEntities,...SYSTEM_TABLES.map((table)=>normalizeSystemTable(table)),...effectiveConfigEntities(resolvedOptions.entities||[]),...config.systemTables||[],...resolvedOptions.authorization?.externalEntities||[]]);await seedClaims(db,tenantCtx.schemaTables,tenantCtx.schemaRelations,tenantClaimEntities,authConfig,logger2)}if(tenantCtx.tenant?.godAdminEmail&&authConfig.godminPassword)await setupGodmin(db,tenantCtx.schemaTables,{...authConfig,godminEmail:tenantCtx.tenant.godAdminEmail},logger2);if(seedConfig){let{runSeed:runSeed2}=await Promise.resolve().then(() => (init_SeedRunner(),exports_SeedRunner));await runSeed2(db,tenantCtx.schemaTables,seedConfig,logger2)}logger2.info(`[Authorization] Tenant schema seeded: ${tenantSchemaName}`)}catch(err){let msg=err instanceof Error?err.message:String(err);logger2.warn(`[Authorization] Failed to seed tenant ${tenantSchemaName}: ${msg}`)}}logger2.info(`[Authorization] Multi-tenant seeding complete for ${tenantSchemas.length} schemas`)}}let sessionsTableRef=schemaTables.userSessions;if(!isConsumerModeOnStart&&sessionsTableRef&&resolvedOptions.authentication?.sessions?.enabled){let{lt:lt4}=await import("drizzle-orm"),expiredCount=await bootStep("auth.expiredSessionCleanup",()=>db.update(sessionsTableRef).set({isActive:!1,revokedAt:new Date,revokedReason:"expired"}).where(and28(eq68(sessionsTableRef.isActive,!0),lt4(sessionsTableRef.expiresAt,new Date))));if(expiredCount!==void 0)logger2.info("[AUTH] Expired sessions cleanup completed",{expiredCount});let sessionCleanupInterval=setInterval(async()=>{try{await db.update(sessionsTableRef).set({isActive:!1,revokedAt:new Date,revokedReason:"expired"}).where(and28(eq68(sessionsTableRef.isActive,!0),lt4(sessionsTableRef.expiresAt,new Date)));let approvalTtlMs=86400000,approvalCutoff=new Date(Date.now()-approvalTtlMs);await db.update(sessionsTableRef).set({isActive:!1,revokedAt:new Date,revokedReason:"approval_token_expired",approvalStatus:"rejected",approvalToken:null}).where(and28(eq68(sessionsTableRef.approvalStatus,"pending"),lt4(sessionsTableRef.approvalRequestedAt,approvalCutoff)))}catch(err){logger2.warn("[AUTH] Session cleanup failed",{error:err})}},3600000);trackInterval(sessionCleanupInterval)}if(isMultiTenant&&tenantRegistry&&resolvedOptions.authentication?.sessions?.enabled){let{lt:lt4}=await import("drizzle-orm"),tenantSchemaNames=tenantRegistry.getAllSchemaNames().filter((name2)=>name2!==targetSchemaName);for(let tenantSchemaName of tenantSchemaNames){let tenantCtx=tenantRegistry.getSchemaContext(tenantSchemaName);if(!tenantCtx)continue;let tenantSessionsTable=tenantCtx.schemaTables.userSessions||tenantCtx.schemaTables.user_sessions||tenantCtx.schemaTables.sessions;if(!tenantSessionsTable)continue;try{await db.update(tenantSessionsTable).set({isActive:!1,revokedAt:new Date,revokedReason:"expired"}).where(and28(eq68(tenantSessionsTable.isActive,!0),lt4(tenantSessionsTable.expiresAt,new Date))),logger2.info(`[AUTH] Tenant session cleanup completed: ${tenantSchemaName}`)}catch(err){let msg=err instanceof Error?err.message:String(err);logger2.warn(`[AUTH] Tenant session cleanup failed for ${tenantSchemaName}: ${msg}`)}}}}});let authMiddleware=async({request,set:set2,server})=>{let refusal=inboundGuard({request,set:set2,body:void 0,params:{},query:{}});if(refusal!==void 0)return refusal;let requestStartTime=Date.now(),requestSchemaTables=schemaTables;if(tenantRegistry){let tenantVettedClientIp=resolveClientIp(server?.requestIP?.(request)?.address??null,request.headers.get("x-forwarded-for")??request.headers.get("x-real-ip"),resolvedOptions.rateLimit?.trustedProxies??[]),tenantResult=tenantRegistry.resolveFromRequest(request,tenantVettedClientIp);if(tenantResult.resolved)requestSchemaTables=tenantResult.context.schemaTables,request.headers.set("x-tenant-schema",tenantResult.context.schemaName);else if(new URL(request.url).pathname!=="/health")return set2.status=tenantResult.statusCode,Response.json(withEnvelopeFlags({isSuccess:!1,message:tenantResult.error,status:tenantResult.statusCode,errors:[{message:tenantResult.error}],data:null}))}request.headers.set("x-request-start-time",String(requestStartTime));let requestId=randomUUID11();if(request.headers.set("x-request-id",requestId),set2.headers["x-request-id"]=requestId,resolvedOptions.security?.disableDefaultHeaders!==!0){set2.headers["X-Content-Type-Options"]="nosniff",set2.headers["X-Frame-Options"]="SAMEORIGIN",set2.headers["Referrer-Policy"]="strict-origin-when-cross-origin";let hstsMaxAge=resolvedOptions.security?.hstsMaxAge??15552000;if(hstsMaxAge>0)set2.headers["Strict-Transport-Security"]=`max-age=${hstsMaxAge}; includeSubDomains`}let url=new URL(request.url),pathname=url.pathname,method=request.method,query=url.search;if(csrfConfig.enabled){let csrfSecure=request.headers.get("x-forwarded-proto")==="https"||url.protocol==="https:"?"; Secure":"",issueCsrfCookie=()=>`${csrfConfig.cookieName}=${randomUUID11()}; Path=/; SameSite=Lax${csrfSecure}`,csrfCookies=parseCookies(request.headers.get("cookie"));if(requiresCsrf({method,cookies:csrfCookies,hasAuthorizationHeader:!!request.headers.get("authorization"),authCookieNames:csrfAuthCookieNames})&&!csrfTokenMatches(csrfCookies[csrfConfig.cookieName],request.headers.get(csrfConfig.headerName)))return set2.headers["Set-Cookie"]=issueCsrfCookie(),set2.status=403,Response.json(withEnvelopeFlags({isSuccess:!1,message:"CSRF token missing or invalid",status:403,errors:[{message:"CSRF token missing or invalid"}],data:null}));else if(!isStateChanging(method)&&!csrfCookies[csrfConfig.cookieName])set2.headers["Set-Cookie"]=issueCsrfCookie()}let socketIp=server?.requestIP?.(request)?.address??null,clientIp=resolveClientIp(socketIp,request.headers.get("x-forwarded-for")??request.headers.get("x-real-ip"),resolvedOptions.rateLimit?.trustedProxies??[]);request.headers.set("x-client-ip",clientIp);let userAgent=request.headers.get("user-agent")||"unknown";if(requestLogConfig.enabled&&requestLogConfig.logArrival&&!isRequestLogExcluded(pathname))logger2.log("debug",`\u2192 ${method} ${pathname}`,{requestId,method,path:pathname,query:requestLogConfig.includeQuery&&query?query:void 0,ip:clientIp,tenant:request.headers.get("x-tenant-schema")||void 0},void 0,void 0,"middleware.request");let tokens,parsedBody={},rawBodyText="";if(request.method!=="GET"&&request.method!=="HEAD")try{rawBodyText=await request.clone().text();let parsed=rawBodyText?JSON.parse(rawBodyText):{};parsedBody=parsed&&typeof parsed==="object"?parsed:{}}catch{parsedBody={}}let auditPayload=audit?.enabled?{id:randomUUID11(),user_id:"unknown",entity_name:pathname.split("/").filter(Boolean)[0]||"root",entity_id:null,operation_type:method,summary:"",old_values:{},new_values:parsedBody,ip_address:clientIp,user_agent:userAgent,timestamp:new Date().toISOString(),path:pathname,query}:null,isPublic=isPublicRoute(publicRoutes,pathname,method,{headers:request.headers,credentialed:()=>{let presented=parseTokenValuesFromHeaders(request.headers,tokenNames);return!!(presented.session_token||presented.access_token)}}),rateLimiterUserBudgetPending=!1,refuseRateLimited=(budget,retryAfter,subject)=>{if(set2.status=429,retryAfter)set2.headers["Retry-After"]=String(retryAfter);if(logger2.warn(`[RateLimit] Blocked ${pathname}: the ${budget} budget is spent (${budget==="address"?clientIp:subject||clientIp})`+(retryAfter?`, retry after ${retryAfter}s`:"")),monitoringService)monitoringService.recordRateLimitBlock();return new Response(JSON.stringify({isSuccess:!1,success:!1,message:"Too Many Requests",status:429,errors:[{message:"Too Many Requests"}],data:null,retryAfter}),{status:429,headers:{"Content-Type":"application/json"}})};if(rateLimiter){let routeCategory=isPublic?"public":"private",authType=resolveAuthType(pathname,resolvedOptions.authentication),category=authType?"auth":routeCategory,sharedPeer=!warnedSharedRateLimitBucket&&(resolvedOptions.rateLimit?.trustedProxies??[]).length===0&&(isNonDiscriminatingPeer(clientIp)?clientIp:peerDiversity.observe(clientIp));if(sharedPeer)warnedSharedRateLimitBucket=!0,logger2.warn(`[RateLimit] Every request so far has arrived from ${sharedPeer}, which cannot tell callers apart: every user shares ONE rate-limit bucket and one person can exhaust it for everybody. Set rateLimit.trustedProxies to the proxy address so X-Forwarded-For is honored, or raise rateLimit.privateRoutes/publicRoutes/authRoutes to values shared traffic cannot reach.`);rateLimiterUserBudgetPending=category==="private";let rateLimitResult=await rateLimiter.check({ip:clientIp,endpoint:pathname,category,authType}),headers=rateLimiter.getHeaders(rateLimitResult);for(let[key2,value2]of Object.entries(headers))set2.headers[key2]=value2;if(!rateLimitResult.allowed)return refuseRateLimited("address",rateLimitResult.retryAfter);let attempted=authType?readAttemptedIdentity(parsedBody,rawBodyText,request.headers.get("content-type")):void 0;if(attempted){let perIdentity=await rateLimiter.check({ip:clientIp,endpoint:pathname,category,authType,identity:attempted});if(!perIdentity.allowed)return refuseRateLimited("account",perIdentity.retryAfter,"the account being attempted")}}if(pathname==="/health")return;if(authentication?.enabled&&!isPublic){let apiKeyRaw=extractApiKeyFromHeader(request.headers),apiKeysTableRef=requestSchemaTables.apiKeys;if(apiKeyRaw&&authentication.apiKeys?.enabled&&apiKeysTableRef&&db){let keyHash=hashApiKey(apiKeyRaw),apiKeyRecord=(await db.select().from(apiKeysTableRef).where(eq68(apiKeysTableRef.keyHash,keyHash)).limit(1))[0];if(!apiKeyRecord)return set2.status=401,logger2.traceSync({message:"Invalid API key",level:"warn",context:{path:pathname,method},audit:toAudit(auditPayload,"Invalid API key")}),Error("Invalid API key");let validation=validateApiKeyRecord(apiKeyRecord);if(!validation.valid)return set2.status=401,logger2.traceSync({message:`API key rejected: ${validation.reason}`,level:"warn",context:{path:pathname,method,keyId:apiKeyRecord.id},audit:toAudit(auditPayload,`API key rejected: ${validation.reason}`)}),Error(validation.reason);let apiKeyUserId=apiKeyRecord.userId,apiKeyUserData;{let ksTable=requestSchemaTables.users;if(ksTable&&db){apiKeyUserData=(await db.select().from(ksTable).where(eq68(ksTable.id,apiKeyUserId)).limit(1))[0];let{evaluateAccountState:evaluateAccountState2}=await Promise.resolve().then(() => exports_accountState),ksState=apiKeyUserData?evaluateAccountState2(apiKeyUserData):{allowed:!1,reason:"inactive",message:"Account is not active"};if(!ksState.allowed)return set2.status=401,logger2.traceSync({message:`API key rejected: account ${ksState.reason}`,level:"warn",context:{path:pathname,method,keyId:apiKeyRecord.id},audit:toAudit(auditPayload,`API key rejected: account ${ksState.reason}`,{userId:apiKeyUserId})}),Error(ksState.message)}}let keyAllowedRoles=apiKeyRecord.allowedRoles||[],keyAllowedClaims=apiKeyRecord.allowedClaims||[],effectiveRoles=keyAllowedRoles,effectiveClaims=keyAllowedClaims,apiKeyClaimScopes={},userRolesTable=requestSchemaTables.userRoles,rolesTable=requestSchemaTables.roles,roleClaimsTable=requestSchemaTables.roleClaims,claimsTable=requestSchemaTables.claims;if(userRolesTable&&rolesTable){let currentUserRoles=(await db.select({name:rolesTable.name}).from(userRolesTable).innerJoin(rolesTable,eq68(rolesTable.id,userRolesTable.roleId)).where(eq68(userRolesTable.userId,apiKeyUserId))).map((r)=>r.name).filter((n)=>n!==void 0);effectiveRoles=intersectPermissions(currentUserRoles,keyAllowedRoles)}if(userRolesTable&&roleClaimsTable&&claimsTable){let userClaimRows=await db.select({action:claimsTable.action,scope:roleClaimsTable.scope}).from(userRolesTable).innerJoin(roleClaimsTable,eq68(roleClaimsTable.roleId,userRolesTable.roleId)).innerJoin(claimsTable,eq68(claimsTable.id,roleClaimsTable.claimId)).where(eq68(userRolesTable.userId,apiKeyUserId)),currentUserClaims=[...new Set(userClaimRows.map((r)=>r.action).filter((a)=>a!==void 0))];effectiveClaims=intersectPermissions(currentUserClaims,keyAllowedClaims);let effectiveClaimSet=new Set(effectiveClaims),apiScopedRows=userClaimRows.filter((r)=>typeof r.action==="string"&&effectiveClaimSet.has(r.action)).map((r)=>({action:r.action,scope:r.scope??null}));if(apiScopedRows.some((r)=>!!r.scope)){let{claimScopes,dropped}=resolveClaimScopes(apiScopedRows,apiKeyUserData,logger2);if(apiKeyClaimScopes=claimScopes,dropped.length>0){let droppedSet=new Set(dropped);effectiveClaims=effectiveClaims.filter((a)=>!droppedSet.has(a))}}}if(db.update(apiKeysTableRef).set({lastUsedAt:new Date,lastUsedIp:clientIp,usageCount:apiKeyRecord.usageCount+1}).where(eq68(apiKeysTableRef.id,apiKeyRecord.id)).catch(()=>{}),effectiveRoles=effectiveRoles.filter((r)=>r!==GODMIN_ROLE_NAME),request.headers.set("x-user-id",apiKeyUserId),request.headers.set("x-auth-type","api_key"),request.headers.set("x-api-key-id",apiKeyRecord.id),request.headers.set("x-api-key-owner-type",apiKeyRecord.ownerType||"personal"),effectiveRoles.length>0)request.headers.set("x-user-roles",encodeHeaderList(effectiveRoles));if(effectiveClaims.length>0)request.headers.set("x-user-claims",encodeHeaderList(effectiveClaims));{let apiEncodedScopes=encodeClaimScopesHeader(apiKeyClaimScopes);if(apiEncodedScopes)request.headers.set("x-user-claim-scopes",apiEncodedScopes)}logger2.info("[AUTH] API key authenticated",{userId:apiKeyUserId,keyId:apiKeyRecord.id,ownerType:apiKeyRecord.ownerType,path:pathname,method,effectiveRoles:effectiveRoles.length,effectiveClaims:effectiveClaims.length});return}if(!authentication.accessToken?.secret)return set2.status=500,logger2.traceSync({message:"Authentication secrets not defined",level:"error",context:{path:pathname,method},audit:toAudit(auditPayload,"Authentication secrets not defined")}),Error("One or more authentication secrets are not defined");if(authentication.mode==="consumer"){tokens=parseTokenValuesFromHeaders(request.headers,tokenNames);let jwtResult=verifyJWT(tokens.access_token||"",envResolved.accessTokenSecret||"",{issuer:authentication.accessToken?.issuer,audience:authentication.accessToken?.audience});if(!jwtResult.valid)return set2.status=401,logger2.traceSync({message:"Invalid or missing access token",level:"warn",context:{path:pathname,method},audit:toAudit(auditPayload,"Invalid or missing access token",{userId:decodeUnverifiedSubject(tokens.access_token)})}),Error("Unauthenticated");let consumerBinding=verifyTenantBinding(jwtResult.payload[TENANT_CLAIM],request.headers.get("x-tenant-schema"),resolvedOptions.authentication?.tenantBinding??"lenient");if(!consumerBinding.ok)return set2.status=401,logger2.traceSync({message:"Access token tenant binding failed",level:"warn",context:{path:pathname,method,reason:consumerBinding.reason},audit:toAudit(auditPayload,"Access token tenant binding failed",{userId:decodeUnverifiedSubject(tokens.access_token)})}),Error("Unauthenticated");let userId=jwtResult.payload.sub,roles=jwtResult.payload.roles,claimsFromToken=jwtResult.payload.claims;if(request.headers.set("x-access-token",tokens.access_token||""),request.headers.set("x-user-id",userId||""),roles&&roles.length>0)request.headers.set("x-user-roles",encodeHeaderList(roles));if(claimsFromToken&&claimsFromToken.length>0)request.headers.set("x-user-claims",encodeHeaderList(claimsFromToken));{let encodedScopes=encodeClaimScopesHeader(jwtResult.payload.claimScopes);if(encodedScopes)request.headers.set("x-user-claim-scopes",encodedScopes)}}else{if(!authentication.refreshToken?.secret||!authentication.sessionToken?.secret)return set2.status=500,logger2.traceSync({message:"Authentication secrets not defined",level:"error",context:{path:pathname,method},audit:toAudit(auditPayload,"Authentication secrets not defined")}),Error("One or more authentication secrets are not defined");if(tokens=parseTokenValuesFromHeaders(request.headers,tokenNames),!tokens.session_token)return set2.status=401,logger2.traceSync({message:"No session token",level:"debug",context:{path:pathname,method},audit:toAudit(auditPayload,"No session token")}),Error("Unauthenticated");let sessionData=await readSession({sessionId:tokens.session_token});if(!sessionData)return set2.status=401,logger2.traceSync({message:"Invalid session",level:"debug",context:{path:pathname,method,sessionId:tokens.session_token},audit:toAudit(auditPayload,"Invalid session")}),Error("Unauthenticated");let sessionsTableCheck=requestSchemaTables.userSessions;if(sessionsTableCheck&&db){let session=(await db.select().from(sessionsTableCheck).where(eq68(sessionsTableCheck.id,tokens.session_token)).limit(1))[0],revokedAtVal=session?.revokedAt,isRevoked=revokedAtVal!=null&&!(typeof revokedAtVal==="object"&&!(revokedAtVal instanceof Date)&&Object.keys(revokedAtVal).length===0);if(!session||session.isActive===!1||isRevoked)return set2.status=401,logger2.traceSync({message:"Session revoked or inactive",level:"warn",context:{path:pathname,method,sessionId:tokens.session_token,isActive:session?.isActive,revokedAt:session?.revokedAt},audit:toAudit(auditPayload,"Session revoked",{userId:sessionData.userId??null})}),Error("Session has been revoked");if(session.expiresAt&&new Date(session.expiresAt)<new Date)return set2.status=401,logger2.traceSync({message:"Session expired",level:"debug",context:{path:pathname,method,sessionId:tokens.session_token,expiresAt:session.expiresAt},audit:toAudit(auditPayload,"Session expired",{userId:sessionData.userId??null})}),Error("Session has expired")}if(isIdleExpired({lastActiveAt:sessionData.lastActiveAt,inactivityTimeout:authentication.sessions?.inactivityTimeout}))return set2.status=401,logger2.traceSync({message:"Session inactive timeout",level:"debug",context:{path:pathname,method,sessionId:tokens.session_token,lastActiveAt:sessionData.lastActiveAt},audit:toAudit(auditPayload,"Session inactive timeout",{userId:sessionData.userId??null})}),Error("Session expired due to inactivity");updateLastActiveAt(tokens.session_token).catch(()=>{});let sessionsTableRef=requestSchemaTables.userSessions;if(sessionsTableRef&&db)db.update(sessionsTableRef).set({lastActivityAt:new Date}).where(eq68(sessionsTableRef.id,tokens.session_token)).catch(()=>{});let jwtResult=verifyJWT(tokens.access_token||"",envResolved.accessTokenSecret||"",{issuer:authentication.accessToken?.issuer,audience:authentication.accessToken?.audience}),isAccessTokenValid=tokens.access_token?jwtResult.valid:!1,isRefreshTokenValid=tokens.refresh_token?verifyJWT(tokens.refresh_token,envResolved.refreshTokenSecret||"",{issuer:authentication.refreshToken?.issuer,audience:authentication.refreshToken?.audience}).valid:!1;if(!isAccessTokenValid&&isRefreshTokenValid&&tokens.refresh_token&&sessionData.rememberMe===!0){let killSwitchUsersTable=requestSchemaTables.users;if(db&&killSwitchUsersTable){let ksUser=(await db.select().from(killSwitchUsersTable).where(eq68(killSwitchUsersTable.id,sessionData.userId)).limit(1))[0],{evaluateAccountState:evaluateAccountState2}=await Promise.resolve().then(() => exports_accountState),ksState=ksUser?evaluateAccountState2(ksUser):{allowed:!1,reason:"inactive",message:"Account is not active"};if(!ksState.allowed)return set2.status=401,logger2.traceSync({message:`Silent refresh blocked - account ${ksState.reason}`,level:"warn",context:{path:pathname,method},audit:toAudit(auditPayload,`Silent refresh blocked - account ${ksState.reason}`,{userId:sessionData.userId??null})}),Error(ksState.message)}let refreshRoles=[],refreshClaims=[],refreshClaimScopes={},refreshUserRolesTable=requestSchemaTables.userRoles,refreshRolesTable=requestSchemaTables.roles,refreshRoleClaimsTable=requestSchemaTables.roleClaims,refreshClaimsTable=requestSchemaTables.claims;if(db&&refreshUserRolesTable&&refreshRolesTable)try{let{fetchUserRolesAndClaims:fetchUserRolesAndClaims2}=await Promise.resolve().then(() => (init_fetchUserRolesAndClaims(),exports_fetchUserRolesAndClaims)),rc=await fetchUserRolesAndClaims2(db,sessionData.userId,{usersTable:killSwitchUsersTable??null,sessionsTable:null,userRolesTable:refreshUserRolesTable,rolesTable:refreshRolesTable,roleClaimsTable:refreshRoleClaimsTable,claimsTable:refreshClaimsTable,oauthAccountsTable:void 0,apiKeysTable:void 0,schemaTables:requestSchemaTables});refreshRoles=rc.roles,refreshClaims=rc.claims,refreshClaimScopes=rc.claimScopes}catch{}let refreshResult=await refreshAccessTokenWithLock(sessionData.userId,sessionData.id,()=>signNewAccessToken({refreshTokenId:tokens.refresh_token,options:resolvedOptions,sessionData,roles:refreshRoles.length>0?refreshRoles:void 0,claims:refreshClaims.length>0?refreshClaims:void 0,claimScopes:refreshClaimScopes,tenant:request.headers.get("x-tenant-schema")??void 0,resolveMode:resolvedOptions.authorization?.jwtClaimsMode==="resolve"&&!!claimsCache}));if(refreshResult.success&&refreshResult.accessToken){tokens.access_token=refreshResult.accessToken;let rawDomain=authentication.cookieDomain,resolvedDomainRaw=rawDomain?process.env[rawDomain]??rawDomain:void 0,resolvedDomain=resolvedDomainRaw==="localhost"||resolvedDomainRaw===".localhost"?void 0:resolvedDomainRaw,domainPart=resolvedDomain?`; Domain=${resolvedDomain}`:"",bufferSeconds=authentication.cookieMaxAgeBufferSeconds??0,maxAge=Math.max(0,parseTimeToSeconds2(authentication.accessToken.expiresIn??"15m")-bufferSeconds),securePart=!resolvedDomain?"":"; Secure",cookieValue=`${tokenNames.access_token}=${refreshResult.accessToken}; Path=/; HttpOnly; SameSite=Lax${securePart}; Max-Age=${maxAge}${domainPart}`;set2.headers["Set-Cookie"]=cookieValue}}if(jwtResult.valid){let fullBinding=verifyTenantBinding(jwtResult.payload[TENANT_CLAIM],request.headers.get("x-tenant-schema"),resolvedOptions.authentication?.tenantBinding??"lenient");if(!fullBinding.ok)return set2.status=401,logger2.traceSync({message:"Access token tenant binding failed",level:"warn",context:{path:pathname,method,reason:fullBinding.reason},audit:toAudit(auditPayload,"Access token tenant binding failed",{userId:jwtResult.payload.sub??null})}),Error("Unauthenticated")}if(jwtResult.valid&&jwtResult.payload.sub&&String(jwtResult.payload.sub)!==String(sessionData.userId))return set2.status=401,logger2.traceSync({message:"Access token subject does not match session",level:"warn",context:{path:pathname,method,sessionUserId:sessionData.userId,tokenSub:jwtResult.payload.sub},audit:toAudit(auditPayload,"Access token/session subject mismatch",{userId:sessionData.userId??null})}),Error("Unauthenticated");let userId=jwtResult.valid?jwtResult.payload.sub:sessionData.userId,roles=jwtResult.valid?jwtResult.payload.roles:void 0,claimsFromToken=jwtResult.valid?jwtResult.payload.claims:void 0;if(!claimsFromToken?.length&&claimsCache&&roles&&roles.length>0)try{let resolvedClaims=await claimsCache.resolveClaimsForRoles(roles);if(resolvedClaims.length>0)claimsFromToken=resolvedClaims}catch{}if(userId&&db&&authentication.cohorts?.enabled){let mwUsersTable=requestSchemaTables.users;if(mwUsersTable)try{let mwCohortId=(await db.select().from(mwUsersTable).where(eq68(mwUsersTable.id,userId)).limit(1))[0]?.cohortId;if(mwCohortId){let mwCohortsTable=requestSchemaTables.userCohorts??requestSchemaTables.user_cohorts;if(mwCohortsTable){let mwExpiresAt=(await db.select().from(mwCohortsTable).where(eq68(mwCohortsTable.id,mwCohortId)).limit(1))[0]?.expiresAt;if(mwExpiresAt&&new Date(mwExpiresAt)<new Date)return set2.status=403,logger2.traceSync({message:"Cohort expired - access denied",level:"warn",context:{path:pathname,method,userId,cohortId:mwCohortId},audit:toAudit(auditPayload,"Cohort expired",{userId:userId??null})}),Error("Your access has expired. Please contact your administrator.")}}}catch{}}if(request.headers.set("x-access-token",tokens.access_token||""),request.headers.set("x-refresh-token",tokens.refresh_token||""),request.headers.set("x-session-id",tokens.session_token||""),request.headers.set("x-user-id",userId||""),roles&&roles.length>0)request.headers.set("x-user-roles",encodeHeaderList(roles));if(claimsFromToken&&claimsFromToken.length>0)request.headers.set("x-user-claims",encodeHeaderList(claimsFromToken));{let encodedScopes=encodeClaimScopesHeader(jwtResult.valid?jwtResult.payload.claimScopes:void 0);if(encodedScopes)request.headers.set("x-user-claim-scopes",encodedScopes)}}}if(rateLimiter&&!isPublic&&rateLimiterUserBudgetPending){let resolvedUserId=request.headers.get("x-user-id");if(resolvedUserId){let perUser=await rateLimiter.check({ip:clientIp,userId:resolvedUserId,endpoint:pathname,category:"private"}),perUserHeaders=rateLimiter.getHeaders(perUser);for(let[key2,value2]of Object.entries(perUserHeaders))set2.headers[key2]=value2;if(!perUser.allowed)return refuseRateLimited("user",perUser.retryAfter,`user ${resolvedUserId}`)}}},afterHandler=({request,set:set2})=>{let afterUrl=new URL(request.url),afterStatus=typeof set2.status==="number"?set2.status:200,afterStartStr=request.headers.get("x-request-start-time"),afterDuration=afterStartStr?Date.now()-parseInt(afterStartStr,10):0;if(requestLogConfig.enabled&&!isRequestLogExcluded(afterUrl.pathname)){let isSlow=afterDuration>=requestLogConfig.slowThresholdMs,level=afterStatus>=500?"error":afterStatus>=400||isSlow?"warn":"info";logger2.log(level,`\u2190 ${request.method} ${afterUrl.pathname} ${afterStatus} (${afterDuration}ms)`,{requestId:request.headers.get("x-request-id")||void 0,method:request.method,path:afterUrl.pathname,query:requestLogConfig.includeQuery&&afterUrl.search?afterUrl.search:void 0,statusCode:afterStatus,durationMs:afterDuration,slow:isSlow||void 0,userId:request.headers.get("x-user-id")||void 0,authType:request.headers.get("x-auth-type")||void 0,tenant:request.headers.get("x-tenant-schema")||void 0,ip:request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||request.headers.get("x-real-ip")||void 0},void 0,void 0,"middleware.request")}if(monitoringService){let startTimeStr=request.headers.get("x-request-start-time"),startTime=startTimeStr?parseInt(startTimeStr,10):Date.now(),responseTimeMs=Date.now()-startTime,url=new URL(request.url),status=typeof set2.status==="number"?set2.status:200;monitoringService.recordRequest({endpoint:url.pathname,method:request.method,status,responseTimeMs,isError:status>=500,errorType:status>=500?"server_error":status>=400?"client_error":void 0})}if(liveMonitoringService){let url=new URL(request.url),headersObj=redactSensitiveHeaders(request.headers);liveMonitoringService.recordRequest({path:url.pathname,method:request.method,timestamp:Date.now(),headers:headersObj})}};recordAdapterStep((app)=>{app.onRequest(authMiddleware).onAfterHandle(afterHandler)});let errorResponder=(ctx)=>{let{set:set2,code,error:error3,request}=ctx,status=statusOfError(code,error3),message="Internal Server Error",pgCode,clientSafe=!1;if(error3 instanceof Error){let cause=error3.cause;if(pgCode=cause?.code,pgCode==="23505")message="A record with this value already exists",clientSafe=!0,status=409;else if(pgCode==="23503")message="Referenced record does not exist",clientSafe=!0,status=400;else if(pgCode==="23502")message=`Missing required field: ${cause?.column||"a required field is empty"}`,clientSafe=!0,status=400;else if(pgCode==="22P02")message=cause?.routine==="string_to_uuid"?"Invalid ID format":"Invalid data format",clientSafe=!0,status=400;else if(pgCode)message=`Database error (${pgCode}): ${cause?.detail||cause?.message||error3.message}`;else if(code==="VALIDATION")message=describeValidationError(error3)??error3.message,clientSafe=!0;else message=error3.message,clientSafe=status<500}try{let errUrl=new URL(request.url),errStartStr=request.headers.get("x-request-start-time");logger2.log(status>=500?"error":"warn",`\u2716 ${request.method} ${errUrl.pathname} ${status}: ${message}`,{requestId:request.headers.get("x-request-id")||void 0,method:request.method,path:errUrl.pathname,statusCode:status,elysiaCode:typeof code==="string"?code:void 0,pgCode,durationMs:errStartStr?Date.now()-parseInt(errStartStr,10):void 0,userId:request.headers.get("x-user-id")||void 0,tenant:request.headers.get("x-tenant-schema")||void 0},status>=500?error3:void 0)}catch(logErr){logger2.error("Failed to log request error",logErr)}set2.status=status;let clientMessage=clientSafe?message:"Internal Server Error",requestId=request.headers.get("x-request-id")||void 0;if(requestId)set2.headers["x-request-id"]=requestId;return Response.json(withEnvelopeFlags({isSuccess:!1,message:clientMessage,status,requestId,errors:[{message:clientMessage}],data:null}))};recordAdapterStep((app)=>{app.onError(errorResponder)});let featureAdminRoles={};for(let[feature,section]of Object.entries(resolvedOptions)){if(!section||typeof section!=="object")continue;let roles=section.adminRoles;if(!Array.isArray(roles))continue;let named=roles.filter((role)=>typeof role==="string"&&role.length>0);if(named.length>0)featureAdminRoles[feature]=named}logger2.info("Creating routes for entities"),createEntityRoutes(plugin,{db,schemaTables,schemaRelations,entities,logger:logger2,databaseUrl:envResolved.databaseUrl,dbPool,storage:resolvedOptions.storage,cdnMedia:{transform:mergeTransformConfig(resolvedOptions.storage?.cdn?.transform),video:mergeVideoConfig(resolvedOptions.storage?.cdn?.video),pdf:mergePdfConfig(resolvedOptions.storage?.cdn?.pdf)},authorization:resolvedOptions.authorization,featureAdminRoles,accountLinks:authentication?.accountLinks,authMode:authentication?.mode,idpUrl:authentication?.idpUrl?process.env[authentication.idpUrl]||authentication.idpUrl:void 0,isEmailServiceAvailable:()=>!!getEmailService()?.isAvailable(),onRoleChanged:({userId:subjectId,roleName,assigned})=>{if(!db)return;let service=new NotificationService({db,schemaTables,config:resolvedOptions.notification??{},logger:logger2,getEmailService}),title=assigned?`New role: ${roleName}`:`Role removed: ${roleName}`,body=assigned?`You have been given the "${roleName}" role in the portal. New sections will appear in your menu.`:`Your "${roleName}" role in the portal has been removed. The sections it opened will no longer appear.`;service.send({user_id:subjectId,title,body,type:"system",source:"authorization",channels:["portal","email"]}).catch(()=>{})},tenantRegistry,getTenantRegistry:()=>tenantRegistry,claimsCache});let isConsumerMode=authentication?.mode==="consumer";if(isMultiTenant&&!isConsumerMode)createTenantRoutes(plugin,{getDb:()=>db,logger:logger2,getTenantRegistry:()=>tenantRegistry,schemaName:targetSchemaName}),logger2.info("[MultiTenant] Tenant routes pre-registered (handlers check runtime readiness)");else if(isMultiTenant&&isConsumerMode){let routePostTenantsRefresh=defineRoute({method:"POST",path:"/tenants/refresh",handler:async(ctx)=>{if(!tenantRegistry||!tenantRegistry.isConsumerMode())return ctx.set.status=503,{success:!1,message:"Tenant registry not ready"};try{return{success:!0,data:await tenantRegistry.syncFromIdp()}}catch(err){let msg=err instanceof Error?err.message:String(err);return ctx.set.status=500,{success:!1,message:`Tenant sync failed: ${msg}`}}}});mountRoutes(plugin,[{routes:[routePostTenantsRefresh]}]),logger2.info("[MultiTenant] Consumer tenant refresh route registered (/tenants/refresh)")}let domainServices=null;if(resolvedOptions.domains?.enabled){if(domainServices=createDomainServices({options:resolvedOptions,logger:logger2,getDb:()=>db,getTenantRegistry:()=>tenantRegistry}),domainServices)createDomainRoutes(plugin,{getDomainService:()=>domainServices?.domainService??null,getRegistrationService:()=>domainServices?.registrationService??null,logger:logger2,basePath:resolvedOptions.domains.basePath||"/domains",resolveTenantIdForSchema:(schema3)=>schema3&&tenantRegistry?tenantRegistry.getTenantBySchemaName(schema3)?.id??null:null}),logger2.info("[Domains] Custom domain routes registered",{provider:domainServices.domainService.config.provider,basePath:resolvedOptions.domains.basePath||"/domains"})}if(authentication?.enabled&&!isConsumerMode&&db){let resolveTableForTenant=(tableName,reqSchemaName)=>{if(reqSchemaName&&tenantRegistry){let ctx=tenantRegistry.getSchemaContext(reqSchemaName);if(ctx?.schemaTables[tableName])return ctx.schemaTables[tableName]}return schemaTables[tableName]},usersTable=schemaTables.users,sessionsTable=schemaTables.userSessions||schemaTables.user_sessions||schemaTables.sessions;if(!sessionsTable&&authentication.sessions?.enabled)logger2.warn("[AUTH] sessions is enabled but user_sessions table not found in schema. Disabling sessions.");if(usersTable){await initiateRedisManager(resolvedOptions);let{createAuthRoutes:createAuthRoutes2}=(init_auth(),__toCommonJS(exports_auth)),{signJWT:signJWT2,verifyJWT:verifyJWT3}=(init_JWT(),__toCommonJS(exports_JWT)),{generateSession:generateSession2,deleteSession:deleteSession2}=(init_SessionStore(),__toCommonJS(exports_SessionStore));createAuthRoutes2(plugin,{authConfig:{db,logger:logger2,usersTable,sessionsTable,userRolesTable:schemaTables.userRoles,rolesTable:schemaTables.roles,roleClaimsTable:schemaTables.roleClaims,claimsTable:schemaTables.claims,authentication:{enabled:authentication.enabled,mode:authentication.mode,get defaultRole(){return resolvedOptions.authentication?.defaultRole||process.env.AUTH_DEFAULT_ROLE},get defaultRoles(){return resolvedOptions.authentication?.defaultRoles},cookieDomain:resolvedOptions.authentication?.cookieDomain,get trustedAppOrigins(){return trustedAppOrigins()},emailExemptDomains:authentication?.emailExemptDomains,accessToken:authentication.accessToken,refreshToken:authentication.refreshToken,sessionToken:authentication.sessionToken,rememberMe:authentication.rememberMe,deviceTrust:authentication.sessions?.deviceTrust}},features:{login:authentication.login,register:authentication.register,logout:authentication.logout,refresh:authentication.refresh,passwordReset:withResolvedRedirects(authentication.passwordReset,["redirectUrl"]),passwordChange:authentication.passwordChange,passwordSet:authentication.passwordSet,sessions:withResolvedRedirects(authentication.sessions,["approvalRedirectUrl"]),magicLink:withResolvedRedirects(authentication.magicLink,["redirectUrl"]),me:authentication.me||{enabled:!0,route:"/auth/me"},invite:withResolvedRedirects(authentication.invite,["redirectUrl"]),captcha:authentication.captcha,oauth:authentication.oauth?.enabled&&envResolved.oauthProviders?{...withResolvedRedirects(authentication.oauth,["successRedirectUrl","errorRedirectUrl"]),providers:liveOAuthProviders(envResolved.oauthProviders)}:void 0,apiKeys:authentication.apiKeys?.enabled?{enabled:!0,route:authentication.apiKeys.route,keyPrefix:authentication.apiKeys.keyPrefix,maxKeysPerUser:authentication.apiKeys.maxKeysPerUser,defaultExpiresIn:authentication.apiKeys.defaultExpiresIn,allowApplicationKeys:authentication.apiKeys.allowApplicationKeys,preventApiKeyManagement:authentication.apiKeys.preventApiKeyManagement}:void 0,webauthn:authentication.webauthn},sessionsTable,oauthAccountsTable:schemaTables.oauthAccounts,oauthStateStore:(()=>{let oauthRedis=getRedisManager();if(!oauthRedis)return;let{createRedisOAuthStateStore:createRedisOAuthStateStore2}=__toCommonJS(exports_stateStore);return createRedisOAuthStateStore2(oauthRedis)})(),apiKeysTable:schemaTables.apiKeys,schemaTables,schemaRelations,tenantRegistry,getTenantRegistry:()=>tenantRegistry,databaseUrl:envResolved.databaseUrl,dbPool,admin:(()=>{let adminCfg=resolvedOptions.authentication?.admin;return{impersonate:{enabled:!0},changeUserId:{enabled:!0},createUser:adminCfg?.createUser??{enabled:!0},passwordTools:adminCfg?.passwordTools??{enabled:!1}}})(),schemaName:targetSchemaName,getEmailService,appName:resolvedOptions.appId,webauthnService:(()=>{if(!authentication.webauthn?.enabled||!db)return null;let{WebAuthnService:WebAuthnService2}=(init_WebAuthn(),__toCommonJS(exports_WebAuthn)),{createDbWebAuthnStorage:createDbWebAuthnStorage2}=(init_dbStorage(),__toCommonJS(exports_dbStorage)),rpName=authentication.webauthn.rpName||resolvedOptions.appId||"Nucleus",rpID=authentication.webauthn.rpID||"localhost",expectedOrigins=authentication.webauthn.expectedOrigins||[`http://${rpID}`,`https://${rpID}`],challengeTtlMs=authentication.webauthn.challengeTtl?parseTimeToSeconds2(authentication.webauthn.challengeTtl)*1000:300000,storage=createDbWebAuthnStorage2({db,resolveTable:resolveTableForTenant});return new WebAuthnService2({rp:{rpName,rpID,expectedOrigins},challengeTtlMs,storage,userVerification:authentication.webauthn?.userVerification,logger:logger2})})(),captchaService:(()=>{let redisManager=getRedisManager();if(!authentication.captcha?.enabled||!redisManager)return null;return new CaptchaService({redis:{get:async(key2)=>{let result=await redisManager.read(key2);return result.success?result.data:null},set:async(key2,value2,options)=>{await redisManager.create(key2,value2,options?.ex)},del:async(key2)=>{await redisManager.remove(key2)},incrementCounter:(key2,delta,ttlSeconds)=>redisManager.incrementCounter(key2,delta,ttlSeconds)},logger:logger2,config:{enabled:!0,type:authentication.captcha.type||"math",difficulty:authentication.captcha.difficulty||"medium",expiresIn:authentication.captcha.expiresIn||"5m",maxAttempts:authentication.captcha.maxAttempts||3,caseSensitive:authentication.captcha.caseSensitive??!1}})})(),tokenResponseConfig:{accessToken:{setHeadersEnabled:authentication.accessToken?.setHeadersEnabled??!0,returnJson:authentication.accessToken?.returnJson??!0},refreshToken:{setHeadersEnabled:authentication.refreshToken?.setHeadersEnabled??!0,returnJson:authentication.refreshToken?.returnJson??!0},sessionToken:{setHeadersEnabled:authentication.sessionToken?.setHeadersEnabled??!0,returnJson:authentication.sessionToken?.returnJson??!0}},helpers:{signAccessToken:(userId,roles,claims,tenant,claimScopes,sessionId)=>{let resolveMode=resolvedOptions.authorization?.jwtClaimsMode==="resolve"&&claimsCache,hasClaimScopes=!resolveMode&&!!claimScopes&&Object.keys(claimScopes).length>0,token=signJWT2({subject:userId,expiresInSeconds:parseTimeToSeconds2(authentication.accessToken?.expiresIn||"15m"),issuer:authentication.accessToken?.issuer,audience:authentication.accessToken?.audience,sessionId,customClaims:{...roles&&roles.length>0?{roles}:{},...!resolveMode&&claims&&claims.length>0?{claims}:{},...hasClaimScopes?{claimScopes}:{},...tenant?{tenant}:{}}},envResolved.accessTokenSecret||"",authentication.accessToken?.algorithm||"HS256");return warnIfAccessTokenTooLargeForCookie(token,resolveMode?"resolve":resolvedOptions.authorization?.jwtClaimsMode),token},signRefreshToken:(userId,rememberMe,sessionId)=>signJWT2({subject:userId,expiresInSeconds:longerLifetime(rememberMe?authentication.rememberMe?.expiresIn:void 0,authentication.refreshToken?.expiresIn,604800),issuer:authentication.refreshToken?.issuer,audience:authentication.refreshToken?.audience,sessionId},envResolved.refreshTokenSecret||"",authentication.refreshToken?.algorithm||"HS256"),verifyRefreshToken:(token)=>verifyJWT3(token,envResolved.refreshTokenSecret||"",{issuer:authentication.refreshToken?.issuer,audience:authentication.refreshToken?.audience}),createSession:async(params)=>{let sessionTtlSeconds=longerLifetime(params.rememberMe?authentication.rememberMe?.expiresIn:void 0,authentication.sessionToken?.expiresIn,2592000),result=await generateSession2({userId:params.userId,deviceInfo:params.deviceInfo,rememberMe:params.rememberMe,loginMethod:params.loginMethod,expiresInSeconds:sessionTtlSeconds});if(!result.success)throw logger2.error("[createSession] failed to create session",{userId:params.userId,error:result.error}),Error(`Session storage unavailable: ${result.error||"unknown error"}`);return result.session.id},destroySession:async(sessionId)=>deleteSession2({sessionId}),saveSessionToDb:async(sessionId,params,reqSchemaName)=>{let resolvedSessionsTable=resolveTableForTenant("userSessions",reqSchemaName)||resolveTableForTenant("user_sessions",reqSchemaName)||resolveTableForTenant("sessions",reqSchemaName)||sessionsTable;if(!resolvedSessionsTable||!db)return;let sessionsConfig=authentication.sessions,deviceInfo=ensureDeviceInfo(params.deviceInfo||{ipAddress:""}),resolvedUsersTableForExempt=resolveTableForTenant("users",reqSchemaName),sessionUserEmail=null;if(resolvedUsersTableForExempt)sessionUserEmail=(await db.select().from(resolvedUsersTableForExempt).where(eq68(resolvedUsersTableForExempt.id,params.userId)).limit(1))[0]?.email??null;let userIsEmailExempt=sessionUserEmail?isEmailExempt(sessionUserEmail,authentication?.emailExemptDomains):!1,deviceFingerprint=deviceInfo.deviceHint?`${deviceInfo.browserName||""}-${deviceInfo.osName||""}-${deviceInfo.deviceType||""}-${deviceInfo.deviceHint}`:`${deviceInfo.browserName||""}-${deviceInfo.osName||""}-${deviceInfo.deviceType||""}`,existingSessions=await db.select().from(resolvedSessionsTable).where(and28(eq68(resolvedSessionsTable.userId,params.userId),eq68(resolvedSessionsTable.isActive,!0))),hasValidFingerprint=deviceFingerprint&&!deviceFingerprint.includes("--unknown")&&!deviceFingerprint.includes("Bot/Crawler")&&!deviceFingerprint.includes("Headless")&&deviceFingerprint!=="--"&&deviceFingerprint!=="--unknown",allUserSessions=await db.select().from(resolvedSessionsTable).where(eq68(resolvedSessionsTable.userId,params.userId)),isNewDevice=hasValidFingerprint?!existingSessions.some((s)=>s.deviceFingerprint===deviceFingerprint):!1,wasPreviouslyApproved=hasValidFingerprint?allUserSessions.some((s)=>{let sess=s;return sess.deviceFingerprint===deviceFingerprint&&sess.approvalStatus==="approved"}):!1,hasAnyApprovedSession=existingSessions.some((s)=>s.approvalStatus==="approved"),isImpersonationLogin=params.loginMethod==="impersonation"||params.loginMethod==="impersonation_stop",isOAuthLogin=params.loginMethod?.startsWith("oauth:"),requiresApproval=!isImpersonationLogin&&!userIsEmailExempt&&sessionsConfig?.trustNewDevices===!1&&isNewDevice&&!wasPreviouslyApproved&&hasValidFingerprint&&hasAnyApprovedSession,deviceTrustCfg=sessionsConfig?.deviceTrust,dtTable=deviceTrustCfg?.enabled?resolveTableForTenant("trustedDevices",reqSchemaName)||resolveTableForTenant("trusted_devices",reqSchemaName):void 0,deviceTrustMint=null,deviceTokenToSet=null;if(deviceTrustCfg?.enabled&&dtTable){let DT=await Promise.resolve().then(() => (init_DeviceTrust(),exports_DeviceTrust)),DTStore=await Promise.resolve().then(() => (init_store3(),exports_store)),dtNow=new Date,dtRows=params.deviceToken?await DTStore.findDeviceRowsByRawToken(db,dtTable,params.deviceToken):[],knownRow=DT.selectUsableDeviceRow(dtRows,params.userId,dtNow),pendingRow=dtRows.find((r)=>(r.userId??r.user_id)===params.userId&&r.status==="pending"),isFirstEver=allUserSessions.length===0&&await DTStore.userHasNoTrustedDevices(db,dtTable,params.userId),decision=DT.decideDeviceApproval({knownDevice:!!knownRow,trustNewDevices:sessionsConfig?.trustNewDevices??!0,hasValidFingerprint:!!hasValidFingerprint,isFirstEverDevice:isFirstEver,loginMethod:params.loginMethod,approvalMethods:deviceTrustCfg.approvalMethods??["password","magic_link","sso","webauthn","oauth:*","register"],onUnidentifiable:deviceTrustCfg.onUnidentifiable??"require_approval",isImpersonation:isImpersonationLogin,isEmailExempt:userIsEmailExempt});if(knownRow)await DTStore.touchDevice(db,dtTable,knownRow.id,deviceInfo.ipAddress).catch(()=>{});if(decision.deny)return logger2.warn("[AUTH] Device-trust denied unidentifiable device",{userId:params.userId}),{requiresApproval:!1,denied:!0};let reuseSessionId=pendingRow?.originSessionId??pendingRow?.origin_session_id;if(decision.requiresApproval&&reuseSessionId)return{requiresApproval:!0,sessionId:reuseSessionId};requiresApproval=decision.requiresApproval,deviceTrustMint=decision.mintToken?decision.trustImmediately?"trusted":"pending":null}logger2.info("[AUTH] Device fingerprint analysis",{userId:params.userId,deviceFingerprint,hasValidFingerprint,isNewDevice,wasPreviouslyApproved,loginMethod:params.loginMethod,isImpersonationLogin,isOAuthLogin,existingSessionCount:existingSessions.length,hasAnyApprovedSession,requiresApproval});let approvalToken=null,approvalStatus="approved";if(requiresApproval){let existingPending=deviceTrustCfg?.enabled?void 0:allUserSessions.find((s)=>{let sess=s;return sess.deviceFingerprint===deviceFingerprint&&(sess.approvalStatus==="pending"||sess.approval_status==="pending")&&sess.approvalToken});if(existingPending){let pendingSess=existingPending,pendingRequestedAt=pendingSess.approvalRequestedAt||pendingSess.approval_requested_at;if(pendingRequestedAt?Date.now()-new Date(pendingRequestedAt).getTime()<86400000:!0)return logger2.info("[AUTH] Reusing existing pending session for same device",{userId:params.userId,deviceFingerprint,existingSessionId:pendingSess.id}),{requiresApproval:!0,sessionId:pendingSess.id}}let{randomBytes:randomBytes8}=await import("crypto");approvalToken=randomBytes8(32).toString("hex"),approvalStatus="pending",logger2.info("[AUTH] New device requires approval",{userId:params.userId,deviceFingerprint,ipAddress:deviceInfo.ipAddress})}let staleBotSessions=existingSessions.filter((s)=>{let sess=s,fp=(sess.deviceFingerprint||"").toLowerCase(),ip=sess.ipAddress||"",ua=(sess.userAgent||"").toLowerCase(),isBotFingerprint=!fp||fp==="--"||fp==="--unknown"||fp.includes("bot/crawler")||fp.includes("headless")||fp.includes("unknown-unknown"),isServerAction=ua.includes("nucleusserveraction")||ua.includes("serveraction")||ua.includes("node-fetch")||ua.includes("undici");return isBotFingerprint&&(ip==="127.0.0.1"||ip==="::1"||ip==="localhost"||!ip)||isServerAction});if(staleBotSessions.length>0){for(let botSession of staleBotSessions)await db.update(resolvedSessionsTable).set({isActive:!1,revokedAt:new Date,revokedReason:"bot_session_cleanup"}).where(eq68(resolvedSessionsTable.id,botSession.id));logger2.info("[AUTH] Cleaned up stale bot/crawler sessions",{userId:params.userId,cleanedCount:staleBotSessions.length})}if(hasValidFingerprint&&!requiresApproval){let sameDeviceOldSessions=existingSessions.filter((s)=>s.deviceFingerprint===deviceFingerprint);for(let oldSession of sameDeviceOldSessions)await db.update(resolvedSessionsTable).set({isActive:!1,revokedAt:new Date,revokedReason:"same_device_relogin"}).where(eq68(resolvedSessionsTable.id,oldSession.id));if(sameDeviceOldSessions.length>0)logger2.info("[AUTH] Revoked old same-device sessions",{userId:params.userId,deviceFingerprint,revokedCount:sameDeviceOldSessions.length})}if(!sessionsConfig?.allowMultipleDevices&&existingSessions.length>0){if((hasValidFingerprint?existingSessions.filter((s)=>s.deviceFingerprint===deviceFingerprint):[]).length===0)for(let oldSession of existingSessions)await db.update(resolvedSessionsTable).set({isActive:!1,revokedAt:new Date,revokedReason:"new_device_login"}).where(eq68(resolvedSessionsTable.id,oldSession.id))}if(sessionsConfig?.maxActiveSessions){let{count:count3}=await import("drizzle-orm"),currentCount=(await db.select({count:count3()}).from(resolvedSessionsTable).where(and28(eq68(resolvedSessionsTable.userId,params.userId),eq68(resolvedSessionsTable.isActive,!0))))[0]?.count||0;if(currentCount>=sessionsConfig.maxActiveSessions){let{asc:asc2}=await import("drizzle-orm"),oldestSessions=await db.select().from(resolvedSessionsTable).where(and28(eq68(resolvedSessionsTable.userId,params.userId),eq68(resolvedSessionsTable.isActive,!0))).orderBy(asc2(resolvedSessionsTable.createdAt)).limit(currentCount-sessionsConfig.maxActiveSessions+1);for(let oldSession of oldestSessions)await db.update(resolvedSessionsTable).set({isActive:!1,revokedAt:new Date,revokedReason:"max_sessions_exceeded"}).where(eq68(resolvedSessionsTable.id,oldSession.id))}}let trustScore=100;if(deviceInfo.isHeadless)trustScore-=50;if(deviceInfo.isBot)trustScore-=40;if(deviceInfo.isSuspicious)logger2.warn("[AUTH] Suspicious login detected",{userId:params.userId,suspiciousPatterns:deviceInfo.suspiciousPatterns,userAgent:deviceInfo.userAgent,ipAddress:deviceInfo.ipAddress});if(isNewDevice)trustScore-=25;if(!deviceInfo.ipAddress||deviceInfo.ipAddress==="unknown")trustScore-=20;if(!deviceInfo.browserName)trustScore-=15;if(!deviceInfo.osName)trustScore-=15;if(!deviceInfo.deviceType||deviceInfo.deviceType==="unknown")trustScore-=10;if(!deviceInfo.deviceName||deviceInfo.deviceName==="Unknown Device")trustScore-=5;let validFingerprint=deviceFingerprint&&!deviceFingerprint.includes("--unknown")&&deviceFingerprint!=="--",validIp=deviceInfo.ipAddress&&deviceInfo.ipAddress!=="unknown";if(validFingerprint){if(existingSessions.filter((s)=>s.deviceFingerprint===deviceFingerprint).length>0)trustScore+=20}if(validIp){if(existingSessions.filter((s)=>s.ipAddress===deviceInfo.ipAddress).length>0)trustScore+=15}trustScore=Math.max(0,Math.min(100,trustScore));let LOW_TRUST_THRESHOLD=50;if(await db.insert(resolvedSessionsTable).values({id:sessionId,userId:params.userId,tokenHash:sessionId,deviceFingerprint,deviceName:deviceInfo.deviceName,deviceType:deviceInfo.deviceType,browserName:deviceInfo.browserName,browserVersion:deviceInfo.browserVersion,osName:deviceInfo.osName,osVersion:deviceInfo.osVersion,ipAddress:deviceInfo.ipAddress,locationCountry:deviceInfo.locationCountry,locationCity:deviceInfo.locationCity,loginMethod:params.loginMethod||"password",rememberMe:params.rememberMe??!1,trustScore,lastActivityAt:new Date,createdAt:new Date,expiresAt:(()=>{let ttl=lifetimeSeconds(authentication.sessionToken?.expiresIn,2592000);return ttl===null?null:new Date(Date.now()+ttl*1000)})(),isActive:approvalStatus==="approved",approvalStatus,approvalToken,approvalRequestedAt:requiresApproval?new Date:null}),shouldNotifyDevice({emailAvailable:Boolean(getEmailService()?.isAvailable()),isImpersonationLogin:Boolean(isImpersonationLogin),notifyOnNewDevice:Boolean(sessionsConfig?.notifyOnNewDevice),isNewDevice,trustScore,lowTrustThreshold:LOW_TRUST_THRESHOLD,requiresApproval:Boolean(requiresApproval)})){let resolvedUsersTable=resolveTableForTenant("users",reqSchemaName);if(resolvedUsersTable){let user=(await db.select().from(resolvedUsersTable).where(eq68(resolvedUsersTable.id,params.userId)).limit(1))[0];if(user?.email&&!isEmailExempt(user.email,authentication?.emailExemptDomains)){let isLowTrust=trustScore<LOW_TRUST_THRESHOLD,sessionsRoute=authentication.sessions?.route||"/auth/sessions",configuredUrl=authentication.sessions?.approvalRedirectUrl||"",isLegacyFrontendUrl=!configuredUrl||configuredUrl.endsWith("/devices"),approvalBase;if(!isLegacyFrontendUrl)approvalBase=configuredUrl;else{let origin=pickTrustedOrigin(params.requestOrigin,configuredUrl,trustedAppOrigins());if(params.requestOrigin&&origin!==params.requestOrigin)logger2.warn("[AUTH] Device-approval link origin refused \u2014 not in trustedAppOrigins",{requested:params.requestOrigin,used:origin||"(none)"});approvalBase=`${origin||"http://localhost:9000"}${sessionsRoute}`}let approveUrl=approvalToken?`${approvalBase}/approve-page?token=${approvalToken}`:"",rejectUrl=approvalToken?`${approvalBase}/reject-page?token=${approvalToken}`:"",subject,emailHtml,brandName=resolvedOptions.appId||"Nucleus",loginTime=new Date().toLocaleString("en-US",{dateStyle:"medium",timeStyle:"short"}),deviceSummary=`${deviceInfo.browserName||"Unknown"} ${deviceInfo.browserVersion||""} on ${deviceInfo.osName||"Unknown"} ${deviceInfo.osVersion||""}`,emailWrapper=(content)=>`
553
558
  <!DOCTYPE html>