exlo 0.0.1 → 0.0.3
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/cli/index.mjs +11 -0
- package/dist/cli/options.d.ts +12 -0
- package/dist/defineConfig.d.ts +3 -3
- package/dist/defineConfig.mjs +1 -1
- package/dist/lib/runtime/runners/node/awslambda.d.ts +1 -2
- package/dist/lib/runtime/runners/node/index.js +8 -8
- package/dist/lib/runtime/runners/node/streamResponse.d.ts +1 -1
- package/dist/lib/runtime/runners/node/utils.d.ts +2 -2
- package/dist/lib/services/index.d.ts +1 -1
- package/dist/main.d.ts +3 -3
- package/dist/plugins/restApi/index.d.ts +10 -0
- package/dist/plugins/restApi/index.mjs +1 -1
- package/dist/plugins/restApi/restApiServer.d.ts +12 -1
- package/dist/plugins/secretsManager/index.d.ts +30 -0
- package/dist/plugins/secretsManager/index.mjs +1 -0
- package/dist/plugins/sns/index.d.ts +1 -1
- package/dist/plugins/sns/index.mjs +1 -1
- package/dist/plugins/sqs/index.mjs +1 -1
- package/dist/plugins/vitest/coverage.d.ts +1 -1
- package/dist/plugins/vitest/index.mjs +1 -1
- package/dist/standalone.d.ts +1 -0
- package/dist/standalone.js +12 -12
- package/dist/standalone.mjs +13 -13
- package/package.json +26 -18
- package/dist/cli.mjs +0 -11
- /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{readFile as I}from"node:fs/promises";import p from"node:path";import{pathToFileURL as O}from"node:url";import{parseArgs as Y}from"node:util";import{run as D}from"exlo";var j="exlo",b="0.0.3";var g=!1,N=e=>{g=e},R=()=>g,a=(e,t)=>{g&&console.log(`\x1B[${e}m${t}\x1B[0m`)},A=e=>console.log(`\x1B[31m${e}\x1B[0m`),$=e=>a("32",e),q=e=>a("33",e),L=e=>a("36",e),S=e=>a("90",e),C=e=>a("94",e),F=e=>a("95",e),_=e=>g?console.log(e):void 0,n={GREEN:$,YELLOW:q,CYAN:L,BR_BLUE:C,RED:A,GREY:S,PINK:F,setDebug:N,getDebug:R,info:_};var x={port:{type:"string",short:"p",default:"0",description:"Lambda server port."},debug:{type:"boolean",default:!1,description:"Enable debug mode. When enabled exlo will print usefull informations."},config:{type:"string",short:"c",description:"Path to exlo 'defineConfig' file."},runtime:{type:"string",short:"r",description:"Lambda default runtime (ex: nodejs22.x, python3.7, ruby2.7 etc.)."},timeout:{type:"string",short:"t",default:"3",description:"Lambda default timeout."},definitions:{type:"string",short:"d",description:"Path to .json, .mjs, .cjs file with Lambda function definitions."},functions:{type:"string",short:"f",multiple:!0,description:"Glob pattern to automatically find and define Lambda handlers."},exclude:{type:"string",short:"X",multiple:!0,default:[".(test|spec)."],description:"RegExp string to exclude found enteries from --functions."},handlerName:{type:"string",default:"handler",description:"Handler function name. To be used with --functions."},env:{type:"string",short:"e",multiple:!0,default:[],description:"Environment variables to be injected into Lambdas. All existing AWS_* are automatically injected.",example:"-e API_KEY=supersecret -e API_URL=https://website.com"},"optimize-build":{type:"boolean",default:!1,description:"externalize dependencies and other stuff"},"shim-require":{type:"boolean",default:!1,description:"shim 'require()', '__dirname' and '__filename' when bundeling Lambdas with ESM format"},help:{type:"boolean",short:"h",skipFullPrint:!0},version:{type:"boolean",short:"v",skipFullPrint:!0},"esbuild-external":{type:"string",multiple:!0,skip:!0},"esbuild-resolveExtensions":{type:"string",multiple:!0,skip:!0},"esbuild-mainFields":{type:"string",multiple:!0,skip:!0},"esbuild-conditions":{type:"string",multiple:!0,skip:!0},"esbuild-entryPoints":{type:"string",multiple:!0,skip:!0},"esbuild-inject":{type:"string",multiple:!0,skip:!0},"esbuild-nodePaths":{type:"string",multiple:!0,skip:!0}};function M(){n.setDebug(!0),n.GREY("Usage examples:"),console.log("exlo -c exlo.config.ts"),console.log(`exlo -p 3000 --debug --functions "src/lambdas/**/*.ts"
|
|
3
|
+
`),n.BR_BLUE("Options:");for(let[e,t]of Object.entries(x)){let i=e;if(t.skip)continue;if(t.short&&(i+=`, -${t.short}`),t.skipFullPrint){n.CYAN(` --${i}
|
|
4
|
+
`);continue}let s=` type: ${t.type}`;t.description&&(s+=`
|
|
5
|
+
description: ${t.description}`),"default"in t&&(s+=`
|
|
6
|
+
default: ${t.default}`),t.example&&(s+=`
|
|
7
|
+
example: ${t.example}`),s+=`
|
|
8
|
+
`,n.CYAN(` --${i}`),n.GREY(s)}n.CYAN(" --esbuild-[option]"),n.GREY(`
|
|
9
|
+
description: where 'options' is one of esbuild build options`),n.GREY(`
|
|
10
|
+
example: exlo --esbuild-external="@aws-sdk/*" --esbuild-external=react --esbuild-format=esm --esbuild-outExtension..js=.mjs --esbuild-outExtension..css=.CSS`),console.log(`
|
|
11
|
+
${j} v${b}`),process.exit(0)}function v(e,t){return!e||Number.isNaN(e)?t:Number(e)}async function P(e){if(e){if(e.endsWith(".js"))throw new Error("Only .json, .mjs and .cjs are supported for --definitions option.");if(e.endsWith(".json"))return JSON.parse(await I(e,"utf-8")).functions;if(e.endsWith(".mjs")||e.endsWith(".cjs"))return(await import(O(p.resolve(process.cwd(),e)).href)).functions}}async function G(e,t,i){if(!i)return;let s=Number(process.versions.node.slice(0,process.versions.node.indexOf(".")));if(s<22)throw new Error("--functions option is only supported on Node22 and higher.");let{glob:u}=await import("node:fs/promises"),m={};for await(let l of u(i)){if(e.some(k=>l.match(k)))continue;let f=p.basename(p.dirname(l)),o=p.parse(l),r;o.name=="index"?m[f]?r=l.replaceAll(p.sep,"_"):r=f:m[o.name]?m[`${f}_${o.name}`]?r=l.replaceAll(p.sep,"_"):r=`${f}_${o.name}`:r=o.name,m[r]={name:r,handler:l.replace(o.ext,`.${t}`),runtime:o.ext==".py"?"python3.7":o.ext==".rb"?"ruby2.7":`nodejs${s}.x`}}return m}function W(e){let t={};for(let i of e){let[s,...u]=i.split("=");t[s]=u.join("=")}return t}var{values:d}=Y({strict:!1,options:x}),{port:B,config:U,debug:z,help:T,runtime:V,definitions:E,timeout:H,functions:y,handlerName:K,exclude:w,env:J}=d;d.version?(console.log(b),process.exit(0)):T&&M();if(E&&y)throw new Error("Can not use --definitions (-d) and --functions (-f) together.");var Q=Array.isArray(w)?w.filter(e=>typeof e=="string").map(e=>new RegExp(e)):[],X=y?await G(Q,K,y):await P(E),c={};for(let e of Object.keys(d).filter(t=>t.startsWith("esbuild-"))){let t=e.split("esbuild-")[1];if(t.includes(".")){let i=t.indexOf("."),s=t.slice(0,i),u=t.slice(i+1);c[s]||(c[s]={}),c[s][u]=d[e]}else c[t]=d[e]}var Z=d["optimize-build"],ee=d["shim-require"],te={debug:z,config:U,port:v(B,0),functions:X,build:{esbuild:c,optimizeBuild:Z,shimRequire:ee},defaults:{environment:W(J),runtime:V,timeout:v(H,3)}};D(te);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface ICliOptions {
|
|
2
|
+
type: "string" | "boolean";
|
|
3
|
+
multiple?: boolean | undefined;
|
|
4
|
+
short?: string | undefined;
|
|
5
|
+
default?: string | boolean | string[] | boolean[] | undefined;
|
|
6
|
+
description?: string;
|
|
7
|
+
example?: string;
|
|
8
|
+
skipFullPrint?: boolean;
|
|
9
|
+
skip?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const options: Record<string, ICliOptions>;
|
|
12
|
+
export {};
|
package/dist/defineConfig.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { ILambdaMock } from "./lib/runtime/rapidApi";
|
|
|
9
9
|
import type { HttpMethod } from "./lib/server/handlers";
|
|
10
10
|
import type { log } from "./lib/utils/colorize";
|
|
11
11
|
import type { IAwsProvider, ILambdaFunction } from "./standalone_types";
|
|
12
|
+
export { version as exloVersion } from "../package.json";
|
|
12
13
|
export type ILambda = {
|
|
13
14
|
/**
|
|
14
15
|
* Set environment variable.
|
|
@@ -32,7 +33,7 @@ export type ILambda = {
|
|
|
32
33
|
onInvokeSuccess: (callback: (input: any, output: any, info?: any) => void) => void;
|
|
33
34
|
} & Omit<ILambdaMock, "invokeSub" | "invokeSuccessSub" | "invokeErrorSub" | "runner">;
|
|
34
35
|
interface IServicesConfig {
|
|
35
|
-
|
|
36
|
+
dynamodb?: DynamoDB;
|
|
36
37
|
s3?: S3;
|
|
37
38
|
sns?: SNS;
|
|
38
39
|
sqs?: SQS;
|
|
@@ -46,7 +47,7 @@ export interface ClientConfigParams {
|
|
|
46
47
|
config: Config;
|
|
47
48
|
options: Options;
|
|
48
49
|
getServices(): IServicesConfig;
|
|
49
|
-
setService<ServiceName extends "s3" | "sqs" | "sns" | "
|
|
50
|
+
setService<ServiceName extends "s3" | "sqs" | "sns" | "dynamodb">(service: ServiceName, client: ServiceName extends "sqs" ? SQS : ServiceName extends "sns" ? SNS : ServiceName extends "dynamodb" ? DynamoDB : ServiceName extends "s3" ? S3 : never): void;
|
|
50
51
|
/** Must be called only inside `onInit`, otherwise it has no effect */
|
|
51
52
|
addLambda(lambdaName: string, config: ILambdaFunction): void;
|
|
52
53
|
setEndpoint(serviceName: string, url: string, asLambdaEnvVar?: `AWS_ENDPOINT_URL_${string}`): void;
|
|
@@ -122,4 +123,3 @@ export interface Options {
|
|
|
122
123
|
}
|
|
123
124
|
export type ConfigDefiner = (this: ClientConfigParams, internalResources: ClientConfigParams) => Promise<Omit<Config, "config" | "options">>;
|
|
124
125
|
export declare function defineConfig(options: Options): ConfigDefiner;
|
|
125
|
-
export {};
|
package/dist/defineConfig.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
var h="0.0.3";function k(e){let c=new Set;e.plugins&&(e.plugins=e.plugins.filter((i,a)=>typeof i!="object"||!i||!("name"in i)?!1:((!i.name||!i.name.length||typeof i.name!="string")&&(i.name=`plugin-${a}`,console.warn(`No Plugin name provided at index ${a}`)),c.has(i.name)?i.name=i.name+a:c.add(i.name),!0)));async function f({stop:i,lambdas:a,region:g,esbuild:b,getServices:y,setService:d,addLambda:p,log:l,setEndpoint:v,getEndpoint:x,getAwsProviderDefaults:C,setAwsProviderDefaults:S}){let r={build:{esbuild:e?.build?.esbuild??{},shimRequire:e?.build?.shimRequire,optimizeBuild:e?.build?.optimizeBuild},server:{port:e.server?.port},onKill:[]};if(e.defaults&&typeof e.defaults=="object"&&!Array.isArray(e.defaults)&&S(e.defaults),e.functions&&typeof e.functions=="object"&&!Array.isArray(e.functions))for(let[m,s]of Object.entries(e.functions))p(m,s);e.services&&(e.services.sqs&&d("sqs",e.services.sqs),e.services.sns&&d("sns",e.services.sns),e.services.dynamodb&&d("dynamodb",e.services.dynamodb));let o={stop:i,lambdas:a,region:g,esbuild:b,options:e,config:r,getServices:y,setService:d,addLambda:p,log:l,setEndpoint:v,getEndpoint:x,getAwsProviderDefaults:C};if(e.plugins){r.server.onReady=async(s,n)=>{for(let t of e.plugins)if(t.server?.onReady)try{await t.server.onReady.call(o,s,n)}catch(u){l.RED(t.name),console.error(u)}},r.buildCallback=async(s,n)=>{for(let t of e.plugins)if(t.buildCallback)try{await t.buildCallback.call(o,s,n)}catch(u){l.RED(t.name),console.error(u),n||process.exit(1)}};let m=e.plugins.reduce((s,n)=>(n.server?.request?.length&&s.push(...n.server.request),s),[]);m?.length&&(r.server.request=m.map(s=>(s.callback=s.callback.bind(o),s)));for(let s of e.plugins)if(s.onKill&&r.onKill.push(s.onKill.bind(o)),s.onInit)try{await s.onInit.call(o)}catch(n){throw l.RED(s.name),n}}return r}return f.plugins=e.plugins??[],f}export{k as defineConfig,h as exloVersion};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
"use strict";var Q=Object.create;var
|
|
1
|
+
"use strict";var Q=Object.create;var N=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var Y=Object.getPrototypeOf,z=Object.prototype.hasOwnProperty;var X=(t,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of K(e))!z.call(t,n)&&n!==r&&N(t,n,{get:()=>e[n],enumerable:!(o=J(e,n))||o.enumerable});return t};var O=(t,e,r)=>(r=t!=null?Q(Y(t)):{},X(e||!t||!t.__esModule?N(r,"default",{value:t,enumerable:!0}):r,t));var C=require("node:util"),i=require("node:worker_threads");var P=require("node:stream"),x=class extends P.Writable{#e=!1;#t;#n=new TypeError('Invalid value "undefined" for header "Content-Type"');_onBeforeFirstWrite;constructor(e){super({highWaterMark:e.highWaterMark,write:e.write}),this.#t=this.write.bind(this),this.write=(o,n,l)=>{o=this.#o(o),!this.#e&&typeof this._onBeforeFirstWrite=="function"&&this._onBeforeFirstWrite(m=>this.#t(m));let a=this.#t(o,n,l);return this.#e||(this.#e=!0),a};let r=this.end.bind(this);this.end=(...o)=>{if(o.length){let[n]=o;n&&typeof n!="function"?(this.write(n),r()):r(...o)}else r(...o)}}#o=e=>(typeof e!="string"&&!Buffer.isBuffer(e)&&e?.constructor!==Uint8Array&&(e=JSON.stringify(e)),e);setContentType=e=>{if(!e)throw this.#n;this.emit("ct",e)}},v=class{static from=(e,r)=>{let o=JSON.stringify(r);return e._onBeforeFirstWrite=n=>{e.setContentType("application/vnd.awslambda.http-integration-response"),n(o),n(new Uint8Array(8))},e}};var $={streamifyResponse:function(e,r){return e.stream=!0,e.streamOpt=r,e},HttpResponseStream:v};var _=require("node:async_hooks"),A=O(require("node:path")),D=require("node:util"),B=process.cwd(),Z=`${B}/`,H="/dist/lambda/router.",F=B.split(A.default.sep).filter(Boolean)[0],M=new RegExp(`\\((/${F}|${F}).*(\\d+):(\\d+)`),ee=clearTimeout.bind(clearTimeout),{AWS_LAMBDA_FUNCTION_NAME:U}=process.env,te=t=>{let e=t.match(/{[\w\W]*}/);if(e)return`${t.slice(0,e.index+1)}
|
|
2
2
|
\x1B[33m try {\x1B[0m
|
|
3
3
|
${t.slice(e.index+1,-1).trim()}
|
|
4
4
|
\x1B[33m } catch (error) {\x1B[0m
|
|
5
5
|
\x1B[35m // handle the error\x1B[0m
|
|
6
6
|
\x1B[33m }\x1B[0m
|
|
7
7
|
}`},R=t=>{let e={errorType:"string",errorMessage:"",trace:[]};if(t instanceof Error)e.errorType=t.name,e.errorMessage=t.message,typeof t.stack=="string"&&(e.trace=t.stack.split(`
|
|
8
|
-
`));else{e.errorType=typeof t;try{e.errorMessage=t.toString()}catch{}}return e},ne=t=>{let e="";try{let r=
|
|
9
|
-
`),
|
|
10
|
-
`.slice(1),r++,e+=" ".repeat(r))}),e},g=class t extends Map{static EMPTY_ARGS=[];static IGNORE=["PROMISE","RANDOMBYTESREQUEST","PerformanceObserver","TIMERWRAP"];onEmpty;requestId;callbackWaitsForEmptyEventLoop=!0;async=!1;hook;static context={};static parentPort;static logTimeoutPossibleCause=e=>{let r=this.context[e];if(!r.timeout)return;let
|
|
11
|
-
${
|
|
8
|
+
`));else{e.errorType=typeof t;try{e.errorMessage=t.toString()}catch{}}return e},ne=t=>{let e="";try{let r=M.exec(t.stack.split("at")[2])?.[0];typeof r=="string"&&(e=r.slice(1))}catch{}return e},G=()=>{let t={assert:console.assert.bind(console.assert),clear:console.clear.bind(console.clear),count:console.count.bind(console.count),countReset:console.countReset.bind(console.countReset),debug:console.debug.bind(console.debug),dir:console.dir.bind(console.dir),dirxml:console.dirxml.bind(console.dirxml),error:console.error.bind(console.error),group:console.group.bind(console.group),groupCollapsed:console.groupCollapsed.bind(console.groupCollapsed),groupEnd:console.groupEnd.bind(console.groupEnd),info:console.info.bind(console.info),log:console.log.bind(console.log),table:console.table.bind(console.table),time:console.time.bind(console.time),timeEnd:console.timeEnd.bind(console.timeEnd),timeLog:console.timeLog.bind(console.timeLog),timeStamp:console.timeStamp.bind(console.timeStamp),trace:console.trace.bind(console.trace),warn:console.warn.bind(console.warn)};console=new Proxy(console,{get(e,r){return typeof e[r]=="function"?(...o)=>{let n={};Error.captureStackTrace(n);let l=ne(n);process.stdout.write(`\x1B[90m${new Date().toISOString()} ${r.toUpperCase()} ${U} ${l?.replace(Z,"")}\x1B[0m
|
|
9
|
+
`),t[r](...o)}:t[r]}})},V=t=>{let e="",r=0;return t.slice().reverse().filter(o=>o&&!o.includes(__dirname)&&!o.includes(H)).forEach(o=>{let n=M.exec(o)?.[0];n&&(e+=`${n}
|
|
10
|
+
`.slice(1),r++,e+=" ".repeat(r))}),e},g=class t extends Map{static EMPTY_ARGS=[];static IGNORE=["PROMISE","RANDOMBYTESREQUEST","PerformanceObserver","TIMERWRAP"];onEmpty;requestId;callbackWaitsForEmptyEventLoop=!0;async=!1;hook;static context={};static parentPort;static logTimeoutPossibleCause=e=>{let r=this.context[e];if(!r.timeout)return;let o=[...r.promises.values()].reduce((n,l)=>(l.took&&(l.stack=l.stack.reverse().map(a=>{let m=M.exec(a)?.[0];if(m)return m.slice(1)}).filter(a=>a&&!a.includes(__dirname)&&!a.includes(H)),l.stack.length&&(delete l.start,n.push(l))),n),[]);if(o=o.filter((n,l)=>{if(n.took>.01)return!n.stack.every(a=>o.find((m,d)=>l!=d&&(m.took>n.took||n.stack.length<m.stack.length)&&m.stack.includes(a)))}),o.length){let n=(0,D.inspect)(o,{colors:!0});process.stdout.write(`'${U}' Timeout possible cause:
|
|
11
|
+
${n}
|
|
12
12
|
`)}};constructor(e){super(),this.requestId=e,t.context[e]={timers:new Map,promises:new Map}}static restoreContext=()=>{Object.values(this.context).forEach(({timers:e})=>{let r=[...e.entries()];r.length&&process.stdout.write(`\x1B[31mWarning! Restoring context from previous invokation...
|
|
13
13
|
This may lead to data leak.\x1B[0m
|
|
14
|
-
`),r.forEach(([n
|
|
15
|
-
`).slice(7);if(r=="PROMISE"&&t.context[this.requestId].promises.set(e,{stack:a,start:Date.now()}),t.IGNORE.includes(r))return this;if(r=="Timeout"){let m=
|
|
14
|
+
`),r.forEach(([o,n])=>{n.interval?setInterval(n._onTimeout,n.interval):setTimeout(n._onTimeout,n.timerValue),e.delete(o)})})};storeContextTimers=()=>{[...this.entries()].forEach(([e,r])=>{if(r.type!="Timeout")return;let{_idleTimeout:o,_idleStart:n,_onTimeout:l,_repeat:a,_destroyed:m}=r.resource,d=o-n;!m&&(d>-1||a)&&t.context[this.requestId].timers.set(e,{timerValue:d,_onTimeout:l,interval:a}),clearTimeout(r.resource)})};isEmpty=()=>!([...this.values()].filter(r=>!(typeof r.resource.hasRef=="function"&&!r.resource.hasRef())).length>0);add=(e,r,o,n)=>{let l={};Error.captureStackTrace(l);let a=l.stack.split(`
|
|
15
|
+
`).slice(7);if(r=="PROMISE"&&t.context[this.requestId].promises.set(e,{stack:a,start:Date.now()}),t.IGNORE.includes(r))return this;if(r=="Timeout"){let m=n._repeat?"setInterval":"setTimeout",d=n._onTimeout.toString(),p=n._onTimeout.bind(n._onTimeout),S=n._timerArgs??t.EMPTY_ARGS;n._onTimeout=()=>{try{p(...S)}catch(T){let c=R(T),E=te(d);t.parentPort.postMessage({channel:"uncaught",type:m,data:{error:c,solution:E}})}}}return this.set(e,{resource:n,triggerAsyncId:o,type:r,stack:a})};destroy=e=>{this.delete(e)&&this.isEmpty()&&this.onEmpty&&this.onEmpty()};resolve=e=>{let r=t.context[this.requestId];if(r){let o=r.promises.get(e);o?.start&&(o.took=(Date.now()-o.start)/1e3)}};enable=()=>{t.restoreContext();let e=this,r=function(n){ee(n),e.destroy(Number(n))};global.clearTimeout=r,global.clearInterval=r,this.hook=(0,_.createHook)({init:this.add,destroy:this.destroy,promiseResolve:this.resolve}).enable()};disable=()=>{this.hook?.disable(),delete this.hook}};globalThis.awslambda=$;var b={RED:t=>{},BLUE:t=>{},PINK:t=>{}},h,oe=new Error("Unable to stringify response body");i.workerData.debug&&(b.RED=t=>{process.stdout.write(`\x1B[31m${t}\x1B[0m
|
|
16
16
|
`)},b.BLUE=t=>{process.stdout.write(`\x1B[34m${t}\x1B[0m
|
|
17
17
|
`)},b.PINK=t=>{process.stdout.write(`\x1B[95m${t}\x1B[0m
|
|
18
18
|
`)},G());g.parentPort=i.parentPort;var y=(t,e)=>{let r=R(e);i.parentPort.postMessage({channel:"fail",data:r,awsRequestId:t})},re=t=>{let e=R(t);i.parentPort.postMessage({channel:"fatal",data:e})},q=()=>{},W=(t,e,r)=>{try{JSON.stringify(r),i.parentPort.postMessage({channel:t,data:r,awsRequestId:e})}catch{y(e,oe)}finally{q()}};process.exit=t=>{process.stdout.write(`\x1B[31musage of process.exit() will throw Runtime error in AWS Lambda\x1B[0m
|
|
19
|
-
`)};process.on("unhandledRejection",t=>{process.stdout.write((0,
|
|
19
|
+
`)};process.on("unhandledRejection",t=>{process.stdout.write((0,C.inspect)(t))});process.on("uncaughtException",t=>{process.stdout.write((0,C.inspect)(t))});var se=async t=>{let{channel:e,data:r,awsRequestId:o}=t;if(e=="import"){let n;try{n=await import(`file://${i.workerData.esOutputPath}?version=${Date.now()}`),process.env.NODE_V8_COVERAGE&&(q=(await import("node:v8")).takeCoverage)}catch(l){re(l);return}if(i.workerData.handlerName=="default"&&typeof n.default=="object"&&typeof n.default.default=="function"?h=n.default.default:h=n[i.workerData.handlerName],typeof h!="function")throw new Error(`${i.workerData.name} > ${i.workerData.handlerName} is not a function`);i.parentPort.postMessage({channel:"import"})}else if(e=="complete"){let n=g.context[o];typeof t.timeout=="boolean"&&(n.timeout=!0)}else if(e=="exec"){let{event:n,clientContext:l}=r,a=!1,m=i.workerData.timeout*1e3,d,p=()=>{a=!0},S=Date.now(),T=()=>Math.max(m-(Date.now()-S),0),c=new g(o),E={functionVersion:"$LATEST",functionName:i.workerData.name,memoryLimitInMB:String(i.workerData.memorySize),logGroupName:`/aws/lambda/${i.workerData.name}`,logStreamName:`${new Date().toLocaleDateString()}[$LATEST]${o.replace(/-/g,"")}`,clientContext:l,identity:void 0,invokedFunctionArn:`arn:aws:lambda:eu-west-1:00000000000:function:${i.workerData.name}`,awsRequestId:o,getRemainingTimeInMillis:T};if(typeof h.stream=="boolean"){let w=(s,u,k)=>{i.parentPort.postMessage({channel:"stream",data:s,awsRequestId:o,type:"write",encoding:u}),k()};d=new x({highWaterMark:h.streamOpt?.highWaterMark,write:w}),d.on("close",()=>{a||(p(),i.parentPort.postMessage({channel:"stream",awsRequestId:o,type:"end"}))}),d.on("error",s=>{a||(p(),b.RED(s),y(o,s))}),d.on("ct",s=>i.parentPort.postMessage({channel:"stream",data:s,awsRequestId:o,type:"ct"}));let f={get callbackWaitsForEmptyEventLoop(){return c.callbackWaitsForEmptyEventLoop},set callbackWaitsForEmptyEventLoop(s){c.callbackWaitsForEmptyEventLoop=s},...E};typeof h(n,d,f)?.catch?.(s=>{d.destroy(),p(),b.RED(s),y(o,s)})?.then!="function"&&(p(),d.destroy(),y(o,new Error("Streaming does not support non-async handlers.")))}else{let w={get callbackWaitsForEmptyEventLoop(){return c.callbackWaitsForEmptyEventLoop},set callbackWaitsForEmptyEventLoop(s){c.callbackWaitsForEmptyEventLoop=s},...E,succeed:s=>{a||(p(),W("succeed",o,s))},fail:s=>{a||(p(),y(o,s))},done:function(s,u){a||(p(),s?y(o,s):W("done",o,u))}},f,L=(s,u)=>{f?b.RED("Invocation has already been reported as done. Cannot call complete more than once per invocation."):f={error:s,res:u}};try{c.enable();let s=h(n,w,L);if(s?.then?.(u=>{c.storeContextTimers(),g.logTimeoutPossibleCause(o),!(f||a)&&(p(),W("return",o,u),c.disable())})?.catch(u=>{p(),y(o,u),c.disable()}),c.async=typeof s?.then=="function",!c.async&&s&&b.RED("Synchronous handler 'return' value is ignored. Consider marking your handler as 'async' or use callback(error, returnValue)"),!a&&(!c.async||f)){let u=()=>{c.storeContextTimers(),c.disable(),f?w.done(f.error,f.res):(p(),W("return",o,null))};if(c.isEmpty())u();else if(!f||c.callbackWaitsForEmptyEventLoop){if(f&&c.callbackWaitsForEmptyEventLoop){b.BLUE("callbackWaitsForEmptyEventLoop...");let k=[...c.values()],j=k[k.length-1],I=V(j.stack);I&&(b.RED("Blocked by:"),b.PINK(I))}c.onEmpty=()=>{u()}}else u()}}catch(s){p(),y(o,s),c.disable()}}}};i.parentPort.on("message",se);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { MessagePort } from "worker_threads";
|
|
1
|
+
import { type AsyncHook } from "node:async_hooks";
|
|
2
|
+
import type { MessagePort } from "node:worker_threads";
|
|
3
3
|
interface LambdaErrorResponse {
|
|
4
4
|
errorType?: string;
|
|
5
5
|
errorMessage: string;
|
package/dist/main.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { DynamoDB } from "@aws-sdk/client-dynamodb";
|
|
|
2
2
|
import type { S3 } from "@aws-sdk/client-s3";
|
|
3
3
|
import type { SNS } from "@aws-sdk/client-sns";
|
|
4
4
|
import type { SQS } from "@aws-sdk/client-sqs";
|
|
5
|
-
import esbuild from "esbuild";
|
|
6
5
|
import type { BuildContext, BuildResult } from "esbuild";
|
|
6
|
+
import esbuild from "esbuild";
|
|
7
7
|
import type { Config } from "./config";
|
|
8
8
|
import type { IAwsProviderDefaults, Options } from "./defineConfig";
|
|
9
9
|
import type { ILambdaMock } from "./lib/runtime/rapidApi";
|
|
@@ -44,9 +44,9 @@ export declare class ExloApp extends Daemon {
|
|
|
44
44
|
s3: S3 | undefined;
|
|
45
45
|
sqs: SQS | undefined;
|
|
46
46
|
sns: SNS | undefined;
|
|
47
|
-
|
|
47
|
+
dynamodb: DynamoDB | undefined;
|
|
48
48
|
};
|
|
49
|
-
setService<ServiceName extends "s3" | "sqs" | "sns" | "
|
|
49
|
+
setService<ServiceName extends "s3" | "sqs" | "sns" | "dynamodb">(service: ServiceName, client: ServiceName extends "sqs" ? SQS : ServiceName extends "sns" ? SNS : ServiceName extends "dynamodb" ? DynamoDB : ServiceName extends "s3" ? S3 : never): void;
|
|
50
50
|
setEndpoint: (serviceName: string, url: string, asLambdaEnvVar?: string) => void;
|
|
51
51
|
getEndpoint: (serviceName: string) => string;
|
|
52
52
|
getAwsProviderDefaults: () => {
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import type { ExloPlugin } from "../../defineConfig";
|
|
2
2
|
import { type IRestApiServer, type RestApiKey } from "./restApiServer";
|
|
3
3
|
export type IRestApiGatewayOptions = Omit<IRestApiServer, "getLambdas" | "ip">;
|
|
4
|
+
export type { RestApiKey };
|
|
5
|
+
export interface RestApiUsagePlan {
|
|
6
|
+
name?: string;
|
|
7
|
+
apiKeys: {
|
|
8
|
+
apiId: string;
|
|
9
|
+
/** API Key Id or name */
|
|
10
|
+
keyId: string;
|
|
11
|
+
}[];
|
|
12
|
+
}
|
|
4
13
|
export interface IRestApiPluginOptions {
|
|
5
14
|
gateways: IRestApiGatewayOptions[];
|
|
6
15
|
apiKeys?: RestApiKey[];
|
|
16
|
+
usagePlans?: RestApiUsagePlan[];
|
|
7
17
|
}
|
|
8
18
|
export declare const restApiPlugin: (options: IRestApiPluginOptions) => ExloPlugin;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as oe}from"node:crypto";import{randomUUID as $}from"node:crypto";import{createServer as te}from"node:http";import{EventSourceMapping as Ae}from"exlo/internal/esm";import{EventSourceMapping as pe}from"exlo/internal/esm";var S=c=>Object.prototype.toString.call(c)=="[object Object]";import _ from"ajv";import A from"ajv-formats";var h=new _({discriminator:!1,useDefaults:!1,validateFormats:!0,jtd:!1,strictRequired:!0,allowUnionTypes:!0,unevaluated:!1,formats:{"date-time":A.get("date-time"),email:A.get("email"),hostname:A.get("hostname"),ipv4:A.get("ipv4"),ipv6:A.get("ipv6"),uri:A.get("uri")}});h.removeKeyword("if");h.removeKeyword("else");h.removeKeyword("then");h.removeKeyword("const");h.removeKeyword("contains");h.removeKeyword("contentEncoding");h.removeKeyword("contentMediaType");h.removeKeyword("contentSchema");h.removeKeyword("nullable");h.removeKeyword("prefixItems");h.removeKeyword("example");h.removeKeyword("examples");h.removeKeyword("deprecated");h.removeKeyword("readOnly");h.removeKeyword("writeOnly");h.removeKeyword("$comment");h.removeKeyword("$defs");h.removeKeyword("exclusiveMinimum");h.removeKeyword("exclusiveMaximum");h.removeKeyword("propertyNames");h.removeKeyword("$id");h.removeKeyword("id");h.addKeyword({keyword:["exclusiveMaximum","exclusiveMinimum"],type:["number"],schemaType:["number"],$data:!0,code(c){if(c.it.rootId!="http://json-schema.org/draft-07/schema#")throw new Error(`unsupported keyword "${c.keyword}"`)}});h.addKeyword({keyword:"propertyNames",type:["object"],schemaType:["object","boolean"],code(c){if(c.it.rootId!="http://json-schema.org/draft-07/schema#")throw new Error('unsupported keyword "propertyNames"')}});h.addKeyword({keyword:"$id",type:["object"],schemaType:["string"],code(c){if(c.it.rootId!="http://json-schema.org/draft-07/schema#")throw new Error('unsupported keyword "$id"')}});h.addKeyword({keyword:"id",schemaType:["string"]});var K=c=>{if(c&&typeof c=="object"&&!Array.isArray(c)){let e;return c.$schema?e={...c,$schema:"http://json-schema.org/draft-07/schema#"}:e=c,h.compile(e)}throw new Error("API Gateway validator Schema must be an object")};var w=class extends Error{res;constructor(e){super("RestApiError"),this.res=e}setHeaders(e){this.res.headers.Connection="keep-alive",this.res.headers["x-amzn-RequestId"]=e,this.res.body?this.res.headers["Content-Length"]=Buffer.byteLength(this.res.body,"utf-8").toString():this.res.headers["Content-Length"]="0"}completeRequest(e,t){e.shouldKeepAlive=!1,this.setHeaders(t),e.writeHead(this.res.statusCode,this.res.statusText,this.res.headers),e.end(this.res.body)}},T=class extends w{constructor(){super({statusCode:405,statusText:"Method Not Allowed",headers:{"Content-Type":"null"},body:"Unsupported HTTP method"})}},M=class extends w{constructor(){super({statusCode:415,statusText:"Unsupported Media Type",headers:{"Content-Type":"application/json","x-amzn-ErrorType":"BadRequestException"},body:'{"message": "Unsupported Media Type"}'})}},I=class extends w{constructor(e){super({statusCode:400,statusText:"Bad Request",headers:{"Content-Type":"application/json","x-amzn-ErrorType":"BadRequestException"},body:e})}},E=class extends w{constructor(){super({statusCode:403,statusText:"Forbidden",headers:{"Content-Type":"application/json","x-amzn-ErrorType":"ForbiddenException"},body:'{"message": "Forbidden"}'})}},P=class extends w{constructor(){super({statusCode:403,statusText:"Forbidden",headers:{"Content-Type":"application/json","x-amzn-ErrorType":"MissingAuthenticationTokenException"},body:'{"message": "Missing Authentication Token"}'})}},m=class extends w{constructor(e=502){super({statusCode:e,statusText:"BadGateway",headers:{"Content-Type":"application/json","x-amzn-ErrorType":"InternalServerErrorException"},body:'{"message": "Internal server error"}'})}};function N(c,e,t){let n=e.split("/").filter(Boolean),o={};if(!n.length){let i;return t[c]?i=c:t.ANY&&(i="ANY"),i?{matchedMethod:i,pathParams:o,resource:t,resourcePath:"/"}:void 0}let a=q(c,n,t,o,"");if(a?.resource)return{matchedMethod:a.matchedMethod,pathParams:o,resource:a.resource,resourcePath:a?.resourcePath}}function q(c,e,t,n,o){if(!e.length)return;let a=`/${e[0]}`;if(a in t){let r=t[a],d=`${o}${a}`;if(e.length===1){let u=r[c]?c:r.ANY?"ANY":null;if(u)return{matchedMethod:u,resource:r,resourcePath:d}}return q(c,e.slice(1),r,n,d)}let i=Object.keys(t).find(r=>r.startsWith("/{")&&!r.endsWith("+}"));if(i){let r=i.slice(2,-1);n[r]=encodeURIComponent(e[0]);let d=t[i],u=`${o}${i}`;if(e.length===1){let g=d[c]?c:d.ANY?"ANY":void 0;if(g)return{matchedMethod:g,resource:d,resourcePath:u}}return q(c,e.slice(1),d,n,u)}let s=Object.keys(t).find(r=>r.startsWith("/{")&&r.endsWith("+}"));if(s){let r=s.match(/\{(.+?)\+\}/)?.[1];r&&(n[r]=e.map(encodeURIComponent).join("/"));let d=t[s],u=`${o}${s}`;if(d.ANY)return{matchedMethod:"ANY",resource:d,resourcePath:u}}}import{getRandomValues as k}from"node:crypto";import{networkInterfaces as B}from"node:os";var C="abcdefghijklmnopqrstuvwxyz0123456789",H=`ABCDEFGHIJKLMNOPQRSTUVWXYZ${C}`,J=40;function O(){let c="",e=new Uint8Array(45);k(e);for(let t=0;t<J;t++)c+=H[e[t]%H.length];return c}var X=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function j(){let c=new Date,e=String(c.getUTCDate()).padStart(2,"0"),t=X[c.getUTCMonth()],n=c.getUTCFullYear(),o=String(c.getUTCHours()).padStart(2,"0"),a=String(c.getUTCMinutes()).padStart(2,"0"),i=String(c.getUTCSeconds()).padStart(2,"0");return{requestTime:`${e}/${t}/${n}:${o}:${a}:${i} +0000`,requestTimeEpoch:c.getTime()}}function D(){let c="127.0.0.1";if(B){let e=Object.values(B()).reduce((t,n)=>(t.push(...n),t),[])?.filter(t=>!t.internal&&t.family==="IPv4").find(Boolean)?.address;e&&(c=e)}return c}function L(){let c=Math.floor(Date.now()/1e3).toString(16),e=k(new Uint8Array(12)).reduce((t,n)=>t+n.toString(16).padStart(2,"0"),"");return`1-${c}-${e}`}function Z(){return k(new Uint8Array(8)).reduce((c,e)=>c+e.toString(16).padStart(2,"0"),"")}function ee(c){let e=c.split("-");if(e.length===3){let t=(BigInt(`0x${e[2]}`)+BigInt(2)).toString(16).padStart(24,"0");return`${e[0]}-${e[1]}-${t}`}return c}function U(c){if(!c)return`Root=${L()}`;let e=c.match(/Root=([^\s;]+)/),t=e?e[1]:L(),n=ee(t);return`Self=1-${Z()};Root=${n}`}function Q(){let c="",e=new Uint8Array(6);crypto.getRandomValues(e);for(let t=0;t<6;t++)c+=C[e[t]%C.length];return c}function V(){let c=new Uint8Array(8);return crypto.getRandomValues(c),Buffer.from(String.fromCharCode(...c),"utf-8").toString("base64url")}var se=["connection","content-md5","content-length","expect","max-forwards","proxy-authenticate","server","te","host","trailer","transfer-encoding","upgrade","www-authenticate","x-forwarded-port","x-forwarded-proto"],re=["AWS4-HMAC-SHA256","Credential","SignedHeaders","Signature"],F=["DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT"],G=["authorization","connection","content-md5","max-forwards","user-agent","www-authenticate","date","server"],z=["expect","host","proxy-authenticate","te","transfer-encoding","trailer","upgrade","via"],ne=/^[a-z0-9-.+#!{}_'`]+\/([a-z0-9-.+#!{}_'`]+|\*{1})$/,v=class c{constructor({id:e,ip:t,getLambdas:n,port:o,stage:a,resources:i,requireStagePrefixInRequestPath:s,models:r,binaryMediaTypes:d,resolveLambdaNameFromArn:u},g){this.logger=g;if(e)this.id=e;else{let l=$();this.id=l.slice(0,l.indexOf("-"))}this.ip=t,this.port=o,this.stage=a??"dev",this.getLambdas=n,this.resources=i,this.resolveLambdaNameFromArn=u,this.deploymentId=Q(),r&&typeof r=="object"?this.models=r:this.models={},typeof s=="boolean"&&(this.requireStagePrefixInRequestPath=s),Array.isArray(d)&&this.compileBinaryTypes(d),this.compileResources(this.resources)}static apiKeys=[];static servers=[];server;id;ip;port;stage;requireStagePrefixInRequestPath=!0;deploymentId;resolveLambdaNameFromArn;binaryMediaTypes=[];getLambdas;resources;compiledResources={};sockets=[];models;compileBinaryTypes(e){for(let t of e){let n=t.toLowerCase();if(n=="*/*"){this.binaryMediaTypes.push(/^.*$/);continue}if(ne.test(n))if(n.endsWith("/*")){let o=new RegExp(`^${n.slice(0,-1)}[a-z0-9-_.$~+#'{}\`!*]+$`);this.binaryMediaTypes.push(o)}else{let o=new RegExp(`^${n}$`);this.binaryMediaTypes.push(o)}}}compileResources(e){for(let[t,n]of Object.entries(e))t.startsWith("/")&&this.compileResources(n);if(!e.resourceId){let t=$();e.resourceId=t.slice(0,t.indexOf("-"))}this.compiledResources[e.resourceId]={};for(let t of F){let n=e[t];if(!n)continue;let o=[],a=[],i={};if(n.request){if(n.request.queryStringParameters)for(let[s,r]of Object.entries(n.request.queryStringParameters))r.required&&o.push(s);if(n.request.headers)for(let[s,r]of Object.entries(n.request.headers))r.required&&a.push(s);if(n.request.body)for(let[s,r]of Object.entries(n.request.body)){let d=this.models[r];i[s]=K(d)}}this.compiledResources[e.resourceId][t]={requiredQueryParams:o,requiredHeaders:a,body:i}}}async start(){this.server=te((t,n)=>{this.requestHandler(t,n)}),await new Promise(t=>{this.server.listen(this.port,t)}),c.servers.push(this),this.port=this.server.address().port,this.server.on("connection",t=>{t.on("close",()=>{let n=this.sockets.findIndex(o=>o==t);n!=-1&&this.sockets.splice(n,1)}),this.sockets.push(t)});let e=this.requireStagePrefixInRequestPath?`/${this.stage}`:"";this.printPaths(this.resources,e)}printPaths(e,t){let n=Object.keys(e);for(let o of n){let a=e[o];if(!o.startsWith("/"))continue;let i=Object.keys(a).filter(r=>F.includes(r)),s=`${t}${o}`;i.length&&this.logger.CYAN(`${i.join(", ")} http://localhost:${this.port}${s}`),this.printPaths(a,s)}}dropConnections(){for(let e of this.sockets)e.writable&&e.destroy()}stop(){this.server?.close()}findResourceFromRequest(e){let{method:t,url:n}=e,o=n.indexOf("?");o==-1&&(o=void 0);let a=n.slice(0,o),i=a;for(;i!="/"&&i.endsWith("/");)i=i.slice(0,-1);let s;o&&(s=n.slice(o));let r=i;if(this.requireStagePrefixInRequestPath){if(!i.startsWith(`/${this.stage}`))return;r=i.slice(`/${this.stage}`.length)}let d=N(t,r,this.resources);if(d)return{resource:d.resource,pathname:i,requestPath:a,rawQueryStringParameters:s,matchedMethod:d.matchedMethod,pathParams:d.pathParams,resourcePath:d.resourcePath}}static async collectBody(e,t){let n=Buffer.alloc(0);return e.on("data",o=>{n=Buffer.concat([n,o])}),new Promise(o=>{e.on("end",async()=>{o(n?n.toString(t?"base64":"utf-8"):null)})})}static getQueryStrings(e){let t=null,n=null;if(!e)return{queryStringParameters:n,multiValueQueryStringParameters:t};let o=e.slice(1).split("&"),a={};for(let s of o){let[r,d]=s.split("="),u=r.split(";"),g=d?.split(";")??[""];if(u.length==1&&g.length==1){let R=decodeURIComponent(u[0]),b=decodeURIComponent(g[0]);R in a?a[R].push(b):a[R]=[b];continue}let l=u.slice(0,-1),p=u[u.length-1],f=decodeURIComponent(p),y=decodeURIComponent(g[0]);if(f in a?a[f].push(y):a[f]=[y],g.length>1){let R=g.slice(1);for(let b of R)l.push(b)}for(let R of l){let b=decodeURIComponent(R);b in a?a[b].push(""):a[b]=[""]}}let i=Object.keys(a);if(i.length){n={},t={};for(let s of i.sort()){let r=a[s];t[s]=r,n[s]=r[r.length-1]}}return{queryStringParameters:n,multiValueQueryStringParameters:t}}static shouldDropHeader(e){return!!se.includes(e)}static parseMultiValueHeaders(e){let t={},n={},o={};for(let i=0;i<e.length;i=i+2){let s=e[i],r=s.toLowerCase(),d=e[i+1]?.trim()??"";if(d=Buffer.from(d,"utf-8").toString("latin1"),r=="x-amzn-trace-id"){t["X-Amzn-Trace-Id"]=[d];continue}if(r=="user-agent"){t["User-Agent"]=[d];continue}if(r=="x-forwarded-for"){o[s]||(o[s]=[]),o[s].push(d);continue}if(c.shouldDropHeader(r))continue;let u=n[r];u&&u!=s&&delete t[u],s in t||(t[s]=[]),t[s].push(d),n[r]=s}let a=Object.keys(o);if(a.length){let i=a[a.length-1];t["X-Forwarded-For"]=o[i]}return t}getHeaders(e){let t=c.parseMultiValueHeaders(e),n=t["X-Amzn-Trace-Id"]?t["X-Amzn-Trace-Id"][0]:void 0;t.Host=[`localhost:${this.port}`],t["X-Amzn-Trace-Id"]=[U(n)],t["X-Forwarded-Proto"]=["http"],t["X-Forwarded-Port"]=[String(this.port)],t["X-Forwarded-For"]||(t["X-Forwarded-For"]=[]),t["X-Forwarded-For"].push(this.ip);let o={},a={},i=Object.keys(t).sort((l,p)=>l.toLowerCase().localeCompare(p.toLowerCase())),s,r,d,u="*/*",g;for(let l of i){let p=t[l],f=l.toLowerCase();if(f=="x-http-method-override"){s=p[p.length-1];continue}if(f=="x-api-key")r=p[p.length-1];else if(f=="authorization"){if(g=p[p.length-1],g.startsWith("AWS4-HMAC-SHA256 Credential=")&&re.every(y=>g.includes(y)))continue}else if(f=="content-type")d=p[p.length-1].toLowerCase();else if(f=="accept")u=p[p.length-1].toLowerCase().split(",")[0];else if(l=="X-Forwarded-For"){let y=p.join(", ");o[l]=[y],a[l]=y;continue}o[l]=p,a[l]=p[p.length-1]}return{headers:a,multiValueHeaders:o,overrideMethod:s,apiKey:r,contentType:d,accept:u,authorization:g}}createRequestContext({foundRequest:e,requestId:t,httpMethod:n,req:o,headers:a,apiKey:i}){let s,r;if(i){s={apiKey:i};let f=c.apiKeys.find(y=>y.value==i)?.id;f&&(r={apiKeyId:f})}let d={cognitoIdentityPoolId:null,accountId:null,cognitoIdentityId:null,...s,caller:null,sourceIp:o.socket.remoteAddress?.includes(":")?this.ip:o.socket.remoteAddress,principalOrgId:null,accessKey:null,cognitoAuthenticationType:null,cognitoAuthenticationProvider:null,userArn:null,...r,userAgent:a["User-Agent"]??null,user:null},{requestTime:u,requestTimeEpoch:g}=j(),l=e.requestPath;return this.requireStagePrefixInRequestPath||(l=`/${this.stage}${e.requestPath}`),{resourceId:e.resource.resourceId,resourcePath:e.resourcePath,httpMethod:n,extendedRequestId:V(),requestTime:u,path:l,accountId:"123456789012",protocol:"HTTP/1.1",stage:this.stage,domainPrefix:"localhost",requestTimeEpoch:g,requestId:t,identity:d,domainName:`http://localhost:${this.port}`,deploymentId:this.deploymentId,apiId:this.id}}async createPayloadFromRequest(e,t,n){let{rawHeaders:o,method:a}=e,{headers:i,multiValueHeaders:s,overrideMethod:r,apiKey:d,contentType:u,accept:g}=this.getHeaders(o),l=!1;u&&this.binaryMediaTypes.find(Y=>Y.test(u))&&(l=!0);let p=await c.collectBody(e,l)||null,{queryStringParameters:f,multiValueQueryStringParameters:y}=c.getQueryStrings(t.rawQueryStringParameters),R=null;Object.keys(t.pathParams).length&&(R=t.pathParams);let b=r??a,W=`/${this.stage}`,x=t.requestPath;return this.requireStagePrefixInRequestPath&&(x=x.slice(W.length),x||(x="/")),{payload:{resource:t.resourcePath,path:x,httpMethod:b,headers:i,multiValueHeaders:s,queryStringParameters:f,multiValueQueryStringParameters:y,pathParameters:R,stageVariables:null,requestContext:this.createRequestContext({foundRequest:t,requestId:n,apiKey:d,req:e,headers:i,httpMethod:b}),isBase64Encoded:l,body:p},contentType:u,accept:g}}validateRequest(e,t,n,o){let{payload:a,contentType:i}=e;if(!F.includes(a.httpMethod))throw new T;if(t.requiresApiKey&&!a.requestContext.identity.apiKeyId)throw new E;let s=i??"application/json";if(s.startsWith("application/json;")){let u=s.replaceAll(" ","").split(";").find(g=>g.startsWith("charset="));if(u&&u.replaceAll(",","")!="charset=utf-8")throw new M;s="application/json"}let r=this.compiledResources[n]?.[o];if(t.validate&&r){if(t.validate.query&&r.requiredQueryParams.length){if(!a.queryStringParameters)throw new I(`{"message": "Missing required request parameters: [${r.requiredQueryParams.join(", ")}]"}`);let l=r.requiredQueryParams.filter(p=>!a.queryStringParameters[p]);if(l.length)throw new I(`{"message": "Missing required request parameters: [${l.join(", ")}]"}`)}if(t.validate.headers&&r.requiredHeaders.length){let l=r.requiredHeaders.filter(p=>!a.headers[p]);if(l.length)throw new I(`{"message": "Missing required request parameters: [${l.join(", ")}]"}`)}if(!t.validate.body||a.isBase64Encoded||this.binaryMediaTypes.find(l=>l.test("anything")))return;let d=r.body[s];if(typeof d!="function")return;if(!a.body)throw new I('{"message":[Unknown error parsing request body]}');let u;try{u=JSON.parse(a.body)}catch{throw new I('{"message":[Unknown error parsing request body]}')}if(!d(u)){this.logger.getDebug()&&console.warn(d.errors);let l=d.errors.map(p=>`instancePath '${p.instancePath}' ${p.message}`).join(", ");throw new I(JSON.stringify({message:`[${l}]`}))}}}async invokeProxyLambda(e,t){let a=e;if(a.startsWith("arn:aws:lambda")){let d=a.split(":");(d.length==7||d.length==8)&&(a=d[6])}let i=this.getLambdas(),s=i.find(d=>d.name==a);if(!s&&typeof this.resolveLambdaNameFromArn=="function"){let d=this.resolveLambdaNameFromArn(e);s=i.find(u=>u.name==d)}if(!s)throw this.logger.RED(`REST APiG can not find Lambda ${e}`),new m;return await s.invoke(t)}async invokeIntegration(e,t,n,o){let a=e.matchedMethod,i=e.resource[a];try{let s=await this.createPayloadFromRequest(t,e,o),{payload:r,accept:d}=s;if(this.validateRequest(s,i,e.resource.resourceId,a),i.integration.lambda?.arn){let u=await this.invokeProxyLambda(i.integration.lambda.arn,r);this.responseFromIntegration(n,u,"lambda-proxy",o,d,a)}else if(typeof i.integration.mock=="function"){let u=await i.integration.mock(r);this.responseFromIntegration(n,u,"lambda-proxy",o,d,a)}else this.logger.RED("REST APiG must have integration"),n.statusCode=500,n.end("No resource integration found")}catch(s){if(s instanceof w){s.completeRequest(n,o);return}console.error(s),new m().completeRequest(n,o)}}async requestHandler(e,t){let n=this.findResourceFromRequest(e),o=$();n?await this.invokeIntegration(n,e,t,o):new P().completeRequest(t,o)}parseIntegrationResponseHeaders(e){let t={},n=typeof e.multiValueHeaders;if(S(e.multiValueHeaders))for(let[i,s]of Object.entries(e.multiValueHeaders)){let r=i,d=i.toLowerCase();if(d=="content-type")r="Content-Type";else if(G.includes(d))r=`x-amzn-Remapped-${r}`;else if(z.includes(d))continue;t[r]=[];for(let u of s)if(typeof u=="string")t[r].push(u);else if(typeof u=="number"||typeof u=="boolean")t[r].push(String(u));else if(u===null)t[r].includes("")||t[r].push("");else throw this.logger.RED(`Invalid integration response headers. MultiValueHeader '${r}' values must be string, number, boolean or null.`),new m}else if(n!="undefined"&&n!==null)throw this.logger.RED("Invalid integration response headers. multiValueHeaders must be an object of with string[] as value type."),new m;if(typeof e.headers=="undefined")return t;if(S(e.headers))for(let[i,s]of Object.entries(e.headers)){let r=i,d=i.toLowerCase();if(d=="content-type")r="Content-Type";else if(G.includes(d))r=`x-amzn-Remapped-${r}`;else if(z.includes(d))continue;if(t[r]||(t[r]=[]),typeof s=="string")t[r].includes(s)||t[r].push(s);else if(typeof s=="number"||typeof s=="boolean"){let u=String(s);t[r].includes(u)||t[r].push(u)}else if(s===null)t[r].includes("")||t[r].push("");else throw this.logger.RED(`Invalid integration response headers. Header '${r}' must be string, number, boolean or null.`),new m}else if(e.headers!==null)throw this.logger.RED("Invalid integration response headers. Headers must be an object or null."),new m;return t}parseIntegrationResponse(e,t){if(!S(e))throw this.logger.RED("Invalid integration response."),new m;let n=["statusCode","body","headers","isBase64Encoded","multiValueHeaders"],o=Object.keys(e).find(d=>!n.includes(d));if(o)throw this.logger.RED(`Invalid integration response. Field '${o}' is not supported.`),new m;let a=200,i;if(e.statusCode){if(Number.isNaN(e.statusCode))throw this.logger.RED("Invalid integration response statusCode"),new m;let d=Number(e.statusCode);if(!(d>99&&d<600))throw this.logger.RED("Invalid integration response statusCode"),new m;a=d}let s=typeof e.body;if(s=="string")i=e.body;else if(s=="number")Number.isNaN(e.body)||(i=String(e.body));else if(s=="boolean")i=String(e.body);else if(s!="undefined"&&e.body!==null)throw this.logger.RED("Invalid integration response body"),new m;if(i&&e.isBase64Encoded&&this.binaryMediaTypes.find(d=>d.test(t))){let d=Buffer.from(i,"base64");if(d.toString("base64")!=i)throw this.logger.RED("Invalid integration response body. Incorrect body base64 encoding"),new m;i=d}let r=this.parseIntegrationResponseHeaders(e);return{statusCode:a,body:i,headers:r}}responseFromIntegration(e,t,n,o,a,i){try{let s=this.parseIntegrationResponse(t,a);e.shouldKeepAlive=!1,s.headers.Connection=["keep-alive"],s.headers["x-amzn-RequestId"]=[o],s.headers["Content-Length"]=["0"],s.body&&s.statusCode!=204&&(s.headers["Content-Length"]=[Buffer.byteLength(s.body,typeof s.body=="string"?"utf-8":"base64").toString()]),s.headers["Content-Type"]?s.headers["Content-Type"]=[s.headers["Content-Type"][0]]:s.headers["Content-Type"]=["application/json"],i=="HEAD"&&s.headers["Content-Length"][0]!="0"&&(s.headers["x-amzn-Remapped-Content-Length"]=s.headers["Content-Length"]),e.writeHead(s.statusCode,s.headers),i=="HEAD"?e.end():e.end(s.body)}catch(s){if(s instanceof w){s.completeRequest(e,o);return}console.error(s),new m().completeRequest(e,o)}}};var ie=D(),Ge=c=>{let e=c.gateways,t=c.apiKeys??[],n={name:"rest-api-plugin",onKill(){for(let o of v.servers)o.stop()},shared:{gateways:e},server:{async onReady(){for(let o of t){if(!o.id){let a=oe();o.id=a.slice(0,a.indexOf("-"))}o.value||(o.value=O(),console.log("API Key",o.name,":",o.value)),v.apiKeys.push(o)}for(let o of e){let a=new v({ip:ie,...o,getLambdas:()=>this.lambdas},this.log);await a.start();let i=n.shared.gateways.find(s=>s.id==o.id);i&&(i.id=a.id,i.port=a.port,i.stage=a.stage),console.log(`REST API [${a.id}]`,`http://localhost:${a.port}`),a.server?.unref()}}},buildCallback(o,a){if(a)for(let i of v.servers)i.dropConnections()}};return n};export{Ge as restApiPlugin};
|
|
1
|
+
import{randomUUID as oe}from"node:crypto";import{randomUUID as $}from"node:crypto";import{createServer as te}from"node:http";import{EventSourceMapping as Ae}from"exlo/internal/esm";import{EventSourceMapping as pe}from"exlo/internal/esm";var S=c=>Object.prototype.toString.call(c)=="[object Object]";import _ from"ajv";import A from"ajv-formats";var g=new _({discriminator:!1,useDefaults:!1,validateFormats:!0,jtd:!1,strictRequired:!0,allowUnionTypes:!0,unevaluated:!1,formats:{"date-time":A.get("date-time"),email:A.get("email"),hostname:A.get("hostname"),ipv4:A.get("ipv4"),ipv6:A.get("ipv6"),uri:A.get("uri")}});g.removeKeyword("if");g.removeKeyword("else");g.removeKeyword("then");g.removeKeyword("const");g.removeKeyword("contains");g.removeKeyword("contentEncoding");g.removeKeyword("contentMediaType");g.removeKeyword("contentSchema");g.removeKeyword("nullable");g.removeKeyword("prefixItems");g.removeKeyword("example");g.removeKeyword("examples");g.removeKeyword("deprecated");g.removeKeyword("readOnly");g.removeKeyword("writeOnly");g.removeKeyword("$comment");g.removeKeyword("$defs");g.removeKeyword("exclusiveMinimum");g.removeKeyword("exclusiveMaximum");g.removeKeyword("propertyNames");g.removeKeyword("$id");g.removeKeyword("id");g.addKeyword({keyword:["exclusiveMaximum","exclusiveMinimum"],type:["number"],schemaType:["number"],$data:!0,code(c){if(c.it.rootId!="http://json-schema.org/draft-07/schema#")throw new Error(`unsupported keyword "${c.keyword}"`)}});g.addKeyword({keyword:"propertyNames",type:["object"],schemaType:["object","boolean"],code(c){if(c.it.rootId!="http://json-schema.org/draft-07/schema#")throw new Error('unsupported keyword "propertyNames"')}});g.addKeyword({keyword:"$id",type:["object"],schemaType:["string"],code(c){if(c.it.rootId!="http://json-schema.org/draft-07/schema#")throw new Error('unsupported keyword "$id"')}});g.addKeyword({keyword:"id",schemaType:["string"]});var F=c=>{if(c&&typeof c=="object"&&!Array.isArray(c)){let e;return c.$schema?e={...c,$schema:"http://json-schema.org/draft-07/schema#"}:e=c,g.compile(e)}throw new Error("API Gateway validator Schema must be an object")};var w=class extends Error{res;constructor(e){super("RestApiError"),this.res=e}setHeaders(e){this.res.headers.Connection="keep-alive",this.res.headers["x-amzn-RequestId"]=e,this.res.body?this.res.headers["Content-Length"]=Buffer.byteLength(this.res.body,"utf-8").toString():this.res.headers["Content-Length"]="0"}completeRequest(e,t){e.shouldKeepAlive=!1,this.setHeaders(t),e.writeHead(this.res.statusCode,this.res.statusText,this.res.headers),e.end(this.res.body)}},P=class extends w{constructor(){super({statusCode:405,statusText:"Method Not Allowed",headers:{"Content-Type":"null"},body:"Unsupported HTTP method"})}},M=class extends w{constructor(){super({statusCode:415,statusText:"Unsupported Media Type",headers:{"Content-Type":"application/json","x-amzn-ErrorType":"BadRequestException"},body:'{"message": "Unsupported Media Type"}'})}},I=class extends w{constructor(e){super({statusCode:400,statusText:"Bad Request",headers:{"Content-Type":"application/json","x-amzn-ErrorType":"BadRequestException"},body:e})}},T=class extends w{constructor(){super({statusCode:403,statusText:"Forbidden",headers:{"Content-Type":"application/json","x-amzn-ErrorType":"ForbiddenException"},body:'{"message": "Forbidden"}'})}},E=class extends w{constructor(){super({statusCode:403,statusText:"Forbidden",headers:{"Content-Type":"application/json","x-amzn-ErrorType":"MissingAuthenticationTokenException"},body:'{"message": "Missing Authentication Token"}'})}},f=class extends w{constructor(e=502){super({statusCode:e,statusText:"BadGateway",headers:{"Content-Type":"application/json","x-amzn-ErrorType":"InternalServerErrorException"},body:'{"message": "Internal server error"}'})}};function N(c,e,t){let n=e.split("/").filter(Boolean),a={};if(!n.length){let o;return t[c]?o=c:t.ANY&&(o="ANY"),o?{matchedMethod:o,pathParams:a,resource:t,resourcePath:"/"}:void 0}let d=q(c,n,t,a,"");if(d?.resource)return{matchedMethod:d.matchedMethod,pathParams:a,resource:d.resource,resourcePath:d?.resourcePath}}function q(c,e,t,n,a){if(!e.length)return;let d=`/${e[0]}`;if(d in t){let r=t[d],i=`${a}${d}`;if(e.length===1){let u=r[c]?c:r.ANY?"ANY":null;if(u)return{matchedMethod:u,resource:r,resourcePath:i}}return q(c,e.slice(1),r,n,i)}let o=Object.keys(t).find(r=>r.startsWith("/{")&&!r.endsWith("+}"));if(o){let r=o.slice(2,-1);n[r]=encodeURIComponent(e[0]);let i=t[o],u=`${a}${o}`;if(e.length===1){let h=i[c]?c:i.ANY?"ANY":void 0;if(h)return{matchedMethod:h,resource:i,resourcePath:u}}return q(c,e.slice(1),i,n,u)}let s=Object.keys(t).find(r=>r.startsWith("/{")&&r.endsWith("+}"));if(s){let r=s.match(/\{(.+?)\+\}/)?.[1];r&&(n[r]=e.map(encodeURIComponent).join("/"));let i=t[s],u=`${a}${s}`;if(i.ANY)return{matchedMethod:"ANY",resource:i,resourcePath:u}}}import{getRandomValues as k}from"node:crypto";import{networkInterfaces as B}from"node:os";var C="abcdefghijklmnopqrstuvwxyz0123456789",H=`ABCDEFGHIJKLMNOPQRSTUVWXYZ${C}`,J=40;function O(){let c="",e=new Uint8Array(45);k(e);for(let t=0;t<J;t++)c+=H[e[t]%H.length];return c}var X=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function j(){let c=new Date,e=String(c.getUTCDate()).padStart(2,"0"),t=X[c.getUTCMonth()],n=c.getUTCFullYear(),a=String(c.getUTCHours()).padStart(2,"0"),d=String(c.getUTCMinutes()).padStart(2,"0"),o=String(c.getUTCSeconds()).padStart(2,"0");return{requestTime:`${e}/${t}/${n}:${a}:${d}:${o} +0000`,requestTimeEpoch:c.getTime()}}function U(){let c="127.0.0.1";if(B){let e=Object.values(B()).reduce((t,n)=>(t.push(...n),t),[])?.filter(t=>!t.internal&&t.family==="IPv4").find(Boolean)?.address;e&&(c=e)}return c}function L(){let c=Math.floor(Date.now()/1e3).toString(16),e=k(new Uint8Array(12)).reduce((t,n)=>t+n.toString(16).padStart(2,"0"),"");return`1-${c}-${e}`}function Z(){return k(new Uint8Array(8)).reduce((c,e)=>c+e.toString(16).padStart(2,"0"),"")}function ee(c){let e=c.split("-");if(e.length===3){let t=(BigInt(`0x${e[2]}`)+BigInt(2)).toString(16).padStart(24,"0");return`${e[0]}-${e[1]}-${t}`}return c}function D(c){if(!c)return`Root=${L()}`;let e=c.match(/Root=([^\s;]+)/),t=e?e[1]:L(),n=ee(t);return`Self=1-${Z()};Root=${n}`}function Q(){let c="",e=new Uint8Array(6);crypto.getRandomValues(e);for(let t=0;t<6;t++)c+=C[e[t]%C.length];return c}function V(){let c=new Uint8Array(8);return crypto.getRandomValues(c),Buffer.from(String.fromCharCode(...c),"utf-8").toString("base64url")}var se=["connection","content-md5","content-length","expect","max-forwards","proxy-authenticate","server","te","host","trailer","transfer-encoding","upgrade","www-authenticate","x-forwarded-port","x-forwarded-proto"],re=["AWS4-HMAC-SHA256","Credential","SignedHeaders","Signature"],K=["DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT"],G=["authorization","connection","content-md5","max-forwards","user-agent","www-authenticate","date","server","content-length"],z=["expect","host","proxy-authenticate","te","transfer-encoding","trailer","upgrade","via"],ne=/^[a-z0-9-.+#!{}_'`]+\/([a-z0-9-.+#!{}_'`]+|\*{1})$/,v=class c{constructor({id:e,ip:t,getLambdas:n,port:a,stage:d,resources:o,requireStagePrefixInRequestPath:s,models:r,binaryMediaTypes:i,resolveLambdaNameFromArn:u},h){this.logger=h;if(e)this.id=e;else{let l=$();this.id=l.slice(0,l.indexOf("-"))}if(c.servers.find(l=>l.id==this.id))throw new Error(`[REST API Gateway] An API with the same id (${this.id}) already exists.`);this.ip=t,this.port=a,this.stage=d??"dev",this.getLambdas=n,this.resources=o,this.resolveLambdaNameFromArn=u,this.deploymentId=Q(),r&&typeof r=="object"?this.models=r:this.models={},typeof s=="boolean"&&(this.requireStagePrefixInRequestPath=s),Array.isArray(i)&&this.compileBinaryTypes(i),this.compileResources(this.resources)}static servers=[];server;id;ip;port;stage;requireStagePrefixInRequestPath=!0;deploymentId;apiKeys=[];resolveLambdaNameFromArn;binaryMediaTypes=[];getLambdas;resources;compiledResources={};sockets=[];models;compileBinaryTypes(e){for(let t of e){let n=t.toLowerCase();if(n=="*/*"){this.binaryMediaTypes.push(/^.*$/);continue}if(ne.test(n))if(n.endsWith("/*")){let a=new RegExp(`^${n.slice(0,-1)}[a-z0-9-_.$~+#'{}\`!*]+$`);this.binaryMediaTypes.push(a)}else{let a=new RegExp(`^${n}$`);this.binaryMediaTypes.push(a)}}}compileResources(e){for(let[t,n]of Object.entries(e))t.startsWith("/")&&this.compileResources(n);if(!e.resourceId){let t=$();e.resourceId=t.slice(0,t.indexOf("-"))}this.compiledResources[e.resourceId]={};for(let t of K){let n=e[t];if(!n)continue;let a=[],d=[],o={};if(n.request){if(n.request.queryStringParameters)for(let[s,r]of Object.entries(n.request.queryStringParameters))r.required&&a.push(s);if(n.request.headers)for(let[s,r]of Object.entries(n.request.headers))r.required&&d.push(s);if(n.request.body)for(let[s,r]of Object.entries(n.request.body)){let i=this.models[r];o[s]=F(i)}}this.compiledResources[e.resourceId][t]={requiredQueryParams:a,requiredHeaders:d,body:o}}}async start(){this.server=te((t,n)=>{this.requestHandler(t,n)}),await new Promise(t=>{this.server.listen(this.port,t)}),c.servers.push(this),this.port=this.server.address().port,this.server.on("connection",t=>{t.on("close",()=>{let n=this.sockets.findIndex(a=>a==t);n!=-1&&this.sockets.splice(n,1)}),this.sockets.push(t)});let e=this.requireStagePrefixInRequestPath?`/${this.stage}`:"";this.printPaths(this.resources,e)}printPaths(e,t){let n=Object.keys(e);for(let a of n){let d=e[a];if(!a.startsWith("/"))continue;let o=Object.keys(d).filter(r=>K.includes(r)),s=`${t}${a}`;o.length&&this.logger.CYAN(`${o.join(", ")} http://localhost:${this.port}${s}`),this.printPaths(d,s)}}dropConnections(){for(let e of this.sockets)e.writable&&e.destroy()}stop(){this.server?.close()}findResourceFromRequest(e){let{method:t,url:n}=e,a=n.indexOf("?");a==-1&&(a=void 0);let d=n.slice(0,a),o=d;for(;o!="/"&&o.endsWith("/");)o=o.slice(0,-1);let s;a&&(s=n.slice(a));let r=o;if(this.requireStagePrefixInRequestPath){if(!o.startsWith(`/${this.stage}`))return;r=o.slice(`/${this.stage}`.length)}let i=N(t,r,this.resources);if(i)return{resource:i.resource,pathname:o,requestPath:d,rawQueryStringParameters:s,matchedMethod:i.matchedMethod,requestMethod:t,pathParams:i.pathParams,resourcePath:i.resourcePath}}static async collectBody(e,t){let n=Buffer.alloc(0);return e.on("data",a=>{n=Buffer.concat([n,a])}),new Promise(a=>{e.on("end",async()=>{a(n?n.toString(t?"base64":"utf-8"):null)})})}static getQueryStrings(e){let t=null,n=null;if(!e)return{queryStringParameters:n,multiValueQueryStringParameters:t};let a=e.slice(1).split("&"),d={};for(let s of a){let[r,i]=s.split("="),u=r.split(";"),h=i?.split(";")??[""];if(u.length==1&&h.length==1){let R=decodeURIComponent(u[0]),b=decodeURIComponent(h[0]);R in d?d[R].push(b):d[R]=[b];continue}let l=u.slice(0,-1),p=u[u.length-1],m=decodeURIComponent(p),y=decodeURIComponent(h[0]);if(m in d?d[m].push(y):d[m]=[y],h.length>1){let R=h.slice(1);for(let b of R)l.push(b)}for(let R of l){let b=decodeURIComponent(R);b in d?d[b].push(""):d[b]=[""]}}let o=Object.keys(d);if(o.length){n={},t={};for(let s of o.sort()){let r=d[s];t[s]=r,n[s]=r[r.length-1]}}return{queryStringParameters:n,multiValueQueryStringParameters:t}}static shouldDropHeader(e){return!!se.includes(e)}static parseMultiValueHeaders(e){let t={},n={},a={};for(let o=0;o<e.length;o=o+2){let s=e[o],r=s.toLowerCase(),i=e[o+1]?.trim()??"";if(i=Buffer.from(i,"utf-8").toString("latin1"),r=="x-amzn-trace-id"){t["X-Amzn-Trace-Id"]=[i];continue}if(r=="user-agent"){t["User-Agent"]=[i];continue}if(r=="x-forwarded-for"){a[s]||(a[s]=[]),a[s].push(i);continue}if(c.shouldDropHeader(r))continue;let u=n[r];u&&u!=s&&delete t[u],s in t||(t[s]=[]),t[s].push(i),n[r]=s}let d=Object.keys(a);if(d.length){let o=d[d.length-1];t["X-Forwarded-For"]=a[o]}return t}getHeaders(e){let t=c.parseMultiValueHeaders(e),n=t["X-Amzn-Trace-Id"]?t["X-Amzn-Trace-Id"][0]:void 0;t.Host=[`localhost:${this.port}`],t["X-Amzn-Trace-Id"]=[D(n)],t["X-Forwarded-Proto"]=["http"],t["X-Forwarded-Port"]=[String(this.port)],t["X-Forwarded-For"]||(t["X-Forwarded-For"]=[]),t["X-Forwarded-For"].push(this.ip);let a={},d={},o=Object.keys(t).sort((l,p)=>l.toLowerCase().localeCompare(p.toLowerCase())),s,r,i,u="*/*",h;for(let l of o){let p=t[l],m=l.toLowerCase();if(m=="x-http-method-override"){s=p[p.length-1];continue}if(m=="x-api-key")r=p[p.length-1];else if(m=="authorization"){if(h=p[p.length-1],h.startsWith("AWS4-HMAC-SHA256 Credential=")&&re.every(y=>h.includes(y)))continue}else if(m=="content-type")i=p[p.length-1].toLowerCase();else if(m=="accept")u=p[p.length-1].toLowerCase().split(",")[0];else if(l=="X-Forwarded-For"){let y=p.join(", ");a[l]=[y],d[l]=y;continue}a[l]=p,d[l]=p[p.length-1]}return{headers:d,multiValueHeaders:a,overrideMethod:s,apiKey:r,contentType:i,accept:u,authorization:h}}createRequestContext({foundRequest:e,requestId:t,httpMethod:n,req:a,headers:d,apiKey:o}){let s,r;if(o){s={apiKey:o};let m=this.apiKeys.find(y=>y.value==o)?.id;m&&(r={apiKeyId:m})}let i={cognitoIdentityPoolId:null,accountId:null,cognitoIdentityId:null,...s,caller:null,sourceIp:a.socket.remoteAddress?.includes(":")?this.ip:a.socket.remoteAddress,principalOrgId:null,accessKey:null,cognitoAuthenticationType:null,cognitoAuthenticationProvider:null,userArn:null,...r,userAgent:d["User-Agent"]??null,user:null},{requestTime:u,requestTimeEpoch:h}=j(),l=e.requestPath;return this.requireStagePrefixInRequestPath||(l=`/${this.stage}${e.requestPath}`),{resourceId:e.resource.resourceId,resourcePath:e.resourcePath,httpMethod:n,extendedRequestId:V(),requestTime:u,path:l,accountId:"123456789012",protocol:"HTTP/1.1",stage:this.stage,domainPrefix:"localhost",requestTimeEpoch:h,requestId:t,identity:i,domainName:`http://localhost:${this.port}`,deploymentId:this.deploymentId,apiId:this.id}}async createPayloadFromRequest(e,t,n){let{rawHeaders:a,method:d}=e,{headers:o,multiValueHeaders:s,overrideMethod:r,apiKey:i,contentType:u,accept:h}=this.getHeaders(a),l=!1;u&&this.binaryMediaTypes.find(Y=>Y.test(u))&&(l=!0);let p=await c.collectBody(e,l)||null,{queryStringParameters:m,multiValueQueryStringParameters:y}=c.getQueryStrings(t.rawQueryStringParameters),R=null;Object.keys(t.pathParams).length&&(R=t.pathParams);let b=r??d,W=`/${this.stage}`,x=t.requestPath;return this.requireStagePrefixInRequestPath&&(x=x.slice(W.length),x||(x="/")),{payload:{resource:t.resourcePath,path:x,httpMethod:b,headers:o,multiValueHeaders:s,queryStringParameters:m,multiValueQueryStringParameters:y,pathParameters:R,stageVariables:null,requestContext:this.createRequestContext({foundRequest:t,requestId:n,apiKey:i,req:e,headers:o,httpMethod:b}),isBase64Encoded:l,body:p},contentType:u,accept:h}}validateRequest(e,t,n,a){let{payload:d,contentType:o}=e;if(!K.includes(d.httpMethod))throw new P;if(t.requiresApiKey&&!d.requestContext.identity.apiKeyId)throw new T;let s=o??"application/json";if(s.startsWith("application/json;")){let u=s.replaceAll(" ","").split(";").find(h=>h.startsWith("charset="));if(u&&u.replaceAll(",","")!="charset=utf-8")throw new M;s="application/json"}let r=this.compiledResources[n]?.[a];if(t.validate&&r){if(t.validate.query&&r.requiredQueryParams.length){if(!d.queryStringParameters)throw new I(`{"message": "Missing required request parameters: [${r.requiredQueryParams.join(", ")}]"}`);let l=r.requiredQueryParams.filter(p=>!d.queryStringParameters[p]);if(l.length)throw new I(`{"message": "Missing required request parameters: [${l.join(", ")}]"}`)}if(t.validate.headers&&r.requiredHeaders.length){let l=r.requiredHeaders.filter(p=>!d.headers[p]);if(l.length)throw new I(`{"message": "Missing required request parameters: [${l.join(", ")}]"}`)}if(!t.validate.body||d.isBase64Encoded||this.binaryMediaTypes.find(l=>l.test("anything")))return;let i=r.body[s];if(typeof i!="function")return;if(!d.body)throw new I('{"message":[Unknown error parsing request body]}');let u;try{u=JSON.parse(d.body)}catch{throw new I('{"message":[Unknown error parsing request body]}')}if(!i(u)){this.logger.getDebug()&&console.warn(i.errors);let l=i.errors.map(p=>`instancePath '${p.instancePath}' ${p.message}`).join(", ");throw new I(JSON.stringify({message:`[${l}]`}))}}}async invokeProxyLambda(e,t){let d=e;if(d.startsWith("arn:aws:lambda")){let i=d.split(":");(i.length==7||i.length==8)&&(d=i[6])}let o=this.getLambdas(),s=o.find(i=>i.name==d);if(!s&&typeof this.resolveLambdaNameFromArn=="function"){let i=this.resolveLambdaNameFromArn(e);s=o.find(u=>u.name==i)}if(!s)throw this.logger.RED(`REST APiG can not find Lambda ${e}`),new f;return await s.invoke(t)}async invokeIntegration(e,t,n,a){let d=e.matchedMethod,o=e.requestMethod,s=e.resource[d];try{let r=await this.createPayloadFromRequest(t,e,a),{payload:i,accept:u}=r;if(this.validateRequest(r,s,e.resource.resourceId,d),s.integration.lambda?.arn){let h=await this.invokeProxyLambda(s.integration.lambda.arn,i);this.responseFromIntegration(n,h,"lambda-proxy",a,u,o)}else if(typeof s.integration.mock=="function"){let h=await s.integration.mock(i);this.responseFromIntegration(n,h,"lambda-proxy",a,u,o)}else this.logger.RED("REST APiG must have integration"),n.statusCode=500,n.end("No resource integration found")}catch(r){if(r instanceof w){r.completeRequest(n,a);return}console.error(r),new f().completeRequest(n,a)}}async requestHandler(e,t){let n=this.findResourceFromRequest(e),a=$();n?await this.invokeIntegration(n,e,t,a):new E().completeRequest(t,a)}parseIntegrationResponseHeaders(e){let t={},n=typeof e.multiValueHeaders;if(S(e.multiValueHeaders))for(let[o,s]of Object.entries(e.multiValueHeaders)){let r=o,i=o.toLowerCase();if(i=="content-type")r="Content-Type";else if(G.includes(i)||i.startsWith("x-amzn-remapped-"))r=`x-amzn-Remapped-${r}`;else if(z.includes(i))continue;t[r]=[];for(let u of s)if(typeof u=="string")t[r].push(u);else if(typeof u=="number"||typeof u=="boolean")t[r].push(String(u));else if(u===null)t[r].includes("")||t[r].push("");else throw this.logger.RED(`Invalid integration response headers. MultiValueHeader '${r}' values must be string, number, boolean or null.`),new f}else if(n!="undefined"&&n!==null)throw this.logger.RED("Invalid integration response headers. multiValueHeaders must be an object of with string[] as value type."),new f;if(typeof e.headers=="undefined")return t;if(S(e.headers))for(let[o,s]of Object.entries(e.headers)){let r=o,i=o.toLowerCase();if(i=="content-type")r="Content-Type";else if(G.includes(i)||i.startsWith("x-amzn-remapped-"))r=`x-amzn-Remapped-${r}`;else if(z.includes(i))continue;if(t[r]||(t[r]=[]),typeof s=="string")t[r].includes(s)||t[r].push(s);else if(typeof s=="number"||typeof s=="boolean"){let u=String(s);t[r].includes(u)||t[r].push(u)}else if(s===null)t[r].includes("")||t[r].push("");else if(typeof s<"u")throw this.logger.RED(`Invalid integration response headers. Header '${r}' must be string, number, boolean or null.`),new f}else if(e.headers!==null)throw this.logger.RED("Invalid integration response headers. Headers must be an object or null."),new f;return t}parseIntegrationResponse(e,t){if(!S(e))throw this.logger.RED("Invalid integration response."),new f;let n=["statusCode","body","headers","isBase64Encoded","multiValueHeaders"],a=Object.keys(e).find(i=>!n.includes(i));if(a)throw this.logger.RED(`Invalid integration response. Field '${a}' is not supported.`),new f;let d=200,o;if(e.statusCode){if(Number.isNaN(e.statusCode))throw this.logger.RED("Invalid integration response statusCode"),new f;let i=Number(e.statusCode);if(!(i>99&&i<600))throw this.logger.RED("Invalid integration response statusCode"),new f;d=i}let s=typeof e.body;if(s=="string")o=e.body;else if(s=="number")Number.isNaN(e.body)||(o=String(e.body));else if(s=="boolean")o=String(e.body);else if(s!="undefined"&&e.body!==null)throw this.logger.RED("Invalid integration response body"),new f;if(o&&e.isBase64Encoded&&this.binaryMediaTypes.find(i=>i.test(t))){let i=Buffer.from(o,"base64");if(i.toString("base64")!=o)throw this.logger.RED("Invalid integration response body. Incorrect body base64 encoding"),new f;o=i}let r=this.parseIntegrationResponseHeaders(e);return{statusCode:d,body:o,headers:r}}responseFromIntegration(e,t,n,a,d,o){try{let s=this.parseIntegrationResponse(t,d);e.shouldKeepAlive=!1,s.headers.Connection=["keep-alive"],s.headers["x-amzn-RequestId"]=[a],s.headers["Content-Length"]=["0"],s.body&&s.statusCode!=204&&(s.headers["Content-Length"]=[Buffer.byteLength(s.body,typeof s.body=="string"?"utf-8":"base64").toString()]),s.headers["Content-Type"]?s.headers["Content-Type"]=[s.headers["Content-Type"][0]]:s.headers["Content-Type"]=["application/json"],o=="HEAD"&&s.headers["Content-Length"][0]!="0"&&(s.headers["x-amzn-Remapped-Content-Length"]||(s.headers["x-amzn-Remapped-Content-Length"]=[]),s.headers["x-amzn-Remapped-Content-Length"].push(s.headers["Content-Length"][0])),e.writeHead(s.statusCode,s.headers),o=="HEAD"?e.end():e.end(s.body)}catch(s){if(s instanceof w){s.completeRequest(e,a);return}console.error(s),new f().completeRequest(e,a)}}};var ie=U(),Ge=c=>{let e=c.gateways,t=c.apiKeys??[],n=c.usagePlans??[],a={name:"rest-api-plugin",onKill(){for(let d of v.servers)d.stop();v.servers=[]},shared:{gateways:e},server:{async onReady(){let d=[];for(let s of t){if(!s.id){let r=oe();s.id=r.slice(0,r.indexOf("-"))}s.value||(s.value=O(),console.log("API Key",s.name,":",s.value)),d.push(s)}let o={};for(let s of n)for(let r of s.apiKeys){let i=d.find(u=>u.id==r.keyId||u.name==r.keyId);if(!i)throw new Error(`[REST Api - Usage Plan] Can not find API Key with id: "${r.keyId}"`);o[r.apiId]||(o[r.apiId]=[]),o[r.apiId].push(i)}for(let s of e){let r=new v({ip:ie,...s,getLambdas:()=>this.lambdas},this.log),i=o[r.id];i&&(r.apiKeys=i),await r.start();let u=a.shared.gateways.find(h=>h.id==s.id);u&&(u.id=r.id,u.port=r.port,u.stage=r.stage),console.log(`REST API [${r.id}]`,`http://localhost:${r.port}`),r.server?.unref()}}},buildCallback(d,o){if(o)for(let s of v.servers)s.dropConnections()}};return a};export{Ge as restApiPlugin};
|
|
@@ -3,7 +3,13 @@ import type { ILambda } from "../../defineConfig";
|
|
|
3
3
|
import type { log } from "../../lib/utils/colorize";
|
|
4
4
|
export interface RestApiKey {
|
|
5
5
|
name: string;
|
|
6
|
+
/**
|
|
7
|
+
* If not provided, a random id will be generated.
|
|
8
|
+
*/
|
|
6
9
|
id?: string;
|
|
10
|
+
/**
|
|
11
|
+
* If not provided, a random key will be generated and printed to the console.
|
|
12
|
+
*/
|
|
7
13
|
value?: string;
|
|
8
14
|
}
|
|
9
15
|
interface IIntegrationResponse {
|
|
@@ -19,6 +25,10 @@ interface ILambdaIntegration {
|
|
|
19
25
|
* Function name or function (un)qualified arn
|
|
20
26
|
*/
|
|
21
27
|
arn: string;
|
|
28
|
+
/**
|
|
29
|
+
* Currently only `AWS_PROXY` integration type is supported.
|
|
30
|
+
* @default true
|
|
31
|
+
*/
|
|
22
32
|
proxy?: true;
|
|
23
33
|
};
|
|
24
34
|
mock?: never;
|
|
@@ -73,6 +83,7 @@ interface IRequestResourceFound {
|
|
|
73
83
|
requestPath: string;
|
|
74
84
|
rawQueryStringParameters?: string;
|
|
75
85
|
matchedMethod: string;
|
|
86
|
+
requestMethod: string;
|
|
76
87
|
pathParams: Record<string, string>;
|
|
77
88
|
resourcePath: string;
|
|
78
89
|
}
|
|
@@ -120,7 +131,6 @@ export interface IRestApiServer {
|
|
|
120
131
|
type httpMethod = keyof Omit<EndpointMethods, "resourceId">;
|
|
121
132
|
export declare class RestApiServer {
|
|
122
133
|
logger: typeof log;
|
|
123
|
-
static apiKeys: RestApiKey[];
|
|
124
134
|
static servers: RestApiServer[];
|
|
125
135
|
server?: Server;
|
|
126
136
|
id: string;
|
|
@@ -129,6 +139,7 @@ export declare class RestApiServer {
|
|
|
129
139
|
stage: string;
|
|
130
140
|
requireStagePrefixInRequestPath: boolean;
|
|
131
141
|
deploymentId: string;
|
|
142
|
+
apiKeys: RestApiKey[];
|
|
132
143
|
private resolveLambdaNameFromArn?;
|
|
133
144
|
private binaryMediaTypes;
|
|
134
145
|
private getLambdas;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getSecretsFromFile, type IRotationRules, type SecretConfig, type SecretsManagerServerOptions } from "local-aws-secrets-manager";
|
|
2
|
+
import type { ExloPlugin } from "../../defineConfig";
|
|
3
|
+
export interface IRotationOptions {
|
|
4
|
+
ClientRequestToken?: string;
|
|
5
|
+
RotateImmediately?: boolean;
|
|
6
|
+
RotationLambdaARN: string;
|
|
7
|
+
RotationRules?: IRotationRules;
|
|
8
|
+
}
|
|
9
|
+
export type ISecretsManagerPluginOptions = Pick<SecretsManagerServerOptions, "port" | "rotationIntervalInSeconds" | "maxRotationCount"> & {
|
|
10
|
+
/**
|
|
11
|
+
* Set `AWS_ENDPOINT_URL_SECRETS_MANAGER` env variable inside lambdas
|
|
12
|
+
* @default true
|
|
13
|
+
*/
|
|
14
|
+
setEndpointEnvVar?: boolean;
|
|
15
|
+
secrets?: {
|
|
16
|
+
[secretId: string]: SecretConfig & {
|
|
17
|
+
rotation?: IRotationOptions;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
enable?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
rotateOnStart?: boolean;
|
|
28
|
+
};
|
|
29
|
+
export { getSecretsFromFile };
|
|
30
|
+
export declare function secretsManagerPlugin(options?: ISecretsManagerPluginOptions): ExloPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as v,createSecretsManagerServer as E,getSecretsFromFile as L}from"local-aws-secrets-manager";function O(t){let f=0,u=!0,m=!0,g=!0;t&&(typeof t.port=="number"&&(f=t.port),typeof t.setEndpointEnvVar=="boolean"&&(u=t.setEndpointEnvVar),typeof t.enable=="boolean"&&(m=t.enable),typeof t.rotateOnStart=="boolean"&&(g=t.rotateOnStart));let p=[],b=async()=>{s.shared.isReady=!0;for(let n of p)await n()},r,i,s={name:"secrets-manager-plugin",shared:{isReady:!1,onReady:n=>{typeof n=="function"?p.push(n):console.warn("onReady callback must be a function")}},async onInit(){if(!m)return;let{region:n,accountId:c}=this.getAwsProviderDefaults(),l=this,a=()=>l.lambdas,e=await E({port:f,region:n,accountId:c,secrets:t?.secrets,maxRotationCount:t?.maxRotationCount,rotationIntervalInSeconds:t?.rotationIntervalInSeconds,invokeLambdaForRotation:async(o,S)=>{let I=o.split(":function:")[1],y=a().find(R=>R.name==I);if(!y){console.warn(`Can not find Lambda "${o}" for Secret rotation. Make sure its declared in exlo config.`);return}try{await y.invoke(S)}catch(R){console.error(R)}}});s.shared.port=e.port,r=e.server,r.unref(),i=e[v];let d=`http://localhost:${e.port}`;this.setEndpoint("secrets_manager",d,u?"AWS_ENDPOINT_URL_SECRETS_MANAGER":void 0),this.log.PINK(`SecretsManager endpoint: ${d}`),b()},server:{async onReady(){if(!(g&&t?.secrets))return;let{region:n,accountId:c}=this.getAwsProviderDefaults();for(let[l,a]of Object.entries(t.secrets)){if(typeof a.rotation?.RotationLambdaARN!="string")continue;let e=a.rotation,o={RotationLambdaARN:e.RotationLambdaARN.startsWith("arn:")?e.RotationLambdaARN:`arn:aws:lambda:${n}:${c}:function:${e.RotationLambdaARN}`,SecretId:l};"ClientRequestToken"in e&&(o.ClientRequestToken=e.ClientRequestToken),"RotateImmediately"in e&&(o.RotateImmediately=e.RotateImmediately),"RotationRules"in e&&(o.RotationRules=e.RotationRules),i.constructor.validateRotateSecretBody(o),i.sm.rotateSecret(o)}}},onKill(){r?.close()}};return s}export{L as getSecretsFromFile,O as secretsManagerPlugin};
|
|
@@ -6,7 +6,7 @@ export interface ISnsOptions {
|
|
|
6
6
|
/** @default 0 */
|
|
7
7
|
port?: number;
|
|
8
8
|
topics?: (Omit<ISnsTopic, "subscribers"> & {
|
|
9
|
-
subscribers
|
|
9
|
+
subscribers?: ISnsTopicSubscriber[];
|
|
10
10
|
})[];
|
|
11
11
|
/**
|
|
12
12
|
* Set `AWS_ENDPOINT_URL_SNS` env variable inside lambdas
|
|
@@ -103,4 +103,4 @@ To confirm the subscription, visit the SubscribeURL included in this message.`,S
|
|
|
103
103
|
<ResponseMetadata>
|
|
104
104
|
<RequestId>${i}</RequestId>
|
|
105
105
|
</ResponseMetadata>
|
|
106
|
-
</ConfirmSubscriptionResponse>`)}};var Yt=s=>{let e=0,t=[],i=!0,r=!0;s&&(typeof s.port=="number"&&(e=s.port),Array.isArray(s.topics)&&(t=s.topics),typeof s.setEndpointEnvVar=="boolean"&&(i=s.setEndpointEnvVar),typeof s.enable=="boolean"&&(r=s.enable));let n;return{name:"sns-plugin",shared:{topics:t},async onInit(){if(!r)return;let{region:l,accountId:p}=this.getAwsProviderDefaults(),f=()=>this.lambdas,g=()=>{let h=s?.sqsClient??this.getServices().sqs;if(!h)throw new a({Code:"InternalError",Message:"Dont know how to send SQS message without SQS client. Use SQS Plugin or provide a client in SNS Plugin options. Make sure target SQS exists."});return h};n=new E(e,l,p,f,g),await n.start();for(let h of t){let c=n.createTopic(h);for(let y of h.subscribers){let w=y.Endpoint;typeof y.Endpoint&&!y.Endpoint.startsWith("arn:aws")&&(y.Protocol=="sqs"?w=`arn:aws:sqs:${l}:${p}:${w}`:y.Protocol=="lambda"&&(w=`arn:aws:lambda:${l}:${p}:function:${w}`)),n.subscribe(c,{...y,Endpoint:w})}}let b=`http://localhost:${n.port}`;if(this.log.PINK(`SNS endpoint: ${b}`),this.setEndpoint("sns",b,i?"AWS_ENDPOINT_URL_SNS":void 0),!this.getServices().sns){let h=new Te({endpoint:b,region:l,credentials:{accessKeyId:"test",secretAccessKey:"test"}});this.setService("sns",h)}},onKill(){n?.stop()},server:{onReady(l,p){n?.requestSubscriptionsConfirmation()}}}};export{Yt as snsPlugin};
|
|
106
|
+
</ConfirmSubscriptionResponse>`)}};var Yt=s=>{let e=0,t=[],i=!0,r=!0;s&&(typeof s.port=="number"&&(e=s.port),Array.isArray(s.topics)&&(t=s.topics),typeof s.setEndpointEnvVar=="boolean"&&(i=s.setEndpointEnvVar),typeof s.enable=="boolean"&&(r=s.enable));let n;return{name:"sns-plugin",shared:{topics:t},async onInit(){if(!r)return;let{region:l,accountId:p}=this.getAwsProviderDefaults(),f=()=>this.lambdas,g=()=>{let h=s?.sqsClient??this.getServices().sqs;if(!h)throw new a({Code:"InternalError",Message:"Dont know how to send SQS message without SQS client. Use SQS Plugin or provide a client in SNS Plugin options. Make sure target SQS exists."});return h};n=new E(e,l,p,f,g),await n.start();for(let h of t){let c=n.createTopic(h);if(h.subscribers)for(let y of h.subscribers){let w=y.Endpoint;typeof y.Endpoint&&!y.Endpoint.startsWith("arn:aws")&&(y.Protocol=="sqs"?w=`arn:aws:sqs:${l}:${p}:${w}`:y.Protocol=="lambda"&&(w=`arn:aws:lambda:${l}:${p}:function:${w}`)),n.subscribe(c,{...y,Endpoint:w})}}let b=`http://localhost:${n.port}`;if(this.log.PINK(`SNS endpoint: ${b}`),this.setEndpoint("sns",b,i?"AWS_ENDPOINT_URL_SNS":void 0),!this.getServices().sns){let h=new Te({endpoint:b,region:l,credentials:{accessKeyId:"test",secretAccessKey:"test"}});this.setService("sns",h)}},onKill(){n?.stop()},server:{onReady(l,p){n?.requestSubscriptionsConfirmation()}}}};export{Yt as snsPlugin};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SQS as q}from"@aws-sdk/client-sqs";import{createSqsServer as b}from"local-aws-sqs";var E=e=>{let o,s,u=!1,i=!1,f
|
|
1
|
+
import{SQS as q}from"@aws-sdk/client-sqs";import{createSqsServer as b}from"local-aws-sqs";var E=e=>{let o,r,s=!1,u=!1,i=!1,f=0,d=!0,y=!0,c=[];e&&(typeof e.validateDlqDestination=="boolean"&&(s=e.validateDlqDestination),typeof e.emulateQueueCreationLifecycle=="boolean"&&(u=e.emulateQueueCreationLifecycle),typeof e.emulateLazyQueues=="boolean"&&(i=e.emulateLazyQueues),Array.isArray(e.queues)&&(c=e.queues),typeof e.port=="number"&&(f=e.port),typeof e.setEndpointEnvVar=="boolean"&&(d=e.setEndpointEnvVar),typeof e.enable=="boolean"&&(y=e.enable));let p=[],S=async()=>{a.shared.isReady=!0;for(let t of p)await t()},n,a={name:"sqs-plugin",shared:{isReady:!1,onReady:t=>{typeof t=="function"?p.push(t):console.warn("onReady callback must be a function")}},onInit:async function(){if(!y)return;let t=this.getAwsProviderDefaults();t.region&&(o=t.region),t.accountId&&(r=t.accountId),n=await b({port:f,region:o,accountId:r,validateDlqDestination:s,emulateQueueCreationLifecycle:u,emulateLazyQueues:i,queues:c}),n.unref();let m=n.address().port;a.shared.port=m;let l=`http://localhost:${m}`;this.getServices().sqs||this.setService("sqs",new q({region:o,endpoint:l,credentials:{accessKeyId:"test",secretAccessKey:"test"}})),this.setEndpoint("sqs",l,d?"AWS_ENDPOINT_URL_SQS":void 0),this.log.PINK(`SQS endpoint: ${l}`),S()},onKill(){n?.close()}};return a};export{E as sqsPlugin};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Vitest } from "vitest/node";
|
|
2
1
|
import type { BaseCoverageProvider } from "vitest/coverage";
|
|
2
|
+
import type { Vitest } from "vitest/node";
|
|
3
3
|
export declare function initMonocart(): Promise<void>;
|
|
4
4
|
export declare function generateLambdaCoverage(vitest: Vitest, coverageMap: any, rawCoverageDir: string): Promise<void>;
|
|
5
5
|
export declare function enhanceCoverageProvider(vitest: Vitest, coverageMaps: any[]): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var F=Object.defineProperty;var O=(o,t)=>()=>(o&&(t=o(o=0)),t);var S=(o,t)=>{for(var r in t)F(o,r,{get:t[r],enumerable:!0})};var C={};S(C,{enhanceCoverageProvider:()=>G,generateLambdaCoverage:()=>T,getBaseCoverageProvider:()=>U,initMonocart:()=>D});import{access as j,readFile as A,rm as M}from"node:fs/promises";import{createRequire as B}from"node:module";import u from"node:path";import{fileURLToPath as N,pathToFileURL as P}from"node:url";async function D(){if(f)return;let{CoverageReport:o}=await import(k);f=o}async function T(o,t,r){if(!f)return;let i=u.join(r,"generated"),n=e=>!1;typeof o.coverageProvider?.isIncluded=="function"?n=e=>o.coverageProvider.isIncluded(e):typeof o.coverageProvider?.testExclude?.shouldInstrument=="function"?n=e=>o.coverageProvider.testExclude.shouldInstrument(e):console.warn(`Coverage by Exlo for Vitest ${o.version} isn't supported yet.`);let p=new f({name:"exlo coverage report",dataDir:r,outputDir:i,reports:["json"],cleanCache:!0,clean:!0,v8Ignore:!0,logging:"off",sourceFilter:e=>n(e.startsWith(m)?e:u.join(m,e)),entryFilter:e=>{let a=e.url.startsWith("file:")?N(e.url):e.url;return!n(a)}});try{await p.generate();let e=u.join(i,"coverage-final.json");try{await j(e)}catch{return}let a=JSON.parse(await A(e,"utf-8"));for(let l of Object.keys(a)){let s=u.join(process.cwd(),l);if(!n(s))continue;let c=a[l];c.path=s,t.addFileCoverage(c)}await M(r,{force:!0,recursive:!0})}catch(e){console.log(e)}}async function G(o,t){if(h)return;let r=q[o.config.coverage.provider],i=w.resolve(r.importPath),n=(await import(P(i).href))[r.exportName];n.prototype[y]=n.prototype.createCoverageMap,n.prototype.createCoverageMap=function(...p){let e=this[y](...p);return t.push(e),e},h=!0}async function U(){let o=w.resolve("vitest/coverage");return(await import(P(o).href)).BaseCoverageProvider}var f,k,w,m,q,y,h,x=O(()=>{"use strict";k="monocart-coverage-reports",w=B(process.cwd());m=process.cwd();q={v8:{importPath:"@vitest/coverage-v8/dist/provider.js",exportName:"V8CoverageProvider"},istanbul:{importPath:"@vitest/coverage-istanbul/dist/provider.js",exportName:"IstanbulCoverageProvider"}},y=Symbol(),h=!1});import{randomUUID as _}from"node:crypto";import b from"node:os";import L from"node:path";import{createAppRunner as W}from"exlo";var I="exlo-vitest-plugin",V=Symbol();function $(o){if(o?.length)return Promise.all(o.map(t=>new Promise(r=>{t.shared.onReady(r)})))}var R=!1,J=(o,t,r)=>{let i=r?.reRunOnRebuild??"failed-only";return{name:I,server:{onReady(n,p){o(this.options.plugins)}},async onInit(){if(R)return;t.onServerRestart(async()=>{await this.stop(),console.warn("Exlo isn't compatible with hot config changes. Please restart your tests."),process.exit()}),t.onClose(async()=>{await this.stop()});let n=async()=>{if(!t.config.coverage.enabled||t.config.coverage.provider=="custom")return;await t.packageInstaller.ensureInstalled("monocart-coverage-reports",t.config.root,"^2.12.9");let{generateLambdaCoverage:e,enhanceCoverageProvider:a,initMonocart:l,getBaseCoverageProvider:s}=await Promise.resolve().then(()=>(x(),C));await l();let c=await s(),g=r?.rawCoverageDir??L.join(b.tmpdir(),`l${_()}`);c.prototype[V]=c.prototype.readCoverageFiles;let d=[];c.prototype.readCoverageFiles=async function(...v){return await e(t,d[d.length-1],g),this[V](...v)},await a(t,d),process.env.NODE_V8_COVERAGE=b.tmpdir();for(let v of this.lambdas)v.setEnv("NODE_V8_COVERAGE",g);R=!0},p=this.options.plugins?.filter(e=>e&&typeof e=="object"&&e.shared?.onReady&&!e.shared.isReady);await $(p),await n()},async buildCallback(n,p){let e=n.errors.length;if(!p&&e){await t.close();return}if(e||!p)return;await t.cancelCurrentRun("keyboard-input");let a=[];if(i=="failed-only")for(let l of t.state.getFailedFilepaths()){let s=t.getModuleSpecifications(l);a.push(...s)}else i=="all"&&(a=await t.globTestSpecifications());a.length&&await t.rerunTestSpecifications(a)}}},E=!1;function oe(o,t){return{name:I,async configureVitest(r){if(E)return;E=!0;let i=typeof o=="string"?{config:o}:o,n,p=await new Promise(async l=>{let s=W(i),c=J(l,r.vitest,t);typeof i.config=="string"?s.exlo.onBeforeApplyConfigCallbacks.push(g=>{g.plugins.push(c)}):i.config?i.config.plugins?i.config.plugins.push(c):i.config.plugins=[c]:i.config={plugins:[c]},await s.exlo.start(),await s.waitServer,n=s.exlo.endpoints}),e={endpoints:n},a=p.find(l=>l.name=="rest-api-plugin");if(a?.shared?.gateways){let l=a.shared.gateways;e.restApiGateways={};for(let s of l){let{id:c,port:g,stage:d}=s;e.restApiGateways[c]={port:g,stage:d}}}r.vitest.provide("exlo",e)}}}export{oe as exloVitestPlugin};
|
package/dist/standalone.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ExloApp } from "./main";
|
|
2
2
|
import type { ICommonConfig, ILambdaFunction, ITriggers, ServerOptions } from "./standalone_types";
|
|
3
3
|
export type { ICommonConfig, ITriggers, ServerOptions, ILambdaFunction };
|
|
4
|
+
export { version as exloVersion } from "../package.json";
|
|
4
5
|
export interface App {
|
|
5
6
|
port: number;
|
|
6
7
|
url: string;
|