Skip to content

Commit

Permalink
feat: add jitter error handling strategy (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
pevisscher committed Oct 19, 2023
1 parent 9242270 commit 7ca7427
Show file tree
Hide file tree
Showing 15 changed files with 4,454 additions and 13,870 deletions.
3 changes: 2 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
* @skyleague/oss
* @skyleague/oss
package-lock.json
6 changes: 6 additions & 0 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ name: Typescript Package CI

on:
push:
branches:
- main
pull_request:
types:
- opened
- synchronize
workflow_dispatch:
inputs:
beta_release:
Expand Down
18,165 changes: 4,361 additions & 13,804 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 7 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@
],
"scripts": {
"build": "npx ts-node --esm build.config.ts",
"build:clean": "npx tsc --build --clean && rm -rf dist",
"build:docs": "npx typedoc",
"check:cost": "npx cost-of-modules --no-install --include-dev",
"check:coverage": "npx vitest run --coverage=true",
"check:full": "npm run lint && npm run check:types && npm run check:coverage && npm run build && npm run check:project",
"check:full": "npm run lint && npm run check:types && npm run check:coverage && npm run check:project",
"check:project": "npx node-standards lint",
"check:types": "npx tsc -p tsconfig.json",
"format": "npx prettier \"**/*.{ts,js,json,yml,yaml,md}\" --write",
Expand All @@ -42,17 +41,15 @@
},
"dependencies": {
"ajv": "^8.12.0",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"tslib": "^2.5.0",
"tslib": "^2.6.2",
"yargs": "^17.7.2"
},
"devDependencies": {
"@skyleague/node-standards": "^3.6.5",
"@skyleague/therefore": "^2.2.1",
"@types/source-map-support": "^0.5.6",
"@types/yargs": "^17.0.24",
"typescript": "^5.0.4"
"@skyleague/node-standards": "^3.10.5",
"@skyleague/therefore": "^3.0.1",
"@types/yargs": "^17.0.29",
"typescript": "^5.2.2"
},
"engines": {
"node": ">=18"
Expand All @@ -68,4 +65,4 @@
"bin/run.js"
]
}
}
}
10 changes: 5 additions & 5 deletions src/commands/compile/input.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Do not manually touch this
*/
/* eslint-disable */
import AjvValidator from 'ajv'
import type { ValidateFunction } from 'ajv'
import { ValidationError } from 'ajv'

