Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@middy/util

Package Overview
Dependencies
Maintainers
3
Versions
136
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@middy/util - npm Package Compare versions

Comparing version 5.0.0-alpha.0 to 5.0.0-alpha.1

81

index.d.ts
import middy from '@middy/core'
import { Context as LambdaContext } from 'aws-lambda'
import { ArrayValues, Choose, DeepAwaited, IsUnknown, SanitizeKey, SanitizeKeys } from './type-utils'
interface Options<Client, ClientOptions> {
AwsClient?: new (...args: any[]) => Client
AwsClient?: new (...[config]: [any] | any) => Client
awsClientOptions?: Partial<ClientOptions>

@@ -15,3 +17,3 @@ awsClientAssumeRole?: string

type HttpError = Error & {
declare class HttpError extends Error {
status: number

@@ -37,9 +39,74 @@ statusCode: number

declare function getInternal (
variables: any,
request: middy.Request
): Promise<any>
type InternalOutput<TVariables> = TVariables extends string[] ? { [key in TVariables[number]]: unknown } : never
declare function sanitizeKey (key: string): string
// get an empty object if false is passed
declare function getInternal<
TContext extends LambdaContext,
TInternal extends Record<string, unknown>
> (
variables: false,
request: middy.Request<unknown, unknown, unknown, TContext, TInternal>
): Promise<{}>
// get all internal values if true is passed (with promises resolved)
declare function getInternal<
TContext extends LambdaContext,
TInternal extends Record<string, unknown>
> (
variables: true,
request: middy.Request<unknown, unknown, unknown, TContext, TInternal>
): Promise<DeepAwaited<TInternal>>
// get a single value
declare function getInternal<
TContext extends LambdaContext,
TInternal extends Record<string, unknown>,
TVars extends keyof TInternal | string
> (
variables: TVars,
request: middy.Request<unknown, unknown, unknown, TContext, TInternal>
): TVars extends keyof TInternal
? Promise<DeepAwaited<{ [_ in SanitizeKey<TVars>]: TInternal[TVars] }>>
: TVars extends string
? IsUnknown<Choose<DeepAwaited<TInternal>, TVars>> extends true
? unknown // could not find the path
: Promise<{ [_ in SanitizeKey<TVars>]: Choose<DeepAwaited<TInternal>, TVars> }>
: unknown // path is not a string or a keyof TInternal
// get multiple values
declare function getInternal<
TContext extends LambdaContext,
TInternal extends Record<string, unknown>,
TVars extends Array<keyof TInternal | string>
> (
variables: TVars,
request: middy.Request<unknown, unknown, unknown, TContext, TInternal>
): Promise<SanitizeKeys<{
[TVar in ArrayValues<TVars>]:
TVar extends keyof TInternal
? DeepAwaited<TInternal[TVar]>
: TVar extends string
? Choose<DeepAwaited<TInternal>, TVar>
: unknown // path is not a string or a keyof TInternal
}>>
// remap object
declare function getInternal<
TContext extends LambdaContext,
TInternal extends Record<string, unknown>,
TMap extends Record<string, keyof TInternal | string>
> (
variables: TMap,
request: middy.Request<unknown, unknown, unknown, TContext, TInternal>
): Promise<{
[P in keyof TMap]:
TMap[P] extends keyof TInternal
? DeepAwaited<TInternal[TMap[P]]>
: TMap[P] extends string
? Choose<DeepAwaited<TInternal>, TMap[P]>
: unknown // path is not a string or a keyof TInternal
}>
declare function sanitizeKey<T extends string> (key: T): SanitizeKey<T>
declare function processCache<Client, ClientOptions> (

@@ -46,0 +113,0 @@ options: Options<Client, ClientOptions>,

521

index.js

@@ -1,253 +0,284 @@

export const createPrefetchClient = (options)=>{
const { awsClientOptions } = options;
const client = new options.AwsClient(awsClientOptions);
if (options.awsClientCapture && options.disablePrefetch) {
return options.awsClientCapture(client);
} else if (options.awsClientCapture) {
console.warn('Unable to apply X-Ray outside of handler invocation scope.');
export const createPrefetchClient = (options) => {
const { awsClientOptions } = options
const client = new options.AwsClient(awsClientOptions)
// AWS XRay
if (options.awsClientCapture && options.disablePrefetch) {
return options.awsClientCapture(client)
} else if (options.awsClientCapture) {
console.warn('Unable to apply X-Ray outside of handler invocation scope.')
}
return client
}
export const createClient = async (options, request) => {
let awsClientCredentials = {}
// Role Credentials
if (options.awsClientAssumeRole) {
if (!request) {
throw new Error('Request required when assuming role', {
cause: { package: '@middy/util' }
})
}
return client;
};
export const createClient = async (options, request)=>{
let awsClientCredentials = {};
if (options.awsClientAssumeRole) {
if (!request) {
throw new Error('Request required when assuming role');
}
awsClientCredentials = await getInternal({
credentials: options.awsClientAssumeRole
}, request);
awsClientCredentials = await getInternal(
{ credentials: options.awsClientAssumeRole },
request
)
}
awsClientCredentials = {
...awsClientCredentials,
...options.awsClientOptions
}
return createPrefetchClient({
...options,
awsClientOptions: awsClientCredentials
})
}
export const canPrefetch = (options = {}) => {
return !options.awsClientAssumeRole && !options.disablePrefetch
}
// Internal Context
export const getInternal = async (variables, request) => {
if (!variables || !request) return {}
let keys = []
let values = []
if (variables === true) {
keys = values = Object.keys(request.internal)
} else if (typeof variables === 'string') {
keys = values = [variables]
} else if (Array.isArray(variables)) {
keys = values = variables
} else if (typeof variables === 'object') {
keys = Object.keys(variables)
values = Object.values(variables)
}
const promises = []
for (const internalKey of values) {
// 'internal.key.sub_value' -> { [key]: internal.key.sub_value }
const pathOptionKey = internalKey.split('.')
const rootOptionKey = pathOptionKey.shift()
let valuePromise = request.internal[rootOptionKey]
if (!isPromise(valuePromise)) {
valuePromise = Promise.resolve(valuePromise)
}
awsClientCredentials = {
...awsClientCredentials,
...options.awsClientOptions
};
return createPrefetchClient({
...options,
awsClientOptions: awsClientCredentials
});
};
export const canPrefetch = (options = {})=>{
return !options.awsClientAssumeRole && !options.disablePrefetch;
};
export const getInternal = async (variables, request)=>{
if (!variables || !request) return {};
let keys = [];
let values = [];
if (variables === true) {
keys = values = Object.keys(request.internal);
} else if (typeof variables === 'string') {
keys = values = [
variables
];
} else if (Array.isArray(variables)) {
keys = values = variables;
} else if (typeof variables === 'object') {
keys = Object.keys(variables);
values = Object.values(variables);
promises.push(
valuePromise.then((value) =>
pathOptionKey.reduce((p, c) => p?.[c], value)
)
)
}
// ensure promise has resolved by the time it's needed
// If one of the promises throws it will bubble up to @middy/core
values = await Promise.allSettled(promises)
const errors = values
.filter((res) => res.status === 'rejected')
.map((res) => res.reason)
if (errors.length) {
throw new Error('Failed to resolve internal values', {
cause: { package: '@middy/util', data: errors }
})
}
return keys.reduce(
(obj, key, index) => ({ ...obj, [sanitizeKey(key)]: values[index].value }),
{}
)
}
const isPromise = (promise) => typeof promise?.then === 'function'
const sanitizeKeyPrefixLeadingNumber = /^([0-9])/
const sanitizeKeyRemoveDisallowedChar = /[^a-zA-Z0-9]+/g
export const sanitizeKey = (key) => {
return key
.replace(sanitizeKeyPrefixLeadingNumber, '_$1')
.replace(sanitizeKeyRemoveDisallowedChar, '_')
}
// fetch Cache
const cache = {} // key: { value:{fetchKey:Promise}, expiry }
export const processCache = (options, fetch = () => undefined, request) => {
let { cacheKey, cacheKeyExpiry, cacheExpiry } = options
cacheExpiry = cacheKeyExpiry?.[cacheKey] ?? cacheExpiry
if (cacheExpiry) {
const cached = getCache(cacheKey)
const unexpired =
cached.expiry && (cacheExpiry < 0 || cached.expiry > Date.now())
if (unexpired && cached.modified) {
const value = fetch(request, cached.value)
cache[cacheKey] = Object.create({
value: { ...cached.value, ...value },
expiry: cached.expiry
})
return cache[cacheKey]
}
const promises = [];
for (const internalKey of values){
const pathOptionKey = internalKey.split('.');
const rootOptionKey = pathOptionKey.shift();
let valuePromise = request.internal[rootOptionKey];
if (!isPromise(valuePromise)) {
valuePromise = Promise.resolve(valuePromise);
}
promises.push(valuePromise.then((value)=>pathOptionKey.reduce((p, c)=>p?.[c], value)));
if (unexpired) {
return { ...cached, cache: true }
}
values = await Promise.allSettled(promises);
const errors = values.filter((res)=>res.status === 'rejected').map((res)=>res.reason);
if (errors.length) {
throw new Error('Failed to resolve internal values', {
cause: errors
});
}
return keys.reduce((obj, key, index)=>({
...obj,
[sanitizeKey(key)]: values[index].value
}), {});
};
const isPromise = (promise)=>typeof promise?.then === 'function';
const sanitizeKeyPrefixLeadingNumber = /^([0-9])/;
const sanitizeKeyRemoveDisallowedChar = /[^a-zA-Z0-9]+/g;
export const sanitizeKey = (key)=>{
return key.replace(sanitizeKeyPrefixLeadingNumber, '_$1').replace(sanitizeKeyRemoveDisallowedChar, '_');
};
const cache = {};
export const processCache = (options, fetch = ()=>undefined, request)=>{
const { cacheExpiry , cacheKey } = options;
if (cacheExpiry) {
const cached = getCache(cacheKey);
const unexpired = cached.expiry && (cacheExpiry < 0 || cached.expiry > Date.now());
if (unexpired && cached.modified) {
const value = fetch(request, cached.value);
cache[cacheKey] = Object.create({
value: {
...cached.value,
...value
},
expiry: cached.expiry
});
return cache[cacheKey];
}
if (unexpired) {
return {
...cached,
cache: true
};
}
}
const value = fetch(request);
const expiry = Date.now() + cacheExpiry;
if (cacheExpiry) {
const refresh = cacheExpiry > 0 ? setInterval(()=>processCache(options, fetch, request), cacheExpiry) : undefined;
cache[cacheKey] = {
value,
expiry,
refresh
};
}
return {
value,
expiry
};
};
export const getCache = (key)=>{
if (!cache[key]) return {};
return cache[key];
};
export const modifyCache = (cacheKey, value)=>{
if (!cache[cacheKey]) return;
clearInterval(cache[cacheKey]?.refresh);
cache[cacheKey] = {
...cache[cacheKey],
value,
modified: true
};
};
export const clearCache = (keys = null)=>{
keys = keys ?? Object.keys(cache);
if (!Array.isArray(keys)) keys = [
keys
];
for (const cacheKey of keys){
clearInterval(cache[cacheKey]?.refresh);
cache[cacheKey] = undefined;
}
};
export const jsonSafeParse = (text, reviver)=>{
if (typeof text !== 'string') return text;
const firstChar = text[0];
if (firstChar !== '{' && firstChar !== '[' && firstChar !== '"') return text;
try {
return JSON.parse(text, reviver);
} catch (e) {}
return text;
};
export const jsonSafeStringify = (value, replacer, space)=>{
try {
return JSON.stringify(value, replacer, space);
} catch (e) {}
return value;
};
export const normalizeHttpResponse = (request)=>{
let { response } = request;
if (typeof response === 'undefined') {
response = {};
} else if (typeof response?.statusCode === 'undefined' && typeof response?.body === 'undefined' && typeof response?.headers === 'undefined') {
response = {
statusCode: 200,
body: response
};
}
response.statusCode ??= 500;
response.headers ??= {};
request.response = response;
return response;
};
const createErrorRegexp = /[^a-zA-Z]/g;
}
const value = fetch(request)
const now = Date.now()
// secrets-manager overrides to unix timestamp
const expiry = cacheExpiry > 86400000 ? cacheExpiry : now + cacheExpiry
const duration = cacheExpiry > 86400000 ? cacheExpiry - now : cacheExpiry
if (cacheExpiry) {
const refresh =
duration > 0
? setInterval(() => processCache(options, fetch, request), duration)
: undefined
cache[cacheKey] = { value, expiry, refresh }
}
return { value, expiry }
}
export const getCache = (key) => {
if (!cache[key]) return {}
return cache[key]
}
// Used to remove parts of a cache
export const modifyCache = (cacheKey, value) => {
if (!cache[cacheKey]) return
clearInterval(cache[cacheKey]?.refresh)
cache[cacheKey] = { ...cache[cacheKey], value, modified: true }
}
export const clearCache = (keys = null) => {
keys = keys ?? Object.keys(cache)
if (!Array.isArray(keys)) keys = [keys]
for (const cacheKey of keys) {
clearInterval(cache[cacheKey]?.refresh)
cache[cacheKey] = undefined
}
}
export const jsonSafeParse = (text, reviver) => {
if (typeof text !== 'string') return text
const firstChar = text[0]
if (firstChar !== '{' && firstChar !== '[' && firstChar !== '"') return text
try {
return JSON.parse(text, reviver)
} catch (e) {}
return text
}
export const jsonSafeStringify = (value, replacer, space) => {
try {
return JSON.stringify(value, replacer, space)
} catch (e) {}
return value
}
export const normalizeHttpResponse = (request) => {
let { response } = request
if (typeof response === 'undefined') {
response = {}
} else if (
typeof response?.statusCode === 'undefined' &&
typeof response?.body === 'undefined' &&
typeof response?.headers === 'undefined'
) {
response = { statusCode: 200, body: response }
}
response.statusCode ??= 500
response.headers ??= {}
request.response = response
return response
}
const createErrorRegexp = /[^a-zA-Z]/g
export class HttpError extends Error {
constructor(code, message, options = {}){
if (message && typeof message !== 'string') {
options = message;
message = undefined;
}
message ??= httpErrorCodes[code];
super(message, options);
const name = httpErrorCodes[code].replace(createErrorRegexp, '');
this.name = name.substr(-5) !== 'Error' ? name + 'Error' : name;
this.status = this.statusCode = code;
this.expose = options.expose ?? code < 500;
constructor (code, message, options = {}) {
if (message && typeof message !== 'string') {
options = message
message = undefined
}
message ??= httpErrorCodes[code]
super(message, options)
const name = httpErrorCodes[code].replace(createErrorRegexp, '')
this.name = name.substr(-5) !== 'Error' ? name + 'Error' : name
this.status = this.statusCode = code // setting `status` for backwards compatibility w/ `http-errors`
this.expose = options.expose ?? code < 500
}
}
export const createError = (code, message, properties = {})=>{
return new HttpError(code, message, properties);
};
export const createError = (code, message, properties = {}) => {
return new HttpError(code, message, properties)
}
const httpErrorCodes = {
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing',
103: 'Early Hints',
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
207: 'Multi-Status',
208: 'Already Reported',
226: 'IM Used',
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Found',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
306: '(Unused)',
307: 'Temporary Redirect',
308: 'Permanent Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Timeout',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Payload Too Large',
414: 'URI Too Long',
415: 'Unsupported Media Type',
416: 'Range Not Satisfiable',
417: 'Expectation Failed',
418: "I'm a teapot",
421: 'Misdirected Request',
422: 'Unprocessable Entity',
423: 'Locked',
424: 'Failed Dependency',
425: 'Unordered Collection',
426: 'Upgrade Required',
428: 'Precondition Required',
429: 'Too Many Requests',
431: 'Request Header Fields Too Large',
451: 'Unavailable For Legal Reasons',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported',
506: 'Variant Also Negotiates',
507: 'Insufficient Storage',
508: 'Loop Detected',
509: 'Bandwidth Limit Exceeded',
510: 'Not Extended',
511: 'Network Authentication Required'
};
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing',
103: 'Early Hints',
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
207: 'Multi-Status',
208: 'Already Reported',
226: 'IM Used',
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Found',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
306: '(Unused)',
307: 'Temporary Redirect',
308: 'Permanent Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Timeout',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Payload Too Large',
414: 'URI Too Long',
415: 'Unsupported Media Type',
416: 'Range Not Satisfiable',
417: 'Expectation Failed',
418: "I'm a teapot",
421: 'Misdirected Request',
422: 'Unprocessable Entity',
423: 'Locked',
424: 'Failed Dependency',
425: 'Unordered Collection',
426: 'Upgrade Required',
428: 'Precondition Required',
429: 'Too Many Requests',
431: 'Request Header Fields Too Large',
451: 'Unavailable For Legal Reasons',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported',
506: 'Variant Also Negotiates',
507: 'Insufficient Storage',
508: 'Loop Detected',
509: 'Bandwidth Limit Exceeded',
510: 'Not Extended',
511: 'Network Authentication Required'
}
{
"name": "@middy/util",
"version": "5.0.0-alpha.0",
"version": "5.0.0-alpha.1",
"description": "🛵 The stylish Node.js middleware engine for AWS Lambda (util package)",

@@ -13,3 +13,2 @@ "type": "module",

},
"main": "./index.cjs",
"module": "./index.js",

@@ -21,6 +20,2 @@ "exports": {

"default": "./index.js"
},
"require": {
"types": "./index.d.ts",
"default": "./index.cjs"
}

@@ -32,3 +27,2 @@ }

"index.js",
"index.cjs",
"index.d.ts",

@@ -67,5 +61,5 @@ "codes.js"

"@aws-sdk/client-ssm": "^3.0.0",
"@middy/core": "5.0.0-alpha.0",
"@middy/core": "5.0.0-alpha.1",
"@types/aws-lambda": "^8.10.76",
"@types/node": "^18.0.0",
"@types/node": "^20.0.0",
"aws-xray-sdk": "^3.3.3"

@@ -78,3 +72,3 @@ },

},
"gitHead": "08c35e3dba9efdad0b86666ce206ce302cc65d07"
"gitHead": "ebce8d5df8783077fa49ba62ee9be20e8486a7f1"
}

@@ -22,4 +22,5 @@ <div align="center">

</a>
<a href="https://lgtm.com/projects/g/middyjs/middy/context:javascript">
<img src="https://img.shields.io/lgtm/grade/javascript/g/middyjs/middy.svg?logo=lgtm&logoWidth=18" alt="Language grade: JavaScript" style="max-width:100%;">
<a href="https://github.com/middyjs/middy/actions/workflows/sast.yml">
<img src="https://github.com/middyjs/middy/actions/workflows/sast.yml/badge.svg
?branch=main&event=push" alt="CodeQL" style="max-width:100%;">
</a>

@@ -26,0 +27,0 @@ <a href="https://bestpractices.coreinfrastructure.org/projects/5280">

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc