nucleus-core-ts 0.9.873 → 0.9.874
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -532,7 +532,7 @@ ${strMDSAlgs}`)}if(attestationStatementAlg!==void 0&&authenticatorGetInfo?.algor
|
|
|
532
532
|
`)};var handleListCohorts=(db,usersTable,cohortsTable)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let rows=await db.select().from(cohortsTable);return{success:!0,data:{items:rows,meta:{totalItems:rows.length}}}};import{eq as eq26}from"drizzle-orm";var handleDeleteCohort=(db,usersTable,cohortsTable,logger2)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};return await db.update(usersTable).set({cohortId:null,updatedAt:new Date}).where(eq26(col7(usersTable,"cohortId"),ctx.params.id)),await db.delete(cohortsTable).where(eq26(col7(cohortsTable,"id"),ctx.params.id)),logger2.audit({entityName:"user_cohorts",entityId:ctx.params.id,operation:"COHORT_DELETE",userId,summary:`Deleted cohort ${ctx.params.id}`}),{success:!0,data:null}};import{eq as eq27}from"drizzle-orm";var handleUpdateCohort=(db,usersTable,cohortsTable)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let updates={updatedAt:new Date};if(ctx.body.name!==void 0)updates.name=ctx.body.name;if(ctx.body.description!==void 0)updates.description=ctx.body.description;if(ctx.body.expiresAt!==void 0)updates.expiresAt=ctx.body.expiresAt?new Date(ctx.body.expiresAt):null;if(ctx.body.metadata!==void 0)updates.metadata=ctx.body.metadata;if(ctx.body.isActive!==void 0)updates.isActive=ctx.body.isActive;let updated=await db.update(cohortsTable).set(updates).where(eq27(col7(cohortsTable,"id"),ctx.params.id)).returning();if(updated.length===0)return ctx.set.status=404,{success:!1,message:"Cohort not found"};return{success:!0,data:updated[0]}};import{t}from"elysia";var CohortBodySchema=t.Object({name:t.String({minLength:1,maxLength:255}),description:t.Optional(t.String()),expiresAt:t.Optional(t.String()),metadata:t.Optional(t.Record(t.String(),t.Unknown()))}),CohortUpdateSchema=t.Object({name:t.Optional(t.String({minLength:1,maxLength:255})),description:t.Optional(t.String()),expiresAt:t.Optional(t.Union([t.String(),t.Null()])),metadata:t.Optional(t.Record(t.String(),t.Unknown())),isActive:t.Optional(t.Boolean())}),BulkCreateUserItem=t.Object({email:t.String({format:"email"}),password:t.String({minLength:8}),profile:t.Optional(t.Object({firstName:t.Optional(t.String()),lastName:t.Optional(t.String())}))}),BulkCreateSchema=t.Object({users:t.Optional(t.Array(BulkCreateUserItem)),emailPrefix:t.Optional(t.String()),emailDomain:t.Optional(t.String()),sharedPassword:t.Optional(t.String({minLength:8})),count:t.Optional(t.Integer({minimum:1,maximum:500})),roleIds:t.Optional(t.Array(t.String()))});function createCohortRoutes(config){let{db,logger:logger2,cohortsTable,usersTable,userRolesTable,profilesTable,basePath}=config,routes=new Elysia2;if(!db||!cohortsTable||!usersTable)return logger2.warn("[Cohort] Skipped \u2014 missing tables",{hasDb:!!db,hasCohortsTable:!!cohortsTable,hasUsersTable:!!usersTable}),routes;return routes.get(basePath,handleListCohorts(db,usersTable,cohortsTable)),routes.get(`${basePath}/:id`,handleGetCohort(db,usersTable,cohortsTable)),routes.post(basePath,handleCreateCohort(db,usersTable,cohortsTable,logger2),{body:CohortBodySchema}),routes.patch(`${basePath}/:id`,handleUpdateCohort(db,usersTable,cohortsTable),{body:CohortUpdateSchema}),routes.delete(`${basePath}/:id`,handleDeleteCohort(db,usersTable,cohortsTable,logger2)),routes.post(`${basePath}/:id/bulk-create`,handleBulkCreateUsers(config,logger2),{body:BulkCreateSchema}),routes.post(`${basePath}/:id/deactivate-users`,handleDeactivateUsers(db,usersTable,logger2)),routes.post(`${basePath}/:id/activate-users`,handleActivateUsers(db,usersTable,logger2)),routes.post(`${basePath}/:id/delete-users`,handleDeleteUsers(db,usersTable,cohortsTable,userRolesTable,profilesTable,logger2)),routes.get(`${basePath}/:id/export`,handleExportUsers(db,usersTable)),routes}init_deviceInfo();import{Elysia as Elysia6}from"elysia";import{Elysia as Elysia3,t as t2}from"elysia";init_Authorization();function getUserId(request){return request.headers.get("x-user-id")}function isChatModerator(request,moderatorRoles){let roles=decodeHeaderList(request.headers.get("x-user-roles"));if(roles.includes(GODMIN_ROLE_NAME))return!0;if(moderatorRoles.length===0)return!1;let allowed=new Set(moderatorRoles.map((role)=>role.toLowerCase()));return roles.some((role)=>allowed.has(role.toLowerCase()))}function chatErrorStatus(code){switch(code){case"forbidden":return 403;case"not_found":return 404;case"validation":return 400;case"conflict":return 409;case"disabled":return 409;default:return 400}}async function guard(set,logger2,fn){try{return{success:!0,data:await fn()}}catch(err){if(err instanceof ChatError)return set.status=chatErrorStatus(err.code),{success:!1,message:err.message};return logger2.error("[Chat] Unhandled route error",{error:err instanceof Error?err.message:String(err)}),set.status=500,{success:!1,message:"Internal server error"}}}function unauthorized(set){return set.status=401,{success:!1,message:"Authentication required"}}function createConversationRoutes(deps){let{logger:logger2,getService}=deps,app=new Elysia3({prefix:deps.config.basePath});return app.get("/conversations",({request,query,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request);return guard(set,logger2,()=>service.listConversations(userId,{limit:query.limit?Number(query.limit):void 0,offset:query.offset?Number(query.offset):void 0}))},{query:t2.Object({limit:t2.Optional(t2.String()),offset:t2.Optional(t2.String())}),detail:{tags:["Chat"],summary:"List my conversations"}}),app.post("/conversations",({request,body,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request),input=body;return guard(set,logger2,()=>{if(input.type==="group"){if(!input.title)throw new ChatError("validation","A group title is required");return service.createGroup({creatorId:userId,title:input.title,description:input.description,avatarFileId:input.avatarFileId,participantIds:input.participantIds??[]})}if(!input.targetUserId)throw new ChatError("validation","targetUserId is required for a direct conversation");return service.getOrCreateDirect(userId,input.targetUserId)})},{body:t2.Object({type:t2.Optional(t2.Union([t2.Literal("direct"),t2.Literal("group")])),targetUserId:t2.Optional(t2.String()),title:t2.Optional(t2.String()),description:t2.Optional(t2.String()),avatarFileId:t2.Optional(t2.String()),participantIds:t2.Optional(t2.Array(t2.String()))}),detail:{tags:["Chat"],summary:"Create or open a conversation"}}),app.get("/conversations/:id",({request,params,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request);return guard(set,logger2,()=>service.getConversation(params.id,userId))},{params:t2.Object({id:t2.String()}),detail:{tags:["Chat"],summary:"Get a conversation"}}),app.post("/conversations/:id/participants",({request,params,body,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request),{userIds}=body;return guard(set,logger2,()=>service.addParticipants(params.id,userId,userIds))},{params:t2.Object({id:t2.String()}),body:t2.Object({userIds:t2.Array(t2.String())}),detail:{tags:["Chat"],summary:"Add group participants"}}),app.delete("/conversations/:id/participants/:userId",({request,params,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request);return guard(set,logger2,async()=>{return await service.removeParticipant(params.id,userId,params.userId),{removed:params.userId}})},{params:t2.Object({id:t2.String(),userId:t2.String()}),detail:{tags:["Chat"],summary:"Remove a participant / leave group"}}),app}import{Elysia as Elysia5,t as t4}from"elysia";init_storage2();var CONTENT_TYPE_SCHEMA=t4.Optional(t4.Union([t4.Literal("text"),t4.Literal("image"),t4.Literal("file"),t4.Literal("video"),t4.Literal("audio")]));function createMessageRoutes(deps){let{logger:logger2,getService}=deps,app=new Elysia5({prefix:deps.config.basePath});return app.get("/conversations/:id/messages",({request,params,query,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request);return guard(set,logger2,()=>service.getMessages({conversationId:params.id,userId,limit:query.limit?Number(query.limit):void 0,before:query.before||void 0}))},{params:t4.Object({id:t4.String()}),query:t4.Object({limit:t4.Optional(t4.String()),before:t4.Optional(t4.String())}),detail:{tags:["Chat"],summary:"Get conversation messages (newest first)"}}),app.post("/conversations/:id/messages",({request,params,body,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request),input=body;return guard(set,logger2,()=>service.sendMessage({conversationId:params.id,senderId:userId,content:input.content,contentType:input.contentType,replyToId:input.replyToId,clientMessageId:input.clientMessageId,attachments:input.attachmentIds?.map((fileId)=>({fileId})),metadata:input.metadata}))},{params:t4.Object({id:t4.String()}),body:t4.Object({content:t4.Optional(t4.String()),contentType:CONTENT_TYPE_SCHEMA,replyToId:t4.Optional(t4.String()),clientMessageId:t4.Optional(t4.String()),attachmentIds:t4.Optional(t4.Array(t4.String())),metadata:t4.Optional(t4.Any())}),detail:{tags:["Chat"],summary:"Send a message (link pre-uploaded attachments)"}}),app.post("/conversations/:id/messages/upload",({request,params,body,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);if(!deps.attachmentsAvailable)return set.status=409,{success:!1,message:"File attachments require storage and CDN to be enabled"};let{service,schemaTables}=getService(request);return guard(set,logger2,async()=>{let filesTable=resolveSchemaTable(schemaTables,"files",logger2);if(!filesTable)throw new ChatError("disabled","Files table unavailable in this schema");let{data,files}=parseFormDataBody(body,deps.storageConfig);if(files.length===0)throw new ChatError("validation","At least one file is required");let{records,failed}=await persistUploadedFiles({db:deps.db,filesTable,files,storageConfig:deps.storageConfig,subFolder:"chat",userId});if(records.length===0)throw new ChatError("validation",failed[0]?.error??"File upload failed");if(deps.cdnMedia)scheduleUploadMediaProcessing(records,{storagePath:deps.storageConfig.basePath,media:deps.cdnMedia,logger:logger2});let content=typeof data.content==="string"?data.content:void 0,replyToId=typeof data.replyToId==="string"?data.replyToId:void 0,clientMessageId=typeof data.clientMessageId==="string"?data.clientMessageId:void 0;return service.sendMessage({conversationId:params.id,senderId:userId,content,replyToId,clientMessageId,attachments:records.map((record)=>({fileId:record.id,originalName:record.originalName,mimeType:record.mimeType,size:record.size}))})})},{type:"formdata",params:t4.Object({id:t4.String()}),body:t4.Object({[deps.storageConfig.formData.dataField]:t4.Optional(t4.Union([t4.String(),t4.Any()])),[deps.storageConfig.formData.filesField]:t4.Optional(t4.Union([t4.File(),t4.Array(t4.File())]))}),detail:{tags:["Chat"],summary:"Send a message with uploaded file attachments"}}),app.post("/conversations/:id/read",({request,params,body,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request),{messageId}=body;return guard(set,logger2,()=>service.markRead(params.id,userId,messageId))},{params:t4.Object({id:t4.String()}),body:t4.Object({messageId:t4.Optional(t4.String())}),detail:{tags:["Chat"],summary:"Mark a conversation as read"}}),app.post("/conversations/:id/typing",({request,params,body,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request),{isTyping}=body;return guard(set,logger2,async()=>{return await service.setTyping(params.id,userId,isTyping??!0),{ok:!0}})},{params:t4.Object({id:t4.String()}),body:t4.Object({isTyping:t4.Optional(t4.Boolean())}),detail:{tags:["Chat"],summary:"Broadcast a typing indicator"}}),app.patch("/messages/:id",({request,params,body,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request),{content}=body;return guard(set,logger2,()=>service.editMessage(params.id,userId,content))},{params:t4.Object({id:t4.String()}),body:t4.Object({content:t4.String()}),detail:{tags:["Chat"],summary:"Edit a message"}}),app.delete("/messages/:id",({request,params,set})=>{let userId=getUserId(request);if(!userId)return unauthorized(set);let{service}=getService(request),asModerator=isChatModerator(request,deps.config.moderatorRoles??[]);return guard(set,logger2,async()=>{if(await service.deleteMessage(params.id,userId,asModerator),asModerator)logger2.warn("[CHAT] Message deleted by a moderator",{actorId:userId,messageId:params.id});return{deleted:params.id}})},{params:t4.Object({id:t4.String()}),detail:{tags:["Chat"],summary:"Delete a message",description:"Sender-only, unless the caller is godmin or holds a role from chat.moderatorRoles \u2014 then any message, and `deletionEnabled` does not apply."}}),app}function createChatRoutes(routeConfig){let{db,schemaTables,config,logger:logger2,broadcaster,tenantRegistry,storageConfig,attachmentsAvailable,cdnBasePath,cdnMedia}=routeConfig,buildService=(tables)=>new ChatService({db,schemaTables:tables,config,logger:logger2,broadcaster,cdnBasePath,attachmentsAvailable}),defaultService=buildService(schemaTables),deps={db,config,logger:logger2,storageConfig,attachmentsAvailable,getService:(request)=>{if(!tenantRegistry)return{service:defaultService,schemaTables};let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return{service:defaultService,schemaTables};let ctx=tenantRegistry.getSchemaContext(schemaName);if(!ctx)return{service:defaultService,schemaTables};return{service:buildService(ctx.schemaTables),schemaTables:ctx.schemaTables}},cdnMedia},routes=new Elysia6;return routes.use(createConversationRoutes(deps)),routes.use(createMessageRoutes(deps)),logger2.info(`[Chat] Routes registered at ${config.basePath}`,{attachmentsAvailable,groupsEnabled:config.groupsEnabled}),{routes,chatService:defaultService}}init_Authorization();init_helpers3();import Elysia7,{t as t5}from"elysia";function createConfigRoutes(routeConfig){let{logger:logger2,resolvedOptions,configFilePath,basePath,onConfigUpdate,db,schemaTables,getRedis}=routeConfig,plugin=new Elysia7({prefix:basePath});return plugin.onBeforeHandle(async({request,set})=>{if(decodeHeaderList(request.headers.get("x-user-roles")).includes(GODMIN_ROLE_NAME))return;let userId=request.headers.get("x-user-id");if(userId&&db&&schemaTables.users)try{let{eq:eq28}=await import("drizzle-orm"),usersTable=schemaTables.users;if((await db.select().from(usersTable).where(eq28(usersTable.id,userId)).limit(1))[0]?.isGod===!0)return}catch(err){logger2.warn("[ConfigManagement] Failed to check isGod from DB",{error:err instanceof Error?err.message:String(err)})}return set.status=403,Response.json({isSuccess:!1,message:"Only godmin users can access config management",data:null})}),plugin.get("/",()=>{let configCopy=JSON.parse(JSON.stringify(resolvedOptions));return{isSuccess:!0,message:"Current running configuration",data:{config:maskSensitiveFields(configCopy),configFilePath,sections:buildSectionsMeta(resolvedOptions)}}},{detail:{tags:["Config Management"],summary:"Get full running configuration",description:"Returns the full running configuration with sensitive fields masked."}}),plugin.get("/sections",()=>({isSuccess:!0,message:"Config section metadata",data:{sections:buildSectionsMeta(resolvedOptions)}}),{detail:{tags:["Config Management"],summary:"List all config sections with metadata",description:"Section list is derived at runtime from the config object \u2014 no hardcoding."}}),plugin.get("/env",()=>{let configCopy=JSON.parse(JSON.stringify(resolvedOptions)),declared=scanEnvVars(configCopy),declaredNames=new Set(declared.map((e)=>e.envName)),undeclared=scanUndeclaredEnv(declaredNames),entries=[...declared,...undeclared],resolvedCount=entries.filter((e)=>e.resolved).length,missingCount=entries.length-resolvedCount,unrecognizedCount=undeclared.length;return{isSuccess:!0,message:`Found ${entries.length} env vars (${resolvedCount} resolved, ${missingCount} missing, ${unrecognizedCount} unrecognized)`,data:{entries,totalCount:entries.length,resolvedCount,missingCount,unrecognizedCount}}},{detail:{tags:["Config Management"],summary:"Get environment variables (declared + undeclared)",description:"Surfaces both (a) DECLARED env vars \u2014 UPPER_SNAKE_CASE references found in the config tree (recognized:true) \u2014 and (b) UNDECLARED env vars actually present in process.env but not declared (recognized:false), minus Kubernetes service-link and OS/interpreter infra noise, so orphaned / junk config surfaces for cleanup. Sensitive values are masked; AI-provider *_API_KEY vars are never surfaced."}}),plugin.get("/overrides",async()=>{let redis=getRedis();if(!redis)return{isSuccess:!0,message:"Redis not available \u2014 no overrides stored",data:{overrides:null,sectionCount:0,sections:[]}};let overrides=await readOverridesFromRedis(redis,resolvedOptions.appId||"nucleus"),sections=overrides?Object.keys(overrides).filter((k)=>{let v=overrides[k];return v!==void 0&&v!==null&&(typeof v!=="object"||Object.keys(v).length>0)}):[];return{isSuccess:!0,message:sections.length>0?`${sections.length} section override(s) stored in Redis`:"No overrides in Redis",data:{overrides:overrides?maskSensitiveFields(overrides):null,sectionCount:sections.length,sections}}},{detail:{tags:["Config Management"],summary:"View Redis config overrides",description:"Returns overrides stored in Redis that will be merged on next restart."}}),plugin.delete("/overrides",async()=>{let redis=getRedis();if(!redis)return{isSuccess:!1,message:"Redis not available",data:null};let existing=await readOverridesFromRedis(redis,resolvedOptions.appId||"nucleus"),clearedSections=existing?Object.keys(existing):[];return await clearOverridesFromRedis(redis,resolvedOptions.appId||"nucleus"),logger2.info("[ConfigManagement] Redis overrides cleared",{clearedSections}),{isSuccess:!0,message:clearedSections.length>0?`Cleared ${clearedSections.length} override(s) from Redis. Restart to revert to config.json defaults.`:"No overrides to clear.",data:{message:"Redis overrides cleared",clearedSections}}},{detail:{tags:["Config Management"],summary:"Clear all Redis config overrides",description:"Removes all stored overrides from Redis. Restart to revert to config.json defaults."}}),plugin.get("/:section",({params})=>{let{section}=params;if(!hasSection(resolvedOptions,section))return{isSuccess:!1,message:`Unknown config section: ${section}`,data:null};let sectionValue=extractSection(resolvedOptions,section),restart=isRestartRequired(section),maskedValue=sectionValue;if(typeof sectionValue==="object"&§ionValue!==null&&!Array.isArray(sectionValue))maskedValue=maskSensitiveFields({[section]:sectionValue})[section];return{isSuccess:!0,message:`Config section: ${section}`,data:{section,config:maskedValue??null,restartRequired:restart}}},{params:t5.Object({section:t5.String()}),detail:{tags:["Config Management"],summary:"Get a specific config section",description:"Returns a specific config section with sensitive fields masked."}}),plugin.patch("/:section",async({params,body})=>{let{section}=params,payload=body;if(!hasSection(resolvedOptions,section))return{isSuccess:!1,message:`Unknown config section: ${section}`,data:null};if(!payload.config||typeof payload.config!=="object")return{isSuccess:!1,message:'Request body must contain a "config" object',data:null};let restart=isRestartRequired(section),currentValue=extractSection(resolvedOptions,section),newValue;if(Array.isArray(currentValue))newValue=payload.config.value!==void 0?payload.config.value:payload.config;else if(typeof currentValue==="object"&¤tValue!==null)newValue=deepMerge(currentValue,payload.config);else if(currentValue===void 0||typeof currentValue==="string"||typeof currentValue==="number"||typeof currentValue==="boolean")newValue=payload.config.value!==void 0?payload.config.value:payload.config;else newValue=payload.config;if(applySectionUpdate(resolvedOptions,section,newValue),!restart)onConfigUpdate(section,newValue);let persistMethod="memory-only",persistWarning=null;if(configFilePath)try{await persistConfigToDisk(configFilePath,resolvedOptions),persistMethod="disk",logger2.info(`[ConfigManagement] Section "${section}" persisted to disk`)}catch(diskErr){let diskMsg=diskErr instanceof Error?diskErr.message:String(diskErr);logger2.warn(`[ConfigManagement] Disk persist failed: ${diskMsg}`);let redis=getRedis();if(redis)try{await persistSectionToRedis(redis,resolvedOptions.appId||"nucleus",section,newValue),persistMethod="redis",logger2.info(`[ConfigManagement] Section "${section}" persisted to Redis (disk unavailable)`)}catch(redisErr){let redisMsg=redisErr instanceof Error?redisErr.message:String(redisErr);logger2.error(`[ConfigManagement] Redis persist also failed: ${redisMsg}`),persistWarning=`Disk: ${diskMsg}. Redis: ${redisMsg}. Changes are in-memory only and will be lost on restart.`}else persistWarning=`Disk: ${diskMsg}. Redis not available. Changes are in-memory only and will be lost on restart.`}logger2.info(`[ConfigManagement] Section "${section}" updated (persist: ${persistMethod}, restart-required: ${restart})`);let updatedValue=extractSection(resolvedOptions,section),maskedUpdated=updatedValue;if(typeof updatedValue==="object"&&updatedValue!==null&&!Array.isArray(updatedValue))maskedUpdated=maskSensitiveFields({[section]:updatedValue})[section];let baseMsg=restart?`Config section "${section}" saved (${persistMethod}). Restart required for changes to take effect.`:`Config section "${section}" updated and applied (${persistMethod}).`;return{isSuccess:!0,message:persistWarning?`${baseMsg} Warning: ${persistWarning}`:baseMsg,data:{section,applied:!restart,restartRequired:restart,config:maskedUpdated}}},{params:t5.Object({section:t5.String()}),body:t5.Object({config:t5.Record(t5.String(),t5.Unknown())}),detail:{tags:["Config Management"],summary:"Update a config section",description:"Deep-merges the payload into the section. Hot-reloadable sections apply immediately. Restart-required sections are saved to disk only."}}),plugin.post("/restart",({body})=>{let delayMs=body.delayMs??1000;logger2.info(`[ConfigManagement] Restart scheduled in ${delayMs}ms`);let scheduledAt=new Date().toISOString();return setTimeout(()=>{logger2.info("[ConfigManagement] Restarting process..."),process.exit(0)},delayMs),{isSuccess:!0,message:`Server restart scheduled in ${delayMs}ms`,data:{message:`Process will exit in ${delayMs}ms. Orchestrator (K8s/PM2/systemd) will restart it.`,scheduledAt}}},{body:t5.Optional(t5.Object({delayMs:t5.Optional(t5.Number())})),detail:{tags:["Config Management"],summary:"Restart the server process",description:"Triggers a graceful process exit after a configurable delay. The orchestrator (K8s, PM2, systemd) is expected to restart the process automatically."}}),logger2.info(`[ConfigManagement] Routes enabled at ${basePath}`),plugin}init_adminGuard();import{Elysia as Elysia8,t as t6}from"elysia";var requireAuth=(ctx)=>ctx.request.headers.get("x-user-id"),fail=(ctx,status,message)=>{return ctx.set.status=status,{success:!1,message,data:null}},callerSchemaOf=(ctx)=>ctx.request.headers.get("x-tenant-schema"),isGodmin=(ctx)=>hasGodminRole(ctx.request),ownsHostname=(ctx,record)=>{if(isGodmin(ctx))return!0;let caller=callerSchemaOf(ctx);return record.schemaName!=null&&record.schemaName===caller};function createHostnameRoutes(config){let base=config.basePath,app=new Elysia8,svc=(ctx)=>{let s=config.getDomainService();if(!s)return ctx.set.status=503,null;return s};app.post(`${base}/hostnames`,async(ctx)=>{let service=svc(ctx);if(!service)return{success:!1,message:"Domains not available",data:null};if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let body=ctx.body,callerSchema=callerSchemaOf(ctx),schemaName=body.schemaName??null,tenantId=body.tenantId??null;if(!hasGodminRole(ctx.request)){if(schemaName&&schemaName!==callerSchema)return fail(ctx,403,"Cannot bind a hostname to another tenant's schema");schemaName=callerSchema;let ownTenantId=config.resolveTenantIdForSchema?.(callerSchema)??null;if(tenantId&&tenantId!==ownTenantId)return fail(ctx,403,"Cannot bind a hostname to another tenant");tenantId=ownTenantId}try{let result=await service.createHostname({hostname:body.hostname,ownerId:body.ownerId,ownerType:body.ownerType,tenantId,schemaName,isPrimary:body.isPrimary});return{success:!0,message:"Custom hostname created",data:{hostname:result.hostname,instructions:result.instructions}}}catch(err){return fail(ctx,400,err instanceof Error?err.message:"Failed to create hostname")}},{body:t6.Object({hostname:t6.String(),ownerId:t6.String(),ownerType:t6.Optional(t6.Union([t6.Literal("tenant"),t6.Literal("organization"),t6.Literal("user"),t6.Literal("external")])),tenantId:t6.Optional(t6.String()),schemaName:t6.Optional(t6.String()),isPrimary:t6.Optional(t6.Boolean())}),detail:{tags:["Domains"],summary:"Create custom hostname"}}),app.get(`${base}/hostnames`,async(ctx)=>{let service=svc(ctx);if(!service)return{success:!1,message:"Domains not available",data:null};if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let url=new URL(ctx.request.url),items;if(isGodmin(ctx))items=await service.listHostnames({ownerId:url.searchParams.get("ownerId")??void 0,tenantId:url.searchParams.get("tenantId")??void 0});else{let ownTenantId=config.resolveTenantIdForSchema?.(callerSchemaOf(ctx))??null;items=ownTenantId?await service.listHostnames({tenantId:ownTenantId}):[]}return{success:!0,message:`Found ${items.length} hostnames`,data:{items}}}),app.get(`${base}/hostnames/:id`,async(ctx)=>{let service=svc(ctx);if(!service)return{success:!1,message:"Domains not available",data:null};if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let record=await service.getHostname(ctx.params.id);if(!record||!ownsHostname(ctx,record))return fail(ctx,404,"Hostname not found");return{success:!0,message:"Hostname found",data:record}}),app.get(`${base}/hostnames/:id/instructions`,async(ctx)=>{let service=svc(ctx);if(!service)return{success:!1,message:"Domains not available",data:null};if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let id=ctx.params.id,record=await service.getHostname(id);if(!record||!ownsHostname(ctx,record))return fail(ctx,404,"Hostname not found");return{success:!0,message:"DNS instructions",data:{instructions:await service.getInstructions(id)}}});let lifecycle=(suffix,run,summary)=>{app.post(`${base}/hostnames/:id/${suffix}`,async(ctx)=>{let service=svc(ctx);if(!service)return{success:!1,message:"Domains not available",data:null};if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let id=ctx.params.id,record=await service.getHostname(id);if(!record||!ownsHostname(ctx,record))return fail(ctx,404,"Hostname not found");try{let data=await run(service,id);if(!data)return fail(ctx,404,"Hostname not found");return{success:!0,message:summary,data}}catch(err){return fail(ctx,400,err instanceof Error?err.message:summary)}})};return lifecycle("verify",(s,id)=>s.verifyHostname(id),"Verification processed"),lifecycle("activate",(s,id)=>s.activateHostname(id),"Hostname activated"),lifecycle("disable",(s,id)=>s.disableHostname(id),"Hostname disabled"),lifecycle("make-primary",(s,id)=>s.makePrimary(id),"Primary hostname set"),lifecycle("refresh-status",(s,id)=>s.refreshStatus(id),"Status refreshed"),app}init_adminGuard();import{Elysia as Elysia9,t as t7}from"elysia";var requireAuth2=(ctx)=>ctx.request.headers.get("x-user-id"),fail2=(ctx,status,message)=>{return ctx.set.status=status,{success:!1,message,data:null}},callerSchemaOf2=(ctx)=>ctx.request.headers.get("x-tenant-schema"),isGodmin2=(ctx)=>hasGodminRole(ctx.request);function createRegistrationRoutes(config){let base=config.basePath,app=new Elysia9,svc=(ctx)=>{let s=config.getRegistrationService();if(!s)return ctx.set.status=503,null;return s},ownTenantId=(ctx)=>config.resolveTenantIdForSchema?.(callerSchemaOf2(ctx))??null,ownsRegistration=(ctx,record)=>{if(isGodmin2(ctx))return!0;let own=ownTenantId(ctx);return record.tenantId!=null&&own!=null&&record.tenantId===own};return app.post(`${base}/registrations`,async(ctx)=>{let service=svc(ctx);if(!service)return{success:!1,message:"Domains not available",data:null};if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let body=ctx.body,tenantId=body.tenantId??null,registrantOwnerType=body.registrantOwnerType;if(!isGodmin2(ctx)){let own=ownTenantId(ctx);if(tenantId&&tenantId!==own)return fail2(ctx,403,"Cannot register a domain for another tenant");if(tenantId=own,registrantOwnerType==="platform")return fail2(ctx,403,"Only a platform admin may register as 'platform'");registrantOwnerType="customer"}try{return{success:!0,message:"Registration requested",data:await service.createRequest({requestedDomain:body.requestedDomain,ownerId:body.ownerId,ownerType:body.ownerType,tenantId,registrantOwnerType,termsVersion:body.termsVersion,termsAccepted:body.termsAccepted,autoRenew:body.autoRenew})}}catch(err){return fail2(ctx,400,err instanceof Error?err.message:"Failed to request registration")}},{body:t7.Object({requestedDomain:t7.String(),ownerId:t7.String(),ownerType:t7.Optional(t7.Union([t7.Literal("tenant"),t7.Literal("organization"),t7.Literal("user"),t7.Literal("external")])),tenantId:t7.Optional(t7.String()),registrantOwnerType:t7.Optional(t7.Union([t7.Literal("customer"),t7.Literal("platform")])),termsVersion:t7.String(),termsAccepted:t7.Boolean(),autoRenew:t7.Optional(t7.Boolean())}),detail:{tags:["Domains"],summary:"Request managed domain registration"}}),app.get(`${base}/registrations`,async(ctx)=>{let service=svc(ctx);if(!service)return{success:!1,message:"Domains not available",data:null};if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let url=new URL(ctx.request.url),items;if(isGodmin2(ctx))items=await service.listRegistrations({ownerId:url.searchParams.get("ownerId")??void 0,tenantId:url.searchParams.get("tenantId")??void 0});else{let own=ownTenantId(ctx);items=own?await service.listRegistrations({tenantId:own}):[]}return{success:!0,message:`Found ${items.length} registrations`,data:{items}}}),app.get(`${base}/registrations/:id`,async(ctx)=>{let service=svc(ctx);if(!service)return{success:!1,message:"Domains not available",data:null};if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let record=await service.getRegistration(ctx.params.id);if(!record||!ownsRegistration(ctx,record))return fail2(ctx,404,"Registration not found");return{success:!0,message:"Registration found",data:record}}),app.post(`${base}/registrations/:id/cancel`,async(ctx)=>{let service=svc(ctx);if(!service)return{success:!1,message:"Domains not available",data:null};if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let id=ctx.params.id,record=await service.getRegistration(id);if(!record||!ownsRegistration(ctx,record))return fail2(ctx,404,"Registration not found");let cancelled=await service.cancel(id);if(!cancelled)return fail2(ctx,404,"Registration not found");return{success:!0,message:"Registration cancelled",data:cancelled}}),app.post(`${base}/registrations/:id/transfer-out`,async(ctx)=>{let service=svc(ctx);if(!service)return{success:!1,message:"Domains not available",data:null};if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let id=ctx.params.id,record=await service.getRegistration(id);if(!record||!ownsRegistration(ctx,record))return fail2(ctx,404,"Registration not found");let out=await service.requestTransferOut(id);if(!out)return fail2(ctx,404,"Registration not found");return{success:!0,message:"Transfer-out requested",data:out}}),app}import{Elysia as Elysia10}from"elysia";function createResolveRoute(config){let base=config.basePath,app=new Elysia10;return app.get(`${base}/resolve`,async(ctx)=>{let service=config.getDomainService();if(!service)return ctx.set.status=503,{success:!1,message:"Domains not available",data:null};let hostname=new URL(ctx.request.url).searchParams.get("hostname")||ctx.request.headers.get("host")||"";if(!hostname)return ctx.set.status=400,{success:!1,message:"hostname is required",data:null};let match=await service.resolveHostname(hostname);if(!match)return ctx.set.status=404,{success:!1,message:"No active hostname matched",data:null};return{success:!0,message:"Resolved",data:match}},{detail:{tags:["Domains"],summary:"Resolve hostname to tenant",description:"Public."}}),app}function createDomainRoutes(app,config){return app.use(createResolveRoute(config)),app.use(createHostnameRoutes(config)),app.use(createRegistrationRoutes(config)),app}import{and as and13,arrayContains,arrayOverlaps,asc,createTableRelationsHelpers,desc as desc5,eq as eq28,getTableName,gt,gte,ilike,inArray as inArray8,isNotNull,isNull as isNull2,like,lt as lt2,lte,ne as ne2,notInArray,or as or2,sql as sql7}from"drizzle-orm";import{drizzle}from"drizzle-orm/node-postgres";import{Elysia as Elysia11,t as t9}from"elysia";init_Authorization();init_utils5();init_adminGuard();init_sensitiveColumns();var snakeToCamel2=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase()),SENSITIVE_SYSTEM_TABLE_SNAKE=["domain_hostnames","domain_registrations","domain_provider_records","domain_verification_challenges","domain_events","payment_sub_merchants","payment_invoices","payment_subscriptions","payment_customers","payment_transactions","payment_methods","payment_products","payment_prices","payment_commission_splits","payment_splits","payment_reserves","payment_ledger_entries","payment_payout_requests","payment_settlement_policies","payment_disputes","payment_webhook_logs","verifications","api_keys","magic_link_tokens","user_sessions","trusted_devices","integration_sources","integration_endpoints","integration_mappings"],SENSITIVE_SYSTEM_TABLES=new Set(SENSITIVE_SYSTEM_TABLE_SNAKE.flatMap((name2)=>[name2,snakeToCamel2(name2)]));function isSensitiveSystemTable(tableName){return SENSITIVE_SYSTEM_TABLES.has(tableName)}function delegatedWriteRoles(question){if(question.isRbacTable)return new Set;let roles=(question.featureSet??[]).flatMap((feature)=>question.featureAdminRoles?.[feature]??[]);return new Set(roles.map((role)=>role.toLowerCase()))}function isFeatureAdmin(question){let allowed=delegatedWriteRoles(question);if(allowed.size===0)return!1;return question.callerRoles.some((role)=>allowed.has(role.toLowerCase()))}init_storage2();function isAnonymousCreate(table,payload){let rule=table.anonymous_create;if(!rule)return!1;if(rule===!0)return!0;let column=rule.column;if(!column)return!1;let value=payload[column]??payload[toCamelCase3(column)]??payload[toSnakeCase2(column)];return isTruthy(value)}function isTruthy(value){if(typeof value==="boolean")return value;if(typeof value==="number")return value!==0;if(typeof value==="string"){let normalized=value.trim().toLowerCase();return normalized==="true"||normalized==="1"||normalized==="yes"||normalized==="on"}return!1}function createAuditIdentity(anonymous,userId,headers){if(anonymous)return{userId:void 0,ipAddress:"anonymous",userAgent:"anonymous"};return{userId:userId||void 0,ipAddress:headers.get("x-forwarded-for")?.split(",")[0]?.trim()||headers.get("x-real-ip")?.trim()||"unknown",userAgent:headers.get("user-agent")||"unknown"}}function toCamelCase3(value){return value.replace(/_([a-z0-9])/g,(_m,c)=>c.toUpperCase())}function toSnakeCase2(value){return value.replace(/[A-Z]/g,(c)=>`_${c.toLowerCase()}`)}var snakeToCamel3=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase());function forceCreateScope(payload,authResult,resolveColumn){let scopeFilters=authResult?.scopeFilters;if(!scopeFilters)return{ok:!0};for(let[field,value]of Object.entries(scopeFilters)){if(!resolveColumn(field))return{ok:!1,field};payload[snakeToCamel3(field)]=value}return{ok:!0}}function decideRelationExpansion(params){let{authEnabled,targetTable,relAuth,isTargetPublicGet}=params;if(!authEnabled||!targetTable)return{expand:!0};if(relAuth===null)return isTargetPublicGet?{expand:!0}:{expand:!1};if(!relAuth.authorized)return{expand:!1};let scope=relAuth.scopeFilters;if(scope&&Object.keys(scope).length>0)return{expand:!0,scope};return{expand:!0}}function resolveRelationScopeColumn(fields,key2,toCamel5){if(Object.hasOwn(fields,key2))return fields[key2];let camel=toCamel5(key2);return Object.hasOwn(fields,camel)?fields[camel]:void 0}import{t as t8}from"elysia";function buildBodySchemaFromEntityColumns(entityColumns){let properties={};if(!entityColumns||entityColumns.length===0)return t8.Object({},{additionalProperties:!0});for(let col8 of entityColumns)switch(col8.type?.toLowerCase()||"string"){case"integer":case"int":case"serial":case"bigserial":case"numeric":case"decimal":properties[col8.name]=col8.notNull?t8.Number():t8.Optional(t8.Number());break;case"boolean":properties[col8.name]=col8.notNull?t8.Boolean():t8.Optional(t8.Boolean());break;case"timestamp":case"timestamptz":case"date":properties[col8.name]=col8.notNull?t8.String({format:"date-time"}):t8.Optional(t8.String({format:"date-time"}));break;case"json":case"jsonb":properties[col8.name]=t8.Optional(t8.Unknown());break;case"uuid":properties[col8.name]=col8.notNull?t8.String({format:"uuid"}):t8.Optional(t8.String({format:"uuid"}));break;default:properties[col8.name]=col8.notNull?t8.String():t8.Optional(t8.String())}return t8.Object(properties,{additionalProperties:!0})}function createBulkUpdateSchema(entityColumns){return t8.Array(t8.Object({id:t8.String(),data:buildBodySchemaFromEntityColumns(entityColumns)}))}var MAX_BULK_ITEMS=500;function collectFilterFields(filter,out=[]){if(filter.operator==="or"||filter.operator==="and"){let group=Array.isArray(filter.value)?filter.value:[];for(let f of group)collectFilterFields(f,out)}else if(filter.field)out.push(String(filter.field));return out}function buildFilterCondition(filter,resolveCol){if(filter.operator==="or"||filter.operator==="and"){let subs=(Array.isArray(filter.value)?filter.value:[]).map((f)=>buildFilterCondition(f,resolveCol)).filter((c)=>c!==null);if(subs.length===0)return null;return(filter.operator==="or"?or2(...subs):and13(...subs))??null}if(!filter.field)return null;let col8=resolveCol(filter.field);if(!col8)return null;let sqlCol=col8,dataType=col8.dataType,coerce2=(v)=>dataType==="date"&&(typeof v==="string"||typeof v==="number")?new Date(v):v;switch(filter.operator){case"eq":return eq28(sqlCol,coerce2(filter.value));case"neq":return ne2(sqlCol,coerce2(filter.value));case"gt":return gt(sqlCol,coerce2(filter.value));case"gte":return gte(sqlCol,coerce2(filter.value));case"lt":return lt2(sqlCol,coerce2(filter.value));case"lte":return lte(sqlCol,coerce2(filter.value));case"like":return like(sqlCol,filter.value);case"ilike":return ilike(sqlCol,filter.value);case"in":return inArray8(sqlCol,filter.value);case"notIn":return notInArray(sqlCol,filter.value);case"arrayOverlaps":return arrayOverlaps(sqlCol,filter.value??[]);case"arrayContains":return arrayContains(sqlCol,filter.value??[]);case"arrayEmpty":return sql7`cardinality(${sqlCol}) = 0`;case"isNull":return isNull2(sqlCol);case"isNotNull":return isNotNull(sqlCol);default:return null}}function createEntityRoutes(app,config){let{db,schemaTables,schemaRelations,entities,logger:logger2,dbPool,storage,cdnMedia,authorization,authMode,idpUrl,featureAdminRoles}=config,getRegistry=()=>config.getTenantRegistry?config.getTenantRegistry():config.tenantRegistry??null,getReqSchema=(request)=>{let tenantRegistry=getRegistry();if(!tenantRegistry)return{tables:schemaTables,relations:schemaRelations};let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return{tables:schemaTables,relations:schemaRelations};let ctx=tenantRegistry.getSchemaContext(schemaName);return ctx?{tables:ctx.schemaTables,relations:ctx.schemaRelations}:{tables:schemaTables,relations:schemaRelations}},storageConfig=mergeStorageConfig(storage),warmUploadedMedia=(uploaded)=>{if(!cdnMedia||!uploaded||uploaded.success.length===0)return;scheduleUploadMediaProcessing(uploaded.success,{storagePath:storageConfig.basePath,media:cdnMedia,logger:logger2})},authEnabled=authorization?.enabled??!1,enforceFieldWrites=authEnabled&&authorization?.enforceFieldWriteClaims!==!1,isConsumerMode=authMode==="consumer",filterWritePayload=(payload,authResult)=>{if(!enforceFieldWrites||!authResult?.allowedFields)return payload;return filterResponseFields(payload,authResult.allowedFields)},auditPathContext=(request)=>{let url=new URL(request.url);return{path:url.pathname,query:url.search}},auditIdentityFor=(table,payload,userId,headers)=>createAuditIdentity(isAnonymousCreate(table,payload),userId,headers),UUID_RE=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,auditEntityId=(id)=>{let s=id==null?"":String(id);return UUID_RE.test(s)?s:null},projectWrittenRow=(row,authResult)=>{let scoped2=authEnabled&&authResult?.allowedFields?filterResponseFields(row,[...authResult.allowedFields,...authResult.allowedRelations??[]]):row;return stripUnclaimedSensitive(redactSensitiveOutput(scoped2),authResult?.allowedFields)};if(!db)return app;function snakeToCamel4(s){return s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase())}let{entities:mergedEntities}=resolveEntities(SYSTEM_TABLES,entities),extendedByName=new Map(mergedEntities.filter((t10)=>entities.some((e)=>e.table_name===t10.table_name&&e.extends===!0)).map((t10)=>[t10.table_name,t10])),effectiveEntities=entities.map((e)=>extendedByName.get(e.table_name)??e),allTables=Object.keys(schemaTables),entityTableNames=effectiveEntities.map((e)=>snakeToCamel4(e.table_name)),systemTableNames=allTables.filter((tbl)=>!entityTableNames.includes(tbl)),systemTableDefMap=new Map(SYSTEM_TABLES.map((t10)=>[snakeToCamel4(t10.table_name),t10])),asMeta=(def)=>def??void 0,hasUnmetRequirement=(def)=>{let requires=def?.requires;if(!requires||requires.length===0)return null;for(let req of requires)if(req==="email"&&!config.isEmailServiceAvailable?.())return"email";return null},deriveGroupName=(def,fallback)=>{if(def?.group_name)return def.group_name;let primaryFeature=def?.feature_set?.[0];if(!primaryFeature)return fallback;return primaryFeature.charAt(0).toUpperCase()+primaryFeature.slice(1)},systemTables2=systemTableNames.filter((name2)=>{let unmet=hasUnmetRequirement(asMeta(systemTableDefMap.get(name2)));if(unmet)return logger2.info(`Skipping ${name2} routes - missing required dependency: ${unmet}`),!1;return!0}).map((name2)=>{let def=systemTableDefMap.get(name2),meta=asMeta(def);return{table_name:def?.table_name??name2,group_name:deriveGroupName(meta,name2),is_form_data:def?.is_form_data===!0,bulk_endpoints_enabled:def?.bulk_endpoints_enabled??!1,excluded_methods:def?.excluded_methods?[...def.excluded_methods]:[],columns:def?.columns?[...def.columns]:void 0}}),allEntities=[...effectiveEntities,...systemTables2],publicGetEntities=new Set;for(let e of allEntities)if(e.is_public?.GET===!0)publicGetEntities.add(e.table_name),publicGetEntities.add(snakeToCamel4(e.table_name));logger2.info(`All entities: ${allEntities.map((e)=>e.table_name).join(", ")}`);for(let table of allEntities){let rawTableName=table.table_name,tableName=snakeToCamel4(rawTableName),swaggerTag=table.group_name||rawTableName,writeFields=enforceFieldWrites?table.columns?.map((c)=>c.name):void 0,defaultDrizzleTable=schemaTables[tableName];if(!defaultDrizzleTable)continue;let tableRelations=schemaRelations[`${tableName}Relations`];logger2.info(`Creating routes for table: ${tableName}`);let relationTargetTable={};{let rels=tableRelations;if(rels?.table&&typeof rels?.config==="function")try{let built=rels.config(createTableRelationsHelpers(rels.table));for(let[relName,rel]of Object.entries(built))if(rel?.referencedTable)relationTargetTable[relName]=getTableName(rel.referencedTable)}catch(err){logger2.warn(`[Authorization] Could not map relations for ${tableName}`,{error:err instanceof Error?err.message:String(err)})}}let defaultTableColumns=defaultDrizzleTable,defaultIdCol=defaultTableColumns.id,database=db,drizzleTable=defaultDrizzleTable,tableColumns=defaultTableColumns,idCol=defaultIdCol,resolveCol=(field)=>{if(Object.hasOwn(tableColumns,field))return tableColumns[field];let camel=snakeToCamel4(field);return Object.hasOwn(tableColumns,camel)?tableColumns[camel]:void 0},getTableForRequest=(request)=>{if(!getRegistry())return{drizzleTable,tableColumns,idCol,resolveCol,schemaTables,schemaRelations};let reqSchema=getReqSchema(request),reqTable=reqSchema.tables[tableName]||drizzleTable,reqCols=reqTable,reqIdCol=reqCols.id;return{drizzleTable:reqTable,tableColumns:reqCols,idCol:reqIdCol,resolveCol:(field)=>{if(Object.hasOwn(reqCols,field))return reqCols[field];let camel=snakeToCamel4(field);return Object.hasOwn(reqCols,camel)?reqCols[camel]:void 0},schemaTables:reqSchema.tables,schemaRelations:reqSchema.relations}},bulkUpdateSchema=createBulkUpdateSchema(table.columns),bulkDeleteSchema=t9.Array(t9.String()),authzLog=logger2.scoped("authorization.check"),performAuthCheck=async(request,method,reqFields,reqRelations)=>{let result=await runAuthCheck(request,method,reqFields,reqRelations);if(result){let userId=request.headers.get("x-user-id"),requestId=request.headers.get("x-request-id")||void 0;if(!result.authorized)authzLog.warn(`Denied ${method} ${table.table_name}`,{requestId,userId,entity:table.table_name,method,reason:result.reason||"no reason provided",mode:isConsumerMode?idpUrl?"idp":"jwt":"local"});else if(result.scopeFilters||result.allowedFields||result.allowedRelations)authzLog.debug(`Allowed ${method} ${table.table_name} (restricted)`,{requestId,userId,entity:table.table_name,method,scopeFilters:result.scopeFilters,allowedFieldCount:result.allowedFields?.length,allowedRelations:result.allowedRelations})}return result},scopeConditionsFor=(authResult,resolveColumn)=>{if(!authEnabled||!authResult?.scopeFilters)return[];let{conditions,unresolvedFields}=buildScopeConditions(authResult.scopeFilters,resolveColumn);if(unresolvedFields.length>0)authzLog.error(`Scope references unknown column(s) on ${table.table_name}; failing closed (0 rows)`,{entity:table.table_name,unresolvedFields});return conditions},runAuthCheck=async(request,method,reqFields,reqRelations,entityName=table.table_name)=>{let userId=request.headers.get("x-user-id");if(!authEnabled||!userId)return null;if(isConsumerMode){let userClaims=decodeHeaderList(request.headers.get("x-user-claims")),userRoles=decodeHeaderList(request.headers.get("x-user-roles")),userClaimScopes=decodeClaimScopesHeader(request.headers.get("x-user-claim-scopes"));if(idpUrl&&authorization?.jwtClaimsMode==="resolve"){let accessToken=request.headers.get("x-access-token")||(request.headers.get("cookie")||"").match(/access_token=([^;]+)/)?.[1]||"";return checkAuthorizationViaIDP({idpUrl,accessToken,method,entity:entityName,requestedFields:reqFields,requestedRelations:reqRelations,logger:logger2})}return checkAuthorizationFromJWT({userClaims,userRoles,method,entity:entityName,requestedFields:reqFields,requestedRelations:reqRelations,logger:logger2,requireClaimSpecificity:authorization?.requireClaimSpecificity,claimScopes:userClaimScopes})}let reqCtx=getTableForRequest(request);return checkAuthorization({userId,method,entity:entityName,requestedFields:reqFields,requestedRelations:reqRelations,db:database,schemaTables:reqCtx.schemaTables,logger:logger2,requireClaimSpecificity:authorization?.requireClaimSpecificity,getUserData:async()=>{if(!database)return;let usersTbl=reqCtx.schemaTables[AUTHORIZATION_TABLE_KEYS.users];if(!usersTbl)return;let idCol2=usersTbl.id;if(!idCol2)return;return(await database.select().from(usersTbl).where(eq28(idCol2,userId)).limit(1))[0]}})},entityRoutes=new Elysia11({prefix:`/${tableName}`}),listLog=logger2.scoped("entity.list"),isRbacTable=tableName===AUTHORIZATION_TABLE_KEYS.roles||tableName===AUTHORIZATION_TABLE_KEYS.claims||tableName===AUTHORIZATION_TABLE_KEYS.userRoles||tableName===AUTHORIZATION_TABLE_KEYS.roleClaims;if(isRbacTable||isSensitiveSystemTable(tableName)){let denyMessage=isRbacTable?"Forbidden: managing roles and claims requires godmin privileges":`Forbidden: writing to '${tableName}' via the generic entity API requires godmin privileges \u2014 use the dedicated route`;entityRoutes.onBeforeHandle(async({request,set})=>{let method=request.method.toUpperCase();if(method==="GET"||method==="HEAD"||method==="OPTIONS")return;if(hasGodminRole(request))return;if(isFeatureAdmin({isRbacTable,featureSet:table.feature_set,featureAdminRoles,callerRoles:decodeHeaderList(request.headers.get("x-user-roles"))}))return;if(await userHasGodminRoleInDb(db,schemaTables,request.headers.get("x-user-id"),logger2))return;return set.status=403,{success:!1,message:denyMessage}})}if(!table.excluded_methods?.includes("GET"))entityRoutes.get("/",async(ctx)=>{if(!database)return{success:!1,message:"DB not initialized"};let{drizzleTable:drizzleTable2,resolveCol:resolveCol2,schemaRelations:schemaRelations2}=getTableForRequest(ctx.request),requestedFields=table.columns?.map((c)=>c.name),requestedRelations=Object.keys(schemaRelations2).filter((k)=>k.startsWith(`${tableName}Relations`)).map((k)=>k.replace("Relations","")),authResult=await performAuthCheck(ctx.request,"GET",requestedFields,requestedRelations);if(authResult&&!authResult.authorized)return ctx.set.status=403,{success:!1,message:authResult.reason||"Forbidden"};let fieldQueryable=(field)=>{if(isSensitiveOutputKey(field))return!1;if(!sensitiveFieldQueryable(field,{authEnabled,allowedFields:authResult?.allowedFields}))return!1;if(authEnabled&&authResult?.allowedFields&&!authResult.allowedFields.includes(field))return!1;return!0},q=parseQueryParams(ctx.query),conditions=[];if(conditions.push(...scopeConditionsFor(authResult,resolveCol2)),q.search&&q.searchFields){for(let f of q.searchFields)if(!fieldQueryable(f.trim()))return ctx.set.status=403,{success:!1,message:`Cannot search on field: ${f.trim()}`};let searchConditions=q.searchFields.map((f)=>{let trimmed=f.trim(),col8=resolveCol2(trimmed);return col8?ilike(col8,`%${q.search}%`):null}).filter((c)=>c!==null);if(searchConditions.length>0){let orCondition=or2(...searchConditions);if(orCondition)conditions.push(orCondition)}}if(Array.isArray(q.filters))for(let filter of q.filters){for(let f of collectFilterFields(filter))if(!fieldQueryable(f))return ctx.set.status=403,{success:!1,message:`Cannot filter on field: ${f}`};let cond=buildFilterCondition(filter,resolveCol2);if(cond)conditions.push(cond)}let projection;if(q.select&&q.select.length>0){let requested=[...new Set(["id",...q.select.map((f)=>f.trim())])].filter(Boolean),columns={};for(let field of requested){if(!fieldQueryable(field))return ctx.set.status=403,{success:!1,message:`Cannot select field: ${field}`};let col8=resolveCol2(field);if(!col8)return ctx.set.status=400,{success:!1,message:`Unknown field in select: ${field}`};columns[field]=col8}if(Object.keys(columns).length>0)projection=columns}let unprojected=database.select().from(drizzleTable2),baseQuery=projection?database.select(projection).from(drizzleTable2):unprojected;if(conditions.length>0){let combined=and13(...conditions);if(combined)baseQuery=baseQuery.where(combined)}if(q.sort&&q.sort.length>0){for(let s of q.sort)if(!fieldQueryable(s.field))return ctx.set.status=403,{success:!1,message:`Cannot sort on field: ${s.field}`};let orderClauses=q.sort.map((s)=>{let col8=resolveCol2(s.field);if(!col8)return null;return s.direction==="desc"?desc5(col8):asc(col8)}).filter((o)=>o!==null);if(orderClauses.length>0)baseQuery=baseQuery.orderBy(...orderClauses)}let page=q.page??1,limit=q.limit??20,offset=q.offset??(page-1)*limit,queryStart=performance.now(),combinedCount=conditions.length>0?and13(...conditions):void 0,countResult=await database.select({n:sql7`count(*)::int`}).from(drizzleTable2).where(combinedCount),totalItems=Number(countResult[0]?.n??0);baseQuery=baseQuery.limit(limit).offset(offset);let items=await baseQuery,meta=buildPaginationMeta(page,limit,offset,totalItems);if(listLog.debug(`List ${rawTableName}: ${items.length}/${totalItems} rows`,{requestId:ctx.request.headers.get("x-request-id")||void 0,table:rawTableName,page,limit,totalItems,returned:items.length,conditionCount:conditions.length,search:q.search||void 0,sort:Array.isArray(q.sort)?q.sort.map((s)=>`${s.field}:${s.direction}`):void 0,queryMs:Math.round(performance.now()-queryStart)}),authEnabled&&authResult?.allowedFields)items=filterResponseFields(items,authResult.allowedFields);return{success:!0,data:{items:items.map((r)=>stripUnclaimedSensitive(redactSensitiveOutput(r),authResult?.allowedFields)),meta}}},{detail:{tags:[swaggerTag],summary:`List ${tableName}`,description:`Get paginated list of ${tableName} records with filtering, sorting, and search`}}),entityRoutes.get("/:id",async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,schemaTables:schemaTables2,schemaRelations:schemaRelations2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return{success:!1,message:"No id column or DB"};let params=ctx.params,q=parseQueryParams(ctx.query),requestedFields=table.columns?.map((c)=>c.name),requestedRelations=q.with?.map((w)=>w.name),authResult=await performAuthCheck(ctx.request,"GET",requestedFields,requestedRelations);if(authResult&&!authResult.authorized)return ctx.set.status=403,{success:!1,message:authResult.reason||"Forbidden"};let getScope=scopeConditionsFor(authResult,resolveCol2),getWhere=getScope.length>0?and13(eq28(idCol2,params.id),...getScope):eq28(idCol2,params.id);if(q.with&&q.with.length>0&&tableRelations&&dbPool){let allowedWith=authEnabled&&authResult?.allowedRelations?q.with.filter((w)=>authResult.allowedRelations?.includes(w.name)??!1):q.with,dbWithRelations=drizzle(dbPool,{schema:{...schemaTables2,...schemaRelations2}}),withClause={};for(let rel of allowedWith){let targetTable=relationTargetTable[rel.name],relAuth=authEnabled&&targetTable?await runAuthCheck(ctx.request,"GET",void 0,void 0,targetTable):null,decision=decideRelationExpansion({authEnabled,targetTable,relAuth,isTargetPublicGet:targetTable?publicGetEntities.has(targetTable):!1});if(!decision.expand)continue;let relScope=decision.scope;if(relScope&&Object.keys(relScope).length>0)withClause[rel.name]={...rel.limit?{limit:rel.limit}:{},where:(fields,ops)=>{let preds=[],unresolved=!1;for(let[k,v]of Object.entries(relScope)){let col8=resolveRelationScopeColumn(fields,k,snakeToCamel4);if(col8)preds.push(ops.eq(col8,v));else unresolved=!0}if(unresolved)return sql7`1 = 0`;return preds.length>0?ops.and(...preds):void 0}};else withClause[rel.name]=rel.limit?{limit:rel.limit}:!0}let result2=await dbWithRelations.query[tableName]?.findFirst({where:getWhere,with:withClause});if(authEnabled&&authResult?.allowedFields&&result2)result2=filterResponseFields(result2,[...authResult.allowedFields,...authResult.allowedRelations??[]]);if(authEnabled&&authResult?.allowedRelations&&result2)result2=filterResponseRelations(result2,authResult.allowedRelations);return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result2||null),authResult?.allowedFields)}}let result=(await database.select().from(drizzleTable2).where(getWhere))[0]||null;if(authEnabled&&authResult?.allowedFields&&result)result=filterResponseFields(result,authResult.allowedFields);return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result),authResult?.allowedFields)}},{detail:{tags:[swaggerTag],summary:`Get ${tableName} by ID`,description:`Get a single ${tableName} record by its ID with optional relations`}}),entityRoutes.get("/distinct/:field",async(ctx)=>{let{drizzleTable:drizzleTable2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return{success:!1,message:"DB not initialized"};let params=ctx.params,authResult=await performAuthCheck(ctx.request,"GET",[params.field],[]);if(authResult&&!authResult.authorized)return ctx.set.status=403,{success:!1,message:authResult.reason||"Forbidden"};if(authEnabled&&authResult?.allowedFields&&!authResult.allowedFields.includes(params.field))return ctx.set.status=403,{success:!1,message:"Forbidden: field not permitted"};if(isSensitiveOutputKey(params.field))return ctx.set.status=403,{success:!1,message:"Forbidden: field not permitted"};if(!sensitiveFieldQueryable(params.field,{authEnabled,allowedFields:authResult?.allowedFields}))return ctx.set.status=403,{success:!1,message:"Forbidden: field not permitted"};let col8=resolveCol2(params.field);if(!col8)return{success:!1,message:"Field not found"};let distinctScope=scopeConditionsFor(authResult,resolveCol2),distinctQuery=database.selectDistinct({value:col8}).from(drizzleTable2);return{success:!0,data:distinctScope.length>0?await distinctQuery.where(and13(...distinctScope)):await distinctQuery}},{detail:{tags:[swaggerTag],summary:`Get distinct ${tableName} values`,description:`Get distinct values for a specific field in ${tableName}`}});if(!table.excluded_methods?.includes("POST"))if(table.is_form_data&&storageConfig.enabled)entityRoutes.post("/",async(ctx)=>{let{drizzleTable:drizzleTable2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return{success:!1,message:"DB not initialized"};let userId=ctx.request.headers.get("x-user-id"),postFormAuthResult=await performAuthCheck(ctx.request,"POST",writeFields);if(postFormAuthResult&&!postFormAuthResult.authorized)return ctx.set.status=403,{success:!1,message:postFormAuthResult.reason||"Forbidden"};let{data,files}=parseFormDataBody(ctx.body,storageConfig),payload=data,uploadedFiles=null;if(files.length>0){if(uploadedFiles=await uploadFiles(files,storageConfig,table.table_name),uploadedFiles.failed.length>0&&uploadedFiles.success.length===0)return{success:!1,message:"File upload failed",errors:uploadedFiles.failed};warmUploadedMedia(uploadedFiles)}let mergeUploadedMetadata=()=>{let firstFile=uploadedFiles?.success[0];if(firstFile)payload={...payload,...buildFileRecordPayload(firstFile,userId)}};if(table.columns)payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,postFormAuthResult),mergeUploadedMetadata();else mergeUploadedMetadata();let scopedForm=forceCreateScope(payload,postFormAuthResult,resolveCol2);if(!scopedForm.ok)return ctx.set.status=403,{success:!1,message:`Forbidden: ownership scope references a column '${scopedForm.field}' this entity does not have`};if(table.columns){let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return{success:!1,message:"Validation failed",errors:validation.errors}}let anonymous=isAnonymousCreate(table,payload);if(userId&&!anonymous)payload.createdBy=userId;let result=await database.insert(drizzleTable2).values(payload).returning();{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:String(result[0]?.id??""),operation:"CREATE",summary:`Created ${table.table_name}`,newValues:result[0],...createAuditIdentity(anonymous,userId,ctx.request.headers),path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),postFormAuthResult?.allowedFields)}},{type:"formdata",body:t9.Object({[storageConfig.formData.dataField]:t9.Optional(t9.Union([t9.String(),t9.Any()])),[storageConfig.formData.filesField]:t9.Optional(t9.Union([t9.File(),t9.Array(t9.File())]))}),detail:{tags:[swaggerTag],summary:`Create ${tableName} with files`,description:`Create a new ${tableName} record with file upload support`}});else entityRoutes.post("/",async(ctx)=>{let{drizzleTable:drizzleTable2,schemaTables:schemaTables2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return{success:!1,message:"DB not initialized"};let payload=ctx.body,userId=ctx.request.headers.get("x-user-id"),postAuthResult=await performAuthCheck(ctx.request,"POST",writeFields);if(postAuthResult&&!postAuthResult.authorized)return ctx.set.status=403,{success:!1,message:postAuthResult.reason||"Forbidden"};if(table.columns)payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,postAuthResult);let scoped2=forceCreateScope(payload,postAuthResult,resolveCol2);if(!scoped2.ok)return ctx.set.status=403,{success:!1,message:`Forbidden: ownership scope references a column '${scoped2.field}' this entity does not have`};if(table.columns){let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return{success:!1,message:"Validation failed",errors:validation.errors}}let anonymous=isAnonymousCreate(table,payload);if(userId&&!anonymous)payload.createdBy=userId;if(tableName===AUTHORIZATION_TABLE_KEYS.userRoles&&payload.userId&&payload.roleId){let cols=drizzleTable2,existing=await database.select().from(drizzleTable2).where(and13(eq28(cols.userId,payload.userId),eq28(cols.roleId,payload.roleId))).limit(1);if(existing.length>0)return{success:!0,data:existing[0]}}let result=await database.insert(drizzleTable2).values(payload).returning();if(tableName===AUTHORIZATION_TABLE_KEYS.roleClaims&&config.claimsCache)config.claimsCache.invalidate().catch(()=>{});if(tableName===AUTHORIZATION_TABLE_KEYS.userRoles&&payload.roleId&&payload.userId)try{let rolesTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.roles],usersTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.users];if(rolesTable&&usersTable){if((await database.select().from(rolesTable).where(eq28(rolesTable.id,payload.roleId)).limit(1))[0]?.name===GODMIN_ROLE_NAME)await database.update(usersTable).set({isGod:!0}).where(eq28(usersTable.id,payload.userId))}}catch(_e){logger2.warn("[Entity] Failed to sync is_god flag on userRole create")}{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:String(result[0]?.id??""),operation:"CREATE",summary:`Created ${table.table_name}`,newValues:result[0],...createAuditIdentity(anonymous,userId,ctx.request.headers),path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),postAuthResult?.allowedFields)}},{body:t9.Object({},{additionalProperties:!0}),detail:{tags:[swaggerTag],summary:`Create ${tableName}`,description:`Create a new ${tableName} record`}});if(!table.excluded_methods?.includes("PUT"))if(table.is_form_data&&storageConfig.enabled)entityRoutes.put("/:id",async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return{success:!1,message:"No id column or DB"};let params=ctx.params,userId=ctx.request.headers.get("x-user-id"),putFormAuthResult=await performAuthCheck(ctx.request,"PUT",writeFields);if(putFormAuthResult&&!putFormAuthResult.authorized)return ctx.set.status=403,{success:!1,message:putFormAuthResult.reason||"Forbidden"};let putFormScope=scopeConditionsFor(putFormAuthResult,resolveCol2),putFormWhere=putFormScope.length>0?and13(eq28(idCol2,params.id),...putFormScope):eq28(idCol2,params.id),{data,files}=parseFormDataBody(ctx.body,storageConfig),payload=data,oldRecord=await database.select().from(drizzleTable2).where(putFormWhere).limit(1);if(putFormScope.length>0&&oldRecord.length===0)return ctx.set.status=403,{success:!1,message:"Forbidden"};if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,putFormAuthResult);let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return{success:!1,message:"Validation failed",errors:validation.errors}}let uploadedFiles=null;if(files.length>0)uploadedFiles=await uploadFiles(files,storageConfig,table.table_name),warmUploadedMedia(uploadedFiles);let result=await database.update(drizzleTable2).set(payload).where(putFormWhere).returning();{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"UPDATE",userId:userId||void 0,summary:`Updated ${table.table_name} (${params.id})`,oldValues:oldRecord[0],newValues:result[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:{record:result[0],files:uploadedFiles?.success||[],fileErrors:uploadedFiles?.failed||[]}}},{type:"formdata",body:t9.Object({[storageConfig.formData.dataField]:t9.Optional(t9.Union([t9.String(),t9.Any()])),[storageConfig.formData.filesField]:t9.Optional(t9.Union([t9.File(),t9.Array(t9.File())]))}),detail:{tags:[swaggerTag],summary:`Update ${tableName} with files`,description:`Full update of ${tableName} record with file upload support`}});else entityRoutes.put("/:id",async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return{success:!1,message:"No id column or DB"};let{params,body:payload}=ctx,userId=ctx.request.headers.get("x-user-id"),putAuthResult=await performAuthCheck(ctx.request,"PUT",writeFields);if(putAuthResult&&!putAuthResult.authorized)return ctx.set.status=403,{success:!1,message:putAuthResult.reason||"Forbidden"};let putScope=scopeConditionsFor(putAuthResult,resolveCol2),putWhere=putScope.length>0?and13(eq28(idCol2,params.id),...putScope):eq28(idCol2,params.id),oldRecord=await database.select().from(drizzleTable2).where(putWhere).limit(1);if(putScope.length>0&&oldRecord.length===0)return ctx.set.status=403,{success:!1,message:"Forbidden"};if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,putAuthResult);let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return{success:!1,message:"Validation failed",errors:validation.errors}}if(payload.updatedAt=new Date,userId)payload.updatedBy=userId;let result=await database.update(drizzleTable2).set(payload).where(putWhere).returning();{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"UPDATE",userId:userId||void 0,summary:`Updated ${table.table_name} (${params.id})`,oldValues:oldRecord[0],newValues:result[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),putAuthResult?.allowedFields)}},{body:t9.Object({},{additionalProperties:!0}),detail:{tags:[swaggerTag],summary:`Update ${tableName}`,description:`Full update of ${tableName} record`}});if(!table.excluded_methods?.includes("PATCH"))entityRoutes.patch("/:id",async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return{success:!1,message:"No id column or DB"};let{params,body:payload}=ctx,userId=ctx.request.headers.get("x-user-id"),patchAuthResult=await performAuthCheck(ctx.request,"PATCH",writeFields);if(patchAuthResult&&!patchAuthResult.authorized)return ctx.set.status=403,{success:!1,message:patchAuthResult.reason||"Forbidden"};let patchScope=scopeConditionsFor(patchAuthResult,resolveCol2),patchWhere=patchScope.length>0?and13(eq28(idCol2,params.id),...patchScope):eq28(idCol2,params.id),oldRecord=await database.select().from(drizzleTable2).where(patchWhere).limit(1);if(patchScope.length>0&&oldRecord.length===0)return ctx.set.status=403,{success:!1,message:"Forbidden"};if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,patchAuthResult);let validation=validatePayload(payload,table.columns,!0);if(!validation.valid)return{success:!1,message:"Validation failed",errors:validation.errors}}if(payload.updatedAt=new Date,userId)payload.updatedBy=userId;let result=await database.update(drizzleTable2).set(payload).where(patchWhere).returning();{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"PATCH",userId:userId||void 0,summary:`Patched ${table.table_name} (${params.id})`,oldValues:oldRecord[0],newValues:result[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),patchAuthResult?.allowedFields)}},{body:t9.Object({},{additionalProperties:!0}),detail:{tags:[swaggerTag],summary:`Patch ${tableName}`,description:`Partial update of ${tableName} record`}});if(!table.excluded_methods?.includes("DELETE"))entityRoutes.delete("/:id",async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,schemaTables:schemaTables2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return{success:!1,message:"No id column or DB"};let params=ctx.params,userId=ctx.request.headers.get("x-user-id"),deleteAuthResult=await performAuthCheck(ctx.request,"DELETE");if(deleteAuthResult&&!deleteAuthResult.authorized)return ctx.set.status=403,{success:!1,message:deleteAuthResult.reason||"Forbidden"};let deleteScope=scopeConditionsFor(deleteAuthResult,resolveCol2),deleteWhere=deleteScope.length>0?and13(eq28(idCol2,params.id),...deleteScope):eq28(idCol2,params.id),oldRecord=await database.select().from(drizzleTable2).where(deleteWhere).limit(1);if(deleteScope.length>0&&oldRecord.length===0)return ctx.set.status=403,{success:!1,message:"Forbidden"};if(await database.delete(drizzleTable2).where(deleteWhere),table.is_form_data&&storageConfig.enabled&&oldRecord[0]){let rec=oldRecord[0],filePath=rec.path,fileName=rec.name;if(typeof filePath==="string"&&typeof fileName==="string"){if(!await deleteFile(filePath,fileName,storageConfig.basePath))logger2.warn(`[Entity] Blob cleanup failed for ${table.table_name} (${params.id})`)}}if(tableName===AUTHORIZATION_TABLE_KEYS.roleClaims&&config.claimsCache)config.claimsCache.invalidate().catch(()=>{});if(tableName===AUTHORIZATION_TABLE_KEYS.userRoles&&oldRecord[0])try{let deletedUserRole=oldRecord[0],rolesTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.roles],usersTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.users];if(rolesTable&&usersTable&&deletedUserRole.roleId&&deletedUserRole.userId){if((await database.select().from(rolesTable).where(eq28(rolesTable.id,deletedUserRole.roleId)).limit(1))[0]?.name===GODMIN_ROLE_NAME)await database.update(usersTable).set({isGod:!1}).where(eq28(usersTable.id,deletedUserRole.userId))}}catch(_e){logger2.warn("[Entity] Failed to sync is_god flag on userRole delete")}{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"DELETE",userId:userId||void 0,summary:`Deleted ${table.table_name} (${params.id})`,oldValues:oldRecord[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:null}},{detail:{tags:[swaggerTag],summary:`Delete ${tableName}`,description:`Delete a ${tableName} record`}});if(table.bulk_endpoints_enabled){if(!table.excluded_methods?.includes("POST"))entityRoutes.post("/bulk",async(ctx)=>{let{drizzleTable:drizzleTable2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return{success:!1,message:"DB not initialized"};let items=ctx.body;if(!Array.isArray(items))return{success:!1,message:"Body must be an array"};if(items.length>MAX_BULK_ITEMS)return ctx.set.status=400,{success:!1,message:`Bulk request exceeds ${MAX_BULK_ITEMS} items`};let userId=ctx.request.headers.get("x-user-id"),bulkPostAuth=await performAuthCheck(ctx.request,"POST",writeFields);if(bulkPostAuth&&!bulkPostAuth.authorized)return ctx.set.status=403,{success:!1,message:bulkPostAuth.reason||"Forbidden"};let sanitizedItems=[];for(let raw of items){let payload=raw;if(table.columns)payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,bulkPostAuth);let scopedItem=forceCreateScope(payload,bulkPostAuth,resolveCol2);if(!scopedItem.ok)return ctx.set.status=403,{success:!1,message:`Forbidden: ownership scope references a column '${scopedItem.field}' this entity does not have`};if(table.columns){let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}if(userId&&!isAnonymousCreate(table,payload))payload.createdBy=userId;sanitizedItems.push(payload)}let txResult=await database.transaction(async(tx)=>{let results=[];for(let payload of sanitizedItems){let result=await tx.insert(drizzleTable2).values(payload).returning();results.push(result[0])}return results}),auditPath=auditPathContext(ctx.request);for(let[i,row]of txResult.entries())await logger2.audit({entityName:table.table_name,entityId:auditEntityId(row.id),operation:"CREATE",summary:`Bulk created ${table.table_name} (${row.id})`,newValues:row,...auditIdentityFor(table,sanitizedItems[i]??{},userId,ctx.request.headers),...auditPath});return{success:!0,data:txResult.map((r)=>projectWrittenRow(r,bulkPostAuth))}},{body:t9.Array(t9.Object({},{additionalProperties:!0})),detail:{tags:[swaggerTag],summary:`Bulk create ${tableName}`,description:`Create multiple ${tableName} records`}});if(!table.excluded_methods?.includes("PUT"))entityRoutes.put("/bulk",async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return{success:!1,message:"No id column or DB"};let items=ctx.body;if(!Array.isArray(items))return{success:!1,message:"Body must be an array"};if(items.length>MAX_BULK_ITEMS)return ctx.set.status=400,{success:!1,message:`Bulk request exceeds ${MAX_BULK_ITEMS} items`};let userId=ctx.request.headers.get("x-user-id"),bulkPutAuth=await performAuthCheck(ctx.request,"PUT",writeFields);if(bulkPutAuth&&!bulkPutAuth.authorized)return ctx.set.status=403,{success:!1,message:bulkPutAuth.reason||"Forbidden"};let bulkPutScope=scopeConditionsFor(bulkPutAuth,resolveCol2),prepared=[];for(let item of items){let payload=item.data;if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,bulkPutAuth);let validation=validatePayload(payload,table.columns,!0);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}if(payload.updatedAt=new Date,userId)payload.updatedBy=userId;prepared.push({id:item.id,payload})}let beforeById=new Map,txResult=await database.transaction(async(tx)=>{if(prepared.length>0){let idFilter=inArray8(idCol2,prepared.map((p)=>p.id)),beforeRows=await tx.select().from(drizzleTable2).where(bulkPutScope.length>0?and13(idFilter,...bulkPutScope):idFilter);for(let row of beforeRows)if(row.id!=null)beforeById.set(String(row.id),row)}let results=[];for(let{id,payload}of prepared){let result=await tx.update(drizzleTable2).set(payload).where(bulkPutScope.length>0?and13(eq28(idCol2,id),...bulkPutScope):eq28(idCol2,id)).returning();if(result[0])results.push(result[0])}return results}),auditPath=auditPathContext(ctx.request);for(let row of txResult){let rowId=String(row.id);await logger2.audit({entityName:table.table_name,entityId:auditEntityId(row.id),operation:"UPDATE",userId:userId||void 0,summary:`Bulk updated ${table.table_name} (${rowId})`,oldValues:beforeById.get(rowId),newValues:row,...auditPath})}return{success:!0,data:txResult.map((r)=>projectWrittenRow(r,bulkPutAuth))}},{body:bulkUpdateSchema,detail:{tags:[swaggerTag],summary:`Bulk update ${tableName}`,description:`Update multiple ${tableName} records`}});if(!table.excluded_methods?.includes("DELETE"))entityRoutes.delete("/bulk",async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return{success:!1,message:"No id column or DB"};let ids=ctx.body;if(!Array.isArray(ids))return{success:!1,message:"Body must be an array of ids"};if(ids.length>MAX_BULK_ITEMS)return ctx.set.status=400,{success:!1,message:`Bulk request exceeds ${MAX_BULK_ITEMS} items`};let bulkDeleteAuth=await performAuthCheck(ctx.request,"DELETE");if(bulkDeleteAuth&&!bulkDeleteAuth.authorized)return ctx.set.status=403,{success:!1,message:bulkDeleteAuth.reason||"Forbidden"};let bulkDeleteScope=scopeConditionsFor(bulkDeleteAuth,resolveCol2),userId=ctx.request.headers.get("x-user-id"),removed=[];await database.transaction(async(tx)=>{for(let id of ids){let r=await tx.delete(drizzleTable2).where(bulkDeleteScope.length>0?and13(eq28(idCol2,id),...bulkDeleteScope):eq28(idCol2,id)).returning();removed.push(...r)}});let auditPath=auditPathContext(ctx.request);for(let row of removed)await logger2.audit({entityName:table.table_name,entityId:auditEntityId(row.id),operation:"DELETE",userId:userId||void 0,summary:`Bulk deleted ${table.table_name} (${row.id})`,oldValues:row,...auditPath});return{success:!0,data:{deleted:removed.length}}},{body:bulkDeleteSchema,detail:{tags:[swaggerTag],summary:`Bulk delete ${tableName}`,description:`Delete multiple ${tableName} records`}})}app.use(entityRoutes)}return app}import Elysia13,{t as t11}from"elysia";import{randomUUID as randomUUID7}from"crypto";var LOCK_TTL_SECONDS2=60,RENEW_INTERVAL_MS=20000,lockKeyFor=(mappingId)=>`integrations:run:${mappingId}`;async function acquireRunLock(store,mappingId,deps={}){let key2=lockKeyFor(mappingId),now=deps.now??Date.now,ttl=deps.ttlSeconds??LOCK_TTL_SECONDS2,token=randomUUID7(),existing=await store.read(key2);if(!existing.success)return{ok:!1,reason:"unavailable",error:existing.error};if(existing.data)return{ok:!1,reason:"held",heldSince:existing.data.acquiredAt??null};let created=await store.create(key2,{token,acquiredAt:now()},ttl);if(!created.success)return{ok:!1,reason:"unavailable",error:created.error};let readBack=await store.read(key2);if(!readBack.success)return{ok:!1,reason:"unavailable",error:readBack.error};if(readBack.data?.token!==token)return{ok:!1,reason:"held",heldSince:readBack.data?.acquiredAt??null};let held=!0,timer=setInterval(()=>{(async()=>{let current=await store.read(key2);if(!current.success)return;if(current.data?.token!==token){held=!1,clearInterval(timer),deps.logger?.warn("[Integrations] Run lock was taken over",{mappingId});return}await store.create(key2,current.data,ttl)})()},RENEW_INTERVAL_MS);return timer.unref?.(),{ok:!0,handle:{token,isHeld:()=>held,release:async()=>{if(clearInterval(timer),!held)return;held=!1;let current=await store.read(key2);if(current.success&¤t.data?.token===token)await store.remove(key2)}}}}import{createHash as createHash2}from"crypto";function buildUrl(baseUrl,path7,params){let base=baseUrl.trim().replace(/\/+$/,"");if(!base)return{ok:!1,error:"The source has no base address."};let raw=(path7??"").trim();if(/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)||raw.startsWith("//"))return{ok:!1,error:`The endpoint path points at another host (${raw}). A path must be relative to the source address.`};let suffix=raw===""?"":raw.startsWith("/")?raw:`/${raw}`,url;try{url=new URL(`${base}${suffix}`)}catch{return{ok:!1,error:`The source address is not a valid URL: ${baseUrl}`}}for(let[key2,value]of Object.entries(params??{})){if(value==null)continue;url.searchParams.set(key2,String(value))}return{ok:!0,url}}function applyAuth(source,url){let headers={accept:"application/json",...source.extraHeaders??{}},token=source.token?.trim();if(!token)return{headers};switch(source.authType){case"bearer":case"oauth2":headers[source.authHeaderName||"Authorization"]=`Bearer ${token}`;break;case"api_key":if(source.authQueryParam)url.searchParams.set(source.authQueryParam,token);else headers[source.authHeaderName||"X-API-Key"]=token;break;case"basic":headers.Authorization=`Basic ${token}`;break;default:break}return{headers}}function describeTransportError(error){let message=error instanceof Error?error.message:String(error),cause=error?.cause,code=cause?.code??"",blob=`${message} ${code} ${cause?.message??""}`;if(/UNABLE_TO_GET_ISSUER_CERT|SELF_SIGNED|self.signed|CERT_|unable to verify|ERR_TLS|altnames/i.test(blob))return`Certificate verification failed (${code||"incomplete chain"}). If this server's certificate is knowingly self-signed or incomplete, turn off certificate verification for this source and try again.`;if(/ENOTFOUND|EAI_AGAIN|getaddrinfo|Unable to resolve/i.test(blob))return`The address could not be resolved (DNS${code?`: ${code}`:""}). Check the source address.`;if(/ECONNREFUSED/i.test(blob))return`The connection was refused (${code||"ECONNREFUSED"}). The address and port are reachable but nothing is listening.`;if(/ETIMEDOUT|timeout|timed out|aborted|AbortError|UND_ERR/i.test(blob))return`The connection timed out (${code||"timeout"}). Check network and firewall access to this address.`;if(/ECONNRESET|EPIPE/i.test(blob))return`The connection was closed by the other side (${code||"ECONNRESET"}).`;return`Connection error: ${message}`}async function fetchOnce(source,request,deps={}){let impl=deps.fetchImpl??fetch,headers={...request.headers},method=request.method.toUpperCase(),hasBody=request.body!==void 0&&method!=="GET"&&method!=="HEAD";if(hasBody&&!headers["content-type"]&&!headers["Content-Type"])headers["content-type"]="application/json";let init={method,headers,cache:"no-store",signal:AbortSignal.timeout(source.timeoutMs??30000),tls:{rejectUnauthorized:source.tlsVerify!==!1},...hasBody?{body:typeof request.body==="string"?request.body:JSON.stringify(request.body)}:{}},response;try{response=await impl(request.url.toString(),init)}catch(error){return deps.logger?.warn("[Integrations] Outbound request failed",{sourceId:source.id,url:request.url.origin+request.url.pathname,error:error instanceof Error?error.message:String(error)}),{ok:!1,status:0,body:null,linkHeader:null,error:describeTransportError(error)}}let text=await response.text().catch(()=>""),body=null;if(text)try{body=JSON.parse(text)}catch{body=text}let linkHeader=response.headers.get("link");if(!response.ok){let detail=typeof body==="string"?body:body==null?"":JSON.stringify(body);return{ok:!1,status:response.status,body,linkHeader,error:`The source returned HTTP ${response.status}${detail?` \u2014 ${detail.slice(0,300)}`:""}`}}return{ok:!0,status:response.status,body,linkHeader}}var EXPIRY_SKEW_MS=5000,DEFAULT_TTL_MS=300000,DEFAULT_REFRESH_TTL_MS=1800000,FIELD_SEPARATOR="\x00";function credentialFingerprint(config){let material=[config.tokenUrl,config.grantType,config.clientId??"",config.clientSecret??"",config.username??"",config.password??"",config.scope??"",config.clientAuth??"basic"].join(FIELD_SEPARATOR);return createHash2("sha256").update(material).digest("hex").slice(0,32)}class TokenCache{entries=new Map;get(sourceId,fingerprint){let entry=this.entries.get(sourceId);if(!entry)return null;if(entry.fingerprint!==fingerprint)return this.entries.delete(sourceId),null;return entry}set(sourceId,token){this.entries.set(sourceId,token)}invalidate(sourceId){this.entries.delete(sourceId)}clear(){this.entries.clear()}}var isUsable=(token,now)=>token.expiresAt>now+EXPIRY_SKEW_MS,canRefresh=(token,now)=>Boolean(token.refreshToken)&&(token.refreshExpiresAt??0)>now+EXPIRY_SKEW_MS;function buildTokenRequest(config,form){let headers={"content-type":"application/x-www-form-urlencoded",accept:"application/json"};if(config.scope&&!form.has("scope"))form.set("scope",config.scope);let clientId=config.clientId??"",clientSecret=config.clientSecret??"";if((config.clientAuth??"basic")==="basic")headers.authorization=`Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;else{if(clientId)form.set("client_id",clientId);if(clientSecret)form.set("client_secret",clientSecret)}return{headers,body:form.toString()}}async function requestToken(config,form,now,fetchImpl){let{headers,body}=buildTokenRequest(config,form),response;try{response=await fetchImpl(config.tokenUrl,{method:"POST",headers,body,cache:"no-store",signal:AbortSignal.timeout(config.timeoutMs??30000),tls:{rejectUnauthorized:config.tlsVerify!==!1}})}catch(error){return{ok:!1,error:`Token request failed \u2014 ${describeTransportError(error)}`}}let text=await response.text().catch(()=>""),json={};try{json=text?JSON.parse(text):{}}catch{}if(!response.ok||!json.access_token){let detail=json.error_description||json.error||text.slice(0,200);return{ok:!1,error:`Could not obtain an OAuth2 token (HTTP ${response.status})${detail?`: ${detail}`:""}`}}return{ok:!0,token:{accessToken:json.access_token,expiresAt:now+(json.expires_in?json.expires_in*1000:DEFAULT_TTL_MS),refreshToken:json.refresh_token,refreshExpiresAt:json.refresh_token?now+(json.refresh_expires_in?json.refresh_expires_in*1000:DEFAULT_REFRESH_TTL_MS):void 0,fingerprint:credentialFingerprint(config)}}}async function getAccessToken(config,cache,deps={}){let fetchImpl=deps.fetchImpl??fetch,now=deps.now?.()??Date.now();if(!config.tokenUrl?.trim())return{ok:!1,error:"No token URL is configured for this source."};let fingerprint=credentialFingerprint(config),cached=cache.get(config.sourceId,fingerprint);if(cached&&isUsable(cached,now))return{ok:!0,accessToken:cached.accessToken,fromCache:!0};if(cached&&canRefresh(cached,now)){let refreshed=await requestToken(config,new URLSearchParams({grant_type:"refresh_token",refresh_token:cached.refreshToken}),now,fetchImpl);if(refreshed.ok)return cache.set(config.sourceId,refreshed.token),{ok:!0,accessToken:refreshed.token.accessToken,fromCache:!1}}let form=new URLSearchParams({grant_type:config.grantType});if(config.grantType==="password")form.set("username",config.username??""),form.set("password",config.password??"");let minted=await requestToken(config,form,now,fetchImpl);if(!minted.ok)return{ok:!1,error:minted.error};return cache.set(config.sourceId,minted.token),{ok:!0,accessToken:minted.token.accessToken,fromCache:!1}}import{and as and14,eq as eq29,inArray as inArray9}from"drizzle-orm";var DEFAULT_IGNORED=["updatedAt","updated_at","updatedBy","updated_by"];function planRevert(input){let ignored=new Set([...DEFAULT_IGNORED,...input.ignoreColumns??[]]),ops={deletes:[],restores:[],reinserts:[],conflicts:[]};for(let image of input.preImages)switch(image.action){case"create":{if(!image.rowId)break;let now=input.current.get(image.rowId);if(!now)break;if(!input.force&&changedSince(image.after,now,ignored)){ops.conflicts.push({id:image.rowId,reason:"This record was edited after the import; deleting it would discard that edit."});break}ops.deletes.push(image.rowId);break}case"update":case"deactivate":{if(!image.rowId||!image.before)break;let now=input.current.get(image.rowId);if(!now)break;if(!input.force&&changedSince(image.after,now,ignored)){ops.conflicts.push({id:image.rowId,reason:"This record was edited after the import; restoring it would discard that edit."});break}ops.restores.push({id:image.rowId,row:image.before});break}case"delete":{if(!image.before)break;if(image.rowId&&input.current.has(image.rowId)){ops.conflicts.push({id:image.rowId,reason:"A record with this identifier exists again; it was not replaced."});break}ops.reinserts.push(image.before);break}default:break}return ops}function changedSince(after,now,ignored){if(!after)return!1;for(let[key2,value]of Object.entries(after)){if(ignored.has(key2))continue;if(!sameValue(value,now[key2]))return!0}return!1}function sameValue(a,b){if(a===b)return!0;if(a==null&&b==null)return!0;if(a instanceof Date&&b instanceof Date)return a.getTime()===b.getTime();if(a instanceof Date||b instanceof Date){let da=a instanceof Date?a:new Date(String(a)),db=b instanceof Date?b:new Date(String(b));return!Number.isNaN(da.getTime())&&!Number.isNaN(db.getTime())?da.getTime()===db.getTime():!1}if(typeof a==="object"||typeof b==="object")try{return JSON.stringify(a)===JSON.stringify(b)}catch{return!1}return String(a)===String(b)}var CHUNK=500;async function applyRevert(deps,ops,meta){let idCol=deps.resolveCol("id");if(!idCol)return{ok:!1,conflicts:ops.conflicts.length,error:`"${meta.targetEntity}" has no id column; its rows cannot be addressed.`};let now=new Date;try{let work=async(tx)=>{let deleted=0,restored=0,reinserted=0;for(let i=0;i<ops.deletes.length;i+=CHUNK){let chunk=ops.deletes.slice(i,i+CHUNK),removed=await tx.delete(deps.drizzleTable).where(inArray9(idCol,chunk)).returning();deleted+=removed.length}for(let restore of ops.restores){let{id:_id,createdAt:_c,createdBy:_cb,...rest}=restore.row,written=await tx.update(deps.drizzleTable).set({...rest,updatedAt:now,updatedBy:meta.userId}).where(and14(eq29(idCol,restore.id))).returning();restored+=written.length}if(ops.reinserts.length>0)for(let i=0;i<ops.reinserts.length;i+=CHUNK){let chunk=ops.reinserts.slice(i,i+CHUNK),written=await tx.insert(deps.drizzleTable).values(chunk).returning();reinserted+=written.length}return{deleted,restored,reinserted}};return{ok:!0,...meta.tx?await work(meta.tx):await deps.db.transaction(work),conflicts:ops.conflicts.length}}catch(error){return deps.logger.error("[Integrations] Revert transaction failed",{targetEntity:meta.targetEntity,error:error instanceof Error?error.message:String(error)}),{ok:!1,conflicts:ops.conflicts.length,error:"The database rejected this undo; it was rolled back and nothing changed."}}}function groupPreImagesByEntity(preImages){let byEntity=new Map;for(let image of preImages){let list=byEntity.get(image.targetEntity);if(list)list.push(image);else byEntity.set(image.targetEntity,[image])}return[...byEntity.entries()].map(([targetEntity,images])=>({targetEntity,preImages:images}))}function touchedRowIds(preImages){let ids=new Set;for(let image of preImages)if(image.rowId)ids.add(image.rowId);return[...ids]}function getByPath(obj,path7){if(!path7)return;let cur=obj;for(let part of path7.split(".")){if(cur==null||typeof cur!=="object")return;cur=cur[part]}return cur}function readSourceValue(external,parent,path7){return readOne(external,parent,path7)}function readOne(external,parent,path7){return path7.startsWith("__parent.")?getByPath(parent,path7.slice(9)):getByPath(external,path7)}var TEMPLATE_TOKEN=/\{([^{}]+)\}/g;function isTemplateSource(source){return TEMPLATE_TOKEN.lastIndex=0,TEMPLATE_TOKEN.test(source)}function renderTemplate(template,external,parent){for(let alternative of template.split("||")){let candidate=alternative.trim();if(candidate==="")continue;let complete=!0,rendered=candidate.replace(TEMPLATE_TOKEN,(_match,path7)=>{let value=readOne(external,parent,path7.trim());if(value==null||String(value).trim()==="")complete=!1;return value==null?"":String(value)});if(complete&&rendered.trim()!=="")return rendered}return(template.split("||").pop()??template).trim().replace(TEMPLATE_TOKEN,(_match,path7)=>{let value=readOne(external,parent,path7.trim());return value==null?"":String(value)})}function extractRecords(body,rootPath){if(rootPath){let at=getByPath(body,rootPath);if(Array.isArray(at))return at;return at&&typeof at==="object"?[at]:[]}let candidate=firstArrayLike(body)??deepFirstArrayOfObjects(body);return Array.isArray(candidate)?candidate:[]}var ENVELOPE_KEYS=["content","items","data","results","records","rows","list","value","responseObject"];function firstArrayLike(body){if(Array.isArray(body))return body;if(!body||typeof body!=="object")return;let b=body;for(let key2 of ENVELOPE_KEYS){let v=b[key2];if(Array.isArray(v))return v;if(v&&typeof v==="object"){let nested=v.items;if(Array.isArray(nested))return nested}}return}function deepFirstArrayOfObjects(body,depth=0){if(depth>5||body==null||typeof body!=="object"||body instanceof Date)return;if(Array.isArray(body))return body.length>0&&typeof body[0]==="object"&&body[0]!==null?body:void 0;let entries=Object.values(body);for(let v of entries)if(Array.isArray(v)&&v.length>0&&typeof v[0]==="object"&&v[0]!==null)return v;for(let v of entries)if(v&&typeof v==="object"&&!Array.isArray(v)){let found=deepFirstArrayOfObjects(v,depth+1);if(found)return found}for(let v of entries)if(Array.isArray(v))return v;return}var FOLD={\u{131}:"i",\u{130}:"I",\u{15f}:"s",\u{15e}:"S",\u{11f}:"g",\u{11e}:"G",\u{f8}:"o",\u{d8}:"O",\u{e6}:"ae",\u{c6}:"AE",\u{df}:"ss",\u{111}:"d",\u{110}:"D",\u{142}:"l",\u{141}:"L"};function foldToAscii(value){return value.replace(/[\u0131\u0130\u015F\u015E\u011F\u011E\u00F8\u00D8\u00E6\u00C6\u00DF\u0111\u0110\u0142\u0141]/g,(ch)=>FOLD[ch]??ch).normalize("NFD").replace(/[\u0300-\u036f]/g,"")}var TRUTHY=new Set(["true","1","yes","y","evet","e","on"]),FALSY=new Set(["false","0","no","n","hayir","hay\u0131r","h","off"]);function applyTransform(value,transform,locale){if(value==null)return{ok:!0,value};switch(transform){case"trim":return{ok:!0,value:String(value).trim()};case"upper":return{ok:!0,value:String(value).trim().toLocaleUpperCase(locale)};case"lower":return{ok:!0,value:String(value).trim().toLocaleLowerCase(locale)};case"toString":return{ok:!0,value:String(value)};case"toNumber":{if(typeof value==="boolean")return{ok:!1,reason:"not a number",raw:value};let s=String(value).trim();if(s==="")return{ok:!0,value:null};let n=Number(s);return Number.isFinite(n)?{ok:!0,value:n}:{ok:!1,reason:"not a number",raw:value}}case"toBoolean":{if(typeof value==="boolean")return{ok:!0,value};let s=String(value).trim().toLocaleLowerCase("tr-TR");if(s==="")return{ok:!0,value:null};if(TRUTHY.has(s))return{ok:!0,value:!0};if(FALSY.has(s))return{ok:!0,value:!1};return{ok:!1,reason:"not a yes/no value",raw:value}}case"emailSafe":return{ok:!0,value:foldToAscii(String(value)).toLowerCase().trim().replace(/[^a-z0-9._@+-]+/g,".").replace(/\.{2,}/g,".").replace(/\.*@\.*/g,"@").replace(/^[.\-_]+|[.\-_]+$/g,"")};case"slug":return{ok:!0,value:foldToAscii(String(value)).toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/-{2,}/g,"-").replace(/^-+|-+$/g,"")};case"pathLeaf":{let parts=String(value).split(/[/\\>\u203A\u00BB]|->/).map((part)=>part.trim()).filter((part)=>part!=="");return{ok:!0,value:parts.length>0?parts[parts.length-1]:""}}case"toList":{if(Array.isArray(value))return{ok:!0,value};let s=String(value).trim();if(s==="")return{ok:!0,value:[]};return{ok:!0,value:s.split(",").map((part)=>part.trim()).filter((part)=>part!=="")}}case"dateOnly":{let s=String(value).trim();if(s==="")return{ok:!0,value:null};let leading=s.match(/^\d{4}-\d{2}-\d{2}/);if(leading)return{ok:!0,value:leading[0]};let d=new Date(s);return Number.isNaN(d.getTime())?{ok:!1,reason:"not a date",raw:value}:{ok:!0,value:d.toISOString().slice(0,10)}}default:return{ok:!0,value}}}function isEmpty(v){return v==null||typeof v==="string"&&v.trim()===""}function mapRecord(external,mappings,parent){let row={},problems=[];for(let fm of mappings){if(!fm.target)continue;let value;if(fm.constant!=null&&fm.constant!=="")value=fm.constant;else{let raw=isTemplateSource(fm.source)?renderTemplate(fm.source,external,parent):readOne(external,parent,fm.source),result=applyTransform(raw,fm.transform);if(!result.ok)problems.push(`${fm.source} \u2192 ${fm.target}: ${result.reason} (${String(result.raw)})`),value=null;else value=result.value}if(isEmpty(value)&&fm.fallback!=null&&fm.fallback!==""){let raw=isTemplateSource(fm.fallback)?renderTemplate(fm.fallback,external,parent):fm.fallback,result=applyTransform(raw,fm.transform);if(result.ok)value=result.value;else problems.push(`fallback \u2192 ${fm.target}: ${result.reason} (${String(result.raw)})`),value=null}if(value!==void 0)row[fm.target]=value}return{row,problems}}function buildMappedRecords(externalRecords,mappings,dedupTargetField,parents){let records=[],issues=[];return externalRecords.forEach((ext,i)=>{let{row,problems}=mapRecord(ext,mappings,parents?.[i]);for(let message of problems)issues.push({sourceIndex:i,kind:"invalid_value",message});let dedupKey=dedupTargetField&&!isEmpty(row[dedupTargetField])?String(row[dedupTargetField]).trim():null;records.push({data:row,dedupKey,sourceIndex:i,external:ext,parent:parents?.[i]})}),{records,issues}}function flattenKeys(obj,prefix="",out=[],depth=0){if(out.length>200||depth>4||obj==null||typeof obj!=="object")return out;if(Array.isArray(obj)){if(prefix)out.push(prefix);return out}for(let[k,v]of Object.entries(obj)){let path7=prefix?`${prefix}.${k}`:k;if(v!=null&&typeof v==="object"&&!Array.isArray(v))flattenKeys(v,path7,out,depth+1);else out.push(path7)}return out}var MAX_FANOUT_COMBINATIONS=5000;function resolvePath2(rawPath,values){let missing=new Set,used=new Set;return{path:rawPath.replace(/\{([a-zA-Z_][\w-]*)\}|:([a-zA-Z_]\w*)/g,(_full,brace,colon)=>{let key2=brace??colon,v=values[key2];if(v==null||v==="")return missing.add(key2),brace?`{${key2}}`:`:${key2}`;return used.add(key2),encodeURIComponent(String(v))}),missing:[...missing],used}}function planFetchOrder(endpoints,roots){let byId=new Map(endpoints.map((e)=>[e.id,e])),order=[],settled=new Set,onPath=[],onPathSet=new Set,visit=(id,requiredBy)=>{if(settled.has(id))return null;if(onPathSet.has(id))return{ok:!1,reason:"cycle",chain:[...onPath.slice(onPath.indexOf(id)),id]};let node=byId.get(id);if(!node)return{ok:!1,reason:"unknown_endpoint",missing:id,requiredBy:requiredBy??id};onPath.push(id),onPathSet.add(id);for(let binding of node.paramBindings??[]){if(binding.from!=="endpoint")continue;let failure=visit(binding.endpointId,id);if(failure)return failure}return onPath.pop(),onPathSet.delete(id),settled.add(id),order.push(id),null};for(let id of roots??endpoints.map((e)=>e.id)){let failure=visit(id,null);if(failure)return failure}return{ok:!0,order}}function expandBindings(bindings,sources,limit=MAX_FANOUT_COMBINATIONS){if(!bindings||bindings.length===0)return{ok:!0,calls:[{values:{}}]};let axes=[];for(let binding of bindings){if(binding.from==="static"){axes.push({param:binding.param,options:[{value:binding.value}]});continue}if(binding.from==="endpoint"){let options=(sources.parents?.[binding.endpointId]??[]).map((record)=>({value:getByPath(record,binding.field),parent:record})).filter((o)=>o.value!=null&&o.value!=="");axes.push({param:binding.param,options});continue}let values=sources.entityValues?.[`${binding.entity}.${binding.field}`]??[];axes.push({param:binding.param,options:values.filter((v)=>v!=null&&v!=="").map((value)=>({value}))})}if(axes.some((a)=>a.options.length===0))return{ok:!0,calls:[]};let combinations=axes.reduce((n,a)=>n*a.options.length,1);if(combinations>limit)return{ok:!1,reason:"too_many",combinations,limit};let calls=[{values:{}}];for(let axis of axes){let next=[];for(let call of calls)for(let option of axis.options)next.push({values:{...call.values,[axis.param]:option.value},parent:option.parent??call.parent});calls=next}return{ok:!0,calls}}function parentEndpointIds(bindings){let ids=new Set;for(let b of bindings??[])if(b.from==="endpoint")ids.add(b.endpointId);return[...ids]}function entityValueKeys(bindings){let keys=new Set;for(let b of bindings??[])if(b.from==="entity")keys.add(`${b.entity}.${b.field}`);return[...keys]}var MAX_PAGES=1000,DEFAULT_PAGE_SIZE=200,initialState2=()=>({pagesFetched:0,recordsFetched:0,cursor:null});function firstPageParams(profile){switch(profile.kind){case"page_number":{let params={[profile.pageParam]:String(profile.firstPage??0)};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return params}case"offset":return{[profile.offsetParam]:"0",[profile.limitParam]:String(pageSize(profile))};case"cursor":{let params={};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return params}case"link_header":{let params={};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return params}default:return{}}}function reportedTotal(profile,body){let path7=profile.kind==="page_number"||profile.kind==="offset"?profile.totalPath:void 0;if(!path7)return null;let raw=getByPath(body,path7);if(typeof raw==="number"&&Number.isFinite(raw)&&raw>=0)return Math.floor(raw);if(typeof raw==="string"&&raw.trim()!==""){let n=Number(raw);if(Number.isFinite(n)&&n>=0)return Math.floor(n)}return null}function nextPage(profile,state,observed){if(profile.kind==="none")return{kind:"done",reason:"single request"};if(state.pagesFetched>=MAX_PAGES)return{kind:"failed",reason:`Paging stopped after ${MAX_PAGES} requests without reaching the end. The source may not be honouring the paging parameters; importing this would be a partial read.`};if(observed.recordCount===0)return{kind:"done",reason:"empty page"};switch(profile.kind){case"page_number":{if(isLastPage(profile.lastPagePath,observed.body))return{kind:"done",reason:"source reported the last page"};let total=reportedTotal(profile,observed.body);if(total!==null&&state.recordsFetched>=total)return{kind:"done",reason:"reported total reached"};if(observed.recordCount<pageSize(profile))return{kind:"done",reason:"short page"};let next=(profile.firstPage??0)+state.pagesFetched,params={[profile.pageParam]:String(next)};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return{kind:"params",params}}case"offset":{let total=reportedTotal(profile,observed.body);if(total!==null&&state.recordsFetched>=total)return{kind:"done",reason:"reported total reached"};if(observed.recordCount<pageSize(profile))return{kind:"done",reason:"short page"};return{kind:"params",params:{[profile.offsetParam]:String(state.recordsFetched),[profile.limitParam]:String(pageSize(profile))}}}case"cursor":{let raw=getByPath(observed.body,profile.nextCursorPath),next=raw==null?"":String(raw).trim();if(next==="")return{kind:"done",reason:"no further cursor"};if(state.cursor!=null&&next===state.cursor)return{kind:"failed",reason:`The source returned the same cursor twice (${next}); paging would not advance.`};let params={[profile.cursorParam]:next};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return{kind:"params",params}}case"link_header":{let url=parseNextLink(observed.linkHeader);if(!url)return{kind:"done",reason:"no rel=next link"};return{kind:"url",url}}default:return{kind:"done",reason:"single request"}}}function pageSize(profile){let declared="pageSize"in profile?profile.pageSize:void 0;return declared&&declared>0?declared:DEFAULT_PAGE_SIZE}function isLastPage(path7,body){if(!path7)return!1;let raw=getByPath(body,path7);if(typeof raw==="boolean")return raw;if(typeof raw==="string")return raw.trim().toLowerCase()==="true";return!1}function parseNextLink(header){if(!header)return null;for(let part of splitLinks(header)){let match=part.match(/<([^>]*)>(.*)/);if(!match)continue;let url=(match[1]??"").trim(),attrs=match[2]??"";if(!url)continue;if(/;\s*rel\s*=\s*["']?\s*next\s*["']?(\s*;|\s*$)/i.test(attrs))return url}return null}function splitLinks(header){let parts=[],depth=0,current="";for(let ch of header){if(ch==="<")depth++;else if(ch===">")depth--;if(ch===","&&depth===0){parts.push(current),current="";continue}current+=ch}if(current.trim()!=="")parts.push(current);return parts}function flattenTree(records,childrenPath){let out=[],truncated=!1,visit=(node,parent,depth)=>{if(!node||typeof node!=="object")return;if(depth>64){truncated=!0;return}let source=node,children=source[childrenPath],{[childrenPath]:_nested,...own}=source;if(out.push({...own,["__depth"]:depth,["__treeParent"]:parent??null}),!Array.isArray(children))return;for(let child of children)visit(child,own,depth+1)};for(let root of records)visit(root,null,0);return{records:out,truncated}}function treeChildrenPath(value){let trimmed=(value??"").trim();return trimmed.length>0?trimmed:null}var DEFAULT_MAX_CONCURRENT=4,DEFAULT_MAX_RECORDS=50000;async function collectOne(source,endpoint,params,deps,counters){let profile=endpoint.pagination??{kind:"none"},maxRecords=deps.maxRecords??DEFAULT_MAX_RECORDS,values={...endpoint.queryParams??{},...params.values},resolved=resolvePath2(endpoint.path,values);if(resolved.missing.length>0)return{ok:!1,error:`This endpoint needs path parameter(s) ${resolved.missing.map((m)=>`{${m}}`).join(", ")} and nothing supplies them. Add a parameter binding, or a static value.`};let baseQuery={};for(let[key2,value]of Object.entries(values)){if(value==null||resolved.used.has(key2))continue;baseQuery[key2]=value}let records=[],total=null,state=initialState2(),pageParams=firstPageParams(profile),absoluteUrl=null;for(;;){if(deps.isCancelled?.())return{ok:!1,error:"Cancelled."};let url;if(absoluteUrl)try{url=new URL(absoluteUrl)}catch{return{ok:!1,error:`The source returned an unusable next-page link: ${absoluteUrl}`}}else{let built=buildUrl(source.baseUrl,resolved.path,{...baseQuery,...pageParams});if(!built.ok)return{ok:!1,error:built.error};url=built.url}let headers={accept:"application/json"};if(deps.bearerToken)headers.Authorization=`Bearer ${deps.bearerToken}`;counters.calls++;let outcome=await fetchOnce({...source,token:deps.bearerToken??source.token},{method:endpoint.method,url,headers,body:endpoint.method.toUpperCase()==="GET"?void 0:endpoint.requestBody??{}},{fetchImpl:deps.fetchImpl});if(!outcome.ok)return{ok:!1,error:outcome.error??`HTTP ${outcome.status}`};let raw=extractRecords(outcome.body,endpoint.responseRootPath),childrenPath=treeChildrenPath(endpoint.treeChildrenPath),flattened=childrenPath?flattenTree(raw,childrenPath):null;if(flattened?.truncated)return{ok:!1,error:"This source answered with a structure that nests into itself. Nothing was imported \u2014 a company that contains itself has no order to write it in."};let page=flattened?flattened.records:raw;if(records.push(...page),counters.records+=page.length,total===null)total=reportedTotal(profile,outcome.body);if(counters.records>maxRecords)return{ok:!1,error:`This run would hold more than ${maxRecords} records at once. Narrow the source with a filter, or raise maxRecordsPerRun.`};deps.onProgress?.(counters.records),state={pagesFetched:state.pagesFetched+1,recordsFetched:records.length,cursor:pageParams[cursorParamOf(profile)??""]??state.cursor};let next=nextPage(profile,state,{recordCount:page.length,body:outcome.body,linkHeader:outcome.linkHeader});if(next.kind==="done")break;if(next.kind==="failed")return{ok:!1,error:next.reason};if(next.kind==="url"){absoluteUrl=next.url,pageParams={};continue}absoluteUrl=null,pageParams=next.params}return{ok:!0,records,total}}function cursorParamOf(profile){return profile.kind==="cursor"?profile.cursorParam:null}function describeCallFailure(call,error){let params=call.values??{},named=Object.entries(params).map(([key2,value])=>`${key2}=${String(value)}`).join(", ");return named?`${named}: ${error}`:error}async function collectEndpoint(source,endpoint,sources,deps={}){let counters={calls:0,records:0},expansion=expandBindings(endpoint.paramBindings??void 0,sources);if(!expansion.ok)return{ok:!1,callCount:0,error:`This endpoint would be called ${expansion.combinations} times (the ceiling is ${expansion.limit}). Narrow the parent list, or remove one of the parameter bindings.`};if(expansion.calls.length===0)return{ok:!0,records:[],reportedTotal:null,callCount:0};let limit=Math.max(1,deps.maxConcurrent??DEFAULT_MAX_CONCURRENT),collected=[],total=null,failure=null,partial=[],cursor=0,worker=async()=>{for(;;){if(failure||deps.isCancelled?.())return;let index=cursor++,call=expansion.calls[index];if(!call)return;let result=await collectOne(source,endpoint,call,deps,counters);if(!result.ok){if(expansion.calls.length>1){partial.push(describeCallFailure(call,result.error));continue}failure??=result.error;return}for(let record of result.records)collected.push({record,parent:call.parent});if(expansion.calls.length===1)total=result.total}};if(await Promise.all(Array.from({length:Math.min(limit,expansion.calls.length)},worker)),failure)return{ok:!1,error:failure,callCount:counters.calls};if(deps.isCancelled?.())return{ok:!1,error:"Cancelled.",callCount:counters.calls};if(partial.length>0&&collected.length===0&&partial.length===expansion.calls.length)return{ok:!1,error:`Every call to this endpoint failed. First: ${partial[0]}`,callCount:counters.calls};return{ok:!0,records:collected,reportedTotal:total,callCount:counters.calls,...partial.length>0?{partialFailures:partial}:{}}}function isDestructive(writeMode,missingPolicy){return writeMode==="replace"||missingPolicy==="delete"||missingPolicy==="deactivate"}function destructiveGate(input){if(!isDestructive(input.writeMode,input.missingPolicy))return{allowed:!0};if(input.partialFailures>0)return{allowed:!1,kind:"incomplete_source",message:`${input.partialFailures} call(s) in this source never answered, so part of it was not read. This run removes records the source does not send, and those unread records would be removed as if the source had dropped them. Fix the failing call, or switch this mapping to leave missing records alone.`};if(input.fetched===0)return{allowed:!1,kind:"incomplete_source",message:"The source returned no records at all. This run removes what the source does not send, so it would empty the target table. A source that is genuinely empty and one that could "+"not be read look the same from here, so this will not proceed on its own \u2014 preview it, "+"and if the source really is empty, remove the rows deliberately."};if(input.reported!==null&&input.fetched<input.reported)return{allowed:!1,kind:"incomplete_source",message:`The source reports ${input.reported} records but only ${input.fetched} were read. This run deletes what the source does not send, so it will not proceed on a partial read. Check the paging settings for this endpoint.`};return{allowed:!0}}var referenceKey=(check)=>`${check.entity}.${check.entityColumn}`,pointsAtOwnTable=(check,targetEntity)=>check.entity===targetEntity;function referenceValuesOf(raw){if(Array.isArray(raw))return{values:raw.map((item)=>String(item??"").trim()).filter((item)=>item!==""),wasList:!0};if(raw===null||raw===void 0)return{values:[],wasList:!1};let single=String(raw).trim();return{values:single===""?[]:[single],wasList:!1}}function resolveReference(raw,ids){let{values,wasList}=referenceValuesOf(raw),found=[],unmatched=[];for(let value of values){let id=ids?.[value];if(id===void 0)unmatched.push(value);else if(!found.includes(id))found.push(id)}return{ids:found,unmatched,wasList}}function resolvedColumnValue(resolution){return resolution.wasList?resolution.ids:resolution.ids[0]??null}function applyReferenceIds(row,references,ids,targetEntity){let next={...row},deferred=[];for(let check of references){if(!check.writeIdTo)continue;let raw=row[check.column];if(pointsAtOwnTable(check,targetEntity)){next[check.writeIdTo]=Array.isArray(raw)?[]:null,deferred.push({check,raw});continue}next[check.writeIdTo]=resolvedColumnValue(resolveReference(raw,ids[referenceKey(check)]))}return{row:next,deferred}}function unambiguousIds(rows){let seen=new Map;for(let row of rows){if(row.value==null||row.id==null)continue;let key2=String(row.value).trim();if(key2==="")continue;seen.set(key2,seen.has(key2)?null:String(row.id))}let ids={};for(let[key2,id]of seen)if(id!==null)ids[key2]=id;return ids}function ambiguousValues(rows){let counts=new Map;for(let row of rows){if(row.value==null||row.id==null)continue;let key2=String(row.value).trim();if(key2==="")continue;counts.set(key2,(counts.get(key2)??0)+1)}return[...counts.entries()].filter(([,n])=>n>1).map(([key2])=>key2)}var isEmpty2=(v)=>v==null||typeof v==="string"&&v.trim()==="";function displayValue(value){if(value!==null&&typeof value==="object")try{return JSON.stringify(value)}catch{return"(a value that cannot be shown)"}return String(value)}function buildFallbackIdentifier(format2,data){let complete=!0,rendered=format2.replace(/\{([^{}]+)\}/g,(_match,path7)=>{let value=data[path7.trim()];if(value==null||String(value).trim()==="")complete=!1;return value==null?"":String(value)});if(!complete)return null;let cleaned=foldToAscii(rendered).toLowerCase().trim().replace(/[^a-z0-9._@+-]+/g,".").replace(/\.{2,}/g,".").replace(/\.*@\.*/g,"@").replace(/^[.\-_]+|[.\-_]+$/g,"");return cleaned===""?null:cleaned}function missingReferences(row,references,known){let broken=[];for(let check of references){let raw=row[check.column];if(isEmpty2(raw))continue;let value=displayValue(raw).trim(),present=known[`${check.entity}.${check.entityColumn}`];if(!present)continue;if(!present.has(value))broken.push({check,value,raw})}return broken}async function loadReferenceValues(references,read){let values={};for(let check of references){let key2=`${check.entity}.${check.entityColumn}`;if(values[key2])continue;let rows=await read(check.entity,check.entityColumn);values[key2]=new Set(rows.filter((v)=>!isEmpty2(v)).map((v)=>String(v).trim()))}return values}async function loadReferenceIds(references,targetEntity,read){let ids={},ambiguous={};for(let check of references){if(!check.writeIdTo)continue;if(pointsAtOwnTable(check,targetEntity))continue;let key2=referenceKey(check);if(ids[key2])continue;let pairs=await read(check.entity,check.entityColumn);ids[key2]=unambiguousIds(pairs);let shared=ambiguousValues(pairs);if(shared.length>0)ambiguous[key2]=shared}return{ids,ambiguous}}function ambiguityWarnings(ambiguous){return Object.entries(ambiguous).map(([key2,values])=>{let shown=values.slice(0,5).join(", "),rest=values.length>5?` and ${values.length-5} more`:"";return`${values.length} value(s) in ${key2} belong to more than one row \u2014 ${shown}${rest}. Records pointing at them are left unlinked rather than linked to whichever row came first.`})}function planWrites(input){let{records,existing,dedupTargetField,writeMode,missingPolicy,missingColumn,missingValue,requiredFields=[],uniqueFields=[],createDefaults={},accountRule=null,existingAccounts={},references=[],referenceValues={},referenceIds={},targetEntity=""}=input,issues=[],warnings=[],creates=[],referenceInserts=[],accountInserts=[],accountJoinInserts=[],plannedAccounts=new Set,plannedRefs=new Set,updates=[],deletes=[],deactivates=[],deferredLinks=[],dedupConfigured=Boolean(dedupTargetField),accountClaims=new Map,accountIdIsUnique=accountRule?uniqueFields.includes(accountRule.idColumn):!1,withAccount=(record,data)=>{if(!accountRule)return data;let raw=data[accountRule.matchFrom];if(isEmpty2(raw)&&accountRule.emailFormat?.trim()){let built=buildFallbackIdentifier(accountRule.emailFormat,data);if(built)raw=built,data[accountRule.matchFrom]=built}if(isEmpty2(raw))return issues.push({sourceIndex:record.sourceIndex,kind:"account_unmatched",message:`${accountRule.matchFrom} is empty, so this record gets no account \u2014 it is imported without one.`,dedupKey:record.dedupKey,row:data}),data;let value=String(raw).trim(),claimedBy=accountClaims.get(value);if(claimedBy&&accountIdIsUnique)return issues.push({sourceIndex:record.sourceIndex,kind:"duplicate_value",message:`${accountRule.matchFrom} is \u201C${value}\u201D, which another record in this import already uses \u2014 and ${accountRule.idColumn} allows one record per account. This one is left out.`,dedupKey:record.dedupKey,row:data,keptRow:claimedBy.data}),null;accountClaims.set(value,record);let existingId=existingAccounts[value];if(existingId)return{...data,[accountRule.idColumn]:existingId};if(!plannedAccounts.has(value)){plannedAccounts.add(value);let accountData={[accountRule.matchColumn]:value};for(let pair of accountRule.values??[]){let from=data[pair.from];if(!isEmpty2(from))accountData[pair.column]=from}accountInserts.push({entity:accountRule.entity,matchColumn:accountRule.matchColumn,matchValue:value,data:accountData});for(let join7 of accountRule.joins??[])accountJoinInserts.push({entity:join7.entity,accountColumn:join7.accountColumn,values:join7.values,matchValue:value})}return data},hasDefaults=Object.keys(createDefaults).length>0,newRecord=(data)=>hasDefaults?{...createDefaults,...data}:data,existingByKey=new Map;if(dedupTargetField)for(let row of existing){let value=row.row[dedupTargetField];if(!isEmpty2(value))existingByKey.set(String(value).trim(),row)}let claimedUnique=new Map;for(let field of uniqueFields){let taken=new Map;for(let existingRow of existing){let value=existingRow.row[field];if(!isEmpty2(value))taken.set(String(value).trim(),existingRow.id)}claimedUnique.set(field,taken)}let matchedIds=new Set,seenInBatch=new Map;for(let sourceRecord of records){let broken=missingReferences(sourceRecord.data,references,referenceValues),resolvedRow=applyReferenceIds(sourceRecord.data,references,referenceIds,targetEntity),record={...sourceRecord,data:resolvedRow.row};for(let link of resolvedRow.deferred){if(!dedupTargetField)continue;let matchValue=record.data[dedupTargetField];if(isEmpty2(matchValue))continue;deferredLinks.push({matchColumn:dedupTargetField,matchValue:String(matchValue).trim(),writeIdTo:link.check.writeIdTo,lookupColumn:link.check.entityColumn,raw:link.raw,isList:Array.isArray(link.raw)})}let blocking=broken.find((b)=>b.check.onMissing==="skipRecord");if(blocking){let{check,value}=blocking;issues.push({sourceIndex:record.sourceIndex,kind:"missing_reference",message:typeof blocking.raw==="object"&&blocking.raw!==null?`${check.column} is not a single value but a whole object \u2014 ${value}. Point the mapping at one field of it, e.g. \`${check.column}.code\`, and it will have something to match against ${check.entity}.${check.entityColumn}.`:`${check.column} is \u201C${value}\u201D, which does not exist in ${check.entity}.${check.entityColumn} \u2014 this record is left out.`,dedupKey:record.dedupKey,row:record.data,reference:{column:check.column,value,entity:check.entity,entityColumn:check.entityColumn}});continue}let keepReferences=()=>{for(let{check,value}of broken){if(check.onMissing!=="create")continue;let token=`${check.entity}.${check.entityColumn}::${value}`;if(plannedRefs.has(token))continue;plannedRefs.add(token);let data={};for(let pair of check.createValues??[]){let fromSource=isTemplateSource(pair.from)?renderTemplate(pair.from,record.external,record.parent):readSourceValue(record.external,record.parent,pair.from),value2=isEmpty2(fromSource)?record.data[pair.from]:fromSource;if(!isEmpty2(value2))data[pair.column]=value2}referenceInserts.push({entity:check.entity,column:check.entityColumn,value,...Object.keys(data).length>0?{data}:{}})}},uniqueCollision=(data,ownRowId)=>{for(let field of uniqueFields){let raw=data[field];if(isEmpty2(raw))continue;let value=String(raw).trim(),holder2=claimedUnique.get(field)?.get(value);if(holder2===void 0)continue;if(holder2!==null&&holder2===ownRowId)continue;return{field,value}}return null},claimUnique=(data,rowId)=>{for(let field of uniqueFields){let raw=data[field];if(isEmpty2(raw))continue;claimedUnique.get(field)?.set(String(raw).trim(),rowId)}},collide=(field,value)=>{issues.push({sourceIndex:record.sourceIndex,kind:"duplicate_value",message:`${field} is \u201C${value}\u201D, which something else already has \u2014 ${field} has to be unique, so this record is left out.`,dedupKey:record.dedupKey,row:record.data})},missingRequired=requiredFields.filter((f)=>isEmpty2(record.data[f])&&isEmpty2(createDefaults[f]));if(missingRequired.length>0){issues.push({sourceIndex:record.sourceIndex,kind:"missing_required",message:`Required field(s) empty: ${missingRequired.join(", ")}`,dedupKey:record.dedupKey,row:record.data});continue}if(!dedupConfigured){let clash2=uniqueCollision(record.data,null);if(clash2){collide(clash2.field,clash2.value);continue}keepReferences(),claimUnique(record.data,null);let withLogin=withAccount(record,newRecord(record.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record.sourceIndex});continue}if(record.dedupKey==null){let clash2=uniqueCollision(record.data,null);if(clash2){collide(clash2.field,clash2.value);continue}issues.push({sourceIndex:record.sourceIndex,kind:"missing_dedup_key",message:"Deduplication field is empty on this record \u2014 it will be added as new."}),keepReferences(),claimUnique(record.data,null);let withLogin=withAccount(record,newRecord(record.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record.sourceIndex});continue}let claimedBy=seenInBatch.get(record.dedupKey);if(claimedBy){issues.push({sourceIndex:record.sourceIndex,kind:"duplicate_in_batch",message:`The source carries this key more than once: ${record.dedupKey} \u2014 this occurrence is skipped.`,dedupKey:record.dedupKey,row:record.data,keptRow:claimedBy});continue}if(seenInBatch.set(record.dedupKey,record.data),writeMode==="replace"||writeMode==="append"){let clash2=uniqueCollision(record.data,null);if(clash2&&!(writeMode==="replace"&&claimedUnique.get(clash2.field)?.get(clash2.value))){collide(clash2.field,clash2.value);continue}keepReferences(),claimUnique(record.data,null);let withLogin=withAccount(record,newRecord(record.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record.sourceIndex});continue}let match=existingByKey.get(record.dedupKey),clash=uniqueCollision(record.data,match?.id??null);if(clash){collide(clash.field,clash.value);continue}if(keepReferences(),claimUnique(record.data,match?.id??null),match)matchedIds.add(match.id),updates.push({id:match.id,data:record.data,before:match.row,sourceIndex:record.sourceIndex});else{let withLogin=withAccount(record,newRecord(record.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record.sourceIndex})}}if(writeMode==="replace")deletes.push(...existing);else if(writeMode==="upsert"&&missingPolicy!=="ignore"){let gone=existing.filter((row)=>!matchedIds.has(row.id));if(missingPolicy==="delete")deletes.push(...gone);else deactivates.push(...gone)}if(!dedupConfigured&&writeMode==="upsert")warnings.push("No deduplication field is set. Nothing can be matched against what is already here, so every record is added as new and each run will duplicate the last.");if(dedupConfigured&&records.length>0&&records.every((r)=>r.dedupKey==null))warnings.push(`The deduplication field is empty on every record \u2014 it is probably not among the mapped fields. As it stands nothing can ever match: all ${records.length} records will be re-added on every run.`);if(writeMode==="append"&&dedupConfigured)warnings.push("Append mode ignores the deduplication field: records already here will be added again.");if(writeMode==="replace"){if(warnings.push(`Replace mode: all ${existing.length} rows currently in the table are deleted and rebuilt from the source. Row identifiers change, so anything referring to them by id breaks.`),dedupConfigured)warnings.push('A deduplication field is set, so "update existing" would keep the current rows and their identifiers. Replace mode is only necessary when records cannot be matched at all.')}if(deactivates.length>0&&(!missingColumn||isEmpty2(missingValue)))warnings.push('The policy for records missing from the source is "deactivate", but no column and value are configured \u2014 those rows will be left untouched.'),deactivates.length=0;return{creates,updates,deletes,deactivates,deferredLinks,issues,warnings,referenceInserts,accountInserts,accountJoinInserts}}var SKIPPING_ISSUE_KINDS=new Set(["missing_required","duplicate_in_batch","duplicate_value","invalid_value","missing_reference"]);function skippedRecordCount(issues){let records=new Set;for(let issue of issues){if(!SKIPPING_ISSUE_KINDS.has(issue.kind))continue;if(issue.sourceIndex<0)continue;records.add(issue.sourceIndex)}return records.size}function summarize(ops,meta){let sampleSize=meta.sampleSize??8,sample=[...ops.creates.map((c)=>c.data),...ops.updates.map((u)=>u.data)].slice(0,sampleSize);return{targetEntity:meta.targetEntity,writeMode:meta.writeMode,totalFetched:meta.totalFetched,reportedTotal:meta.reportedTotal,toCreate:ops.creates.length,toUpdate:ops.updates.length,toSkip:skippedRecordCount(ops.issues),missing:ops.deletes.length+ops.deactivates.length,missingPolicy:meta.missingPolicy,existingCount:meta.existingCount,dedupConfigured:meta.dedupConfigured,issues:ops.issues,warnings:ops.warnings,referenceInserts:ops.referenceInserts,accountsToCreate:ops.accountInserts.length,sample}}import{and as and15,eq as eq30,inArray as inArray10}from"drizzle-orm";var INSERT_CHUNK=500,DELETE_CHUNK=1000;function validateAll(ops,columns,validatePayload2){let issues=[];for(let op of ops.creates){let result=validatePayload2(op.data,columns,!1);if(!result.valid)issues.push({sourceIndex:op.sourceIndex,kind:"invalid_value",message:result.errors.map((e)=>`${e.field}: ${e.message}`).join("; "),row:op.data})}for(let op of ops.updates){let result=validatePayload2(op.data,columns,!0);if(!result.valid)issues.push({sourceIndex:op.sourceIndex,kind:"invalid_value",message:result.errors.map((e)=>`${e.field}: ${e.message}`).join("; "),row:op.data})}return issues}function referenceRow(columns,column,value,extra){let row={...extra,[column]:value},unfillable=[];for(let col8 of columns){let name2=camel(col8.name);if(name2===column)continue;if(SYSTEM_COLUMNS.has(name2))continue;if(row[name2]!==void 0)continue;if(!(col8.notNull===!0&&col8.default===void 0&&!col8.defaultRaw&&!col8.nullable))continue;if(TEXTUAL_TYPES.has(String(col8.type)))row[name2]=value;else unfillable.push(name2)}return unfillable.length>0?{ok:!1,unfillable}:{ok:!0,row}}var SYSTEM_COLUMNS=new Set(["id","createdAt","updatedAt","createdBy","updatedBy"]),TEXTUAL_TYPES=new Set(["text","varchar","char"]),camel=(s)=>s.replace(/_([a-z])/g,(_m,l)=>l.toUpperCase());function linkAccount(data,rule,idByValue){let value=data[rule.matchFrom];if(value==null)return{};let id=idByValue.get(String(value).trim());return id?{[rule.idColumn]:id}:{}}function groupDeferred(links){let groups=new Map;for(let link of links){let key2=`${link.writeIdTo}|${link.lookupColumn}|${link.matchColumn}`,group=groups.get(key2)??{writeIdTo:link.writeIdTo,lookupColumn:link.lookupColumn,matchColumn:link.matchColumn,links:[]};group.links.push(link),groups.set(key2,group)}return[...groups.values()]}async function applyWrites(deps,input){let{ops,targetEntity,userId}=input,scope=input.scopeConditions??[],writeIssues=[],cancelled=()=>input.isCancelled?.()===!0,invalid=validateAll(ops,deps.columns,deps.validatePayload);if(invalid.length>0)return{ok:!1,failure:{kind:"validation_failed",message:`${invalid.length} record(s) did not pass validation; nothing was written.`,issues:invalid}};let refGroups=new Map;for(let insert of ops.referenceInserts){let resolved=deps.resolveEntity?.(insert.entity);if(!resolved)return{ok:!1,failure:{kind:"write_failed",message:`This mapping would add \u201C${insert.value}\u201D to "${insert.entity}", which does not exist.`}};let built=referenceRow(resolved.columns,insert.column,insert.value,insert.data);if(!built.ok)return{ok:!1,failure:{kind:"write_failed",message:`\u201C${insert.value}\u201D cannot be added to "${insert.entity}" automatically: ${built.unfillable.join(", ")} is required and cannot be filled from a ${insert.column}. Add the missing ${insert.entity} by hand, or set this reference to leave those records out.`}};let group=refGroups.get(insert.entity)??{table:resolved.table,rows:[]};group.rows.push(built.row),refGroups.set(insert.entity,group)}let accountRule=input.account??null,accountEntity=ops.accountInserts[0]?.entity,accountTable=accountEntity?deps.resolveEntity?.(accountEntity):null;if(ops.accountInserts.length>0&&(!accountRule||!accountTable))return{ok:!1,failure:{kind:"write_failed",message:accountRule?`This import creates accounts in "${accountEntity}", which does not exist.`:"This import creates accounts but no password was supplied, so nothing was written."}};if(cancelled())return{ok:!1,failure:{kind:"cancelled",message:"Cancelled before writing."}};let idCol=deps.resolveCol("id");if(!idCol)return{ok:!1,failure:{kind:"write_failed",message:`Target "${targetEntity}" has no id column; the engine cannot address its rows.`}};let preImages=[],now=new Date,deactivating=ops.deactivates.length>0&&input.missingColumn&&input.missingValue!=null?{column:input.missingColumn,value:input.missingValue}:null;try{let linksMade=0;return{ok:!0,...await deps.db.transaction(async(tx)=>{let created=0,accountsCreated=0,referencesCreated=0,updated=0,deleted=0,deactivated=0;if(ops.deletes.length>0){let ids=ops.deletes.map((r)=>r.id);for(let i=0;i<ids.length;i+=DELETE_CHUNK){if(cancelled())throw new CancelledError;let chunk=ids.slice(i,i+DELETE_CHUNK),where=scope.length?and15(inArray10(idCol,chunk),...scope):inArray10(idCol,chunk),removed=await tx.delete(deps.drizzleTable).where(where).returning();deleted+=removed.length;for(let row of removed)preImages.push({targetEntity,rowId:String(row.id),action:"delete",before:row,after:null})}}for(let op of ops.updates){if(cancelled())throw new CancelledError;let where=scope.length?and15(eq30(idCol,op.id),...scope):eq30(idCol,op.id),written=await tx.update(deps.drizzleTable).set({...op.data,updatedAt:now,updatedBy:userId}).where(where).returning();if(written.length===0)throw new ScopeError(op.id);updated+=written.length,preImages.push({targetEntity,rowId:op.id,action:"update",before:op.before,after:written[0]??null})}if(deactivating){let ids=ops.deactivates.map((r)=>r.id);for(let i=0;i<ids.length;i+=DELETE_CHUNK){if(cancelled())throw new CancelledError;let chunk=ids.slice(i,i+DELETE_CHUNK),where=scope.length?and15(inArray10(idCol,chunk),...scope):inArray10(idCol,chunk),written=await tx.update(deps.drizzleTable).set({[deactivating.column]:deactivating.value,updatedAt:now,updatedBy:userId}).where(where).returning();deactivated+=written.length;let beforeById=new Map(ops.deactivates.map((r)=>[r.id,r.row]));for(let row of written){let id=String(row.id);preImages.push({targetEntity,rowId:id,action:"deactivate",before:beforeById.get(id)??null,after:row})}}}for(let[entity,group]of refGroups){let rows=group.rows.map((r)=>({...r,createdAt:now,updatedAt:now,createdBy:userId}));for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let written=await tx.insert(group.table).values(rows.slice(i,i+INSERT_CHUNK)).returning();referencesCreated+=written.length;for(let row of written)preImages.push({targetEntity:entity,rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}let accountIdByValue=new Map;if(accountRule&&accountTable&&ops.accountInserts.length>0){let rows=ops.accountInserts.map((account)=>({...account.data,[accountRule.passwordColumn]:accountRule.passwordHash,createdAt:now,updatedAt:now,createdBy:userId}));for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let written=await tx.insert(accountTable.table).values(rows.slice(i,i+INSERT_CHUNK)).returning();accountsCreated+=written.length;for(let row of written){let value=row[accountRule.matchColumn];if(row.id!=null&&value!=null)accountIdByValue.set(String(value).trim(),String(row.id));preImages.push({targetEntity:ops.accountInserts[0]?.entity??"",rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}}if(accountRule&&ops.accountJoinInserts.length>0){let byEntity=new Map;for(let join7 of ops.accountJoinInserts){let accountId=accountIdByValue.get(join7.matchValue);if(!accountId){writeIssues.push({sourceIndex:-1,kind:"account_role_missing",message:`No account was found for \u201C${join7.matchValue}\u201D, so its ${join7.entity} row was not written \u2014 that person can sign in and do nothing.`});continue}let rows=byEntity.get(join7.entity)??[];rows.push({...join7.values,[join7.accountColumn]:accountId,createdAt:now,updatedAt:now,createdBy:userId}),byEntity.set(join7.entity,rows)}for(let[entity,rows]of byEntity){let resolved=deps.resolveEntity?.(entity);if(!resolved){writeIssues.push({sourceIndex:-1,kind:"account_role_missing",message:`The \u201C${entity}\u201D table is not available here, so ${rows.length} role membership(s) were not written. The accounts exist but hold no role.`});continue}for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let written=await tx.insert(resolved.table).values(rows.slice(i,i+INSERT_CHUNK)).returning();for(let row of written)preImages.push({targetEntity:entity,rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}}if(ops.creates.length>0){let rows=ops.creates.map((c)=>({...c.data,...accountRule&&c.data[accountRule.idColumn]==null?linkAccount(c.data,accountRule,accountIdByValue):{},createdAt:now,updatedAt:now,createdBy:userId}));for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let chunk=rows.slice(i,i+INSERT_CHUNK),written=await tx.insert(deps.drizzleTable).values(chunk).returning();created+=written.length;for(let row of written)preImages.push({targetEntity,rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}if(ops.deferredLinks.length>0)for(let group of groupDeferred(ops.deferredLinks)){if(cancelled())throw new CancelledError;let lookupCol=deps.resolveCol(group.lookupColumn),matchCol=deps.resolveCol(group.matchColumn),targetCol=deps.resolveCol(group.writeIdTo);if(!lookupCol||!matchCol||!targetCol){deps.logger.error(`[Integrations] Cannot fill "${group.writeIdTo}": ${[!lookupCol?group.lookupColumn:null,!matchCol?group.matchColumn:null,!targetCol?group.writeIdTo:null].filter(Boolean).join(", ")} is not a column of "${targetEntity}".`);continue}let rows=await tx.select({v:lookupCol,id:idCol}).from(deps.drizzleTable),idByValue={};for(let row of rows){if(row.v==null||row.v===""||row.id==null)continue;let key2=String(row.v).trim();if(!idByValue[key2])idByValue[key2]=String(row.id)}let imageByMatch=new Map;for(let image of preImages){let value=image.after?.[group.matchColumn];if(value!=null)imageByMatch.set(String(value).trim(),image)}for(let link of group.links){let resolution=resolveReference(link.raw,idByValue);if(resolution.ids.length===0)continue;let value=link.isList?resolution.ids:resolution.ids[0],where=scope.length?and15(eq30(matchCol,link.matchValue),...scope):eq30(matchCol,link.matchValue);if((await tx.update(deps.drizzleTable).set({[group.writeIdTo]:value}).where(where).returning()).length===0)continue;linksMade+=1;let image=imageByMatch.get(String(link.matchValue).trim());if(image?.after)image.after={...image.after,[group.writeIdTo]:value}}}return{created,accountsCreated,referencesCreated,updated,deleted,deactivated,linksMade}}),preImages,issues:writeIssues}}catch(error){if(error instanceof CancelledError)return{ok:!1,failure:{kind:"cancelled",message:"Cancelled while writing; the transaction was rolled back and nothing changed."}};if(error instanceof ScopeError)return{ok:!1,failure:{kind:"write_failed",message:`Row ${error.rowId} is outside what this account may write. Nothing was written.`}};return deps.logger.error(`[Integrations] Write transaction failed for "${targetEntity}": ${reasonOf2(error)}`),{ok:!1,failure:{kind:"write_failed",message:"The database rejected this import; it was rolled back and nothing changed."}}}}function reasonOf2(error){if(!(error instanceof Error))return String(error);let extra=error;return[error.message,extra.detail?`detail: ${String(extra.detail)}`:"",extra.constraint?`constraint: ${String(extra.constraint)}`:"",extra.hint?`hint: ${String(extra.hint)}`:"",extra.code?`code: ${String(extra.code)}`:""].filter(Boolean).join(" \u2014 ")}class CancelledError extends Error{constructor(){super("cancelled");this.name="CancelledError"}}class ScopeError extends Error{rowId;constructor(rowId){super(`row ${rowId} outside scope`);this.rowId=rowId;this.name="ScopeError"}}var DAY_MS=86400000;async function executeRun(context,deps,lock){let{mapping,source}=context,empty={runId:null,status:"failed",failure:null,plan:null,created:0,accountsCreated:0,referencesCreated:0,updated:0,deleted:0,deactivated:0,externalCalls:0,issues:[]},opened=await deps.repo.openRun({mappingId:mapping.id,targetEntity:mapping.targetEntity,writeMode:mapping.writeMode,trigger:context.trigger,userId:context.userId});if(!opened.ok)return{...empty,failure:{kind:"write_failed",message:`Could not open a run record: ${opened.error}`}};let runId=opened.runId;deps.onRunOpened?.(runId);let cancelChecked=0,cancelSeen=!1,isCancelled=()=>{if(cancelSeen)return!0;if(deps.abortSignal?.aborted)return cancelSeen=!0,!0;if(!lock.isHeld())return cancelSeen=!0,!0;return!1},refreshCancelFlag=async()=>{if(cancelChecked++,await deps.repo.isCancelRequested(runId))cancelSeen=!0},progress=(p)=>deps.publishProgress?.({runId,...p}),finish=async(outcome)=>{let revertible=outcome.status==="succeeded";return await deps.repo.closeRun(runId,{status:outcome.status,totalFetched:outcome.plan?.totalFetched??0,sourceReportedTotal:outcome.plan?.reportedTotal??null,createdCount:outcome.created,accountsCreatedCount:outcome.accountsCreated,referencesCreatedCount:outcome.referencesCreated,updatedCount:outcome.updated,skippedCount:outcome.plan?.toSkip??0,deletedCount:outcome.deleted,deactivatedCount:outcome.deactivated,errorCount:outcome.failure?1:0,externalCallCount:outcome.externalCalls,issues:outcome.issues,errors:outcome.failure?[outcome.failure.message]:[],finishedAt:new Date,preImagesExpireAt:revertible?new Date(Date.now()+(deps.retainPreImagesMs??30*DAY_MS)):null}),progress({phase:"done",message:outcome.failure?.message}),{runId,...outcome}};progress({phase:"fetching"});let nodes=context.endpoints.map((e)=>({id:e.id,paramBindings:e.paramBindings??void 0})),order=planFetchOrder(nodes,[context.rootEndpointId]);if(!order.ok){let failure=order.reason==="cycle"?{kind:"cycle_detected",message:`These endpoints depend on each other in a loop (${order.chain.join(" \u2192 ")}), so there is no correct place to start.`,chain:order.chain}:{kind:"fetch_failed",message:`"${order.requiredBy}" takes a parameter from an endpoint that no longer exists (${order.missing}).`};return finish({...empty,failure,status:"failed"})}let bearerToken=null;if(context.oauth){let token=await getAccessToken(context.oauth,deps.tokenCache,{fetchImpl:deps.fetchImpl});if(!token.ok)return finish({...empty,failure:{kind:"fetch_failed",message:token.error}});bearerToken=token.accessToken}let byId=new Map(context.endpoints.map((e)=>[e.id,e])),parents={},entityValues={},externalCalls=0,rootRecords=[],fetchWarnings=[],reportedTotal2=null;for(let endpointId of order.order){let endpoint=byId.get(endpointId);if(!endpoint)continue;if(await refreshCancelFlag(),isCancelled())return finish({...empty,status:"cancelled",externalCalls,failure:{kind:"cancelled",message:"Cancelled before anything was written."}});for(let key2 of entityValueKeys(endpoint.paramBindings??void 0)){if(entityValues[key2])continue;let[entity,field]=key2.split(".");entityValues[key2]=await deps.repo.readEntityColumn(entity,field)}let collected=await collectEndpoint(source,endpoint,{parents,entityValues},{fetchImpl:deps.fetchImpl,bearerToken,maxConcurrent:deps.maxConcurrentFanout,maxRecords:deps.maxRecordsPerRun,isCancelled,onProgress:(n)=>progress({phase:"fetching",processed:n})});if(externalCalls+=collected.callCount,!collected.ok){let cancelled=isCancelled();return finish({...empty,externalCalls,status:cancelled?"cancelled":"failed",failure:cancelled?{kind:"cancelled",message:"Cancelled before anything was written."}:{kind:"fetch_failed",message:collected.error}})}if(collected.partialFailures?.length)fetchWarnings.push(...collected.partialFailures.map((failure)=>`Could not read part of this source \u2014 ${failure}`));if(parents[endpointId]=collected.records.map((r)=>r.record),endpointId===context.rootEndpointId)rootRecords=collected.records,reportedTotal2=collected.reportedTotal}for(let id of Object.keys(parents))if(!(id===context.rootEndpointId||context.endpoints.some((e)=>parentEndpointIds(e.paramBindings??void 0).includes(id))))delete parents[id];let verdict=destructiveGate({writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,fetched:rootRecords.length,reported:reportedTotal2,partialFailures:fetchWarnings.length});if(!verdict.allowed)return finish({...empty,externalCalls,failure:{kind:verdict.kind,message:fetchWarnings.length?`${verdict.message}
|
|
533
533
|
|
|
534
534
|
${fetchWarnings.join(`
|
|
535
|
-
`)}`:verdict.message,fetched:rootRecords.length,reported:reportedTotal2??0}});progress({phase:"mapping",total:rootRecords.length});let mapped=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:mapped.records,existing,dedupTargetField:mapping.dedupTargetField,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,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(...mapped.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)});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 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]})}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 sets=[];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.`};sets.push(field)}return{ok:!0,fields:{minutes:sets[0],hours:sets[1],daysOfMonth:sets[2],months:sets[3],daysOfWeek:sets[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}).`}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(error){deps.logger.error("[Integrations] Could not read the schedules",{error:error instanceof Error?error.message:String(error)});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(error){deps.logger.error(`[Integrations] Scheduled import failed to start: ${mapping.name}`,{error:error instanceof Error?error.message:String(error)})}},timer=setInterval(()=>{tick()},MINUTE2);if(typeof timer==="object"&&timer&&"unref"in timer)timer.unref();return{stop:()=>{stopped=!0,clearInterval(timer)}}}var asObject=(value)=>value&&typeof value==="object"&&!Array.isArray(value)?value:null,asArray=(value)=>Array.isArray(value)?value:[],text=(value)=>{if(value==null)return null;let s=String(value);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,...rest}=binding;return{...rest,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),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(value){let problems=[],doc=asObject(value);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_utils5();init_utils6();init_godminGuard();init_utils5();import Elysia12,{t as t10}from"elysia";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,value]of Object.entries(values)){if(value==null||resolved.used.has(key2))continue;query[key2]=value}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 mapped=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:mapped.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(...mapped.issues),ops.warnings.push(...ambiguityWarnings(references.ambiguous));let invalid=lookups.validateRows?.(ops.creates)??[];for(let problem of invalid){let create3=ops.creates.find((c)=>c.sourceIndex===problem.sourceIndex);ops.issues.push({sourceIndex:problem.sourceIndex,kind:"invalid_value",message:problem.message,row:create3?.data})}if(invalid.length>0){let refused=new Set(invalid.map((problem)=>problem.sourceIndex));ops.creates=ops.creates.filter((create3)=>!refused.has(create3.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 and16,eq as eq31,inArray as inArray11}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(eq31(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(eq31(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(eq31(col8(endpoints,"sourceId"),sourceId)),[sourceRow]=await deps.db.select().from(sources).where(eq31(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(eq31(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(eq31(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(eq31(col8(endpoints,"id"),endpointId))}catch(error){deps.logger.warn("[Integrations] Could not cache the endpoint sample",{endpointId,error:error instanceof Error?error.message:String(error)})}}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,missingValue:row.missingRecordValue??null,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(eq31(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return null;let endpointRows=await deps.db.select().from(endpoints).where(eq31(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,document2,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=document2.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(eq31(col8(sources,"slug"),c.slug)).limit(1),sourceId,created=!1;if(existingSource)sourceId=String(existingSource.id),await tx.update(sources).set(connectionValues).where(eq31(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(eq31(col8(endpoints,"sourceId"),sourceId)),endpointIdByName=new Map;for(let row of existingEndpoints)endpointIdByName.set(String(row.name),String(row.id));for(let endpoint of document2.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(eq31(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 document2.endpoints){let id=endpointIdByName.get(endpoint.name);if(!id)continue;let bindings=(endpoint.paramBindings??[]).map((binding)=>{if(binding.from!=="endpoint")return binding;let{endpointName,...rest}=binding;return{...rest,endpointId:endpointIdByName.get(String(endpointName))??""}});await tx.update(endpoints).set({paramBindings:bindings,updatedAt:now}).where(eq31(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 document2.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,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(eq31(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}).returning())[0]?.id;return id?{ok:!0,runId:String(id)}:{ok:!1,error:"The run record was created but returned no identifier."}}catch(error){return{ok:!1,error:error instanceof Error?error.message:String(error)}}},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(eq31(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(eq31(col8(mappings,"id"),String(mappingId)));return{ok:!0}}catch(error){return deps.logger.error("[Integrations] Could not close the run record",{runId,error:error instanceof Error?error.message:String(error)}),{ok:!1,error:"Could not write the run record."}}},async savePreImages(runId,images){let table=runItems();if(!table||images.length===0)return;let CHUNK2=500;try{for(let i=0;i<images.length;i+=CHUNK2)await deps.db.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})))}catch(error){deps.logger.error("[Integrations] The run succeeded but its before-images could not be stored; it cannot be undone",{runId,error:error instanceof Error?error.message:String(error)})}},async isCancelRequested(runId){let table=runs();if(!table)return!1;try{let[row]=await deps.db.select().from(table).where(eq31(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),map={};for(let row of rows){if(row.v==null||row.v===""||row.id==null)continue;let key2=String(row.v).trim();if(!map[key2])map[key2]=String(row.id)}return map},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(inArray11(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(eq31(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(eq31(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(and16(eq31(col8(table,"id"),runId),eq31(col8(table,"status"),"running"))).returning()).length>0}var fail3=(set,status,message)=>{return set.status=status,{isSuccess:!1,message,data:null}};function createInspectRoutes(routeConfig,deps,tokenCache){let plugin=new Elysia12,settings=routeConfig.resolvedOptions.integrations??{};return plugin.post("/sources/:id/test",async({params,set})=>{let repoDeps=deps();if(!repoDeps)return fail3(set,503,"The database is not available.");let context=await resolveEndpointContext(repoDeps,{sourceId:params.id});if(!context.ok)return fail3(set,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}},{detail:{tags:["Integrations"],summary:"Check a source is reachable and its credentials are accepted"}}),plugin.post("/endpoints/:id/sample",async({params,body,set})=>{let repoDeps=deps();if(!repoDeps)return fail3(set,503,"The database is not available.");let context=await resolveEndpointContext(repoDeps,{endpointId:params.id});if(!context.ok)return fail3(set,404,context.error);let{source,oauth,endpoint}=context.context;if(!endpoint)return fail3(set,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}},{body:t10.Optional(t10.Object({pathParams:t10.Optional(t10.Record(t10.String(),t10.Unknown()))})),detail:{tags:["Integrations"],summary:"Call one endpoint once and report its real shape"}}),plugin.post("/preview",async({body,set})=>{let repoDeps=deps();if(!repoDeps)return fail3(set,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail3(set,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(set,422,result.error);return{isSuccess:!0,message:"Nothing was written.",data:{plan:result.plan,externalCalls:result.externalCalls}}},{body:t10.Object({mappingId:t10.String()}),detail:{tags:["Integrations"],summary:"Dry-run an import: counts, warnings and a sample, with no writes"}}),plugin}var fail4=(set,status,message)=>{return set.status=status,{isSuccess:!1,message,data:null}},tokenCache=new TokenCache;function createIntegrationRoutes(routeConfig){let{logger:logger2,basePath,db,schemaTables,resolvedOptions,getEncryptionKey,getLockStore,publish}=routeConfig,plugin=new Elysia13({prefix:basePath}),entities=resolveEntities(SYSTEM_TABLES,resolvedOptions.entities??[],{consumerMode:resolvedOptions.authentication?.mode==="consumer"}).entities,settings=resolvedOptions.integrations??{},topicPrefix=settings.realtimeTopicPrefix||"integrations",guard2=createGodminGuard2({logger:logger2,db,schemaTables,area:"data integrations",allowedRoles:routeConfig.adminRoles??settings.adminRoles});plugin.onBeforeHandle(guard2);let deps=()=>db?{db,schemaTables,entities,logger:logger2,getEncryptionKey}:null;plugin.use(createInspectRoutes(routeConfig,deps,tokenCache));let 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??[]}}}};plugin.post("/runs",async({body,set,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail4(set,404,chain.error);let declared=chain.chain.mapping,answer=body.referenceMode,mapping=answer?{...declared,references:(declared.references??[]).map((check)=>check.onMissing==="ignore"?check:{...check,onMissing:answer})}:declared,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(set,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 set.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(set,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(set,503,`The lock service is unavailable: ${lock.error}`);return set.status=409,{isSuccess:!1,message:"This mapping is already importing. Wait for it to finish, or cancel it.",data:{heldSince:lock.heldSince}}}let writer=writerFor(repoDeps,mapping.targetEntity);if(!writer)return await lock.handle.release(),fail4(set,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(error)=>{let reason=error instanceof Error?error.message:String(error);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()),set.status=202,{isSuccess:!0,message:"The import has started.",data:{runId,mappingId:mapping.id,targetEntity:mapping.targetEntity}}},{body:t11.Object({mappingId:t11.String(),confirm:t11.Optional(t11.Boolean()),sharedPassword:t11.Optional(t11.String()),referenceMode:t11.Optional(t11.Union([t11.Literal("create"),t11.Literal("skipRecord")]))}),detail:{tags:["Integrations"],summary:"Start an import; answers 202 with the run identifier"}});let 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:"schedule",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((error)=>{logger2.error("[Integrations] Scheduled import threw outside its own error handling",{mappingId,error:error instanceof Error?error.message:String(error)})}).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)});return plugin.get("/sources/:id/export",async({params,set})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");let bundle=await readConnectionBundle(repoDeps,params.id);if(!bundle)return fail4(set,404,"This connection no longer exists.");let document2=buildTransferDocument({...bundle,exportedAt:new Date().toISOString()});return{isSuccess:!0,message:`${document2.endpoints.length} call(s) and ${document2.mappings.length} mapping(s). Credentials are NOT included \u2014 they are asked for again on the other side.`,data:document2}},{detail:{tags:["Integrations"],summary:"Export a connection, its calls and its mappings \u2014 without any secret"}}),plugin.post("/import",async({body,set,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");let checked=validateTransferDocument(body?.document);if(!checked.ok||!checked.document)return set.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(set,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}},{body:t11.Object({document:t11.Unknown()}),detail:{tags:["Integrations"],summary:"Bring in an exported connection; re-running it updates rather than duplicates"}}),plugin.post("/runs/:id/cancel",async({params,set})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");if(!await requestCancel(repoDeps,params.id))return fail4(set,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}}},{detail:{tags:["Integrations"],summary:"Ask a running import to stop"}}),plugin.post("/runs/:id/revert",async({params,body,set,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");let run=await readRun(repoDeps,params.id);if(!run)return fail4(set,404,"This run no longer exists.");if(run.status!=="succeeded")return fail4(set,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(set,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(set,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 set.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(set,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(error){let message=failure??(error instanceof Error?error.message:"The undo could not be completed.");return fail4(set,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:eq32}=await import("drizzle-orm");await repoDeps.db.update(runsTable).set({status:"reverted",revertedAt:new Date,revertedBy:userId}).where(eq32(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}}},{body:t11.Optional(t11.Object({force:t11.Optional(t11.Boolean())})),detail:{tags:["Integrations"],summary:"Undo a run that succeeded"}}),plugin.put("/sources/:id/credentials",async({params,body,set})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");let key2=getEncryptionKey();if(!key2)return fail4(set,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(set,503,"The integration tables are not present in this schema.");let patch={},hints={},apply=(value,column,hintColumn,name2)=>{if(value===void 0)return;let trimmed=value.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:eq32}=await import("drizzle-orm");return await repoDeps.db.update(table).set({...patch,updatedAt:new Date}).where(eq32(table.id,params.id)),tokenCache.invalidate(params.id),{isSuccess:!0,message:"Saved.",data:{hints}}},{body:t11.Object({token:t11.Optional(t11.String()),oauthClientSecret:t11.Optional(t11.String()),oauthPassword:t11.Optional(t11.String())}),detail:{tags:["Integrations"],summary:"Store a source credential; only a masked preview is returned"}}),logger2.info(`[Integrations] Routes enabled at ${basePath}`),plugin}function parseDuration(value){if(!value)return;let match=value.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_adminGuard();import Elysia14,{t as t12}from"elysia";function parseTimeToMs2(time2){let match=time2.match(/^(\d+)(ms|s|m|h)$/);if(!match||!match[1]||!match[2])return 5000;let value=parseInt(match[1],10);switch(match[2]){case"ms":return value;case"s":return value*1000;case"m":return value*60*1000;case"h":return value*60*60*1000;default:return 5000}}function createMonitoringRoutes(config){let{getService,logger:logger2,endpoints,db,schemaTables}=config,plugin=new Elysia14({prefix:endpoints.basePath}),snapshotOrNull=async()=>await getService()?.getLatestSnapshot()??null,alertsOrEmpty=()=>getService()?.getActiveAlerts()??[];if(plugin.onBeforeHandle(createPrivilegedGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"monitoring",{allowRoles:endpoints.allowRoles})),!endpoints.enabled)return plugin;if(endpoints.stream.enabled)plugin.get(endpoints.stream.path,async function*({request}){logger2.info("[Monitoring] SSE stream connected");let intervalMs=parseTimeToMs2(endpoints.stream.interval),isConnected=!0;request.signal.addEventListener("abort",()=>{isConnected=!1,logger2.info("[Monitoring] SSE stream disconnected")});let initialSnapshot=await snapshotOrNull();if(initialSnapshot)yield{event:"snapshot",data:JSON.stringify(initialSnapshot)};while(isConnected){if(await new Promise((resolve2)=>setTimeout(resolve2,intervalMs)),!isConnected)break;let snapshot=await snapshotOrNull();if(snapshot)yield{event:"snapshot",data:JSON.stringify(snapshot)};let alerts=alertsOrEmpty();if(alerts.length>0)yield{event:"alerts",data:JSON.stringify(alerts)}}},{detail:{tags:["Monitoring"],summary:"Stream real-time monitoring data",description:"Server-Sent Events stream for real-time monitoring metrics"}});if(endpoints.snapshot.enabled)plugin.get(endpoints.snapshot.path,async()=>{let snapshot=await snapshotOrNull();if(!snapshot)return{isSuccess:!1,message:"No monitoring data available",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)plugin.get(endpoints.history.path,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:t12.Object({minutes:t12.Optional(t12.Numeric())}),detail:{tags:["Monitoring"],summary:"Get monitoring history",description:"Returns historical monitoring data for the specified time range"}});if(endpoints.alerts.enabled)plugin.get(endpoints.alerts.path,()=>{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"}}),plugin.post(`${endpoints.alerts.path}/:alertId/acknowledge`,({params})=>{if(!(getService()?.acknowledgeAlert(params.alertId)??!1))return{isSuccess:!1,message:"Alert not found",data:null};return{isSuccess:!0,message:"Alert acknowledged",data:{alertId:params.alertId}}},{params:t12.Object({alertId:t12.String()}),detail:{tags:["Monitoring"],summary:"Acknowledge an alert",description:"Mark an alert as acknowledged"}});return logger2.info(`[Monitoring] Routes enabled at ${endpoints.basePath} (stream: ${endpoints.stream.enabled}, snapshot: ${endpoints.snapshot.enabled}, history: ${endpoints.history.enabled}, alerts: ${endpoints.alerts.enabled})`),plugin}init_adminGuard();import Elysia15,{t as t13}from"elysia";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}
|
|
535
|
+
`)}`:verdict.message,fetched:rootRecords.length,reported:reportedTotal2??0}});progress({phase:"mapping",total:rootRecords.length});let mapped=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:mapped.records,existing,dedupTargetField:mapping.dedupTargetField,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,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(...mapped.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)});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 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]})}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 sets=[];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.`};sets.push(field)}return{ok:!0,fields:{minutes:sets[0],hours:sets[1],daysOfMonth:sets[2],months:sets[3],daysOfWeek:sets[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}).`}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(error){deps.logger.error("[Integrations] Could not read the schedules",{error:error instanceof Error?error.message:String(error)});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(error){deps.logger.error(`[Integrations] Scheduled import failed to start: ${mapping.name}`,{error:error instanceof Error?error.message:String(error)})}},timer=setInterval(()=>{tick()},MINUTE2);if(typeof timer==="object"&&timer&&"unref"in timer)timer.unref();return{stop:()=>{stopped=!0,clearInterval(timer)}}}var asObject=(value)=>value&&typeof value==="object"&&!Array.isArray(value)?value:null,asArray=(value)=>Array.isArray(value)?value:[],text=(value)=>{if(value==null)return null;let s=String(value);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,...rest}=binding;return{...rest,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),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(value){let problems=[],doc=asObject(value);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_utils5();init_utils6();init_godminGuard();init_utils5();import Elysia12,{t as t10}from"elysia";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,value]of Object.entries(values)){if(value==null||resolved.used.has(key2))continue;query[key2]=value}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 mapped=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:mapped.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(...mapped.issues),ops.warnings.push(...ambiguityWarnings(references.ambiguous));let invalid=lookups.validateRows?.(ops.creates)??[];for(let problem of invalid){let create3=ops.creates.find((c)=>c.sourceIndex===problem.sourceIndex);ops.issues.push({sourceIndex:problem.sourceIndex,kind:"invalid_value",message:problem.message,row:create3?.data})}if(invalid.length>0){let refused=new Set(invalid.map((problem)=>problem.sourceIndex));ops.creates=ops.creates.filter((create3)=>!refused.has(create3.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 and16,eq as eq31,inArray as inArray11}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(eq31(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(eq31(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(eq31(col8(endpoints,"sourceId"),sourceId)),[sourceRow]=await deps.db.select().from(sources).where(eq31(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(eq31(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(eq31(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(eq31(col8(endpoints,"id"),endpointId))}catch(error){deps.logger.warn("[Integrations] Could not cache the endpoint sample",{endpointId,error:error instanceof Error?error.message:String(error)})}}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,missingValue:row.missingRecordValue??null,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(eq31(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return null;let endpointRows=await deps.db.select().from(endpoints).where(eq31(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,document2,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=document2.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(eq31(col8(sources,"slug"),c.slug)).limit(1),sourceId,created=!1;if(existingSource)sourceId=String(existingSource.id),await tx.update(sources).set(connectionValues).where(eq31(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(eq31(col8(endpoints,"sourceId"),sourceId)),endpointIdByName=new Map;for(let row of existingEndpoints)endpointIdByName.set(String(row.name),String(row.id));for(let endpoint of document2.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(eq31(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 document2.endpoints){let id=endpointIdByName.get(endpoint.name);if(!id)continue;let bindings=(endpoint.paramBindings??[]).map((binding)=>{if(binding.from!=="endpoint")return binding;let{endpointName,...rest}=binding;return{...rest,endpointId:endpointIdByName.get(String(endpointName))??""}});await tx.update(endpoints).set({paramBindings:bindings,updatedAt:now}).where(eq31(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 document2.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,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(eq31(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}).returning())[0]?.id;return id?{ok:!0,runId:String(id)}:{ok:!1,error:"The run record was created but returned no identifier."}}catch(error){return{ok:!1,error:error instanceof Error?error.message:String(error)}}},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(eq31(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(eq31(col8(mappings,"id"),String(mappingId)));return{ok:!0}}catch(error){return deps.logger.error("[Integrations] Could not close the run record",{runId,error:error instanceof Error?error.message:String(error)}),{ok:!1,error:"Could not write the run record."}}},async savePreImages(runId,images){let table=runItems();if(!table||images.length===0)return;let CHUNK2=500;try{for(let i=0;i<images.length;i+=CHUNK2)await deps.db.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})))}catch(error){deps.logger.error("[Integrations] The run succeeded but its before-images could not be stored; it cannot be undone",{runId,error:error instanceof Error?error.message:String(error)})}},async isCancelRequested(runId){let table=runs();if(!table)return!1;try{let[row]=await deps.db.select().from(table).where(eq31(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),map={};for(let row of rows){if(row.v==null||row.v===""||row.id==null)continue;let key2=String(row.v).trim();if(!map[key2])map[key2]=String(row.id)}return map},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(inArray11(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(eq31(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(eq31(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(and16(eq31(col8(table,"id"),runId),eq31(col8(table,"status"),"running"))).returning()).length>0}var fail3=(set,status,message)=>{return set.status=status,{isSuccess:!1,message,data:null}};function createInspectRoutes(routeConfig,deps,tokenCache){let plugin=new Elysia12,settings=routeConfig.resolvedOptions.integrations??{};return plugin.post("/sources/:id/test",async({params,set})=>{let repoDeps=deps();if(!repoDeps)return fail3(set,503,"The database is not available.");let context=await resolveEndpointContext(repoDeps,{sourceId:params.id});if(!context.ok)return fail3(set,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}},{detail:{tags:["Integrations"],summary:"Check a source is reachable and its credentials are accepted"}}),plugin.post("/endpoints/:id/sample",async({params,body,set})=>{let repoDeps=deps();if(!repoDeps)return fail3(set,503,"The database is not available.");let context=await resolveEndpointContext(repoDeps,{endpointId:params.id});if(!context.ok)return fail3(set,404,context.error);let{source,oauth,endpoint}=context.context;if(!endpoint)return fail3(set,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}},{body:t10.Optional(t10.Object({pathParams:t10.Optional(t10.Record(t10.String(),t10.Unknown()))})),detail:{tags:["Integrations"],summary:"Call one endpoint once and report its real shape"}}),plugin.post("/preview",async({body,set})=>{let repoDeps=deps();if(!repoDeps)return fail3(set,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail3(set,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(set,422,result.error);return{isSuccess:!0,message:"Nothing was written.",data:{plan:result.plan,externalCalls:result.externalCalls}}},{body:t10.Object({mappingId:t10.String()}),detail:{tags:["Integrations"],summary:"Dry-run an import: counts, warnings and a sample, with no writes"}}),plugin}var fail4=(set,status,message)=>{return set.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:and17,eq:eq32,inArray:inArray12}=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(and17(eq32(runsTable.mappingId,mappingId),inArray12(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(error){logger2.error("[Integrations] Could not close abandoned runs",error)}}var tokenCache=new TokenCache;function createIntegrationRoutes(routeConfig){let{logger:logger2,basePath,db,schemaTables,resolvedOptions,getEncryptionKey,getLockStore,publish}=routeConfig,plugin=new Elysia13({prefix:basePath}),entities=resolveEntities(SYSTEM_TABLES,resolvedOptions.entities??[],{consumerMode:resolvedOptions.authentication?.mode==="consumer"}).entities,settings=resolvedOptions.integrations??{},topicPrefix=settings.realtimeTopicPrefix||"integrations",guard2=createGodminGuard2({logger:logger2,db,schemaTables,area:"data integrations",allowedRoles:routeConfig.adminRoles??settings.adminRoles});plugin.onBeforeHandle(guard2);let deps=()=>db?{db,schemaTables,entities,logger:logger2,getEncryptionKey}:null;plugin.use(createInspectRoutes(routeConfig,deps,tokenCache));let 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??[]}}}};plugin.post("/runs",async({body,set,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail4(set,404,chain.error);let declared=chain.chain.mapping,answer=body.referenceMode,mapping=answer?{...declared,references:(declared.references??[]).map((check)=>check.onMissing==="ignore"?check:{...check,onMissing:answer})}:declared,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(set,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 set.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(set,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(set,503,`The lock service is unavailable: ${lock.error}`);return set.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(set,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(error)=>{let reason=error instanceof Error?error.message:String(error);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()),set.status=202,{isSuccess:!0,message:"The import has started.",data:{runId,mappingId:mapping.id,targetEntity:mapping.targetEntity}}},{body:t11.Object({mappingId:t11.String(),confirm:t11.Optional(t11.Boolean()),sharedPassword:t11.Optional(t11.String()),referenceMode:t11.Optional(t11.Union([t11.Literal("create"),t11.Literal("skipRecord")]))}),detail:{tags:["Integrations"],summary:"Start an import; answers 202 with the run identifier"}});let 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:"schedule",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((error)=>{logger2.error("[Integrations] Scheduled import threw outside its own error handling",{mappingId,error:error instanceof Error?error.message:String(error)})}).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)});return plugin.get("/sources/:id/export",async({params,set})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");let bundle=await readConnectionBundle(repoDeps,params.id);if(!bundle)return fail4(set,404,"This connection no longer exists.");let document2=buildTransferDocument({...bundle,exportedAt:new Date().toISOString()});return{isSuccess:!0,message:`${document2.endpoints.length} call(s) and ${document2.mappings.length} mapping(s). Credentials are NOT included \u2014 they are asked for again on the other side.`,data:document2}},{detail:{tags:["Integrations"],summary:"Export a connection, its calls and its mappings \u2014 without any secret"}}),plugin.post("/import",async({body,set,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");let checked=validateTransferDocument(body?.document);if(!checked.ok||!checked.document)return set.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(set,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}},{body:t11.Object({document:t11.Unknown()}),detail:{tags:["Integrations"],summary:"Bring in an exported connection; re-running it updates rather than duplicates"}}),plugin.post("/runs/:id/cancel",async({params,set})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");if(!await requestCancel(repoDeps,params.id))return fail4(set,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}}},{detail:{tags:["Integrations"],summary:"Ask a running import to stop"}}),plugin.post("/runs/:id/revert",async({params,body,set,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");let run=await readRun(repoDeps,params.id);if(!run)return fail4(set,404,"This run no longer exists.");if(run.status!=="succeeded")return fail4(set,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(set,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(set,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 set.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(set,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(error){let message=failure??(error instanceof Error?error.message:"The undo could not be completed.");return fail4(set,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:eq32}=await import("drizzle-orm");await repoDeps.db.update(runsTable).set({status:"reverted",revertedAt:new Date,revertedBy:userId}).where(eq32(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}}},{body:t11.Optional(t11.Object({force:t11.Optional(t11.Boolean())})),detail:{tags:["Integrations"],summary:"Undo a run that succeeded"}}),plugin.put("/sources/:id/credentials",async({params,body,set})=>{let repoDeps=deps();if(!repoDeps)return fail4(set,503,"The database is not available.");let key2=getEncryptionKey();if(!key2)return fail4(set,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(set,503,"The integration tables are not present in this schema.");let patch={},hints={},apply=(value,column,hintColumn,name2)=>{if(value===void 0)return;let trimmed=value.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:eq32}=await import("drizzle-orm");return await repoDeps.db.update(table).set({...patch,updatedAt:new Date}).where(eq32(table.id,params.id)),tokenCache.invalidate(params.id),{isSuccess:!0,message:"Saved.",data:{hints}}},{body:t11.Object({token:t11.Optional(t11.String()),oauthClientSecret:t11.Optional(t11.String()),oauthPassword:t11.Optional(t11.String())}),detail:{tags:["Integrations"],summary:"Store a source credential; only a masked preview is returned"}}),logger2.info(`[Integrations] Routes enabled at ${basePath}`),plugin}function parseDuration(value){if(!value)return;let match=value.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_adminGuard();import Elysia14,{t as t12}from"elysia";function parseTimeToMs2(time2){let match=time2.match(/^(\d+)(ms|s|m|h)$/);if(!match||!match[1]||!match[2])return 5000;let value=parseInt(match[1],10);switch(match[2]){case"ms":return value;case"s":return value*1000;case"m":return value*60*1000;case"h":return value*60*60*1000;default:return 5000}}function createMonitoringRoutes(config){let{getService,logger:logger2,endpoints,db,schemaTables}=config,plugin=new Elysia14({prefix:endpoints.basePath}),snapshotOrNull=async()=>await getService()?.getLatestSnapshot()??null,alertsOrEmpty=()=>getService()?.getActiveAlerts()??[];if(plugin.onBeforeHandle(createPrivilegedGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"monitoring",{allowRoles:endpoints.allowRoles})),!endpoints.enabled)return plugin;if(endpoints.stream.enabled)plugin.get(endpoints.stream.path,async function*({request}){logger2.info("[Monitoring] SSE stream connected");let intervalMs=parseTimeToMs2(endpoints.stream.interval),isConnected=!0;request.signal.addEventListener("abort",()=>{isConnected=!1,logger2.info("[Monitoring] SSE stream disconnected")});let initialSnapshot=await snapshotOrNull();if(initialSnapshot)yield{event:"snapshot",data:JSON.stringify(initialSnapshot)};while(isConnected){if(await new Promise((resolve2)=>setTimeout(resolve2,intervalMs)),!isConnected)break;let snapshot=await snapshotOrNull();if(snapshot)yield{event:"snapshot",data:JSON.stringify(snapshot)};let alerts=alertsOrEmpty();if(alerts.length>0)yield{event:"alerts",data:JSON.stringify(alerts)}}},{detail:{tags:["Monitoring"],summary:"Stream real-time monitoring data",description:"Server-Sent Events stream for real-time monitoring metrics"}});if(endpoints.snapshot.enabled)plugin.get(endpoints.snapshot.path,async()=>{let snapshot=await snapshotOrNull();if(!snapshot)return{isSuccess:!1,message:"No monitoring data available",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)plugin.get(endpoints.history.path,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:t12.Object({minutes:t12.Optional(t12.Numeric())}),detail:{tags:["Monitoring"],summary:"Get monitoring history",description:"Returns historical monitoring data for the specified time range"}});if(endpoints.alerts.enabled)plugin.get(endpoints.alerts.path,()=>{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"}}),plugin.post(`${endpoints.alerts.path}/:alertId/acknowledge`,({params})=>{if(!(getService()?.acknowledgeAlert(params.alertId)??!1))return{isSuccess:!1,message:"Alert not found",data:null};return{isSuccess:!0,message:"Alert acknowledged",data:{alertId:params.alertId}}},{params:t12.Object({alertId:t12.String()}),detail:{tags:["Monitoring"],summary:"Acknowledge an alert",description:"Mark an alert as acknowledged"}});return logger2.info(`[Monitoring] Routes enabled at ${endpoints.basePath} (stream: ${endpoints.stream.enabled}, snapshot: ${endpoints.snapshot.enabled}, history: ${endpoints.history.enabled}, alerts: ${endpoints.alerts.enabled})`),plugin}init_adminGuard();import Elysia15,{t as t13}from"elysia";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}
|
|
536
536
|
data: ${payload}
|
|
537
537
|
|
|
538
538
|
`)};function createLiveMonitoringRoutes(config){let{getService,logger:logger2,basePath,streamInterval,db,schemaTables}=config,plugin=new Elysia15({prefix:basePath});plugin.onBeforeHandle(createGodminGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"live monitoring"));let requireService=()=>{let svc=getService();if(!svc)throw Error("Live monitoring service not initialized");return svc};return plugin.get("/health",()=>{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"}}),plugin.get("/settings",()=>{return requireService().getSettings()},{detail:{tags:["Live Monitoring"],summary:"Get live monitoring settings"}}),plugin.patch("/settings",({body})=>{return requireService().changeSettings(body)},{body:t13.Partial(t13.Object({logMemory:t13.Boolean(),logCpu:t13.Boolean(),logDapr:t13.Boolean(),logWebSocket:t13.Boolean(),memoryLogInterval:t13.Number(),cpuLogInterval:t13.Number(),memoryLogLimit:t13.Number(),cpuLogLimit:t13.Number(),daprLogLimit:t13.Number(),wsLogLimit:t13.Number(),requestLogLimit:t13.Number()})),detail:{tags:["Live Monitoring"],summary:"Change live monitoring settings"}}),plugin.get("/logs",()=>{return requireService().getLogs()},{detail:{tags:["Live Monitoring"],summary:"Get all live monitoring logs"}}),plugin.get("/logs/stream",({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"}}),logger2.info(`[LiveMonitoring] Routes enabled at ${basePath} (stream interval: ${streamInterval}ms)`),plugin}init_adminGuard();import Elysia16,{t as t14}from"elysia";var STREAM_HEADERS2={"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache, no-transform",Connection:"keep-alive"},textEncoder2=new TextEncoder,encodeEvent2=(event,data)=>textEncoder2.encode(`event: ${event}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.874",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|