exlo 0.0.2 → 0.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/cli/index.mjs +5 -5
- package/dist/defineConfig.mjs +1 -1
- package/dist/plugins/dynamodb/src/batch.d.ts +24 -0
- package/dist/plugins/dynamodb/src/failure/getBatchItemFailures.d.ts +4 -0
- package/dist/plugins/dynamodb/src/failure/index.d.ts +28 -0
- package/dist/plugins/dynamodb/src/failure/s3.d.ts +13 -0
- package/dist/plugins/dynamodb/src/failure/sns.d.ts +11 -0
- package/dist/plugins/dynamodb/src/failure/sqs.d.ts +12 -0
- package/dist/plugins/dynamodb/src/index.d.ts +48 -0
- package/dist/plugins/dynamodb/src/index.mjs +1 -0
- package/dist/plugins/dynamodb/src/proxy/calculateItemSizeInBytes.d.ts +1 -0
- package/dist/plugins/dynamodb/src/proxy/dynamoDbDateTime.d.ts +13 -0
- package/dist/plugins/dynamodb/src/proxy/index.d.ts +17 -0
- package/dist/plugins/dynamodb/src/proxy/index.mjs +1 -0
- package/dist/plugins/dynamodb/src/proxy/proxy.d.ts +56 -0
- package/dist/plugins/dynamodb/src/proxy/proxy.test.d.ts +1 -0
- package/dist/plugins/dynamodb/src/proxy/proxyDynamoStream.d.ts +76 -0
- package/dist/plugins/dynamodb/src/proxy/sequenceNumber.d.ts +16 -0
- package/dist/plugins/dynamodb/src/proxy/worker.d.ts +1 -0
- package/dist/plugins/dynamodb/src/proxy/worker.mjs +1 -0
- package/dist/plugins/dynamodb/src/stream.d.ts +24 -0
- package/dist/plugins/dynamodb/src/streamhandler.d.ts +28 -0
- package/dist/plugins/dynamodb/src/subscriber.d.ts +57 -0
- package/dist/plugins/dynamodb/src/tumbling.d.ts +30 -0
- package/dist/plugins/dynamodb/src/worker.d.ts +1 -0
- package/dist/plugins/dynamodb/src/worker.mjs +1 -0
- package/dist/standalone.js +10 -10
- package/dist/standalone.mjs +10 -10
- package/package.json +9 -9
package/dist/cli/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{readFile as
|
|
3
|
-
`),
|
|
2
|
+
import{readFile as D}from"node:fs/promises";import p from"node:path";import{pathToFileURL as I}from"node:url";import{parseArgs as O}from"node:util";import{run as Y}from"exlo";var j="exlo",b="0.0.4";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,i={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(){i.setDebug(!0),i.GREY("Usage examples:"),console.log("exlo -c exlo.config.ts"),console.log(`exlo -p 3000 --debug --functions "src/lambdas/**/*.ts"
|
|
3
|
+
`),i.BR_BLUE("Options:");for(let[e,t]of Object.entries(x)){let n=e;if(t.skip)continue;if(t.short&&(n+=`, -${t.short}`),t.skipFullPrint){i.CYAN(` --${n}
|
|
4
4
|
`);continue}let s=` type: ${t.type}`;t.description&&(s+=`
|
|
5
5
|
description: ${t.description}`),"default"in t&&(s+=`
|
|
6
6
|
default: ${t.default}`),t.example&&(s+=`
|
|
7
7
|
example: ${t.example}`),s+=`
|
|
8
|
-
`,
|
|
9
|
-
description: where 'options' is one of esbuild build options`),
|
|
8
|
+
`,i.CYAN(` --${n}`),i.GREY(s)}i.CYAN(" --esbuild-[option]"),i.GREY(`
|
|
9
|
+
description: where 'options' is one of esbuild build options`),i.GREY(`
|
|
10
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
|
|
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 D(e,"utf-8")).functions;if(e.endsWith(".mjs")||e.endsWith(".cjs"))return(await import(I(p.resolve(process.cwd(),e)).href)).functions}}async function G(e,t,n){if(!n)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(n)){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 n of e){let[s,...u]=n.split("=");t[s]=u.join("=")}return t}var{values:d}=O({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 n=t.indexOf("."),s=t.slice(0,n),u=t.slice(n+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)}};Y(te);
|
package/dist/defineConfig.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var h="0.0.
|
|
1
|
+
var h="0.0.4";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:y,getServices:b,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:y,options:e,config:r,getServices:b,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};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TumblingWindow } from "./tumbling";
|
|
2
|
+
interface IBatchConfig {
|
|
3
|
+
batchSize: number;
|
|
4
|
+
maximumBatchingWindow: number;
|
|
5
|
+
maximumRecordAgeInSeconds: number;
|
|
6
|
+
onComplete: (batch: Batch, isFinalInvokeForWindow: boolean) => void;
|
|
7
|
+
onRecordExpire?: (DDBStreamBatchInfo: any) => void;
|
|
8
|
+
tumbling?: TumblingWindow;
|
|
9
|
+
}
|
|
10
|
+
export declare class Batch {
|
|
11
|
+
#private;
|
|
12
|
+
maximumRecordAgeInSeconds: number;
|
|
13
|
+
onComplete: (batch: Batch, isFinalInvokeForWindow: boolean) => void;
|
|
14
|
+
onRecordExpire?: (DDBStreamBatchInfo: any) => void;
|
|
15
|
+
records: any[];
|
|
16
|
+
closed: boolean;
|
|
17
|
+
tumbling?: TumblingWindow;
|
|
18
|
+
getStreamEvent: (records?: any[]) => any;
|
|
19
|
+
constructor({ batchSize, maximumBatchingWindow, onComplete, onRecordExpire, maximumRecordAgeInSeconds, tumbling, }: IBatchConfig);
|
|
20
|
+
setRecord(record: any, DDBStreamBatchInfo: any): boolean;
|
|
21
|
+
removeRecord(id: string): void;
|
|
22
|
+
hasRecord(id: string): any;
|
|
23
|
+
}
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface IOnFailureDestinationHandlerMessage {
|
|
2
|
+
requestContext: {
|
|
3
|
+
requestId: string;
|
|
4
|
+
functionArn: string;
|
|
5
|
+
condition: "RetryAttemptsExhausted" | "RecordAgeExceeded";
|
|
6
|
+
approximateInvokeCount: number;
|
|
7
|
+
};
|
|
8
|
+
responseContext: {
|
|
9
|
+
statusCode: number;
|
|
10
|
+
executedVersion: "$LATEST";
|
|
11
|
+
functionError: "Unhandled";
|
|
12
|
+
};
|
|
13
|
+
version: "1.0";
|
|
14
|
+
timestamp: string;
|
|
15
|
+
DDBStreamBatchInfo: {
|
|
16
|
+
shardId: string;
|
|
17
|
+
startSequenceNumber: string;
|
|
18
|
+
endSequenceNumber: string;
|
|
19
|
+
approximateArrivalOfFirstRecord: string;
|
|
20
|
+
approximateArrivalOfLastRecord: string;
|
|
21
|
+
batchSize: number;
|
|
22
|
+
streamArn: string;
|
|
23
|
+
};
|
|
24
|
+
payload?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface IOnFailureDestinationHandler {
|
|
27
|
+
report(message: IOnFailureDestinationHandlerMessage): Promise<void> | void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { S3 } from "@aws-sdk/client-s3";
|
|
2
|
+
import type { IOnFailureDestinationHandler, IOnFailureDestinationHandlerMessage } from ".";
|
|
3
|
+
export declare class OnFailureS3Reporter implements IOnFailureDestinationHandler {
|
|
4
|
+
#private;
|
|
5
|
+
bucketArn: string;
|
|
6
|
+
esmId: string;
|
|
7
|
+
getS3Client: () => S3;
|
|
8
|
+
private get client();
|
|
9
|
+
private get bucketName();
|
|
10
|
+
constructor(bucketArn: string, esmId: string, getS3Client: () => S3);
|
|
11
|
+
private createObjectKey;
|
|
12
|
+
report(message: IOnFailureDestinationHandlerMessage): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SNS } from "@aws-sdk/client-sns";
|
|
2
|
+
import type { IOnFailureDestinationHandler, IOnFailureDestinationHandlerMessage } from ".";
|
|
3
|
+
export declare class OnFailureSnsReporter implements IOnFailureDestinationHandler {
|
|
4
|
+
#private;
|
|
5
|
+
topicArn: string;
|
|
6
|
+
getSnsClient: () => SNS;
|
|
7
|
+
private get client();
|
|
8
|
+
constructor(topicArn: string, getSnsClient: () => SNS);
|
|
9
|
+
report(message: IOnFailureDestinationHandlerMessage): Promise<void>;
|
|
10
|
+
private send;
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SQS } from "@aws-sdk/client-sqs";
|
|
2
|
+
import type { IOnFailureDestinationHandler, IOnFailureDestinationHandlerMessage } from ".";
|
|
3
|
+
export declare class OnFailureSqsReporter implements IOnFailureDestinationHandler {
|
|
4
|
+
#private;
|
|
5
|
+
queueArn: string;
|
|
6
|
+
getSqsClient: () => SQS;
|
|
7
|
+
private getQueueUrl;
|
|
8
|
+
private get client();
|
|
9
|
+
constructor(queueArn: string, getSqsClient: () => SQS);
|
|
10
|
+
report(message: IOnFailureDestinationHandlerMessage): Promise<void>;
|
|
11
|
+
private send;
|
|
12
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
|
|
2
|
+
import type { S3 } from "@aws-sdk/client-s3";
|
|
3
|
+
import type { SNS } from "@aws-sdk/client-sns";
|
|
4
|
+
import type { SQS } from "@aws-sdk/client-sqs";
|
|
5
|
+
import type { ExloPlugin } from "exlo/defineConfig";
|
|
6
|
+
export type DynamoDbPluginInstance = ExloPlugin<{
|
|
7
|
+
isReady: boolean;
|
|
8
|
+
onReady: (cb: Function) => void;
|
|
9
|
+
proxyEndpoint?: string;
|
|
10
|
+
}>;
|
|
11
|
+
export interface IDynamoDbPluginConfig {
|
|
12
|
+
dynamoDbClientConfig?: DynamoDBClientConfig;
|
|
13
|
+
/**
|
|
14
|
+
* Set `AWS_ENDPOINT_URL_DYNAMODB` env variable inside lambdas
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
setEndpointEnvVar?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Create a local proxy server for DynamoDB Local.
|
|
20
|
+
* Strongly Recommended as AWS DynamoDB Local has various issues with Streams.
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
proxy?: {
|
|
24
|
+
/**
|
|
25
|
+
* @default 0 (random port)
|
|
26
|
+
*/
|
|
27
|
+
port?: number;
|
|
28
|
+
/**
|
|
29
|
+
* @default 3
|
|
30
|
+
*/
|
|
31
|
+
maxShards?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Set `AWS_ENDPOINT_URL_DYNAMODB_STREAMS` env variable inside lambdas
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
setEndpointEnvVar?: boolean;
|
|
37
|
+
inheritStreamsFromExistingTables?: boolean;
|
|
38
|
+
} | boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Used by On Failure Destinations
|
|
41
|
+
*/
|
|
42
|
+
services?: {
|
|
43
|
+
sns?: SNS;
|
|
44
|
+
sqs?: SQS;
|
|
45
|
+
s3?: S3;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export declare const dynamoDbPlugin: (config?: IDynamoDbPluginConfig) => DynamoDbPluginInstance;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var N=(n=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(n,{get:(t,e)=>(typeof require<"u"?require:t)[e]}):n)(function(n){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+n+'" is not supported')});import L from"node:path";import{Worker as _}from"node:worker_threads";import k from"node:path";import{createRequire as O}from"node:module";var f=N,g={};function A(n){if(g[n])return g[n];if(typeof f=="function"&&typeof f.resolve=="function"){let e=k.resolve(f.resolve(n),"../../");return g[n]=e,e}f=O(new URL(".",import.meta.url));let t=k.resolve(f.resolve(n),"../../");return g[n]=t,t}import{randomUUID as H}from"node:crypto";import $ from"node:events";import{EventSourceMapping as Q}from"exlo/internal/esm";var y=class{#e;#t;maximumRecordAgeInSeconds;onComplete;onRecordExpire;records=[];closed=!1;tumbling;#n={};#i;getStreamEvent;constructor({batchSize:t,maximumBatchingWindow:e,onComplete:i,onRecordExpire:s,maximumRecordAgeInSeconds:o,tumbling:r}){this.maximumRecordAgeInSeconds=o,this.#e=t,this.#t=e,this.onComplete=i,this.onRecordExpire=s,this.tumbling=r,this.tumbling?(this.getStreamEvent=a=>this.tumbling.getTimeWindowEvent(a??this.records),this.tumbling.onComplete||(this.tumbling.onComplete=()=>{this.onComplete(this,!0)})):this.getStreamEvent=a=>({Records:a??this.records})}#s(){this.records.length==this.#e&&(this.closed=!0,clearTimeout(this.#i),this.onComplete(this,!1))}setRecord(t,e){return this.closed?!1:(this.records.push(t),this.#s(),this.#r(t.eventID,e),this.records.length==1&&(this.#i=setTimeout(()=>{this.closed||(this.closed=!0,this.onComplete(this,!1))},this.#t).unref()),!0)}removeRecord(t){let e=this.records.findIndex(i=>i.eventID==t);e!=-1&&(clearTimeout(this.#n[t]),this.records.splice(e,1))}hasRecord(t){return this.records.find(e=>e.eventID==t)}#r(t,e){this.#n[t]=setTimeout(()=>{let i=this.records.findIndex(s=>s.eventID==t);i!=-1&&(this.records.splice(i,1),this.onRecordExpire&&this.onRecordExpire(e))},this.maximumRecordAgeInSeconds).unref()}};var W=n=>typeof n=="string"&&n.length,C=(n,t)=>{if(!(typeof t>"u"||t===null||typeof t=="object"&&(t.batchItemFailures===null||Array.isArray(t.batchItemFailures)&&!t.batchItemFailures.length))&&typeof t=="object"&&Array.isArray(t.batchItemFailures)){if(t.batchItemFailures.some(s=>!s.itemIdentifier||!W(s.itemIdentifier)||!n.find(o=>o.eventID==s.itemIdentifier)))throw new Error("ReportBatchItemFailures: complete failure.");let e=[],i=[];for(let s of n)t.batchItemFailures.find(r=>r.itemIdentifier==s.eventID)?i.push(s):e.push(s.eventID);return{success:e,failures:i}}};import{randomUUID as M}from"node:crypto";var S=class{constructor(t,e,i){this.bucketArn=t;this.esmId=e;this.getS3Client=i}#e;get client(){return this.#e||(this.#e=this.getS3Client()),this.#e}#t;get bucketName(){return this.#t||(this.#t=this.bucketArn.split(":")[5]),this.#t}createObjectKey(t){let[e,i]=new Date().toISOString().split("T"),[s,o,r]=e.split("-"),[a,c,d]=i.split(":");return`aws/lambda/${this.esmId}/${t}/${s}/${o}/${r}/${e}T${a}.${c}.${d}-${M()}`}async report(t){await this.client.putObject({Bucket:this.bucketName,Key:this.createObjectKey(t.DDBStreamBatchInfo.shardId),Body:JSON.stringify(t)})}};var w=class{constructor(t,e){this.topicArn=t;this.getSnsClient=e}#e;get client(){return this.#e||(this.#e=this.getSnsClient()),this.#e}async report(t){let e={...t,payload:void 0};await this.send(e)}async send(t){return this.client.publish({TopicArn:this.topicArn,Message:JSON.stringify(t)})}};var I=class{constructor(t,e){this.queueArn=t;this.getSqsClient=e}#e;#t;async getQueueUrl(){if(!this.#e){let e=this.queueArn.split(":")[5],i=await this.client.getQueueUrl({QueueName:e});this.#e=i.QueueUrl}return this.#e}get client(){return this.#t||(this.#t=this.getSqsClient()),this.#t}async report(t){let e={...t,payload:void 0};await this.send(e)}async send(t){return this.client.sendMessage({QueueUrl:await this.getQueueUrl(),MessageBody:JSON.stringify(t)})}};var q=n=>n&&!Array.isArray(n)&&Object.keys(n).length,x=class{shardId;eventSourceARN;window={start:"",end:""};state={};isFinalInvokeForWindow=!1;isWindowTerminatedEarly=!1;onComplete;constructor({shardId:t,eventSourceARN:e,tumblingWindowInSeconds:i}){this.shardId=t,this.eventSourceARN=e;let s=new Date;this.window.start=s.toISOString(),s.setSeconds(s.getSeconds()+i),this.window.end=s.toISOString(),setTimeout(()=>{this.isFinalInvokeForWindow=!0,this.onComplete?.()},i*1e3).unref()}setState(t){if(q(t))return this.isFinalInvokeForWindow||(this.state=t),this;throw new Error("Invalide state object")}getTimeWindowEvent(t){return{Records:t,window:this.window,state:this.state,shardId:this.shardId,eventSourceARN:this.eventSourceARN,isFinalInvokeForWindow:this.isFinalInvokeForWindow,isWindowTerminatedEarly:this.isWindowTerminatedEarly}}};var B=n=>new Promise(t=>setTimeout(t,n*1e3).unref()),U=86400*1e3,F=class extends Q{init(){}enable(){}disable(){}},v=class n extends ${constructor(e,i,s,o,r,a){super();this.getAwsServices=o;this.region=r;this.accountId=a;this.event=e,this.lambdaName=s,this.TableName=e.TableName,this.enabled=e.enabled,this.startingPosition=e.startingPosition,this.invoke=i,this.batchSize=e.batchSize,this.functionResponseType=e.functionResponseType,this.filterPatterns=e.filterPatterns,this.bisectBatchOnFunctionError=e.bisectBatchOnFunctionError,this.tumblingWindowInSeconds=e.tumblingWindowInSeconds,this.maximumRetryAttempts=e.maximumRetryAttempts,e.maximumRecordAgeInSeconds&&(this.maximumRecordAgeInSeconds=e.maximumRecordAgeInSeconds*1e3),!e.maximumBatchingWindow||Number.isNaN(e.maximumBatchingWindow)||e.maximumBatchingWindow==-1?this.maximumBatchingWindow=0:this.maximumBatchingWindow=e.maximumBatchingWindow*1e3;let c;if(Array.isArray(this.filterPatterns)){c=[];for(let d of this.filterPatterns)c.push({Pattern:JSON.stringify(d)});c.length||(c=void 0)}this.#n=new F({BatchSize:this.batchSize,Enabled:this.enabled,EventSourceArn:"",FunctionName:this.lambdaName,FilterCriteria:c?{Filters:c}:void 0}),typeof e.onFailureDestination=="string"&&(this.setFailureHandler(e.onFailureDestination),this.#n.config.DestinationConfig={OnFailure:{Destination:e.onFailureDestination}}),this.on("invoke",(d,m)=>{let p=this.#e.find(l=>!l.closed),R=p?.setRecord(d,m);if(!p||!R){let l=new y({batchSize:this.batchSize,maximumBatchingWindow:this.maximumBatchingWindow,maximumRecordAgeInSeconds:this.maximumRecordAgeInSeconds,tumbling:this.#a(m),onComplete:async(u,T)=>{n.invokationLog(this.TableName,this.lambdaName);let{error:h}=await this.callLambda(u);if(h&&await this.#i(u,m),T){let E=this.#e.findIndex(b=>b==u);if(E!=-1){let b=this.#t.findIndex(P=>P==this.#e[E].tumbling);b!=-1&&this.#t.splice(b,1),this.#e.splice(E,1)}}},onRecordExpire:this.failure?()=>{this.failure.report(this.createFailureMessage({totalRetry:1,condition:"RecordAgeExceeded",DDBStreamBatchInfo:m}))}:void 0});this.#e.push(l),l.setRecord(d,m)}})}lambdaName;TableName;enabled;startingPosition;invoke;batchSize;maximumBatchingWindow;maximumRetryAttempts;maximumRecordAgeInSeconds=U;bisectBatchOnFunctionError;tumblingWindowInSeconds;functionResponseType;filterPatterns;failure;event;#e=[];#t=[];#n;static invokationLog=process.env.SLS_DEGUG=="*"?(e,i)=>console.log(`\x1B[35mDynamoDB Stream:\x1B[0m \x1B[94m${e}\x1B[0m \x1B[35m|||/\x1B[0m \x1B[33m${i}\x1B[0m`):(e,i)=>{};setFailureHandler(e){let i=this,[s,o,r]=e.split(":");switch(r){case"sns":this.failure=new w(e,()=>{let a=i.getAwsServices().sns;if(!a)throw new Error("Dont know how to send SNS message without SNS client. Use SNS Plugin or provide SNS client in DynamoDB Plugin options. Make sure target SNS Topic exists.");return a});break;case"sqs":this.failure=new I(e,()=>{let a=i.getAwsServices().sqs;if(!a)throw new Error("Dont know how to send SQS message without SQS client. Use SQS Plugin or provide SQS client in DynamoDB Plugin options. Make sure target SQS Queue exists.");return a});break;case"s3":this.failure=new S(e,this.#n.UUID,()=>{let a=i.getAwsServices().s3;if(!a)throw new Error("Dont know how to store S3 Object without S3 client. Use S3 Plugin or provide S3 client in DynamoDB Plugin options. Make sure target S3 Bucket exists.");return a});break;default:throw new Error(`Invalid onFailureDestination ARN: ${e}. Service must be sns, sqs or s3`)}}setRecords=async(e,i)=>{let[s,o]=this.#n.filterRecords(e);for(let r of s)this.emit("invoke",r,i)};async callLambda(e,i){let s={},o=e.getStreamEvent(i);try{let r=await this.invoke(o,{kind:"ddb",event:this.event});if(this.tumblingWindowInSeconds&&e.tumbling.setState(r.state),this.functionResponseType=="ReportBatchItemFailures"){let a=C(e.records,r);if(a){let{success:c,failures:d}=a;for(let m of c)e.removeRecord(m);d.length&&(s.error=new Error("has failed items"))}else for(let c of e.records)e.removeRecord(c.eventID)}else for(let a of e.records)e.removeRecord(a.eventID)}catch(r){s.error=r}return s}async#i(e,i){if(this.maximumRetryAttempts)return this.#r(e,i);this.#s(e)}async#s(e){for(;e.records.length;)console.log(`\x1B[35mDynamoDB Stream retry until record expires!: ${this.TableName} |||/ ${this.lambdaName}\x1B[0m`),await this.callLambda(e),await B(.6)}async#r(e,i){let s=0;for(;s<=this.maximumRetryAttempts-1;){s++;let o=[e.records];if(this.bisectBatchOnFunctionError){let r=Math.ceil(e.records.length/2);o=this.#o(e.records,r)}console.log(`\x1B[35mDynamoDB Stream retry n\xB0${s}: ${this.TableName} |||/ ${this.lambdaName}\x1B[0m`);for(let r of o)await this.callLambda(e,r);if(!e.records.length)break;await B(.6)}if(this.failure&&e.records.length){let o={Records:e.records.slice()};for(let r of e.records)e.removeRecord(r.eventID);await this.failure.report(this.createFailureMessage({totalRetry:s,condition:"RetryAttemptsExhausted",DDBStreamBatchInfo:i,payload:o}))}}createFailureMessage({totalRetry:e,payload:i,DDBStreamBatchInfo:s,condition:o}){return{requestContext:{approximateInvokeCount:e,condition:o,functionArn:`arn:aws:lambda:${this.region}:${this.accountId}:function:${this.lambdaName}`,requestId:H()},timestamp:new Date().toISOString(),version:"1.0",responseContext:{executedVersion:"$LATEST",functionError:"Unhandled",statusCode:200},DDBStreamBatchInfo:s,payload:i?JSON.stringify(i):void 0}}#o(e,i){let s=[];for(let o=0;o<e.length;o+=i){let r=e.slice(o,o+i);s.push(r)}return s}#a=({shardId:e,streamArn:i})=>{if(this.tumblingWindowInSeconds){let s=this.#t.find(r=>!r.isFinalInvokeForWindow&&r.shardId==e);if(s)return s;let o=new x({shardId:e,eventSourceARN:i,tumblingWindowInSeconds:this.tumblingWindowInSeconds});return this.#t.push(o),o}}};var D=class{constructor(t,e,i,s){this.getAwsServices=e;this.region=i;this.accountId=s;this.#e=t,this.getSlsDeclarations(),this.getListenableTables()}subscribers=[];#e=[];listenableTables=[];getSlsDeclarations=()=>{for(let t of this.#e){let e=t.ddb;if(e.length)for(let i of e){let s=new v(i,t.invoke,t.name,this.getAwsServices,this.region,this.accountId);this.subscribers.push(s)}}};getListenableTables=()=>{for(let t of this.subscribers){let{TableName:e,enabled:i,startingPosition:s}=t,o=this.listenableTables.findIndex(r=>r.TableName==e);if(i){let r={TableName:e,startingPosition:s};s&&(r.startingPosition=s),o==-1?this.listenableTables.push(r):this.listenableTables[o]={...this.listenableTables[o],...r}}}};setRecords=({records:t,TableName:e,DDBStreamBatchInfo:i})=>{for(let s of this.subscribers)s.TableName==e&&s.setRecords(t,i)}};async function V(n){let{startProxy:t}=await import("exlo/dynamodbProxy"),[e,i]=await t(n);return e.unref(),i}var we=n=>{let t,e,i=[],s=async()=>{o.shared.isReady=!0;for(let r of i)await r()},o={name:"ddblocal-stream",shared:{isReady:!1,onReady:r=>{typeof r=="function"?i.push(r):console.warn("onReady callback must be a function")}},onInit:async function(){let{region:r,accountId:a}=this.getAwsProviderDefaults(),c={endpoint:"http://localhost:8000",region:r,credentials:{accessKeyId:"test",secretAccessKey:"test"}},d=n?.dynamoDbClientConfig??c,m;if(n?.proxy){let l={dynamoDbLocalClientConfig:d,region:r,accountId:a},u=!0;typeof n?.proxy=="object"&&(typeof n.proxy.setEndpointEnvVar=="boolean"&&(u=n.proxy.setEndpointEnvVar),typeof n.proxy.port=="number"&&(l.proxyPort=n.proxy.port),typeof n.proxy.maxShards=="number"&&(l.maxShards=n.proxy.maxShards),typeof n.proxy.inheritStreamsFromExistingTables=="boolean"&&(l.inheritStreamsFromExistingTables=n.proxy.inheritStreamsFromExistingTables));let h=`http://localhost:${await V(l)}`;this.setEndpoint("dynamodb_streams",h,u?"AWS_ENDPOINT_URL_DYNAMODB_STREAMS":void 0),o.shared.proxyEndpoint=h,m=h}else m=d.endpoint;let p=this;t=new D(this.lambdas,()=>{let l={...p.getServices()};return n?.services&&(n.services.s3&&(l.s3=n.services.s3),n.services.sns&&(l.sns=n.services.sns),n.services.sqs&&(l.sqs=n.services.sqs)),l},r,a);let R=L.join(A("exlo"),"dist/plugins/dynamodb/src/worker.mjs");if(e=new _(R,{name:"dynamodb-streams",workerData:{clientConfig:{...d,endpoint:m}}}),e.unref(),e.on("message",async l=>{l.channel=="ready"?await s():t.setRecords(l)}),typeof m=="string"){let l=typeof n?.setEndpointEnvVar=="boolean"?n.setEndpointEnvVar:!0;this.setEndpoint("dynamodb",m,l?"AWS_ENDPOINT_URL_DYNAMODB":void 0)}},onKill(){e?.terminate()},server:{async onReady(r){return e?.postMessage({channel:"init",tables:t.listenableTables}),new Promise(a=>{i.unshift(a)})}}};return o};export{we as dynamoDbPlugin};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function calculateItemSizeInBytes(item: any): number;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class DynamoDbDateTime {
|
|
2
|
+
#private;
|
|
3
|
+
constructor(fromDate?: Date);
|
|
4
|
+
getRawValue(): Date;
|
|
5
|
+
eq(other: DynamoDbDateTime): boolean;
|
|
6
|
+
gt(other: DynamoDbDateTime): boolean;
|
|
7
|
+
gte(other: DynamoDbDateTime): boolean;
|
|
8
|
+
lt(other: DynamoDbDateTime): boolean;
|
|
9
|
+
lte(other: DynamoDbDateTime): boolean;
|
|
10
|
+
toISOString(): string;
|
|
11
|
+
getTime(): number;
|
|
12
|
+
toJSON(): number;
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import type { DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
|
|
3
|
+
export interface IDynamoDbProxyServerConfig {
|
|
4
|
+
dynamoDbLocalClientConfig: DynamoDBClientConfig;
|
|
5
|
+
/**
|
|
6
|
+
* @default 0 (random port)
|
|
7
|
+
*/
|
|
8
|
+
proxyPort?: number;
|
|
9
|
+
/**
|
|
10
|
+
* @default 3
|
|
11
|
+
*/
|
|
12
|
+
maxShards?: number;
|
|
13
|
+
region?: string;
|
|
14
|
+
accountId?: string;
|
|
15
|
+
inheritStreamsFromExistingTables?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function startProxy(config: IDynamoDbProxyServerConfig): Promise<[Worker, number]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var i=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,o)=>(typeof require<"u"?require:e)[o]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});import f from"node:path";import{Worker as y}from"node:worker_threads";import s from"node:path";import{createRequire as l}from"node:module";var n=i,t={};function m(r){if(t[r])return t[r];if(typeof n=="function"&&typeof n.resolve=="function"){let o=s.resolve(n.resolve(r),"../../");return t[r]=o,o}n=l(new URL(".",import.meta.url));let e=s.resolve(n.resolve(r),"../../");return t[r]=e,e}var d=f.join(m("exlo"),"dist/plugins/dynamodb/src/proxy/worker.mjs");async function h(r){return new Promise(e=>{let o=new y(d,{name:"dynamodb-proxy",workerData:r});o.on("message",({proxyPort:a})=>{e([o,a])})})}export{h as startProxy};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
2
|
+
import type { DynamoDB } from "@aws-sdk/client-dynamodb";
|
|
3
|
+
import { type IStreamDescription, ProxyDynamoStream } from "./proxyDynamoStream";
|
|
4
|
+
export declare class ProxyDynamoDbServer {
|
|
5
|
+
client: DynamoDB;
|
|
6
|
+
proxyPort: number;
|
|
7
|
+
maxShards: number;
|
|
8
|
+
inheritStreamsFromExistingTables: boolean;
|
|
9
|
+
accountId: string;
|
|
10
|
+
region: string;
|
|
11
|
+
private static collectBuffer;
|
|
12
|
+
private static collectBody;
|
|
13
|
+
private static collectJsonObjectOrReturnError;
|
|
14
|
+
private redirect;
|
|
15
|
+
private safeReturnThrownError;
|
|
16
|
+
private dynamoDbLocalEndpoint;
|
|
17
|
+
server?: Server;
|
|
18
|
+
streams: ProxyDynamoStream[];
|
|
19
|
+
itemsWithTTL: Record<string, {
|
|
20
|
+
keySchema: IStreamDescription["KeySchema"];
|
|
21
|
+
ttlFieldName: string;
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
items: Map<string, {
|
|
24
|
+
ttl: number;
|
|
25
|
+
oldImage: any;
|
|
26
|
+
key: any;
|
|
27
|
+
}>;
|
|
28
|
+
}>;
|
|
29
|
+
getStreamByTableName(TableName: string): ProxyDynamoStream | undefined;
|
|
30
|
+
private getTableKeySchemaAttributeNames;
|
|
31
|
+
getStreamByArn(StreamArn: string): ProxyDynamoStream | undefined;
|
|
32
|
+
private trackTTLRecord;
|
|
33
|
+
setRecord(TableName: string, NewImage: any, OldImage: any, isTTL?: boolean): void;
|
|
34
|
+
private getBodyIfJsonObject;
|
|
35
|
+
private static isValidStreamArn;
|
|
36
|
+
private static isValidShardIteratorType;
|
|
37
|
+
constructor(client: DynamoDB, proxyPort?: number, maxShards?: number, inheritStreamsFromExistingTables?: boolean, accountId?: string, region?: string);
|
|
38
|
+
GetRecords(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
39
|
+
GetShardIterator(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
40
|
+
ListStreams(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
41
|
+
DescribeStream(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
42
|
+
CreateTable(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
43
|
+
DeleteTable(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
44
|
+
UpdateTable(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
45
|
+
UpdateTimeToLive(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
46
|
+
PutItem(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
47
|
+
DeleteItem(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
48
|
+
UpdateItem(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
49
|
+
private safeBatchGetItems;
|
|
50
|
+
BatchWriteItem(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
51
|
+
TransactWriteItems(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
52
|
+
private setDynamoDbLocalEndpoint;
|
|
53
|
+
private inheritStreams;
|
|
54
|
+
private watchTTL;
|
|
55
|
+
init(): Promise<number>;
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { DynamoDbDateTime } from "./dynamoDbDateTime";
|
|
2
|
+
import { SequenceNumber } from "./sequenceNumber";
|
|
3
|
+
interface IProxyRecord {
|
|
4
|
+
awsRegion: string;
|
|
5
|
+
dynamodb: {
|
|
6
|
+
ApproximateCreationDateTime: DynamoDbDateTime;
|
|
7
|
+
Keys: Record<string, any>;
|
|
8
|
+
NewImage: any;
|
|
9
|
+
OldImage: any;
|
|
10
|
+
SequenceNumber: SequenceNumber;
|
|
11
|
+
SizeBytes: number;
|
|
12
|
+
StreamViewType: "NEW_IMAGE" | "OLD_IMAGE" | "NEW_AND_OLD_IMAGES" | "KEYS_ONLY";
|
|
13
|
+
};
|
|
14
|
+
eventID: string;
|
|
15
|
+
eventName: "INSERT" | "MODIFY" | "REMOVE";
|
|
16
|
+
eventSource: string;
|
|
17
|
+
eventVersion: string;
|
|
18
|
+
userIdentity?: {
|
|
19
|
+
type: "Service";
|
|
20
|
+
principalId: "dynamodb.amazonaws.com";
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare class ProxyShardIterator {
|
|
24
|
+
shardId: string;
|
|
25
|
+
iteratorType: "TRIM_HORIZON" | "LATEST" | "AT_SEQUENCE_NUMBER" | "AFTER_SEQUENCE_NUMBER";
|
|
26
|
+
position: number;
|
|
27
|
+
expires: Date;
|
|
28
|
+
constructor(shardId: string, iteratorType: "TRIM_HORIZON" | "LATEST" | "AT_SEQUENCE_NUMBER" | "AFTER_SEQUENCE_NUMBER", position: number, expires: Date);
|
|
29
|
+
static fromString(stringValue: string): ProxyShardIterator;
|
|
30
|
+
isExpired(): boolean;
|
|
31
|
+
toString(): string;
|
|
32
|
+
}
|
|
33
|
+
export declare class ProxyDynamoShard {
|
|
34
|
+
#private;
|
|
35
|
+
ParentShardId?: string;
|
|
36
|
+
SequenceNumberRange: {
|
|
37
|
+
EndingSequenceNumber?: string;
|
|
38
|
+
StartingSequenceNumber?: SequenceNumber;
|
|
39
|
+
};
|
|
40
|
+
ShardId: string;
|
|
41
|
+
constructor(id: number);
|
|
42
|
+
getId(): number;
|
|
43
|
+
setRecord(record: IProxyRecord): void;
|
|
44
|
+
get records(): IProxyRecord[];
|
|
45
|
+
getRecords(iterator: ProxyShardIterator, limit?: number): {
|
|
46
|
+
Records: IProxyRecord[];
|
|
47
|
+
nextIterator: ProxyShardIterator | null;
|
|
48
|
+
};
|
|
49
|
+
getIterator(shardIteratorType: "TRIM_HORIZON" | "LATEST" | "AT_SEQUENCE_NUMBER" | "AFTER_SEQUENCE_NUMBER", sequenceNumber?: SequenceNumber): ProxyShardIterator;
|
|
50
|
+
}
|
|
51
|
+
export interface IStreamDescription {
|
|
52
|
+
CreationRequestDateTime: DynamoDbDateTime;
|
|
53
|
+
KeySchema: {
|
|
54
|
+
AttributeName: string;
|
|
55
|
+
KeyType: "HASH" | "RANGE";
|
|
56
|
+
}[];
|
|
57
|
+
LastEvaluatedShardId?: string;
|
|
58
|
+
Shards: ProxyDynamoShard[];
|
|
59
|
+
StreamArn: string;
|
|
60
|
+
StreamLabel: string;
|
|
61
|
+
StreamStatus: "ENABLING" | "ENABLED" | "DISABLING" | "DISABLED";
|
|
62
|
+
StreamViewType: "NEW_IMAGE" | "OLD_IMAGE" | "NEW_AND_OLD_IMAGES" | "KEYS_ONLY";
|
|
63
|
+
TableName: string;
|
|
64
|
+
}
|
|
65
|
+
export declare class ProxyDynamoStream {
|
|
66
|
+
#private;
|
|
67
|
+
TableName: string;
|
|
68
|
+
StreamDescription: IStreamDescription;
|
|
69
|
+
TableArn: string;
|
|
70
|
+
get maxShards(): number;
|
|
71
|
+
constructor(TableName: string, StreamViewType: IStreamDescription["StreamViewType"], KeySchema: IStreamDescription["KeySchema"], maxShards: number, accountId?: string, region?: string);
|
|
72
|
+
findShardByHashId(id: number): ProxyDynamoShard;
|
|
73
|
+
setRecord(newImage: any, oldImage: any, isTTL: boolean): void;
|
|
74
|
+
getShardById(ShardId: string): ProxyDynamoShard | undefined;
|
|
75
|
+
}
|
|
76
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class SequenceNumber {
|
|
2
|
+
#private;
|
|
3
|
+
static create(): SequenceNumber;
|
|
4
|
+
static createFromString(stringValue: string): SequenceNumber;
|
|
5
|
+
constructor(v: bigint);
|
|
6
|
+
getRawValue(): bigint;
|
|
7
|
+
plusplus(): void;
|
|
8
|
+
dup(): SequenceNumber;
|
|
9
|
+
eq(other: SequenceNumber): boolean;
|
|
10
|
+
gt(other: SequenceNumber): boolean;
|
|
11
|
+
gte(other: SequenceNumber): boolean;
|
|
12
|
+
lt(other: SequenceNumber): boolean;
|
|
13
|
+
lte(other: SequenceNumber): boolean;
|
|
14
|
+
toJSON(): string;
|
|
15
|
+
toString(): string;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{parentPort as H,workerData as b}from"node:worker_threads";import{DynamoDB as Q}from"@aws-sdk/client-dynamodb";import D,{createServer as W}from"node:http";import{isDeepStrictEqual as w}from"node:util";import{EventSourceMapping as he}from"exlo/internal/esm";import{EventSourceMapping as ae}from"exlo/internal/esm";var g=c=>Object.prototype.toString.call(c)=="[object Object]";import{randomUUID as q}from"node:crypto";var A=new TextEncoder;function O(c){if(!c)return 0;let t=0,e={};for(let a in c){if(!c.hasOwnProperty(a))continue;let n={sizeOfName:A.encode(a).length,attributeSize:_(c[a])};n.total=n.sizeOfName+n.attributeSize,t+=n.total,e[a]=n}return t}function _(c){if(!c)return 0;if(c.hasOwnProperty("B"))return atob(c.B).length;if(c.hasOwnProperty("S"))return A.encode(c.S).length;if(c.hasOwnProperty("N"))return C(c.N);if(c.hasOwnProperty("BS")){let t=0;for(let e=0;e<c.BS.length;e++)t+=atob(c.BS[e]).length;return t}if(c.hasOwnProperty("SS")){let t=0;for(let e=0;e<c.SS.length;e++)t+=A.encode(c.SS[e]).length;return t}if(c.hasOwnProperty("NS")){let t=0;for(let e=0;e<c.NS.length;e++)t+=C(c.NS[e]);return t}if(c.hasOwnProperty("BOOL")||c.hasOwnProperty("NULL"))return 1;if(c.hasOwnProperty("M")){let t=3;for(let e in c.M)c.M.hasOwnProperty(e)&&(t+=A.encode(e).length,t+=_(c.M[e]),t+=1);return t}if(c.hasOwnProperty("L")){let t=3;for(let e=0;e<c.L.length;e++)t+=_(c.L[e]),t+=1;return t}throw`unknown data type in ${JSON.stringify(c)}`}var x=class{value=0;rawValue;constructor(t){this.rawValue=t,this.value=Number(t)}toFixed(){return this.rawValue.split(".")[0]}};function C(c){let t=new x(c);if(t.value==0)return 1;let e=t.toFixed(),a=U(e.replace("-",""))+1;return e.startsWith("-")&&a++,a>21&&(a=21),a}function U(c){if(c.indexOf(".")!==-1){let t=c.split("."),e=t[0],a=t[1];return e==="0"&&(e="",a=V(a,!0)),e.length%2!==0&&(e=`Z${e}`),a.length%2!==0&&(a=`${a}Z`),U(e+a)}return c=V(c,!0,!0),Math.ceil(c.length/2)}function V(c,t,e){for(;t;){let a=c.replace(/^(0{2})/,"");if(a.length==c.length)break;c=a}for(;e;){let a=c.replace(/(0{2})$/,"");if(a.length==c.length)break;c=a}return c}var R=class{#e;constructor(t=new Date){this.#e=t}getRawValue(){return this.#e}eq(t){return this.#e==t.getRawValue()}gt(t){return this.#e>t.getRawValue()}gte(t){return this.#e>=t.getRawValue()}lt(t){return this.#e<t.getRawValue()}lte(t){return this.#e<=t.getRawValue()}toISOString(){return this.#e.toISOString()}getTime(){return this.#e.getTime()}toJSON(){return this.#e.getTime()/1e3}};var T=class c{static create(){return new c(BigInt("100000000000000000000000"))}static createFromString(t){return new c(BigInt(t))}#e;constructor(t){this.#e=t}getRawValue(){return this.#e}plusplus(){this.#e++}dup(){return new c(this.#e)}eq(t){return this.#e==t.getRawValue()}gt(t){return this.#e>t.getRawValue()}gte(t){return this.#e>=t.getRawValue()}lt(t){return this.#e<t.getRawValue()}lte(t){return this.#e<=t.getRawValue()}toJSON(){return this.#e.toString()}toString(){return this.#e.toString()}};var v=class c{constructor(t,e,a,n){this.shardId=t;this.iteratorType=e;this.position=a;this.expires=n}static fromString(t){let e=JSON.parse(Buffer.from(t,"base64").toString("utf-8")),{shardId:a,position:n,expires:i,iteratorType:r}=e;return new c(a,r,n,new Date(i))}isExpired(){return new Date>this.expires}toString(){let t=JSON.stringify({shardId:this.shardId,iteratorType:this.iteratorType,position:this.position,expires:this.expires});return Buffer.from(t,"utf-8").toString("base64")}},B=class{ParentShardId;SequenceNumberRange;ShardId;#e=[];#t;constructor(t){this.#t=t,this.ShardId=`shardId-${q()}`,this.SequenceNumberRange={}}getId(){return this.#t}setRecord(t){this.SequenceNumberRange.StartingSequenceNumber||(this.SequenceNumberRange.StartingSequenceNumber=t.dynamodb.SequenceNumber),this.#e.push(t)}get records(){return this.#e}getRecords(t,e=1e3){let a=this.#e.slice(t.position,t.position+e),n=null,i=t.position+a.length;return n=this.getIterator("LATEST"),n.iteratorType=t.iteratorType,n.position=i,this.SequenceNumberRange.EndingSequenceNumber&&!this.#e[i]&&(n=null),{Records:a,nextIterator:n}}getIterator(t,e){let a;if(t=="TRIM_HORIZON")a=0;else if(t=="LATEST")a=this.#e.length;else if(e){let n=this.#e.findIndex(i=>i.dynamodb.SequenceNumber.eq(e));n==-1?a=this.#e.length+1:t=="AT_SEQUENCE_NUMBER"?a=n:a=n+1}else throw new Error("SequenceNumber is requied for this shardIteratorType");return new v(this.ShardId,t,a,new Date)}},E=class{constructor(t,e,a,n,i="123456789012",r="us-east-1"){this.TableName=t;this.#t=n,this.#n=i,this.#a=r;let o=new R,s=o.toISOString();s=s.split("Z")[0],this.#e=T.create(),this.TableArn=`arn:aws:dynamodb:${r}:${i}:table/${t}`,this.StreamDescription={CreationRequestDateTime:o,KeySchema:a,Shards:[],StreamArn:`${this.TableArn}/stream/${s}`,StreamStatus:"ENABLED",StreamViewType:e,StreamLabel:s,TableName:t};for(let d=1;d<=n;d++)this.StreamDescription.Shards.push(new B(d))}StreamDescription;TableArn;#e;#t;#a;#n;get maxShards(){return this.#t}findShardByHashId(t){let e=this.StreamDescription.Shards.find(n=>n.getId()==t);if(e)return e;let a=new B(t);return this.StreamDescription.Shards.push(a),a}setRecord(t,e,a){let n=this.#a,i=q().replaceAll("-",""),r="1.1",o="aws:dynamodb",s;t&&e?s="MODIFY":t?s="INSERT":s="REMOVE";let d=t??e,u={},m={};for(let{AttributeName:M,KeyType:J}of this.StreamDescription.KeySchema)u[M]=d[M],J=="HASH"&&(m=u[M]);this.#e.plusplus();let h=this.#e.dup(),l=["NEW_IMAGE","NEW_AND_OLD_IMAGES"].includes(this.StreamDescription.StreamViewType)?t:void 0,f=["OLD_IMAGE","NEW_AND_OLD_IMAGES"].includes(this.StreamDescription.StreamViewType)?e:void 0,y=O(l)+O(f)+O(u),I={ApproximateCreationDateTime:new R,Keys:u,NewImage:l,OldImage:f,SequenceNumber:h,SizeBytes:y,StreamViewType:this.StreamDescription.StreamViewType},N={awsRegion:n,dynamodb:I,eventID:i,eventName:s,eventSource:o,eventVersion:r};a&&(N.userIdentity={type:"Service",principalId:"dynamodb.amazonaws.com"});let S=m.S??m.N??m.B,p=k(S,this.maxShards);this.findShardByHashId(p).setRecord(N)}getShardById(t){return this.StreamDescription.Shards.find(e=>e.ShardId==t)}};function $(c){let t=0;for(let e=0;e<c.length;e++)t=t*31+c.charCodeAt(e)>>>0;return t}function k(c,t=3){let a=$(c)%t;return a==0?1:a}var L=class c{constructor(t,e=0,a=3,n=!1,i="123456789012",r="us-east-1"){this.client=t;this.proxyPort=e;this.maxShards=a;this.inheritStreamsFromExistingTables=n;this.accountId=i;this.region=r}static async collectBuffer(t){let e=Buffer.alloc(0);return t.on("data",a=>{e=Buffer.concat([e,a])}),new Promise(a=>{t.on("end",async()=>{a(e)})})}static async collectBody(t){return(await this.collectBuffer(t)).toString("utf-8")}static async collectJsonObjectOrReturnError(t,e){try{let a=JSON.parse(await this.collectBody(t));if(!a||Array.isArray(a)||typeof a!="object")throw new Error("Invalid request");return a}catch{e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"Invalid Request Body"}));return}}redirect(t,e,a){let n=D.request(new URL(t.url,this.dynamoDbLocalEndpoint),{headers:t.headers,method:t.method},i=>{e.writeHead(i.statusCode,i.statusMessage,i.headers),i.pipe(e)});a&&n.write(a)}safeReturnThrownError(t,e,a,n){if(a?.$response?.body){let i=a.$response.body,r={};for(let d of Object.keys(a))d.startsWith("$")||(r[d]=a[d]);let o={};typeof i.headers.server=="string"&&(o.server=i.headers.server),typeof i.headers.date=="string"&&(o.date=i.headers.date),typeof i.headers["x-amzn-requestid"]=="string"&&(o["x-amzn-requestid"]=i.headers["x-amzn-requestid"]),o["content-type"]="application/x-amz-json-1.0";let s=JSON.stringify(r);o["content-length"]=Buffer.from(s).byteLength,e.writeHead(i.statusCode,i.statusMessage,o),e.end(s)}else return this.redirect(t,e,n)}dynamoDbLocalEndpoint="http://localhost:8000";server;streams=[];itemsWithTTL={};getStreamByTableName(t){return this.streams.find(e=>(e.TableName==t||e.TableArn==t)&&e.StreamDescription.StreamStatus=="ENABLED")}getTableKeySchemaAttributeNames(t){let e=this.streams.find(a=>a.TableName==t||a.TableArn==t);if(e)return e.StreamDescription.KeySchema.map(a=>a.AttributeName)}getStreamByArn(t){return this.streams.find(e=>e.StreamDescription.StreamArn==t)}trackTTLRecord(t,e){if(!e)return;let a=this.itemsWithTTL[t];if(a)try{let n={};for(let o of a.keySchema)n[o.AttributeName]=e[o.AttributeName];let i=JSON.stringify(n);if(!e[a.ttlFieldName]){a.items.delete(i);return}let r=new Date(Number(e[a.ttlFieldName].N)*1e3);a.items.set(i,{oldImage:e,ttl:r.getTime(),key:n})}catch(n){console.error(n)}}setRecord(t,e,a,n=!1){let i=this.getStreamByTableName(t);if(i&&i.setRecord(e,a,n),!n&&!e&&a){let r=this.itemsWithTTL[t];if(!r)return;let o={};for(let d of r.keySchema)o[d.AttributeName]=a[d.AttributeName];let s=JSON.stringify(o);r.items.delete(s);return}this.trackTTLRecord(t,e)}async getBodyIfJsonObject(t,e){let a=await c.collectBuffer(t),n=a.toString("utf-8"),i;try{if(i=JSON.parse(n),!i||Array.isArray(i)||typeof i!="object")throw new Error("");return{buf:a,rawBody:n,parsedBody:i}}catch{return this.redirect(t,e,a)}}static isValidStreamArn(t){if(!t||typeof t!="string")return!1;let e=t.split("/");if(e.length!=4)return!1;let[a,n,i,r]=e;if(i!="stream"||!new Date(r).getTime())return!1;let o=a.split(":");if(o.length!=6)return!1;let[s,d,u,m,h,l]=o;return!(!(s=="arn"&&u=="dynamodb"&&l=="table")||!["aws","aws-cn","aws-us-gov"].includes(d))}static isValidShardIteratorType(t){return!t||typeof t!="string"?!1:["AFTER_SEQUENCE_NUMBER","LATEST","AT_SEQUENCE_NUMBER","TRIM_HORIZON"].includes(t)}async GetRecords(t,e){let a=await c.collectJsonObjectOrReturnError(t,e);if(!a)return;let{ShardIterator:n,Limit:i}=a,r=typeof n=="string"?n:"",[o,s,d]=r.split("|"),u=this.getStreamByArn(o);if(!u){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"Invalid ShardIterator"}));return}let m;try{m=v.fromString(d)}catch{e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"Invalid ShardIterator"}));return}let h=u.getShardById(m.shardId);if(!h){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"Invalid ShardIterator"}));return}let{Records:l,nextIterator:f}=h.getRecords(m,i?Number(i):void 0),y=null;f&&(y=`${u.StreamDescription.StreamArn}|${h.getId()}|${f.toString()}`),e.end(JSON.stringify({NextShardIterator:y,Records:l}))}async GetShardIterator(t,e){let a=await c.collectJsonObjectOrReturnError(t,e);if(!a)return;let{ShardId:n,ShardIteratorType:i,StreamArn:r,SequenceNumber:o}=a;if(!c.isValidStreamArn(r)){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"Invalid StreamArn"}));return}let s=this.getStreamByArn(r);if(!s){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazonaws.dynamodb.v20120810#ResourceNotFoundException",message:`Requested resource not found: Stream: ${r} not found`}));return}if(!c.isValidShardIteratorType(i)){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",message:`1 validation error detected: Value '${i}' at 'shardIteratorType' failed to satisfy constraint: Member must satisfy enum value set: [AFTER_SEQUENCE_NUMBER, LATEST, AT_SEQUENCE_NUMBER, TRIM_HORIZON]`}));return}let d=s.getShardById(n);if(!d){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazonaws.dynamodb.v20120810#ResourceNotFoundException",message:"Requested resource not found: Shard does not exist"}));return}if(["AT_SEQUENCE_NUMBER","AFTER_SEQUENCE_NUMBER"].includes(i)){if(!o){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"One or more parameter values were invalid: SequenceNumber cannot be null"}));return}}else if(o){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"One or more parameter values were invalid: SequenceNumber should not be passed for this iterator type"}));return}let u;if(typeof o<"u")if(typeof o=="string"){let m;if(o.length<21)m=`1 validation error detected: Value '${o}' at 'sequenceNumber' failed to satisfy constraint: Member must have length greater than or equal to 21`;else try{u=T.createFromString(o)}catch{m="Invalid SequenceNumber for the shard"}if(m){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:m}));return}}else{let m;if(typeof o=="object"&&o?m="Start of structure or map found where not expected":typeof o=="number"?m="NUMBER_VALUE cannot be converted to String":typeof o=="boolean"&&(m=`${String(o).toUpperCase()}_VALUE cannot be converted to String`),m){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.service#SerializationException",Message:m}));return}}if(u){if(!d.SequenceNumberRange.StartingSequenceNumber){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"Invalid SequenceNumber for the shard"}));return}if(u.lt(d.SequenceNumberRange.StartingSequenceNumber)){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"Invalid SequenceNumber for the shard"}));return}if(d.SequenceNumberRange.EndingSequenceNumber&&u.gt(T.createFromString(d.SequenceNumberRange.EndingSequenceNumber))){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"Invalid SequenceNumber for the shard"}));return}}try{let m=d.getIterator(i,u);e.end(JSON.stringify({ShardIterator:`${s.StreamDescription.StreamArn}|${d.getId()}|${m.toString()}`}))}catch(m){console.error(m),e.end()}}async ListStreams(t,e){let a=await c.collectJsonObjectOrReturnError(t,e);if(!a)return;let{ExclusiveStartStreamArn:n,Limit:i,TableName:r}=a,o=i??100,s=r?this.streams.filter(d=>d.TableName==r||d.TableArn==r):this.streams;if(n){let d=s.findIndex(u=>u.StreamDescription.StreamArn==n);d!=-1&&(s=s.slice(d+1,o-1))}e.end(JSON.stringify({Streams:s.map(({TableName:d,StreamDescription:u})=>({StreamArn:u.StreamArn,StreamLabel:u.StreamLabel,TableName:d}))}))}async DescribeStream(t,e){let a=await c.collectJsonObjectOrReturnError(t,e);if(!a)return;let{StreamArn:n,ExclusiveStartShardId:i,Limit:r}=a;if(!c.isValidStreamArn(n)){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazon.coral.validate#ValidationException",Message:"Invalid StreamArn"}));return}let o=this.streams.find(u=>u.StreamDescription.StreamArn==n);if(!o){e.statusCode=400,e.end(JSON.stringify({__type:"com.amazonaws.dynamodb.v20120810#ResourceNotFoundException",message:`Requested resource not found: Stream: ${n} not found`}));return}let s=r??100,d=o.StreamDescription.Shards;if(i){let u=d.findIndex(m=>m.ShardId==i);u!=-1&&(d=d.slice(u+1,s-1))}e.end(JSON.stringify({StreamDescription:{...o.StreamDescription,Shards:d}}))}async CreateTable(t,e){t.pipe(D.request(new URL(t.url,this.dynamoDbLocalEndpoint),{headers:t.headers,method:t.method},async a=>{if(e.writeHead(a.statusCode,a.statusMessage,a.headers),a.statusCode==200){let n=await c.collectBuffer(a),i=JSON.parse(n.toString("utf-8")),{TableName:r,KeySchema:o,TableArn:s,StreamSpecification:d}=i.TableDescription;d?.StreamEnabled&&this.streams.push(new E(r,d.StreamViewType,o,this.maxShards,this.accountId,this.region)),e.end(n)}else a.pipe(e)}))}async DeleteTable(t,e){t.pipe(D.request(new URL(t.url,this.dynamoDbLocalEndpoint),{headers:t.headers,method:t.method},async a=>{if(e.writeHead(a.statusCode,a.statusMessage,a.headers),a.statusCode==200){let n=await c.collectBuffer(a),i=JSON.parse(n.toString("utf-8")),{TableName:r}=i.TableDescription,o=this.streams.findIndex(s=>s.TableName==r||s.TableArn==r);o!=-1&&this.streams.splice(o,1),delete this.itemsWithTTL[r],e.end(n)}else a.pipe(e)}))}async UpdateTable(t,e){t.pipe(D.request(new URL(t.url,this.dynamoDbLocalEndpoint),{headers:t.headers,method:t.method},async a=>{if(e.writeHead(a.statusCode,a.statusMessage,a.headers),a.statusCode!=200){a.pipe(e);return}let n=await c.collectBuffer(a),i=JSON.parse(n.toString("utf-8")),{TableName:r,StreamSpecification:o,KeySchema:s}=i.TableDescription;if(o){let d=this.getStreamByTableName(r);typeof o.StreamEnabled=="boolean"?d?d.StreamDescription.StreamStatus=o.StreamEnabled?"ENABLED":"DISABLED":o.StreamEnabled&&this.streams.push(new E(r,o.StreamViewType,s,this.maxShards)):d?d.StreamDescription.StreamViewType=o.StreamViewType:this.streams.push(new E(r,o.StreamViewType,s,this.maxShards))}else{let d=this.streams.find(u=>u.TableName==r||u.TableArn==r);d&&(d.StreamDescription.StreamStatus="DISABLED")}e.end(n)}))}async UpdateTimeToLive(t,e){let a=await this.getBodyIfJsonObject(t,e);if(!a)return;let{buf:n,parsedBody:i}=a;try{let r=await this.client.updateTimeToLive(i);r.$metadata.httpStatusCode&&(e.statusCode=r.$metadata.httpStatusCode),r.$metadata.requestId&&e.setHeader("x-amzn-RequestId",r.$metadata.requestId);let{TableName:o}=i,s=this.streams.find(d=>d.TableName==o||d.TableArn==o);if(s){let{TimeToLiveSpecification:d}=r,{AttributeName:u,Enabled:m}=d,h=m,l=u;this.itemsWithTTL[o]??={items:new Map,ttlFieldName:l,enabled:h,keySchema:s.StreamDescription.KeySchema},this.itemsWithTTL[o].enabled=h,this.itemsWithTTL[o].ttlFieldName=l}e.end(JSON.stringify(r))}catch(r){this.safeReturnThrownError(t,e,r,n)}}async PutItem(t,e){let a=await this.getBodyIfJsonObject(t,e);if(!a)return;let{buf:n,rawBody:i,parsedBody:r}=a;try{let o=["NONE","ALL_OLD"],s=r.ReturnValues,d="";s?o.includes(s)?d="ALL_OLD":d=s:d="ALL_OLD";let u={...r,ReturnValues:d},m=await this.client.putItem(u);m.$metadata.httpStatusCode&&(e.statusCode=m.$metadata.httpStatusCode),m.$metadata.requestId&&e.setHeader("x-amzn-RequestId",m.$metadata.requestId);let{TableName:h,Item:l}=r,f=l,y=m.Attributes;this.setRecord(h,f,y);let I=JSON.stringify({...m,Attributes:s=="ALL_OLD"?m.Attributes:void 0});e.end(I)}catch(o){this.safeReturnThrownError(t,e,o,n)}}async DeleteItem(t,e){let a=await this.getBodyIfJsonObject(t,e);if(!a)return;let{buf:n,parsedBody:i}=a;try{let r=["NONE","ALL_OLD"],o=i.ReturnValues,s="";o?r.includes(o)?s="ALL_OLD":s=o:s="ALL_OLD";let d={...i,ReturnValues:s},u=await this.client.deleteItem(d);u.$metadata.httpStatusCode&&(e.statusCode=u.$metadata.httpStatusCode),u.$metadata.requestId&&e.setHeader("x-amzn-RequestId",u.$metadata.requestId);let{TableName:m}=i,h=void 0,l=u.Attributes;l&&this.setRecord(m,h,l);let f=JSON.stringify({...u,Attributes:o=="ALL_OLD"?u.Attributes:void 0});e.end(f)}catch(r){this.safeReturnThrownError(t,e,r,n)}}async UpdateItem(t,e){let a=await this.getBodyIfJsonObject(t,e);if(!a)return;let{buf:n,rawBody:i,parsedBody:r}=a;try{let{TableName:o,Key:s}=r,d;try{d=(await this.client.getItem({TableName:o,Key:s,ConsistentRead:!0})).Item}catch{}let u=await this.client.updateItem(r),{Item:m}=await this.client.getItem({TableName:o,Key:s,ConsistentRead:!0});u.$metadata.httpStatusCode&&(e.statusCode=u.$metadata.httpStatusCode),u.$metadata.requestId&&e.setHeader("x-amzn-RequestId",u.$metadata.requestId),this.setRecord(o,m,d);let h=JSON.stringify(u);e.end(h)}catch(o){this.safeReturnThrownError(t,e,o,n)}}async safeBatchGetItems(t){let e={},a=t;do try{let{Responses:n,UnprocessedKeys:i}=await this.client.batchGetItem({RequestItems:a});if(n)for(let[r,o]of Object.entries(n))e[r]?e[r]=e[r].concat(o):e[r]=o;i&&Object.keys(i).length?a=i:a=void 0}catch{a=void 0}while(a);return e}async BatchWriteItem(t,e){let a=await this.getBodyIfJsonObject(t,e);if(!a)return;let{buf:n,parsedBody:i}=a;if(!g(i.RequestItems))return this.redirect(t,e,n);let r={};for(let[s,d]of Object.entries(i.RequestItems)){if(!Array.isArray(d))return this.redirect(t,e,n);let u=this.getTableKeySchemaAttributeNames(s);if(!u)return this.redirect(t,e,n);r[s]={Keys:[],ConsistentRead:!0};for(let m of d){if(!g(m))return this.redirect(t,e,n);if(g(m.DeleteRequest)){if(!g(m.DeleteRequest.Key))return this.redirect(t,e,n);let h={};for(let l of u)h[l]=m.DeleteRequest.Key[l];r[s].Keys.push(h)}else if(g(m.PutRequest)){if(!g(m.PutRequest.Item))return this.redirect(t,e,n);let h={};for(let l of u)h[l]=m.PutRequest.Item[l];r[s].Keys.push(h)}else return this.redirect(t,e,n)}}let o=await this.safeBatchGetItems(r);try{let s=await this.client.batchWriteItem(i),d=await this.safeBatchGetItems(r);for(let[u,m]of Object.entries(r))if(this.getStreamByTableName(u))for(let l of m.Keys){let f=Object.keys(l),y=o[u].find(N=>{let S={};for(let p of f)S[p]=N[p];return w(l,S)}),I=d[u].find(N=>{let S={};for(let p of f)S[p]=N[p];return w(l,S)});(I||y)&&this.setRecord(u,I,y)}s.$metadata.httpStatusCode&&(e.statusCode=s.$metadata.httpStatusCode),s.$metadata.requestId&&e.setHeader("x-amzn-RequestId",s.$metadata.requestId),e.end(JSON.stringify(s))}catch(s){this.safeReturnThrownError(t,e,s,n)}}async TransactWriteItems(t,e){let a=await this.getBodyIfJsonObject(t,e);if(!a)return;let{buf:n,parsedBody:i}=a;if(!Array.isArray(i.TransactItems))return this.redirect(t,e,n);let r={};for(let s of i.TransactItems){if(!g(s))return this.redirect(t,e,n);if(s.Delete){if(!g(s.Delete)||typeof s.Delete.TableName!="string"||!s.Delete.Key)return this.redirect(t,e,n);r[s.Delete.TableName]??={Keys:[],ConsistentRead:!0},r[s.Delete.TableName].Keys.find(u=>w(u,s.Delete.Key))||r[s.Delete.TableName].Keys.push(s.Delete.Key)}else if(s.Update){if(!g(s.Update)||typeof s.Update.TableName!="string"||!s.Update.Key)return this.redirect(t,e,n);r[s.Update.TableName]??={Keys:[],ConsistentRead:!0},r[s.Update.TableName].Keys.find(u=>w(u,s.Update.Key))||r[s.Update.TableName].Keys.push(s.Update.Key)}else if(s.Put){if(!g(s.Put)||typeof s.Put.TableName!="string"||!g(s.Put.Item))return this.redirect(t,e,n);let d=this.getTableKeySchemaAttributeNames(s.Put.TableName);if(!d)return this.redirect(t,e,n);r[s.Put.TableName]??={Keys:[],ConsistentRead:!0};let u={};for(let h of d)u[h]=s.Put.Item[h];r[s.Put.TableName].Keys.find(h=>w(h,u))||r[s.Put.TableName].Keys.push(u)}}let o=await this.safeBatchGetItems(r);try{let s=await this.client.transactWriteItems(i),d=await this.safeBatchGetItems(r);for(let[u,m]of Object.entries(r))if(this.getStreamByTableName(u))for(let l of m.Keys){let f=Object.keys(l),y=o[u].find(N=>{let S={};for(let p of f)S[p]=N[p];return w(l,S)}),I=d[u].find(N=>{let S={};for(let p of f)S[p]=N[p];return w(l,S)});(I||y)&&this.setRecord(u,I,y)}s.$metadata.httpStatusCode&&(e.statusCode=s.$metadata.httpStatusCode),s.$metadata.requestId&&e.setHeader("x-amzn-RequestId",s.$metadata.requestId),e.end(JSON.stringify(s))}catch(s){this.safeReturnThrownError(t,e,s,n)}}async setDynamoDbLocalEndpoint(){try{let t="http://localhost:8000",e=await this.client.config.endpoint?.();e&&(t=`${e.protocol}${e.hostname}`,e.port&&(t+=`:${e.port}`),e.path&&e.path!="/"&&(t+=e.path)),this.dynamoDbLocalEndpoint=t}catch{console.warn("Can not resolve DynamoDB Local endpoint. Using default http://localhost:8000")}}async inheritStreams(){let t;do{let e={ExclusiveStartTableName:t},{TableNames:a,LastEvaluatedTableName:n}=await this.client.listTables(e);for(let i of a){let{Table:r}=await this.client.describeTable({TableName:i});if(r?.StreamSpecification?.StreamEnabled&&r.StreamSpecification.StreamViewType&&r.KeySchema?.every(s=>s.AttributeName&&s.KeyType)){let s=new E(i,r.StreamSpecification.StreamViewType,r.KeySchema,this.maxShards);this.streams.push(s)}let{TimeToLiveDescription:o}=await this.client.describeTimeToLive({TableName:i});o?.TimeToLiveStatus=="ENABLED"&&(this.itemsWithTTL[i]={enabled:!0,ttlFieldName:o.AttributeName,keySchema:r.KeySchema,items:new Map})}t=n}while(t)}watchTTL(){setInterval(async()=>{for(let[t,e]of Object.entries(this.itemsWithTTL)){if(!e.enabled)continue;let a=Date.now();for(let[n,i]of e.items.entries())if(!(i.ttl>a))try{(await this.client.getItem({TableName:t,Key:i.key,ConsistentRead:!0})).Item||(e.items.delete(n),this.setRecord(t,void 0,i.oldImage,!0))}catch(r){console.error(r)}}},1e3).unref()}async init(){let t=this;await this.setDynamoDbLocalEndpoint(),this.inheritStreamsFromExistingTables&&await this.inheritStreams();let e=W(async(a,n)=>{switch(a.headers["x-amz-target"]?.split(".")[1]){case"GetRecords":return t.GetRecords(a,n);case"GetShardIterator":return t.GetShardIterator(a,n);case"ListStreams":return t.ListStreams(a,n);case"DescribeStream":return t.DescribeStream(a,n);case"CreateTable":return t.CreateTable(a,n);case"DeleteTable":return t.DeleteTable(a,n);case"UpdateTable":return t.UpdateTable(a,n);case"PutItem":return t.PutItem(a,n);case"UpdateItem":return t.UpdateItem(a,n);case"DeleteItem":return t.DeleteItem(a,n);case"BatchWriteItem":return t.BatchWriteItem(a,n);case"TransactWriteItems":return t.TransactWriteItems(a,n);case"UpdateTimeToLive":return t.UpdateTimeToLive(a,n);default:a.pipe(D.request(new URL(a.url,this.dynamoDbLocalEndpoint),{headers:a.headers,method:a.method},o=>{n.writeHead(o.statusCode,o.statusMessage,o.headers),o.pipe(n)}))}});return await new Promise(a=>{e.listen(this.proxyPort,()=>{a(void 0)})}),this.server=e,t.watchTTL(),e.address().port}};var F=3,K=!1,P="123456789012",z="us-east-1";b.maxShards&&(typeof b.maxShards=="number"&&b.maxShards>0?F=Math.round(b.maxShards):console.warn("When provided, maxShards must be a positive integer. Using default 3."));typeof b.inheritStreamsFromExistingTables=="boolean"&&(K=b.inheritStreamsFromExistingTables);typeof b.accountId=="string"&&(P=b.accountId);typeof b.region=="string"&&(z=b.region);var G=new Q(b.dynamoDbLocalClientConfig),j=new L(G,b.proxyPort,F,K,P,z),Y=await j.init();H.postMessage({proxyPort:Y});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import EventEmitter from "node:events";
|
|
2
|
+
import { DynamoDBClient, type DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
|
|
3
|
+
import { DynamoDBStreams, type Shard, type ShardIteratorType } from "@aws-sdk/client-dynamodb-streams";
|
|
4
|
+
export declare class DynamoStream extends EventEmitter {
|
|
5
|
+
#private;
|
|
6
|
+
cli: DynamoDBClient;
|
|
7
|
+
streamCli: DynamoDBStreams;
|
|
8
|
+
TableName: string;
|
|
9
|
+
ShardIteratorType: ShardIteratorType;
|
|
10
|
+
constructor(config: {
|
|
11
|
+
TableName: string;
|
|
12
|
+
startingPosition: ShardIteratorType;
|
|
13
|
+
}, clientConfig: DynamoDBClientConfig);
|
|
14
|
+
private runningShards;
|
|
15
|
+
private completedShards;
|
|
16
|
+
init(): Promise<void>;
|
|
17
|
+
private start;
|
|
18
|
+
getAvailableStream(): Promise<import("@aws-sdk/client-dynamodb-streams").StreamDescription | undefined>;
|
|
19
|
+
describeStream(StreamArn: string): Promise<import("@aws-sdk/client-dynamodb-streams").StreamDescription | undefined>;
|
|
20
|
+
getShardIterator(Shard: Shard, StreamArn: string): Promise<string | undefined>;
|
|
21
|
+
private getNextWatcherTimeout;
|
|
22
|
+
totalConsumedRecords: number;
|
|
23
|
+
watch(Shard: Shard, StreamArn: string): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { S3 } from "@aws-sdk/client-s3";
|
|
2
|
+
import type { SNS } from "@aws-sdk/client-sns";
|
|
3
|
+
import type { SQS } from "@aws-sdk/client-sqs";
|
|
4
|
+
import { Subscriber } from "./subscriber";
|
|
5
|
+
export declare class StreamsHandler {
|
|
6
|
+
#private;
|
|
7
|
+
getAwsServices: () => {
|
|
8
|
+
sqs?: SQS;
|
|
9
|
+
sns?: SNS;
|
|
10
|
+
s3?: S3;
|
|
11
|
+
};
|
|
12
|
+
region: string;
|
|
13
|
+
accountId: string;
|
|
14
|
+
subscribers: Subscriber[];
|
|
15
|
+
listenableTables: any[];
|
|
16
|
+
constructor(lambdas: any[], getAwsServices: () => {
|
|
17
|
+
sqs?: SQS;
|
|
18
|
+
sns?: SNS;
|
|
19
|
+
s3?: S3;
|
|
20
|
+
}, region: string, accountId: string);
|
|
21
|
+
getSlsDeclarations: () => void;
|
|
22
|
+
getListenableTables: () => void;
|
|
23
|
+
setRecords: ({ records, TableName, DDBStreamBatchInfo, }: {
|
|
24
|
+
records: any[];
|
|
25
|
+
TableName: string;
|
|
26
|
+
DDBStreamBatchInfo: any;
|
|
27
|
+
}) => void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import EventEmitter from "node:events";
|
|
2
|
+
import type { S3 } from "@aws-sdk/client-s3";
|
|
3
|
+
import type { SNS } from "@aws-sdk/client-sns";
|
|
4
|
+
import type { SQS } from "@aws-sdk/client-sqs";
|
|
5
|
+
import { Batch } from "./batch";
|
|
6
|
+
import type { IOnFailureDestinationHandler } from "./failure/index";
|
|
7
|
+
interface Config {
|
|
8
|
+
TableName: string;
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
startingPosition: "TRIM_HORIZON" | "LATEST";
|
|
11
|
+
batchSize: number;
|
|
12
|
+
maximumBatchingWindow?: number;
|
|
13
|
+
maximumRetryAttempts?: number;
|
|
14
|
+
maximumRecordAgeInSeconds?: number;
|
|
15
|
+
bisectBatchOnFunctionError?: boolean;
|
|
16
|
+
tumblingWindowInSeconds?: number;
|
|
17
|
+
functionResponseType?: string;
|
|
18
|
+
filterPatterns?: any[];
|
|
19
|
+
onFailureDestination?: string;
|
|
20
|
+
}
|
|
21
|
+
type Invoke = (event: any, info?: any) => Promise<void | any>;
|
|
22
|
+
export declare class Subscriber extends EventEmitter {
|
|
23
|
+
#private;
|
|
24
|
+
getAwsServices: () => {
|
|
25
|
+
sqs?: SQS;
|
|
26
|
+
sns?: SNS;
|
|
27
|
+
s3?: S3;
|
|
28
|
+
};
|
|
29
|
+
region: string;
|
|
30
|
+
accountId: string;
|
|
31
|
+
lambdaName: string;
|
|
32
|
+
TableName: string;
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
startingPosition: "TRIM_HORIZON" | "LATEST";
|
|
35
|
+
invoke: Invoke;
|
|
36
|
+
batchSize: number;
|
|
37
|
+
maximumBatchingWindow: number;
|
|
38
|
+
maximumRetryAttempts?: number;
|
|
39
|
+
maximumRecordAgeInSeconds: number;
|
|
40
|
+
bisectBatchOnFunctionError?: boolean;
|
|
41
|
+
tumblingWindowInSeconds?: number;
|
|
42
|
+
functionResponseType?: string;
|
|
43
|
+
filterPatterns?: any[];
|
|
44
|
+
failure?: IOnFailureDestinationHandler;
|
|
45
|
+
event: any;
|
|
46
|
+
static invokationLog: (TableName: string, lambdaName: string) => void;
|
|
47
|
+
constructor(event: Config, invoke: Invoke, name: string, getAwsServices: () => {
|
|
48
|
+
sqs?: SQS;
|
|
49
|
+
sns?: SNS;
|
|
50
|
+
s3?: S3;
|
|
51
|
+
}, region: string, accountId: string);
|
|
52
|
+
private setFailureHandler;
|
|
53
|
+
setRecords: (records: any[], DDBStreamBatchInfo: any) => Promise<void>;
|
|
54
|
+
callLambda(batch: Batch, Records?: any[]): Promise<any>;
|
|
55
|
+
private createFailureMessage;
|
|
56
|
+
}
|
|
57
|
+
export {};
|