@middy/util
Advanced tools
Comparing version 5.0.0-alpha.0 to 5.0.0-alpha.1
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"> |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47
4
17936
5
376