Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(typescript): getInternal type safety + other type improvements #1093

Merged
merged 31 commits into from
Oct 8, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9a56a94
feat: type for Internal storage with partial ssm support
m-radzikowski Aug 26, 2023
68be775
chore: fix formatting
m-radzikowski Aug 27, 2023
533162e
feat: type definition improvements for getInternal
lmammino Sep 8, 2023
4397f79
chore: made ts-lint happy
lmammino Sep 8, 2023
4d4ec6e
chore: added comment about the current state of `getInternal` type
lmammino Sep 8, 2023
35137d8
chore: ts-lint --fix the whole world, please
lmammino Sep 8, 2023
331f342
chore: updated SSM types and tests to make sure internal context is p…
lmammino Sep 8, 2023
f7e04d1
chore: type changes
lmammino Sep 8, 2023
6abc083
chore: applied @wtfzambo suggestions
lmammino Sep 15, 2023
a76918a
feat: getInternal type supports resolving promises and property paths…
lmammino Sep 16, 2023
6f06dd4
chore: resolve conflict with packages/util/package-lock.json
lmammino Sep 16, 2023
50d42a0
feat: fixes issues with the SSM middleware removing type-fest and lim…
lmammino Sep 16, 2023
35ba1df
chore: ts-lint
lmammino Sep 16, 2023
70428f5
chore: replaces some never with unknown as suggested by @jfet97
lmammino Sep 17, 2023
6eba6db
chore: resolve conflict with 5.0 branch
lmammino Sep 22, 2023
dc7b448
chore: resolve conflict with 5.0 branch, again
lmammino Sep 22, 2023
b3d1192
feat: added improved types for AppConfig middleware
lmammino Sep 22, 2023
705438d
Merge branch 'release/5.0' into internal-typing
lmammino Sep 22, 2023
49c8f22
chore: re-aligned appconfig with v5 branch
lmammino Sep 22, 2023
1076c83
fix(http-json-body-parser): tests in Node 20
lmammino Sep 29, 2023
abd56f6
fix(ws-json-body-parser): tests in Node 20
lmammino Sep 29, 2023
05cd8ec
feat(dynamodb): improved typing
lmammino Sep 29, 2023
a2ab8d7
feat(s3): improved types
lmammino Sep 29, 2023
4716ef7
feat(s3): improved types
lmammino Sep 29, 2023
1ce5bdd
feat: updated types for rds-signer
lmammino Oct 6, 2023
6d376fd
feat: updated types for rds-signer
lmammino Oct 6, 2023
be5ccbf
feat(s3-object-response): improved type definitions and tests
lmammino Oct 7, 2023
b5d2415
feat(secrets-manager): improved type definitions and tests
lmammino Oct 7, 2023
4f5a619
feat(service-discovery): improved type definitions and tests
lmammino Oct 8, 2023
3c4c524
feat(sts): improved type definitions and tests
lmammino Oct 8, 2023
a673016
feat(rds-signer): removed unnecessary `database` property
lmammino Oct 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 39 additions & 29 deletions packages/core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,34 @@ export interface Request<
TEvent = any,
TResult = any,
TErr = Error,
TContext extends LambdaContext = LambdaContext
TContext extends LambdaContext = LambdaContext,
TInternal extends Record<string, unknown> = {}
> {
event: TEvent
context: TContext
response: TResult | null
error: TErr | null
internal: {
[key: string]: any
}
internal: TInternal
}

declare type MiddlewareFn<
TEvent = any,
TResult = any,
TErr = Error,
TContext extends LambdaContext = LambdaContext
> = (request: Request<TEvent, TResult, TErr, TContext>) => any
TContext extends LambdaContext = LambdaContext,
TInternal extends Record<string, unknown> = {}
> = (request: Request<TEvent, TResult, TErr, TContext, TInternal>) => any

export interface MiddlewareObj<
TEvent = unknown,
TResult = any,
TErr = Error,
TContext extends LambdaContext = LambdaContext
TContext extends LambdaContext = LambdaContext,
TInternal extends Record<string, unknown> = {}
> {
before?: MiddlewareFn<TEvent, TResult, TErr, TContext>
after?: MiddlewareFn<TEvent, TResult, TErr, TContext>
onError?: MiddlewareFn<TEvent, TResult, TErr, TContext>
before?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
after?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
onError?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
}

// The AWS provided Handler type uses void | Promise<TResult> so we have no choice but to follow and suppress the linter warning
Expand All @@ -79,57 +80,63 @@ export interface MiddyfiedHandler<
TEvent = any,
TResult = any,
TErr = Error,
TContext extends LambdaContext = LambdaContext
TContext extends LambdaContext = LambdaContext,
TInternal extends Record<string, unknown> = {}
> extends MiddyInputHandler<TEvent, TResult, TContext>,
MiddyInputPromiseHandler<TEvent, TResult, TContext> {
use: UseFn<TEvent, TResult, TErr, TContext>
before: AttachMiddlewareFn<TEvent, TResult, TErr, TContext>
after: AttachMiddlewareFn<TEvent, TResult, TErr, TContext>
onError: AttachMiddlewareFn<TEvent, TResult, TErr, TContext>
use: UseFn<TEvent, TResult, TErr, TContext, TInternal>
before: AttachMiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
after: AttachMiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
onError: AttachMiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
handler: <TAdditional>(
handler: MiddlewareHandler<
LambdaHandler<TEvent & TAdditional, TResult>,
TContext
>
) => MiddyfiedHandler<TEvent, TResult, TErr, TContext>
) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>
}

declare type AttachMiddlewareFn<
TEvent = any,
TResult = any,
TErr = Error,
TContext extends LambdaContext = LambdaContext
TContext extends LambdaContext = LambdaContext,
TInternal extends Record<string, unknown> = {}
> = (
middleware: MiddlewareFn<TEvent, TResult, TErr, TContext>
) => MiddyfiedHandler<TEvent, TResult, TErr, TContext>
middleware: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>

declare type AttachMiddlewareObj<
TEvent = any,
TResult = any,
TErr = Error,
TContext extends LambdaContext = LambdaContext
TContext extends LambdaContext = LambdaContext,
TInternal extends Record<string, unknown> = {}
> = (
middleware: MiddlewareObj<TEvent, TResult, TErr, TContext>
) => MiddyfiedHandler<TEvent, TResult, TErr, TContext>
middleware: MiddlewareObj<TEvent, TResult, TErr, TContext, TInternal>
) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>

declare type UseFn<
TEvent = any,
TResult = any,
TErr = Error,
TContext extends LambdaContext = LambdaContext
> = <TMiddleware extends MiddlewareObj<any, any, Error, any>>(
TContext extends LambdaContext = LambdaContext,
TInternal extends Record<string, unknown> = {}
> = <TMiddleware extends MiddlewareObj<any, any, Error, any, any>>(
middlewares: TMiddleware | TMiddleware[]
) => TMiddleware extends MiddlewareObj<
infer TMiddlewareEvent,
any,
Error,
infer TMiddlewareContext
infer TMiddlewareContext,
infer TMiddlewareInternal
>
? MiddyfiedHandler<
TMiddlewareEvent & TEvent,
TResult,
TErr,
TMiddlewareContext & TContext
TMiddlewareContext & TContext,
TMiddlewareInternal & TInternal
> // always true
: never

Expand All @@ -149,11 +156,14 @@ declare function middy<
TEvent = unknown,
TResult = any,
TErr = Error,
TContext extends LambdaContext = LambdaContext
TContext extends LambdaContext = LambdaContext,
TInternal extends Record<string, unknown> = {}
> (
handler?: MiddlewareHandler<LambdaHandler<TEvent, TResult>, TContext> | PluginObject,
handler?:
| MiddlewareHandler<LambdaHandler<TEvent, TResult>, TContext>
| PluginObject,
plugin?: PluginObject
): MiddyfiedHandler<TEvent, TResult, TErr, TContext>
): MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>

