@gnar-engine/core 1.0.3 → 1.0.4
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/core.js +1 -1
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import Z from"fastify";import ee from"@fastify/cors";import re from"@fastify/rate-limit";import X from"pino";import Y from"path";var Te=Y.dirname(new URL(import.meta.url).pathname),n=X({level:process.env.LOG_MODE||"info",transport:{target:"pino-pretty",options:{colorize:!0}}});n.table=(e,r)=>{Array.isArray(e)&&e.length>0?r?console.table(e,r):console.table(e):typeof e=="object"?console.table([e]):n.info(e)};var w=null,B=3e3,U={init:e=>{w=Z({}),B=e.port||3e3,w.register(ee,{origin:e.allowedOrigins||[],methods:e.allowedMethods||["GET","POST","PUT","DELETE"],allowedHeaders:e.allowedHeaders||["Content-Type","Authorization"],preflight:!0,optionsSuccessStatus:204,credentials:!0}),e?.rateLimiting?.max&&e?.rateLimiting?.timeWindow&&w.register(re,e.rateLimiting)},registerRoutes:({controllers:e})=>{e.forEach(r=>{Object.values(r).forEach(s=>{w.route(s)})}),n.info("Control service registered http routes "+w.printRoutes())},addHook:(e,r)=>{w.addHook(e,r)},start:async()=>{try{await w.listen({port:B,host:"0.0.0.0"}),n.info(`HTTP server started on port ${B}`)}catch(e){n.error("Error starting HTTP server: "+e),process.exit(1)}},setErrorHandler:e=>{w.setErrorHandler(e)}};import te from"amqplib";var A="",y=null,C=null,N={arguments:{"x-queue-type":"quorum"}},Q=e=>{A=e},O=async()=>{if(y)return y;try{console.log("Connecting to RabbitMQ:",A),y=await te.connect(A)}catch(e){return console.log("Error connecting to RabbitMQ:",e),y=null,await new Promise(r=>setTimeout(r,3e3)),O({rabbitConnectionUrl:A})}return y},S=async()=>{if(C)return C;y||await O();try{C=await y.createChannel()}catch(e){return console.error("Error creating RabbitMQ channel:",e),C=null,await new Promise(r=>setTimeout(r,3e3)),S()}return C},P=async(e,r,s)=>{let t=await S();return await t.assertQueue(e,N),await t.prefetch(r),(await t.consume(e,async a=>{a&&(async()=>{try{await s(a,t)}catch(i){console.error("Error handling message:",i),t.nack(a,!1,!1)}})()})).consumerTag},oe=async(e,r,s,t)=>{await O(),await S();let o=await S();return await o.assertQueue(e,N),await o.prefetch(r),t&&await o.cancel(t),P(e,r,s)},H=async(e,r,s)=>{await O();let t=await S();await t.assertQueue(e,N),await t.prefetch(r);let o=await P(e,r,s);return y.on("close",async()=>{console.log("RabbitMQ connection closed, attempting to reconnect..."),await se(e,r,s,o)}),console.log("RabbitMQ connection established and consumer registered:",o),o},se=async(e,r,s,t)=>{console.log("Reconnecting to RabbitMQ...");let o=oe(e,r,s,t);return console.log("RabbitMQ connection re-established and consumer registered:",o),o};var j=async(e,r)=>{let s=`${e}Queue`,t;try{t=await S(),await t.assertQueue(s,{arguments:{"x-queue-type":"quorum"}});let o=ne(),a=o,i=await t.assertQueue("",{exclusive:!0});return new Promise((d,b)=>{let E=setTimeout(()=>{I(),b(new Error(`No response from ${e} service`))},5e3),I=()=>{t.cancel(a).catch(()=>{}),t.deleteQueue(i.queue).catch(()=>{}),clearTimeout(E)};t.consume(i.queue,q=>{q.properties.correlationId===o&&(I(),d(JSON.parse(q.content.toString())),t.ack(q))},{noAck:!1,consumerTag:a}).catch(b),t.sendToQueue(s,Buffer.from(JSON.stringify(r)),{replyTo:i.queue,correlationId:o})})}catch(o){if(t)try{await t.close()}catch(a){console.error("Failed to close channel:",a)}throw new Error(`Failed to send message to ${e}: ${o.message}`)}},L=async(e,r)=>{let s=`${e}Queue`,t;try{t=await S(),await t.assertQueue(s,{arguments:{"x-queue-type":"quorum"}}),t.sendToQueue(s,Buffer.from(JSON.stringify(r)))}catch(o){if(t)try{await t.close()}catch(a){console.error("Failed to close channel:",a)}throw new Error(`Failed to send message to ${e}: ${o.message}`)}},ne=()=>Math.random().toString(36).substring(7);import x,{WebSocketServer as ae}from"ws";import{v4 as ie}from"uuid";var F={wsMap:new Map,pendingCalls:[],async init(e,r){e.serviceName=r,this.startServer();let s=1;for(;;)try{e.serviceName!=="controlService"&&(n.info("serviceName "+JSON.stringify(e)),await this.connect("controlService",e));break}catch{s>=e.maxInitialConnectionAttempts?(n.error(`Initial WS connection to control service failed after ${s} attempts. Exiting.`),process.exit(1)):(n.error(`Initial WS connection to control service failed (attempt ${s}). Retrying in 3s...`),s++,await new Promise(o=>setTimeout(o,3e3)))}setInterval(async()=>{let t=[];try{t=await g.execute("controlService.getServices",{})}catch(a){n.error("Failed to get service registry. "+a);return}let o=t.map(a=>a.name===e.serviceName||this.wsMap[a.name]&&this.wsMap[a.name].readyState===x.OPEN?Promise.resolve():this.connect(a.name,e));try{await Promise.all(o)}catch(a){n.error("Failed to connect to some services",a)}},e.reconnectInterval||1e4)},startServer(e=5e3){let r=new ae({port:e});n.info(`WS Server listening on port ${e}`),r.on("connection",(s,t)=>{let o=this.identifyPeer(t);n.info(`Inbound connection from ${o}`),this.wsMap.set(o,s),s.on("message",a=>{this.handleMessage(o,a)}),s.on("close",()=>{n.info(`Peer disconnected: ${o}`),this.wsMap.delete(o)})})},async connect(e,r){let t=`ws://${e.replace("Service","-service").toLowerCase()}:5000`;return this.wsMap.has(e)&&this.wsMap.get(e).readyState===x.OPEN?this.wsMap.get(e):new Promise((o,a)=>{let i=new x(t,{headers:{"x-api-key":"my-secret-key","x-service-name":r.serviceName}});i.on("open",()=>{n.info(`Connected to peer ${e} at ${t}`),this.wsMap.set(e,i),i.on("message",d=>{this.handleMessage(e,d)}),i.on("close",()=>{n.info(`Peer disconnected: ${e}`),this.wsMap.delete(e)}),o(i)}),i.on("error",d=>{n.error(`Failed to connect to peer ${e} at ${t}`,d),a(d)})})},async handleMessage(e,r){let s;try{s=JSON.parse(r)}catch{n.error("Invalid JSON from peer:",r);return}if(s.type==="request"&&s.commandName)try{let t=await g.execute(s.commandName,s.payload);this.sendResponse(e,s.messageId,t)}catch(t){this.sendResponse(e,s.messageId,null,t.message)}else if(s.type==="response"){let t=this.pendingCalls.findIndex(a=>a.messageId===s.messageId);if(t===-1){n.error(`No pending call for messageId ${s.messageId}`);return}let o=this.pendingCalls.splice(t,1)[0];clearTimeout(o.timeout),s.error?o.reject(new Error(s.error)):o.resolve(s.response)}else n.error("Unknown message type",s)},async send(e,r,s,t=1e4){let o=this.wsMap.get(e);if(!o||o.readyState!==x.OPEN)throw new Error(`WebSocket not connected to ${e}`);let a=ie(),i={type:"request",messageId:a,commandName:r,payload:s};return o.send(JSON.stringify(i)),new Promise((d,b)=>{let E=setTimeout(()=>{this.pendingCalls=this.pendingCalls.filter(I=>I.messageId!==a),b(new Error(`Timeout waiting for response from ${e}`)),n.error(`Timeout waiting for response on call: ${JSON.stringify(i)}`)},t);this.pendingCalls.push({messageId:a,resolve:d,reject:b,timeout:E})})},sendResponse(e,r,s=null,t=null){let o=this.wsMap.get(e);!o||o.readyState!==x.OPEN||o.send(JSON.stringify({type:"response",messageId:r,response:s,error:t}))},identifyPeer(e){return e.headers["x-service-name"]||`unknown-${Date.now()}`}};var h={manifest:{commandList:[],commandImplementations:{},schemas:{}},addCommand({commandName:e,handlerFunction:r}){h.manifest.commandImplementations[e]={function:r.toString()},h.manifest.commandList.push(e)},addSchema({schemaName:e,schema:r}){h.manifest.schemas[e]=r}};var g={config:{},handlers:new Map,init(e){this.config=e},register(e,r){this.handlers.set(e,r),h.addCommand({commandName:e,handlerFunction:r})},async execute(e,r,s={}){let{fireAndForget:t=!1}=s;if(this.config.architecture=="modular-monolith"){let o=this.handlers.get(e);if(!o)throw new Error(`Command "${e}" not registered`);return await o(r)}else{let[o,a]=e.split(".");if(!o||!a){let i=this.config.serviceName+"."+e,d=this.handlers.get(i);if(!d)throw new Error(`Command "${i}" not registered`);return await d(r)}if(o===this.config.serviceName){let i=this.handlers.get(e);if(!i)throw new Error(`Command "${e}" not registered`);return await i(r)}if(t){L(o,{method:e,data:r});return}return await F.send(o,e,r)}}};var _={routeHandlers:{},defaultHandlers:{runMigrations:async e=>{let r=e.data?.migration;await g.execute("runMigrations",{migration:r})},runSeeders:async e=>{let r=e.data?.seeder;await g.execute("runSeeders",{seeder:r})},healthCheck:async()=>({status:"ok"})},async init({config:e,handlers:r={}}){if(!e?.queueName)throw new Error("Queue name is required for message controller initialization");this.routeHandlers={...this.defaultHandlers,...r},await H(e.queueName,e.prefetch||20,this.handleMessage.bind(this))},async handleMessage(e,r){if(!e)return;let s;try{s=JSON.parse(e.content.toString())}catch(i){return n.error("Invalid JSON message received:",i),r.ack(e)}let t=s.method;t.includes(".")&&(t=t.split(".").slice(1).join("."));let o={correlationId:e.properties.correlationId},a=this.routeHandlers[t];if(!a)return r.sendToQueue(e.properties.replyTo,Buffer.from(JSON.stringify({error:"Method not found"})),o),r.ack(e);try{let i=await a(s,e,r);e.properties.replyTo&&r.sendToQueue(e.properties.replyTo,Buffer.from(JSON.stringify(i??{ok:!0})),o)}catch(i){e.properties.replyTo&&r.sendToQueue(e.properties.replyTo,Buffer.from(JSON.stringify({error:i.message})),o)}finally{r.ack(e)}}};import{MongoClient as ce}from"mongodb";import J from"mysql2/promise";var l=null,de=5e3,D=async e=>{if(l)return l;try{switch(e.type){case"mongodb":return await le({host:e.host,user:e.user,password:e.password,database:e.database,port:e.port,connectionArgs:e.connectionArgs});case"mysql":return await me({host:e.host,user:e.user,password:e.password,database:e.database,connectionLimit:e.connectionLimit,queueLimit:e.queueLimit});default:throw new Error("Unsupported database type: "+e.type)}}catch(r){throw n.error("Error initializing database connection: "+r.message),r}},le=async({host:e,user:r,password:s,database:t,port:o=27017,connectionArgs:a={}})=>{try{n.info("Connecting to mongo..");let i=`mongodb://${r}:${encodeURIComponent(s)}@${e}:${o}/${t}`;return n.info(`MongoDB connection URL: ${i}`),l=(await ce.connect(i,a)).db(),n.info("MongoDB connected successfully"),l}catch(i){throw n.error("MongoDB connection error: "+i),i}},me=async({host:e,user:r,password:s,database:t,connectionLimit:o=10,queueLimit:a=20,maxRetries:i=5})=>{if(l)return l;let d=0;for(;d<i;)try{return await ue({host:e,user:r,password:s,database:t}),n.info("Establishing new MySQL pool..."),l=await J.createPool({host:e,user:r,password:s,database:t,waitForConnections:!0,connectionLimit:o,queueLimit:a}),n.info("MySQL pool established successfully"),l}catch(b){if(n.error(`MySQL connection attempt ${d+1} failed: ${b.message}`),d++,d>=i)throw n.error("Max retries reached. Could not connect to MySQL."),new Error("Could not connect to the database after multiple attempts");await new Promise(E=>setTimeout(E,de))}},ue=async({host:e,user:r,password:s,database:t})=>{if(n.info(`[assertDbExists] Attempting to connect to MySQL on host ${e} as ${r}`),n.info(`[assertDbExists] Target DB: ${t}`),!t||!e||!r||!s)throw n.error("[assertDbExists] Missing required variables: database, host, user, or password"),new Error("Missing required variables for DB connection");try{let o=await J.createConnection({host:e,user:r,password:s});n.info(`[assertDbExists] Connected to MySQL, asserting database '${t}'`);let[a]=await o.query(`CREATE DATABASE IF NOT EXISTS \`${t}\``);await o.end()}catch(o){throw n.error("[assertDbExists] \u274C Error asserting database existence"),n.error(o.stack||o.message||o),o}},W=async()=>{if(!l)throw new Error("Database connection not initialized");try{return!0}catch(e){throw e}};import pe from"fs";import he from"path";var f={config:null,runSeeders:async({config:e})=>{try{n.info("Running seeders"),f.config=e,n.info("Seeders config: "+JSON.stringify(f.config));let r=process.env.NODE_ENV||"development",s,t;try{if(s=process.env.GLOBAL_SERVICE_BASE_DIR+"/db/seeders/"+r,t=(await pe.promises.readdir(s)).sort(),t.length==0){n.info("No seeders found for environment: "+r);return}}catch{n.info("No seeders found for environment: "+r);return}for(let o of t){let i=await import(he.join(s,o));if(await f.checkSeederAlreadyRun(o))n.info("Seeder already run: "+o);else try{n.info("Running seeder: "+o),await i.up(),await f.markSeederAsRun(o)}catch(d){n.error("Error running seeder "+o+": "+d)}}n.info("Seeders completed successfully")}catch(r){throw n.error("Error running seeders: "+r),r}},checkSeederAlreadyRun:async e=>{let r=[];if(f.config.db.type=="mysql"){let[s]=await l.query('SHOW TABLES LIKE "seeders"');if(s.length==0)return!1;let[t]=await l.query("SELECT * FROM seeders WHERE name = ?",[e]);r=t}else r=await l.collection("seeders").find({name:e}).toArray();return r.length!=0},markSeederAsRun:async e=>{f.config.db.type=="mysql"?await l.execute("INSERT INTO seeders (name) VALUES (?)",[e]):await l.collection("seeders").insertOne({name:e,runAt:new Date})}};var z=async({seeder:e})=>{f.runSeeders()},K=async()=>{try{await D()}catch(e){n.error("[Internal health check] Failed - Exiting. Error connecting to MongoDB: "+e),process.exit(1)}};process.env.NODE_ENV!=="development"&&(process.on("uncaughtException",e=>{n.error("Uncaught Exception: "+e),process.exit(1)}),process.on("unhandledRejection",(e,r)=>{n.error("Unhandled Rejection at: "+r+". Reason: "+e),process.exit(1)}));var G=e=>{e.setErrorHandler((r,s,t)=>r.validation?t.code(400).send({statusCode:400,error:"Bad Request",message:r.message}):r instanceof R?t.code(404).send({statusCode:404,error:"Not Found",message:r.message}):r instanceof M?t.code(400).send({statusCode:400,error:"Bad Request",message:r.message}):r instanceof $?t.code(401).send({statusCode:401,error:"Unauthorized",message:r.message}):r.statusCode===429?t.code(429).send({statusCode:429,error:"Too Many Requests",message:"You have exceeded the request limit."}):r instanceof k?t.code(500).send({statusCode:500,error:"Failed Health Check",message:r.message}):(n.error(r),t.code(500).send({statusCode:500,error:"Internal Server Error",message:"Something went wrong",details:process.env.NODE_ENV==="development"?r.stack:""})))},R=class extends Error{constructor(r="Resource not found"){super(r),this.name="NotFoundError",this.statusCode=404}},M=class extends Error{constructor(r="Bad request"){super(r),this.name="BadRequestError",this.statusCode=400}},$=class extends Error{constructor(r="Unauthorised"){super(r),this.name="UnauthorisedError",this.statusCode=401}},k=class extends Error{constructor(r="Failed health check"){super(r),this.name="FailedHealthCheckError",this.statusCode=500}};import fe from"fs";import ge from"path";var T={config:null,runMigrations:async({config:e})=>{try{n.info("Running migrations"),T.config=e;let r=process.env.GLOBAL_SERVICE_BASE_DIR+"/db/migrations",s=(await fe.promises.readdir(r)).sort();for(let t of s){let a=await import(ge.join(r,t));await T.checkMigrationAlreadyRun(t)?n.info("Migration already run: "+t):(n.info("Running migration: "+t),await a.up(),await T.markMigrationAsRun(t))}n.info("Migrations completed successfully")}catch(r){throw n.error("Error running migrations: "+r),r}},checkMigrationAlreadyRun:async e=>{let[r]=await l.query('SHOW TABLES LIKE "migrations"');if(r.length==0)return!1;if(e=="01-init.js")return!0;let[s]=await l.query("SELECT * FROM migrations WHERE name = ?",[e]);return s.length!=0},markMigrationAsRun:async e=>{await l.execute("INSERT INTO migrations (name) VALUES (?)",[e])}};import we from"ajv";import ye from"ajv-formats";var v=new we({allErrors:!0,useDefaults:!0});ye(v);v.addFormat("mysql-date",/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);var Se={compile:e=>{h.addSchema(e);let r=v.compile(e.schema);return s=>{if(!r(s)){let o=[];return r.errors.map(a=>{o.push(a.instancePath+" "+a.message)}),{errors:o.join(", ")}}return!1}},addFormat:(e,r)=>{v.addFormat(e,r)},addKeyword:(e,r)=>{v.addKeyword(e,r)},addSchema:e=>{h.addSchema(e),v.addSchema(e.schema,e.$id)}},V=Se;import be from"assert";import ve from"fs";import Ee from"path";var u={testsDirectory:`${process.env.GLOBAL_SERVICE_BASE_DIR}/tests/commands`,failed:0,prepFns:[],tests:[],testResults:[],prep:e=>{u.prepFns.push(e)},run:(e,r)=>{u.tests.push({name:e,fn:r})},assert:be,runCommandTests:async()=>{console.log("=============================="),console.log("Running command tests...");let e=ve.readdirSync(u.testsDirectory).filter(r=>r.endsWith(".js")).sort();for(let r of e){u.prepFns=[],u.tests=[];let s=Ee.join(u.testsDirectory,r);try{await import(s)}catch(t){u.failed++,console.error(`\u274C Failed to load ${r}: ${t.message}`);continue}for(let t of u.prepFns)try{await t()}catch(o){u.failed++,console.error(`\u274C Test preparation failed - ${o.message}`)}for(let t of u.tests)try{await t.fn(),u.testResults.push({name:t.name,error:null})}catch(o){u.failed++,u.testResults.push({name:t.name,error:o})}}console.table(u.testResults.map(r=>({Test:r.name,Result:r.error?`\u274C Failed - ${r.error.message}`:"\u2705 Passed"}))),u.failed>0?console.error(`\u274C Some Integration tests failed: ${u.failed} error(s)`):console.log("\u2705 All integration tests passed!")}};var m={client:null,bucket:"",uploadsUrl:"",init:async e=>{if(!e.bucket)throw new Error("S3 storage driver requires a bucket name.");if(!e.region)throw new Error("S3 storage driver requires a region.");if(!e.uploadsUrl)throw new Error("S3 storage driver requires an uploadsUrl.");let{S3Client:r,PutObjectCommand:s,GetObjectCommand:t,DeleteObjectCommand:o}=await import("@aws-sdk/client-s3");m.S3Client=r,m.PutObjectCommand=s,m.GetObjectCommand=t,m.DeleteObjectCommand=o,m.bucket=e.bucket,m.uploadsUrl=e.uploadsUrl,m.client=new r({region:e.region,credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})},upload:async({file:e,key:r,contentType:s})=>{let t=new m.PutObjectCommand({Bucket:m.bucket,Key:r,Body:e,ContentType:s});return await m.client.send(t),r},download:async({key:e,stream:r})=>{let s=new m.GetObjectCommand({Bucket:m.bucket,Key:e}),t=await m.client.send(s);if(r)return t.Body;{let o=[];for await(let a of t.Body)o.push(a);return Buffer.concat(o)}},delete:async({key:e})=>{let r=new m.DeleteObjectCommand({Bucket:m.bucket,Key:e});await m.client.send(r)},getUrl:async({key:e})=>`${m.uploadsUrl}/${e}`};var p={driverName:"",driver:null,uploadsUrl:"",init:async e=>{try{if(!e.driver){n.info("No storage driver specified, skipping storage initialization.");return}switch(e.driver){case"s3":p.driverName="s3",p.driver=m;break;default:throw new Error(`Unsupported storage type: ${e.driver}`)}await p.driver.init(e),p.uploadsUrl=e.uploadsUrl||""}catch(r){throw n.error(`Storage initialization error: ${r.message}`),r}},upload:async({file:e,key:r,contentType:s,metadata:t})=>{try{let o=await p.driver.upload({file:e,key:r,contentType:s,metadata:t});return`${p.uploadsUrl}/${o}`}catch(o){throw n.error(`Storage upload error: ${o.message}`),o}},download:async({key:e,stream:r})=>{try{return await p.driver.download({key:e,stream:r})}catch(s){throw n.error(`Storage download error: ${s.message}`),s}},delete:async({key:e})=>{try{return await p.driver.delete({key:e})}catch(r){throw n.error(`Storage delete error: ${r.message}`),r}},getUrl:async({key:e,expiresIn:r})=>{try{return await p.driver.getUrl({key:e,expiresIn:r})}catch(s){throw n.error(`Storage getUrl error: ${s.message}`),s}}};import{v4 as Ce}from"uuid";import{v5 as xe}from"uuid";var Re=await import(process.env.GLOBAL_SERVICE_BASE_DIR+"config.js"),Me=Re.config,c={init:async e=>{c.http=U,e.http&&await c.http.init(e.http),c.commands=g,c.commands.init(e);try{c.db=await D(e.db)}catch(r){n.error("Error connecting to database: "+r),process.exit(1)}if(c.db.checkConnection=W,c.db.migrations=T,c.db.seeders=f,c.http.addHook("onReady",async()=>{setInterval(()=>{g.execute("internalHealthCheck",{})},6e4)}),G(c.http),Q(e.message?.url||""),c.message=_,c.message.sendAwaitResponse=j,c.message.sendAndForget=L,c.webSockets=F,c.commands.register(`${e.serviceName}.runSeeders`,z),c.commands.register(`${e.serviceName}.internalHealthCheck`,K),c.schema=V,c.logger=n,c.error={notFound:R,badRequest:M,unauthorised:$,failedHealthCheck:k},c.utils={uuid:()=>Ce(),hash:(r,s)=>xe(r,s)},c.http.addHook("onRequest",async(r,s)=>{let{url:t,method:o}=r;if(!t.endsWith("/")&&!t.includes(".")&&t!=="/"){let d=new URL(r.raw.url,`http://${r.headers.host}`);d.pathname+="/",r.raw.url=d.pathname+(d.search||"")}let a=r.raw.headers.authorization||"",i=a?a.split(" ")[1]:"";if(i){let d=await c.commands.execute("userService.getAuthenticatedUser",{token:i});d&&(r.user=d)}}),c.registerService=async()=>{if(e.serviceName!=="controlService")try{await c.commands.execute("controlService.registerService",{service:{name:e.serviceName,manifest:h.manifest}}),c.logger.info(`Service ${e.serviceName} registered with control service.`)}catch(r){c.logger.error(`Failed to register service ${e.serviceName} with control service: ${r.message}`)}},c.test=u,e.storage&&e.storage.driver)p.init(e.storage),c.storage=p;else{let r=()=>{throw new Error("Storage service not configured - please configure storage in config.js")};c.storage={upload:r,download:r,getUrl:r}}}};await c.init(Me);var Zr=c,{commands:et,http:rt,message:tt,db:ot,schema:st,logger:nt,error:at,utils:it,registerService:ct,webSockets:dt,test:lt,storage:mt}=c;export{et as commands,ot as db,Zr as default,at as error,rt as http,nt as logger,tt as message,ct as registerService,st as schema,mt as storage,lt as test,it as utils,dt as webSockets};
|
|
1
|
+
import oe from"fastify";import se from"@fastify/cors";import ne from"@fastify/rate-limit";import re from"pino";import te from"path";var Oe=te.dirname(new URL(import.meta.url).pathname),n=re({level:process.env.LOG_MODE||"info",transport:{target:"pino-pretty",options:{colorize:!0}}});n.table=(e,r)=>{Array.isArray(e)&&e.length>0?r?console.table(e,r):console.table(e):typeof e=="object"?console.table([e]):n.info(e)};var w=null,Q=3e3,H={init:e=>{w=oe({}),Q=e.port||3e3,w.register(se,{origin:e.allowedOrigins||[],methods:e.allowedMethods||["GET","POST","PUT","DELETE"],allowedHeaders:e.allowedHeaders||["Content-Type","Authorization"],preflight:!0,optionsSuccessStatus:204,credentials:!0}),e?.rateLimiting?.max&&e?.rateLimiting?.timeWindow&&w.register(ne,e.rateLimiting)},registerRoutes:({controllers:e})=>{e.forEach(r=>{Object.values(r).forEach(o=>{w.route(o)})}),n.info("Control service registered http routes "+w.printRoutes())},addHook:(e,r)=>{w.addHook(e,r)},start:async()=>{try{await w.listen({port:Q,host:"0.0.0.0"}),n.info(`HTTP server started on port ${Q}`)}catch(e){n.error("Error starting HTTP server: "+e),process.exit(1)}},setErrorHandler:e=>{w.setErrorHandler(e)}};import ae from"amqplib";var O="",y=null,C=null,U={arguments:{"x-queue-type":"quorum"}},j=e=>{O=e},D=async()=>{if(y)return y;try{console.log("Connecting to RabbitMQ:",O),y=await ae.connect(O)}catch(e){return console.log("Error connecting to RabbitMQ:",e),y=null,await new Promise(r=>setTimeout(r,3e3)),D({rabbitConnectionUrl:O})}return y},b=async()=>{if(C)return C;y||await D();try{C=await y.createChannel()}catch(e){return console.error("Error creating RabbitMQ channel:",e),C=null,await new Promise(r=>setTimeout(r,3e3)),b()}return C},_=async(e,r,o)=>{let t=await b();return await t.assertQueue(e,U),await t.prefetch(r),(await t.consume(e,async a=>{a&&(async()=>{try{await o(a,t)}catch(i){console.error("Error handling message:",i),t.nack(a,!1,!1)}})()})).consumerTag},ie=async(e,r,o,t)=>{await D(),await b();let s=await b();return await s.assertQueue(e,U),await s.prefetch(r),t&&await s.cancel(t),_(e,r,o)},K=async(e,r,o)=>{await D();let t=await b();await t.assertQueue(e,U),await t.prefetch(r);let s=await _(e,r,o);return y.on("close",async()=>{console.log("RabbitMQ connection closed, attempting to reconnect..."),await ce(e,r,o,s)}),console.log("RabbitMQ connection established and consumer registered:",s),s},ce=async(e,r,o,t)=>{console.log("Reconnecting to RabbitMQ...");let s=ie(e,r,o,t);return console.log("RabbitMQ connection re-established and consumer registered:",s),s};var z=async(e,r)=>{let o=`${e}Queue`,t;try{t=await b(),await t.assertQueue(o,{arguments:{"x-queue-type":"quorum"}});let s=le(),a=s,i=await t.assertQueue("",{exclusive:!0});return new Promise((l,S)=>{let v=setTimeout(()=>{I(),S(new Error(`No response from ${e} service`))},5e3),I=()=>{t.cancel(a).catch(()=>{}),t.deleteQueue(i.queue).catch(()=>{}),clearTimeout(v)};t.consume(i.queue,N=>{N.properties.correlationId===s&&(I(),l(JSON.parse(N.content.toString())),t.ack(N))},{noAck:!1,consumerTag:a}).catch(S),t.sendToQueue(o,Buffer.from(JSON.stringify(r)),{replyTo:i.queue,correlationId:s})})}catch(s){if(t)try{await t.close()}catch(a){console.error("Failed to close channel:",a)}throw new Error(`Failed to send message to ${e}: ${s.message}`)}},q=async(e,r)=>{let o=`${e}Queue`,t;try{t=await b(),await t.assertQueue(o,{arguments:{"x-queue-type":"quorum"}}),t.sendToQueue(o,Buffer.from(JSON.stringify(r)))}catch(s){if(t)try{await t.close()}catch(a){console.error("Failed to close channel:",a)}throw new Error(`Failed to send message to ${e}: ${s.message}`)}},le=()=>Math.random().toString(36).substring(7);import x,{WebSocketServer as de}from"ws";import{v4 as me}from"uuid";var L={wsMap:new Map,pendingCalls:[],async init(e,r){e.serviceName=r,this.startServer();let o=1;for(;;)try{e.serviceName!=="controlService"&&(n.info("serviceName "+JSON.stringify(e)),await this.connect("controlService",e));break}catch{o>=e.maxInitialConnectionAttempts?(n.error(`Initial WS connection to control service failed after ${o} attempts. Exiting.`),process.exit(1)):(n.error(`Initial WS connection to control service failed (attempt ${o}). Retrying in 3s...`),o++,await new Promise(s=>setTimeout(s,3e3)))}setInterval(async()=>{let t=[];try{t=await f.execute("controlService.getServices",{})}catch(a){n.error("Failed to get service registry. "+a);return}let s=t.map(a=>a.name===e.serviceName||this.wsMap[a.name]&&this.wsMap[a.name].readyState===x.OPEN?Promise.resolve():this.connect(a.name,e));try{await Promise.all(s)}catch(a){n.error("Failed to connect to some services",a)}},e.reconnectInterval||1e4)},startServer(e=5e3){new de({port:e}).on("connection",(o,t)=>{let s=this.identifyPeer(t);this.wsMap.set(s,o),o.on("message",a=>{this.handleMessage(s,a)}),o.on("close",()=>{n.info(`Peer disconnected: ${s}`),this.wsMap.delete(s)})})},async connect(e,r){let t=`ws://${e.replace("Service","-service").toLowerCase()}:5000`;return this.wsMap.has(e)&&this.wsMap.get(e).readyState===x.OPEN?this.wsMap.get(e):new Promise((s,a)=>{let i=new x(t,{headers:{"x-api-key":"my-secret-key","x-service-name":r.serviceName}});i.on("open",()=>{n.info(`Connected to peer ${e} at ${t}`),this.wsMap.set(e,i),i.on("message",l=>{this.handleMessage(e,l)}),i.on("close",()=>{n.info(`Peer disconnected: ${e}`),this.wsMap.delete(e)}),s(i)}),i.on("error",l=>{n.error(`Failed to connect to peer ${e} at ${t}`,l),a(l)})})},async handleMessage(e,r){let o;try{o=JSON.parse(r)}catch{n.error("Invalid JSON from peer:",r);return}if(o.type==="request"&&o.commandName)try{let t=await f.execute(o.commandName,o.payload);this.sendResponse(e,o.messageId,t)}catch(t){this.sendResponse(e,o.messageId,null,t.message)}else if(o.type==="response"){let t=this.pendingCalls.findIndex(a=>a.messageId===o.messageId);if(t===-1){n.error(`No pending call for messageId ${o.messageId}`);return}let s=this.pendingCalls.splice(t,1)[0];clearTimeout(s.timeout),o.error?s.reject(new Error(o.error)):s.resolve(o.response)}else n.error("Unknown message type",o)},async send(e,r,o,t=1e4){let s=this.wsMap.get(e);if(!s||s.readyState!==x.OPEN)throw new Error(`WebSocket not connected to ${e}`);let a=me(),i={type:"request",messageId:a,commandName:r,payload:o};return s.send(JSON.stringify(i)),new Promise((l,S)=>{let v=setTimeout(()=>{this.pendingCalls=this.pendingCalls.filter(I=>I.messageId!==a),S(new Error(`Timeout waiting for response from ${e}`)),n.error(`Timeout waiting for response on call: ${JSON.stringify(i)}`)},t);this.pendingCalls.push({messageId:a,resolve:l,reject:S,timeout:v})})},sendResponse(e,r,o=null,t=null){let s=this.wsMap.get(e);!s||s.readyState!==x.OPEN||s.send(JSON.stringify({type:"response",messageId:r,response:o,error:t}))},identifyPeer(e){return e.headers["x-service-name"]||`unknown-${Date.now()}`}};var h={manifest:{commandList:[],commandImplementations:{},schemas:{}},addCommand({commandName:e,handlerFunction:r}){h.manifest.commandImplementations[e]={function:r.toString()},h.manifest.commandList.push(e)},addSchema({schemaName:e,schema:r}){h.manifest.schemas[e]=r}};var f={config:{},handlers:new Map,init(e){this.config=e},register(e,r){this.handlers.set(e,r),h.addCommand({commandName:e,handlerFunction:r})},async execute(e,r,o={}){let{fireAndForget:t=!1}=o;if(this.config.architecture=="modular-monolith"){let s=this.handlers.get(e);if(!s)throw new Error(`Command "${e}" not registered`);return await s(r)}else{let[s,a]=e.split(".");if(!s||!a){let i=this.config.serviceName+"."+e,l=this.handlers.get(i);if(!l)throw new Error(`Command "${i}" not registered`);return await l(r)}if(s===this.config.serviceName){let i=this.handlers.get(e);if(!i)throw new Error(`Command "${e}" not registered`);return await i(r)}if(t){q(s,{method:e,data:r});return}return await L.send(s,e,r)}}};var J={routeHandlers:{},defaultHandlers:{runMigrations:async e=>{let r=e.data?.migration;await f.execute("runMigrations",{migration:r})},runSeeders:async e=>{let r=e.data?.seeder;await f.execute("runSeeders",{seeder:r})},healthCheck:async()=>({status:"ok"})},async init({config:e,handlers:r={}}){if(!e?.queueName)throw new Error("Queue name is required for message controller initialization");this.routeHandlers={...this.defaultHandlers,...r},await K(e.queueName,e.prefetch||20,this.handleMessage.bind(this))},async handleMessage(e,r){if(!e)return;let o;try{o=JSON.parse(e.content.toString())}catch(i){return n.error("Invalid JSON message received:",i),r.ack(e)}let t=o.method;t.includes(".")&&(t=t.split(".").slice(1).join("."));let s={correlationId:e.properties.correlationId},a=this.routeHandlers[t];if(!a)return r.sendToQueue(e.properties.replyTo,Buffer.from(JSON.stringify({error:"Method not found"})),s),r.ack(e);try{let i=await a(o,e,r);e.properties.replyTo&&r.sendToQueue(e.properties.replyTo,Buffer.from(JSON.stringify(i??{ok:!0})),s)}catch(i){e.properties.replyTo&&r.sendToQueue(e.properties.replyTo,Buffer.from(JSON.stringify({error:i.message})),s)}finally{r.ack(e)}}};import{MongoClient as ue}from"mongodb";import W from"mysql2/promise";var d=null,P="",pe=5e3,B=async e=>{if(d)return d;P=e.type;try{switch(e.type){case"mongodb":return await he({host:e.host,user:e.user,password:e.password,database:e.database,port:e.port,connectionArgs:e.connectionArgs});case"mysql":return await fe({host:e.host,user:e.user,password:e.password,database:e.database,connectionLimit:e.connectionLimit,queueLimit:e.queueLimit});default:throw new Error("Unsupported database type: "+e.type)}}catch(r){throw n.error("Error initializing database connection: "+r.message),r}},he=async({host:e,user:r,password:o,database:t,port:s=27017,connectionArgs:a={}})=>{try{n.info("Connecting to mongo..");let i=`mongodb://${r}:${encodeURIComponent(o)}@${e}:${s}/${t}`;return n.info(`MongoDB connection URL: ${i}`),d=(await ue.connect(i,a)).db(),n.info("MongoDB connected successfully"),d}catch(i){throw n.error("MongoDB connection error: "+i),i}},fe=async({host:e,user:r,password:o,database:t,connectionLimit:s=10,queueLimit:a=20,maxRetries:i=5})=>{if(d)return d;let l=0;for(;l<i;)try{return await ge({host:e,user:r,password:o,database:t}),n.info("Establishing new MySQL pool..."),d=await W.createPool({host:e,user:r,password:o,database:t,waitForConnections:!0,connectionLimit:s,queueLimit:a}),n.info("MySQL pool established successfully"),d}catch(S){if(n.error(`MySQL connection attempt ${l+1} failed: ${S.message}`),l++,l>=i)throw n.error("Max retries reached. Could not connect to MySQL."),new Error("Could not connect to the database after multiple attempts");await new Promise(v=>setTimeout(v,pe))}},ge=async({host:e,user:r,password:o,database:t})=>{if(n.info(`[assertDbExists] Attempting to connect to MySQL on host ${e} as ${r}`),n.info(`[assertDbExists] Target DB: ${t}`),!t||!e||!r||!o)throw n.error("[assertDbExists] Missing required variables: database, host, user, or password"),new Error("Missing required variables for DB connection");try{let s=await W.createConnection({host:e,user:r,password:o});n.info(`[assertDbExists] Connected to MySQL, asserting database '${t}'`);let[a]=await s.query(`CREATE DATABASE IF NOT EXISTS \`${t}\``);await s.end()}catch(s){throw n.error("[assertDbExists] \u274C Error asserting database existence"),n.error(s.stack||s.message||s),s}},G=async()=>{if(!d)throw new Error("Database connection not initialized");try{return!0}catch(e){throw e}},V=async()=>{if(!d)throw new Error("Database connection not initialized");if(R.environment!=="test")throw new Error("Cannot reset mongo database outside of test mode!");try{let e=await d.collections();for(let r of e)await r.deleteMany({});console.log("MongoDB database reset successfully")}catch(e){throw console.error("Error resetting MongoDB database: "+e.message),e}},F=async()=>{if(!d)throw new Error("Database connection not initialized");if(R.environment!=="test"&&R.environment!=="development")throw new Error("Cannot reset mysql database outside of test or development mode!");try{let[e]=await d.query("SHOW TABLES");await d.query("SET FOREIGN_KEY_CHECKS = 0");for(let r of e){let o=Object.values(r)[0];await d.query(`DROP TABLE \`${o}\``)}await d.query("SET FOREIGN_KEY_CHECKS = 1"),console.log("MySQL database reset successfully")}catch(e){throw console.error("Error resetting MySQL database: "+e.message),e}};import we from"fs";import ye from"path";var g={config:null,runSeeders:async({config:e})=>{try{n.info("Running seeders"),g.config=e;let r=process.env.NODE_ENV||"development",o,t;try{if(o=process.env.GLOBAL_SERVICE_BASE_DIR+"/db/seeders/"+r,t=(await we.promises.readdir(o)).sort(),t.length==0){n.info("No seeders found for environment: "+r);return}}catch{n.info("No seeders found for environment: "+r);return}for(let s of t){let i=await import(ye.join(o,s));if(await g.checkSeederAlreadyRun(s))n.info("Seeder already run: "+s);else try{n.info("Running seeder: "+s),await i.up(),await g.markSeederAsRun(s)}catch(l){n.error("Error running seeder "+s+": "+l)}}n.info("Seeders completed successfully")}catch(r){throw n.error("Error running seeders: "+r),r}},checkSeederAlreadyRun:async e=>{let r=[];if(g.config.db.type=="mysql"){let[o]=await d.query('SHOW TABLES LIKE "seeders"');if(o.length==0)return!1;let[t]=await d.query("SELECT * FROM seeders WHERE name = ?",[e]);r=t}else r=await d.collection("seeders").find({name:e}).toArray();return r.length!=0},markSeederAsRun:async e=>{g.config.db.type=="mysql"?await d.execute("INSERT INTO seeders (name) VALUES (?)",[e]):await d.collection("seeders").insertOne({name:e,runAt:new Date})}};var Y=async({seeder:e})=>{g.runSeeders()},X=async()=>{try{await B()}catch(e){n.error("[Internal health check] Failed - Exiting. Error connecting to MongoDB: "+e),process.exit(1)}};process.env.NODE_ENV!=="development"&&(process.on("uncaughtException",e=>{n.error("Uncaught Exception: "+e),process.exit(1)}),process.on("unhandledRejection",(e,r)=>{n.error("Unhandled Rejection at: "+r+". Reason: "+e),process.exit(1)}));var Z=e=>{e.setErrorHandler((r,o,t)=>r.validation?t.code(400).send({statusCode:400,error:"Bad Request",message:r.message}):r instanceof M?t.code(404).send({statusCode:404,error:"Not Found",message:r.message}):r instanceof $?t.code(400).send({statusCode:400,error:"Bad Request",message:r.message}):r instanceof k?t.code(401).send({statusCode:401,error:"Unauthorized",message:r.message}):r.statusCode===429?t.code(429).send({statusCode:429,error:"Too Many Requests",message:"You have exceeded the request limit."}):r instanceof T?t.code(500).send({statusCode:500,error:"Failed Health Check",message:r.message}):(n.error(r),t.code(500).send({statusCode:500,error:"Internal Server Error",message:"Something went wrong",details:process.env.NODE_ENV==="development"?r.stack:""})))},M=class extends Error{constructor(r="Resource not found"){super(r),this.name="NotFoundError",this.statusCode=404}},$=class extends Error{constructor(r="Bad request"){super(r),this.name="BadRequestError",this.statusCode=400}},k=class extends Error{constructor(r="Unauthorised"){super(r),this.name="UnauthorisedError",this.statusCode=401}},T=class extends Error{constructor(r="Failed health check"){super(r),this.name="FailedHealthCheckError",this.statusCode=500}};import be from"fs";import Se from"path";var A={config:null,runMigrations:async({config:e})=>{try{if(e.db.type!=="mysql"){n.info("Migrations only supported for MySQL");return}e.resetDatabase&&(e.environment=="development"||e.envenvironment=="test")&&(n.info("Resetting database..."),await F()),n.info("Running migrations"),A.config=e;let r=process.env.GLOBAL_SERVICE_BASE_DIR+"/db/migrations",o=(await be.promises.readdir(r)).sort();for(let t of o){let a=await import(Se.join(r,t));await A.checkMigrationAlreadyRun(t)?n.info("Migration already run: "+t):(n.info("Running migration: "+t),await a.up(),await A.markMigrationAsRun(t))}n.info("Migrations completed successfully")}catch(r){throw n.error("Error running migrations: "+r),r}},checkMigrationAlreadyRun:async e=>{let[r]=await d.query('SHOW TABLES LIKE "migrations"');if(r.length==0)return!1;if(e=="01-init.js")return!0;let[o]=await d.query("SELECT * FROM migrations WHERE name = ?",[e]);return o.length!=0},markMigrationAsRun:async e=>{await d.execute("INSERT INTO migrations (name) VALUES (?)",[e])}};import Ee from"ajv";import ve from"ajv-formats";var E=new Ee({allErrors:!0,useDefaults:!0});ve(E);E.addFormat("mysql-date",/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);var Ce={compile:e=>{h.addSchema(e);let r=E.compile(e.schema);return o=>{if(!r(o)){let s=[];return r.errors.map(a=>{s.push(a.instancePath+" "+a.message)}),{errors:s.join(", ")}}return!1}},addFormat:(e,r)=>{E.addFormat(e,r)},addKeyword:(e,r)=>{E.addKeyword(e,r)},addSchema:e=>{h.addSchema(e),E.addSchema(e.schema,e.$id)}},ee=Ce;import xe from"assert";import Re from"fs";import Me from"path";var u={testsDirectory:`${process.env.GLOBAL_SERVICE_BASE_DIR}/tests/commands`,failed:0,prepFns:[],tests:[],testResults:[],prep:e=>{u.prepFns.push(e)},run:(e,r)=>{u.tests.push({name:e,fn:r})},assert:xe,runCommandTests:async()=>{console.log("=============================="),console.log("Running command tests...");let e=Re.readdirSync(u.testsDirectory).filter(r=>r.endsWith(".js")).sort();for(let r of e){u.prepFns=[],u.tests=[];let o=Me.join(u.testsDirectory,r);try{await import(o)}catch(t){u.failed++,console.error(`\u274C Failed to load ${r}: ${t.message}`);continue}for(let t of u.prepFns)try{await t()}catch(s){u.failed++,console.error(`\u274C Test preparation failed - ${s.message}`)}for(let t of u.tests)try{await t.fn(),u.testResults.push({name:t.name,error:null})}catch(s){u.failed++,u.testResults.push({name:t.name,error:s})}}console.table(u.testResults.map(r=>({Test:r.name,Result:r.error?`\u274C Failed - ${r.error.message}`:"\u2705 Passed"}))),u.failed>0?console.error(`\u274C Some Integration tests failed: ${u.failed} error(s)`):console.log("\u2705 All integration tests passed!");try{switch(P){case"mongodb":await V();break;case"mysql":await F();break}}catch(r){throw n.error("Error resetting test database: "+r.message),r}}};var m={client:null,bucket:"",uploadsUrl:"",init:async e=>{if(!e.bucket)throw new Error("S3 storage driver requires a bucket name.");if(!e.region)throw new Error("S3 storage driver requires a region.");if(!e.uploadsUrl)throw new Error("S3 storage driver requires an uploadsUrl.");let{S3Client:r,PutObjectCommand:o,GetObjectCommand:t,DeleteObjectCommand:s}=await import("@aws-sdk/client-s3");m.S3Client=r,m.PutObjectCommand=o,m.GetObjectCommand=t,m.DeleteObjectCommand=s,m.bucket=e.bucket,m.uploadsUrl=e.uploadsUrl,m.client=new r({region:e.region,credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}})},upload:async({file:e,key:r,contentType:o})=>{let t=new m.PutObjectCommand({Bucket:m.bucket,Key:r,Body:e,ContentType:o});return await m.client.send(t),r},download:async({key:e,stream:r})=>{let o=new m.GetObjectCommand({Bucket:m.bucket,Key:e}),t=await m.client.send(o);if(r)return t.Body;{let s=[];for await(let a of t.Body)s.push(a);return Buffer.concat(s)}},delete:async({key:e})=>{let r=new m.DeleteObjectCommand({Bucket:m.bucket,Key:e});await m.client.send(r)},getUrl:async({key:e})=>`${m.uploadsUrl}/${e}`};var p={driverName:"",driver:null,uploadsUrl:"",init:async e=>{try{if(!e.driver){n.info("No storage driver specified, skipping storage initialization.");return}switch(e.driver){case"s3":p.driverName="s3",p.driver=m;break;default:throw new Error(`Unsupported storage type: ${e.driver}`)}await p.driver.init(e),p.uploadsUrl=e.uploadsUrl||""}catch(r){throw n.error(`Storage initialization error: ${r.message}`),r}},upload:async({file:e,key:r,contentType:o,metadata:t})=>{try{let s=await p.driver.upload({file:e,key:r,contentType:o,metadata:t});return`${p.uploadsUrl}/${s}`}catch(s){throw n.error(`Storage upload error: ${s.message}`),s}},download:async({key:e,stream:r})=>{try{return await p.driver.download({key:e,stream:r})}catch(o){throw n.error(`Storage download error: ${o.message}`),o}},delete:async({key:e})=>{try{return await p.driver.delete({key:e})}catch(r){throw n.error(`Storage delete error: ${r.message}`),r}},getUrl:async({key:e,expiresIn:r})=>{try{return await p.driver.getUrl({key:e,expiresIn:r})}catch(o){throw n.error(`Storage getUrl error: ${o.message}`),o}}};import{v4 as $e}from"uuid";import{v5 as ke}from"uuid";var Te=await import(process.env.GLOBAL_SERVICE_BASE_DIR+"config.js"),R=Te.config,c={init:async e=>{c.config=e,c.http=H,e.http&&await c.http.init(e.http),c.commands=f,c.commands.init(e);try{c.db=await B(e.db)}catch(r){n.error("Error connecting to database: "+r),process.exit(1)}if(c.db.checkConnection=G,c.db.migrations=A,c.db.seeders=g,c.http.addHook("onReady",async()=>{setInterval(()=>{f.execute("internalHealthCheck",{})},6e4)}),Z(c.http),j(e.message?.url||""),c.message=J,c.message.sendAwaitResponse=z,c.message.sendAndForget=q,c.webSockets=L,c.commands.register(`${e.serviceName}.runSeeders`,Y),c.commands.register(`${e.serviceName}.internalHealthCheck`,X),c.schema=ee,c.logger=n,c.error={notFound:M,badRequest:$,unauthorised:k,failedHealthCheck:T},c.utils={uuid:()=>$e(),hash:(r,o)=>ke(r,o)},c.http.addHook("onRequest",async(r,o)=>{let{url:t,method:s}=r;if(!t.endsWith("/")&&!t.includes(".")&&t!=="/"){let l=new URL(r.raw.url,`http://${r.headers.host}`);l.pathname+="/",r.raw.url=l.pathname+(l.search||"")}let a=r.raw.headers.authorization||"",i=a?a.split(" ")[1]:"";if(i){let l=await c.commands.execute("userService.getAuthenticatedUser",{token:i});l&&(r.user=l)}}),c.registerService=async()=>{if(e.serviceName!=="controlService")try{await c.commands.execute("controlService.registerService",{service:{name:e.serviceName,manifest:h.manifest}}),c.logger.info(`Service ${e.serviceName} registered with control service.`)}catch(r){c.logger.error(`Failed to register service ${e.serviceName} with control service: ${r.message}`)}},c.test=u,e.storage&&e.storage.driver)p.init(e.storage),c.storage=p;else{let r=()=>{throw new Error("Storage service not configured - please configure storage in config.js")};c.storage={upload:r,download:r,getUrl:r}}}};await c.init(R);var ot=c,{commands:st,http:nt,message:at,db:it,schema:ct,logger:lt,error:dt,utils:mt,registerService:ut,webSockets:pt,test:ht,storage:ft}=c;export{st as commands,R as config,it as db,ot as default,dt as error,nt as http,lt as logger,at as message,ut as registerService,ct as schema,ft as storage,ht as test,mt as utils,pt as webSockets};
|