export interface LambdaIntegrationParameters {
FunctionName: string
Expand All @@ -14,7 +14,7 @@ export interface LambdaIntegrationParameters {

export const LambdaIntegrationParameters = {
validate: (await import('./schemas/lambda-integration-parameters.schema.js'))
.validate10 as unknown as ValidateFunction<LambdaIntegrationParameters>,
.validate as ValidateFunction<LambdaIntegrationParameters>,
get schema() {
return LambdaIntegrationParameters.validate.schema
},
Expand All @@ -24,7 +24,7 @@ export const LambdaIntegrationParameters = {
is: (o: unknown): o is LambdaIntegrationParameters => LambdaIntegrationParameters.validate(o) === true,
assert: (o: unknown) => {
if (!LambdaIntegrationParameters.validate(o)) {
throw new AjvValidator.ValidationError(LambdaIntegrationParameters.errors ?? [])
throw new ValidationError(LambdaIntegrationParameters.errors ?? [])
}
},
} as const
Expand All @@ -38,7 +38,7 @@ export interface StateMachineCompileInput {

export const StateMachineCompileInput = {
validate: (await import('./schemas/state-machine-compile-input.schema.js'))
.validate10 as unknown as ValidateFunction<StateMachineCompileInput>,
.validate as ValidateFunction<StateMachineCompileInput>,
get schema() {
return StateMachineCompileInput.validate.schema
},
Expand All @@ -48,7 +48,7 @@ export const StateMachineCompileInput = {
is: (o: unknown): o is StateMachineCompileInput => StateMachineCompileInput.validate(o) === true,
assert: (o: unknown) => {
if (!StateMachineCompileInput.validate(o)) {
throw new AjvValidator.ValidationError(StateMachineCompileInput.errors ?? [])
throw new ValidationError(StateMachineCompileInput.errors ?? [])
}
},
} as const

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as compile from './compile/index.js'

import packageJson from '../../package.json' assert { type: 'json' }

import { install } from 'source-map-support'
import type { CommandModule } from 'yargs'
import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'
Expand All @@ -11,8 +10,6 @@ const { bin } = packageJson
const commands = { compile }

export async function run(): Promise<void> {
install()

let cli = yargs(hideBin(process.argv)).scriptName(Object.keys(bin)[0] ?? 'cli')
for (const command of Object.values(commands)) {
cli = cli.command(command.default as unknown as CommandModule)
Expand Down
74 changes: 38 additions & 36 deletions src/lib/validate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,42 +27,44 @@ it('lambda retry injection', () => {
},
})
).toMatchInlineSnapshot(`
{
"StartAt": "Enter",
"States": {
"Enter": {
"Next": "SomeLambda",
"Type": "Pass",
},
"Exit": {
"Type": "Succeed",
},
"SomeLambda": {
"Next": "Exit",
"Resource": "arn:aws:lambda:\${aws_region}:\${aws_account_id}:function:some-lambda",
"Retry": [
{
"BackoffRate": 1.5,
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
],
"IntervalSeconds": 0.2,
"MaxAttempts": 17,
},
{
"BackoffRate": 1.5,
"ErrorEquals": [
"Lambda.TooManyRequestsException",
],
"IntervalSeconds": 2,
"MaxAttempts": 13,
},
],
"Type": "Task",
},
{
"StartAt": "Enter",
"States": {
"Enter": {
"Next": "SomeLambda",
"Type": "Pass",
},
"Exit": {
"Type": "Succeed",
},
"SomeLambda": {
"Next": "Exit",
"Resource": "arn:aws:lambda:\${aws_region}:\${aws_account_id}:function:some-lambda",
"Retry": [
{
"BackoffRate": 1.5,
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
],
"IntervalSeconds": 0.2,
"JitterStrategy": "FULL",
"MaxAttempts": 17,
},
{
"BackoffRate": 1.5,
"ErrorEquals": [
"Lambda.TooManyRequestsException",
],
"IntervalSeconds": 2,
"JitterStrategy": "FULL",
"MaxAttempts": 13,
},
],
"Type": "Task",
},
}
},
}
`)
})
15 changes: 13 additions & 2 deletions src/lib/validate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { SafeStateMachine } from '../types/index.js'
import { StateMachine } from '../types/index.js'
import type { State } from '../types/sfn.type.js'
import { StateMachine, type State } from '../types/sfn.type.js'

export function validateStateMachine<States extends StateMachine['States']>({
definition,
Expand Down Expand Up @@ -124,12 +123,16 @@ interface RetryOptions {
backoffRate?: number
intervalSeconds?: number
maxAttempts?: number
maxDelaySeconds?: number
jitterStrategy?: 'NONE' | 'FULL'
}
serviceExceptions?: {
enabled?: boolean
backoffRate?: number
intervalSeconds?: number
maxAttempts?: number
maxDelaySeconds?: number
jitterStrategy?: 'NONE' | 'FULL'
}
}
}
Expand Down Expand Up @@ -159,6 +162,10 @@ function injectRetryOptions({
BackoffRate: retryOptions?.lambda?.tooManyRequests?.backoffRate ?? 1.5,
IntervalSeconds: retryOptions?.lambda?.tooManyRequests?.intervalSeconds ?? 2,
MaxAttempts: retryOptions?.lambda?.tooManyRequests?.maxAttempts ?? 13,
JitterStrategy: retryOptions?.lambda?.tooManyRequests?.jitterStrategy ?? 'FULL',
...(retryOptions?.lambda?.tooManyRequests?.maxDelaySeconds !== undefined
? { MaxDelaySeconds: retryOptions.lambda.tooManyRequests.maxDelaySeconds }
: {}),
},
...(state.Retry ?? []),
]
Expand All @@ -177,6 +184,10 @@ function injectRetryOptions({
BackoffRate: retryOptions?.lambda?.serviceExceptions?.backoffRate ?? 1.5,
IntervalSeconds: retryOptions?.lambda?.serviceExceptions?.intervalSeconds ?? 0.2,
MaxAttempts: retryOptions?.lambda?.serviceExceptions?.maxAttempts ?? 17,
JitterStrategy: retryOptions?.lambda?.serviceExceptions?.jitterStrategy ?? 'FULL',
...(retryOptions?.lambda?.serviceExceptions?.maxDelaySeconds !== undefined
? { MaxDelaySeconds: retryOptions.lambda.serviceExceptions.maxDelaySeconds }
: {}),
},
...(state.Retry ?? []),
]
Expand Down
4 changes: 3 additions & 1 deletion src/types/base.schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { $array, $boolean, $number, $object, $optional, $ref, $string, $unknown } from '@skyleague/therefore'
import { $array, $boolean, $enum, $number, $object, $optional, $ref, $string, $unknown } from '@skyleague/therefore'

export const baseState = {
Comment: $optional($string),
Expand All @@ -18,6 +18,8 @@ export const retryOptions = $object({
IntervalSeconds: $optional($number),
MaxAttempts: $optional($number),
BackoffRate: $optional($number),
MaxDelaySeconds: $optional($number),
JitterStrategy: $optional($enum(['FULL', 'NONE'])),
})
export const catchOptions = $object({
ErrorEquals: $array($string, { minItems: 1 }),
Expand Down
2 changes: 2 additions & 0 deletions src/types/base.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ export interface RetryOptions {
IntervalSeconds?: number
MaxAttempts?: number
BackoffRate?: number
MaxDelaySeconds?: number
JitterStrategy?: 'FULL' | 'NONE'
}
13 changes: 11 additions & 2 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import type { StateMachine } from './sfn.type.js'

export { ChoiceState } from './choice.type.js'
export { FailState, MapState, ParallelState, PassState, StateMachine, SucceedState, TaskState, WaitState } from './sfn.type.js'
export type { ChoiceState } from './choice.type.js'
export type {
FailState,
MapState,
ParallelState,
PassState,
StateMachine,
SucceedState,
TaskState,
WaitState,
} from './sfn.type.js'

export type SafeStateMachine<States extends StateMachine['States']> = Omit<StateMachine, 'StartAt' | 'States'> & {
States: States
Expand Down
2 changes: 1 addition & 1 deletion src/types/schemas/state-machine.schema.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/types/sfn.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
*/
/* eslint-disable */
import type { ValidateFunction } from 'ajv'
import { CatchOptions, RetryOptions } from './base.type.js'
import { ChoiceState } from './choice.type.js'
import { type CatchOptions, type RetryOptions } from './base.type.js'
import { type ChoiceState } from './choice.type.js'

export interface FailState {
Type: 'Fail'
Expand Down Expand Up @@ -108,7 +108,7 @@ export interface StateMachine {
}

export const StateMachine = {
validate: (await import('./schemas/state-machine.schema.js')).validate10 as unknown as ValidateFunction<StateMachine>,
validate: (await import('./schemas/state-machine.schema.js')).validate as ValidateFunction<StateMachine>,
get schema() {
return StateMachine.validate.schema
},
Expand Down

0 comments on commit 7ca7427

Please sign in to comment.