declare namespace middy {
export {
Expand Down
44 changes: 28 additions & 16 deletions packages/ssm/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,39 @@ import middy from '@middy/core'
import { Options as MiddyOptions } from '@middy/util'
import { Context as LambdaContext } from 'aws-lambda'
import { SSMClient, SSMClientConfig } from '@aws-sdk/client-ssm'
import { JsonValue } from 'type-fest'

interface Options<AwsSSMClient = SSMClient>
extends MiddyOptions<AwsSSMClient, SSMClientConfig> {}
export type ParamType<T> = string & { __returnType?: T }
export declare function ssmParam<T> (path: string): ParamType<T>

// eslint-disable-next-line @typescript-eslint/no-unused-vars
type ExtractSingles<T> = T extends `/${infer _}` ? never : T

export type Context<TOptions extends Options | undefined> = TOptions extends {
setToContext: true
export interface SSMOptions<AwsSSMClient = SSMClient>
extends Omit<MiddyOptions<AwsSSMClient, SSMClientConfig>, 'fetchData'> {
fetchData?: { [key: string]: string | ParamType<unknown> }
}
? LambdaContext &
Record<ExtractSingles<keyof TOptions['fetchData']>, JsonValue> &
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(keyof TOptions['fetchData'] extends `${infer _P}/${infer _S}`
? Record<string, JsonValue>
: unknown)

export type Context<TOptions extends SSMOptions | undefined> =
TOptions extends { setToContext: true }
? TOptions extends { fetchData: infer TFetchData }
? LambdaContext & {
[Key in keyof TFetchData]: TFetchData[Key] extends ParamType<infer T>
? T
: unknown
}
: never
: LambdaContext

declare function ssm<TOptions extends Options> (
export type Internal<TOptions extends SSMOptions | undefined> =
TOptions extends SSMOptions
? TOptions extends { fetchData: infer TFetchData }
? {
[Key in keyof TFetchData]: TFetchData[Key] extends ParamType<infer T>
? T
: unknown
}
: {}
: {}

declare function ssm<TOptions extends SSMOptions> (
options?: TOptions
): middy.MiddlewareObj<unknown, any, Error, Context<TOptions>>
): middy.MiddlewareObj<unknown, any, Error, Context<TOptions>, Internal<TOptions>>

export default ssm
4 changes: 4 additions & 0 deletions packages/ssm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,8 @@ const ssmMiddleware = (opts = {}) => {
before: ssmMiddlewareBefore
}
}

export default ssmMiddleware
export function ssmParam (name) {
return name
}
55 changes: 53 additions & 2 deletions packages/ssm/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import middy from '@middy/core'
import { getInternal } from '@middy/util'
import { SSMClient } from '@aws-sdk/client-ssm'
import { captureAWSv3Client } from 'aws-xray-sdk'
import { expectType } from 'tsd'
import ssm, { Context } from '.'
import { expectType, expectAssignable } from 'tsd'
import ssm, { Context, ssmParam } from '.'
import { Context as LambdaContext } from 'aws-lambda/handler'

// use with default options
expectType<middy.MiddlewareObj<unknown, any, Error, Context<undefined>>>(ssm())
Expand All @@ -24,3 +26,52 @@ const options = {
expectType<middy.MiddlewareObj<unknown, any, Error, Context<typeof options>>>(
ssm(options)
)

expectType<middy.MiddlewareObj<unknown, any, Error, LambdaContext, Record<'lorem' | 'ipsum', unknown>>>(
ssm({
fetchData: {
lorem: '/lorem',
ipsum: '/lorem'
}
})
)

const handler = middy(async (event: {}, context: LambdaContext) => {
return await Promise.resolve({})
})

// chain of multiple ssm middleware
handler
.use(
ssm({
fetchData: {
defaults: ssmParam<string>('/dev/defaults')
},
cacheKey: 'ssm-defaults'
})
)
.use(
ssm({
fetchData: {
accessToken: ssmParam<string>('/dev/service_name/access_token'), // single value
dbParams: ssmParam<{ user: string, pass: string }>('/dev/service_name/database/') // object of values, key for each path
},
cacheExpiry: 15 * 60 * 1000,
cacheKey: 'ssm-secrets',
setToContext: true
})
)
// ... other middleware that fetch
.before(async (request) => {
const data = await getInternal(
['accessToken', 'dbParams', 'defaults'],
request
)

expectType<string>(data.accessToken)
expectType<{ user: string, pass: string }>(data.dbParams)
expectType<string>(data.defaults)

// make sure data is set to context as well (only for the second instantiation of the middleware)
expectAssignable<{ accessToken: string, dbParams: { user: string, pass: string } }>(request.context)
})
3 changes: 1 addition & 2 deletions packages/ssm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@
"@aws-sdk/client-ssm": "^3.0.0",
"@middy/core": "4.6.0",
"@types/aws-lambda": "^8.10.101",
"aws-xray-sdk": "^3.3.3",
"type-fest": "^4.0.0"
"aws-xray-sdk": "^3.3.3"
},
"gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431"
}
77 changes: 72 additions & 5 deletions packages/util/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
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 (...[config]: [any] | any) => Client
Expand Down Expand Up @@ -33,12 +35,77 @@ declare function canPrefetch<Client, ClientOptions> (
options: Options<Client, ClientOptions>
): boolean

declare function getInternal (
variables: any,
request: middy.Request
): Promise<any>
type InternalOutput<TVariables> = TVariables extends string[] ? { [key in TVariables[number]]: unknown } : never

// 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 (key: string): string
declare function sanitizeKey<T extends string> (key: T): SanitizeKey<T>

declare function processCache<Client, ClientOptions> (
options: Options<Client, ClientOptions>,
Expand Down
Loading
Loading