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

[Feature request] Include TypeScript types #43

Closed
zirkelc opened this issue Apr 2, 2025 · 13 comments · Fixed by #48
Closed

[Feature request] Include TypeScript types #43

zirkelc opened this issue Apr 2, 2025 · 13 comments · Fixed by #48

Comments

@zirkelc
Copy link
Contributor

zirkelc commented Apr 2, 2025

Serverless provides the TypeScript types in a separate package @serverless/typescript which is outdated and has a few open issues, like this serverless/typescript#27 which more than 4 years old and has a simple fix described in this comment serverless/typescript#27 (comment)

I'd like to know if you would interested in including the types directly into this package. I could prepare a new PR that simply copies the types from @serverless/typescript and then we could gradually fix them over time.

@mnapoli
Copy link
Contributor

mnapoli commented Apr 2, 2025

Given it seems to be just a few files, I'd be happy to merge a PR that adds this.

@rageycomma
Copy link

Is it worth making oss-serverless/types[script] package?

@zirkelc
Copy link
Contributor Author

zirkelc commented Apr 2, 2025

Is it worth making oss-serverless/types[script] package?

I think integrating the types directly is much better developer experience, because IMO a separate types package makes it unnecessarily complicated to release new features and it risks that the implementation doesn't match the types (like it is now with Serverless).

@rageycomma
Copy link

Is it worth making oss-serverless/types[script] package?

I think integrating the types directly is much better developer experience, because IMO a separate types package makes it unnecessarily complicated to release new features and it risks that the implementation doesn't match the types (like it is now with Serverless).

Phew. That’s what I would prefer as well.

@mnapoli
Copy link
Contributor

mnapoli commented Apr 2, 2025

+1 I don't want to maintain another package and have it become out of date 😅

@rageycomma
Copy link

Has PR: #45

@zirkelc
Copy link
Contributor Author

zirkelc commented Apr 3, 2025

Thank you for the PR! Unfortunately, these are the wrong types 😅 The package @serverless/typescript only contains the types for the severless.ts (serverless.yml) and is generated from the respective JSON Schema. vHere is how they do it:

They define a local plugin called schema in this file:
https://github.com/serverless/typescript/blob/892f1fac6c6818c8855d43dda8065fe46bd06303/src/plugin.ts#L12-L40

This plugin defines a command npx sls schema that is invoked in GitHub Actions:
https://github.com/serverless/typescript/blob/892f1fac6c6818c8855d43dda8065fe46bd06303/.github/workflows/update-and-release.yml#L33-L36

The plugin uses the json-schema-to-typescript package to create the types for the severless.ts based on this schema:

serverless.configSchemaHandler.defineProvider('aws', {
definitions: {
awsAccountId: {
type: 'string',
pattern: '^\\d{12}$',
},
awsAlbListenerArn: {
type: 'string',
pattern: ALB_LISTENER_REGEXP.source,
},
awsAlexaEventToken: {
type: 'string',
minLength: 0,
maxLength: 256,
pattern: '^[a-zA-Z0-9._\\-]+$',
},
awsApiGatewayAbbreviatedArn: {
type: 'string',
pattern: '^execute-api:/',
},
awsApiGatewayApiKeys: {
type: 'array',
items: {
anyOf: [
{ type: 'string' },
{
$ref: '#/definitions/awsApiGatewayApiKeysProperties',
},
{
type: 'object',
maxProperties: 1,
additionalProperties: {
type: 'array',
items: {
oneOf: [
{ type: 'string' },
{ $ref: '#/definitions/awsApiGatewayApiKeysProperties' },
],
},
},
},
],
},
},
awsApiGatewayApiKeysProperties: {
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'string' },
description: { type: 'string' },
customerId: { type: 'string' },
enabled: { type: 'boolean' },
},
additionalProperties: false,
},
awsHttpApiPayload: {
type: 'string',
enum: ['1.0', '2.0'],
},
awsArn: {
anyOf: [
{ $ref: '#/definitions/awsArnString' },
{ $ref: '#/definitions/awsCfFunction' },
],
},
awsArnString: {
type: 'string',
pattern: '^arn:',
},
awsCfArrayInstruction: {
anyOf: [
{
type: 'array',
items: { $ref: '#/definitions/awsCfInstruction' },
},
{ $ref: '#/definitions/awsCfSplit' },
],
},
awsSecretsManagerArnString: {
type: 'string',
pattern: 'arn:[a-z-]+:secretsmanager:[a-z0-9-]+:\\d+:secret:[A-Za-z0-9/_+=.@-]+',
},
awsCfFunction: {
anyOf: [
{ $ref: '#/definitions/awsCfImport' },
{ $ref: '#/definitions/awsCfJoin' },
{ $ref: '#/definitions/awsCfGetAtt' },
{ $ref: '#/definitions/awsCfRef' },
{ $ref: '#/definitions/awsCfSub' },
{ $ref: '#/definitions/awsCfBase64' },
{ $ref: '#/definitions/awsCfToJsonString' },
],
},
awsCfGetAtt: {
type: 'object',
properties: {
'Fn::GetAtt': {
type: 'array',
minItems: 2,
maxItems: 2,
items: { type: 'string', minLength: 1 },
},
},
required: ['Fn::GetAtt'],
additionalProperties: false,
},
awsCfGetAZs: {
type: 'object',
properties: {
'Fn::GetAZs': {
oneOf: [{ type: 'string', minLength: 1 }, { $ref: '#/definitions/awsCfRef' }],
},
},
required: ['Fn::GetAZs'],
additionalProperties: false,
},
awsCfImport: {
type: 'object',
properties: {
'Fn::ImportValue': {},
},
additionalProperties: false,
required: ['Fn::ImportValue'],
},
awsCfIf: {
type: 'object',
properties: {
'Fn::If': {
type: 'array',
minItems: 3,
maxItems: 3,
items: { $ref: '#/definitions/awsCfInstruction' },
},
},
required: ['Fn::If'],
additionalProperties: false,
},
awsCfImportLocallyResolvable: {
type: 'object',
properties: {
'Fn::ImportValue': { type: 'string' },
},
additionalProperties: false,
required: ['Fn::ImportValue'],
},
awsCfInstruction: {
anyOf: [{ type: 'string', minLength: 1 }, { $ref: '#/definitions/awsCfFunction' }],
},
awsCfJoin: {
type: 'object',
properties: {
'Fn::Join': {
type: 'array',
minItems: 2,
maxItems: 2,
items: [{ type: 'string' }, { type: 'array' }],
additionalItems: false,
},
},
required: ['Fn::Join'],
additionalProperties: false,
},
awsCfSelect: {
type: 'object',
properties: {
'Fn::Select': {
type: 'array',
minItems: 2,
maxItems: 2,
items: {
anyOf: [
{ type: 'number' },
{ type: 'string' },
{ type: 'array' },
{ $ref: '#/definitions/awsCfFindInMap' },
{ $ref: '#/definitions/awsCfGetAtt' },
{ $ref: '#/definitions/awsCfGetAZs' },
{ $ref: '#/definitions/awsCfIf' },
{ $ref: '#/definitions/awsCfSplit' },
{ $ref: '#/definitions/awsCfRef' },
],
},
},
},
required: ['Fn::Select'],
additionalProperties: false,
},
awsCfRef: {
type: 'object',
properties: {
Ref: { type: 'string', minLength: 1 },
},
required: ['Ref'],
additionalProperties: false,
},
awsCfSplit: {
type: 'object',
properties: {
'Fn::Split': {
type: 'array',
minItems: 2,
maxItems: 2,
items: {
oneOf: [{ type: 'string' }, { $ref: '#/definitions/awsCfFunction' }],
},
},
},
required: ['Fn::Split'],
additionalProperties: false,
},
awsCfFindInMap: {
type: 'object',
properties: {
'Fn::FindInMap': {
type: 'array',
minItems: 3,
maxItems: 3,
items: {
oneOf: [{ type: 'string' }, { $ref: '#/definitions/awsCfFunction' }],
},
},
},
required: ['Fn::FindInMap'],
additionalProperties: false,
},
awsCfSub: {
type: 'object',
properties: {
'Fn::Sub': {},
},
required: ['Fn::Sub'],
additionalProperties: false,
},
awsCfBase64: {
type: 'object',
properties: {
'Fn::Base64': {},
},
},
awsCfToJsonString: {
type: 'object',
properties: {
'Fn::ToJsonString': {
anyOf: [{ type: 'object' }, { type: 'array' }],
},
},
required: ['Fn::ToJsonString'],
additionalProperties: false,
},
awsIamPolicyAction: { type: 'array', items: { type: 'string' } },
awsIamPolicyPrincipal: {
anyOf: [
{ const: '*' },
{
type: 'object',
properties: {
AWS: {
anyOf: [
{ const: '*' },
{ $ref: '#/definitions/awsCfIf' },
{
type: 'array',
items: {
anyOf: [
{ $ref: '#/definitions/awsAccountId' },
{ $ref: '#/definitions/awsArn' },
],
},
},
],
},
Federated: { type: 'array', items: { type: 'string' } },
Service: { type: 'array', items: { type: 'string' } },
CanonicalUser: { type: 'array', items: { type: 'string' } },
},
additionalProperties: false,
},
],
},
awsIamPolicyResource: {
anyOf: [
{ const: '*' },
{ $ref: '#/definitions/awsArn' },
{ $ref: '#/definitions/awsLogicalResourceId' },
{
type: 'array',
items: {
anyOf: [
{ const: '*' },
{ $ref: '#/definitions/awsArn' },
{ $ref: '#/definitions/awsLogicalResourceId' },
],
},
},
],
},
// Definition of Statement taken from https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html#policies-grammar-bnf
awsIamPolicyStatements: {
type: 'array',
items: {
type: 'object',
properties: {
Sid: { type: 'string' },
Effect: { enum: ['Allow', 'Deny'] },
Action: { $ref: '#/definitions/awsIamPolicyAction' },
NotAction: { $ref: '#/definitions/awsIamPolicyAction' },
Principal: { $ref: '#/definitions/awsIamPolicyPrincipal' },
NotPrincipal: { $ref: '#/definitions/awsIamPolicyPrincipal' },
Resource: { $ref: '#/definitions/awsIamPolicyResource' },
NotResource: { $ref: '#/definitions/awsIamPolicyResource' },
Condition: { type: 'object' },
},
additionalProperties: false,
allOf: [
{ required: ['Effect'] },
{ oneOf: [{ required: ['Action'] }, { required: ['NotAction'] }] },
{ oneOf: [{ required: ['Resource'] }, { required: ['NotResource'] }] },
],
},
},
awsKmsArn: {
anyOf: [
{ $ref: '#/definitions/awsCfFunction' },
{ type: 'string', pattern: '^arn:aws[a-z-]*:kms' },
],
},
awsLambdaArchitecture: { enum: ['arm64', 'x86_64'] },
awsLambdaEnvironment: {
type: 'object',
patternProperties: {
'^[A-Za-z_][a-zA-Z0-9_]*$': {
anyOf: [
{ const: '' },
{ $ref: '#/definitions/awsCfInstruction' },
{ $ref: '#/definitions/awsCfIf' },
{ $ref: '#/definitions/awsCfSelect' },
],
},
},
additionalProperties: false,
},
awsLambdaLayers: {
type: 'array',
items: { $ref: '#/definitions/awsArn' },
},
awsLambdaMemorySize: { type: 'integer', minimum: 128, maximum: 10240 },
awsLambdaRole: {
anyOf: [
{ type: 'string', minLength: 1 },
{ $ref: '#/definitions/awsCfSub' },
{ $ref: '#/definitions/awsCfImport' },
{ $ref: '#/definitions/awsCfGetAtt' },
],
},
awsLambdaRuntime: {
enum: [
'dotnet6',
'dotnet8',
'go1.x',
'java21',
'java17',
'java11',
'java8',
'java8.al2',
'nodejs14.x',
'nodejs16.x',
'nodejs18.x',
'nodejs20.x',
'nodejs22.x',
'provided',
'provided.al2',
'provided.al2023',
'python3.7',
'python3.8',
'python3.9',
'python3.10',
'python3.11',
'python3.12',
'python3.13',
'ruby2.7',
'ruby3.2',
'ruby3.3',
],
},
awsLambdaRuntimeManagement: {
oneOf: [
{ enum: ['auto', 'onFunctionUpdate'] },
{
type: 'object',
properties: {
mode: { enum: ['auto', 'onFunctionUpdate', 'manual'] },
arn: { $ref: '#/definitions/awsArn' },
},
additionalProperties: false,
},
],
},
awsLambdaTimeout: { type: 'integer', minimum: 1, maximum: 900 },
awsLambdaTracing: { anyOf: [{ enum: ['Active', 'PassThrough'] }, { type: 'boolean' }] },
awsLambdaVersioning: { type: 'boolean' },
awsLambdaVpcConfig: {
type: 'object',
properties: {
ipv6AllowedForDualStack: { type: 'boolean' },
securityGroupIds: {
anyOf: [
{
type: 'array',
items: {
anyOf: [
{ $ref: '#/definitions/awsCfInstruction' },
{ $ref: '#/definitions/awsCfIf' },
],
},
maxItems: 5,
},
{ $ref: '#/definitions/awsCfSplit' },
{ $ref: '#/definitions/awsCfFindInMap' },
],
},
subnetIds: {
anyOf: [
{
type: 'array',
items: {
anyOf: [
{ $ref: '#/definitions/awsCfInstruction' },
{ $ref: '#/definitions/awsCfIf' },
],
},
maxItems: 16,
},
{ $ref: '#/definitions/awsCfSplit' },
{ $ref: '#/definitions/awsCfFindInMap' },
],
},
},
additionalProperties: false,
required: ['securityGroupIds', 'subnetIds'],
},
awsLogicalResourceId: {
type: 'string',
pattern: '^[#A-Za-z0-9-_./]+[*]?$',
},
awsLogGroupName: {
type: 'string',
pattern: '^[/#A-Za-z0-9-_.]+$',
},
awsLogRetentionInDays: {
type: 'number',
// https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutRetentionPolicy.html#API_PutRetentionPolicy_RequestSyntax
enum: [
1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 2192, 2557, 2922,
3288, 3653,
],
},
awsLogDataProtectionPolicy: {
type: 'object',
properties: {
Name: { type: 'string' },
Description: { type: 'string' },
Version: { type: 'string' },
Statement: { type: 'array' },
},
additionalProperties: false,
required: ['Name', 'Version', 'Statement'],
},
awsResourceCondition: { type: 'string' },
awsResourceDependsOn: { type: 'array', items: { type: 'string' } },
awsResourcePolicyResource: {
anyOf: [
{ const: '*' },
{ $ref: '#/definitions/awsArn' },
// API Gateway Resource Policy resource property abbreviated syntax - https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-resource-policies-examples.html
{ $ref: '#/definitions/awsApiGatewayAbbreviatedArn' },
{
type: 'array',
items: {
anyOf: [
{ const: '*' },
{ $ref: '#/definitions/awsArn' },
// API Gateway Resource Policy resource property abbreviated syntax - https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-resource-policies-examples.html
{ $ref: '#/definitions/awsApiGatewayAbbreviatedArn' },
],
},
},
],
},
awsResourcePolicyStatements: {
type: 'array',
items: {
type: 'object',
properties: {
Sid: { type: 'string' },
Effect: { enum: ['Allow', 'Deny'] },
Action: { $ref: '#/definitions/awsIamPolicyAction' },
NotAction: { $ref: '#/definitions/awsIamPolicyAction' },
Principal: { $ref: '#/definitions/awsIamPolicyPrincipal' },
NotPrincipal: { $ref: '#/definitions/awsIamPolicyPrincipal' },
Resource: { $ref: '#/definitions/awsResourcePolicyResource' },
NotResource: { $ref: '#/definitions/awsResourcePolicyResource' },
Condition: { type: 'object' },
},
additionalProperties: false,
allOf: [
{ required: ['Effect'] },
{ oneOf: [{ required: ['Action'] }, { required: ['NotAction'] }] },
{ oneOf: [{ required: ['Resource'] }, { required: ['NotResource'] }] },
],
},
},
awsResourceTags: {
type: 'object',
patternProperties: {
'^(?!aws:)[\\w./=+:\\-_\\x20]{1,128}$': {
type: 'string',
maxLength: 256,
},
},
additionalProperties: false,
},
awsS3BucketName: {
type: 'string',
// pattern sourced from https://stackoverflow.com/questions/50480924/regex-for-s3-bucket-name
pattern:
'(?!^(\\d{1,3}\\.){3}\\d{1,3}$)(^(([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])\\.)*([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$)',
minLength: 3,
maxLength: 63,
},
ecrImageUri: {
type: 'string',
pattern:
'^\\d+\\.dkr\\.ecr\\.[a-z0-9-]+..amazonaws.com\\/([^@]+)|([^@:]+@sha256:[a-f0-9]{64})$',
},
filterPatterns: {
type: 'array',
minItems: 1,
maxItems: 10,
items: { type: 'object' },
},
},
provider: {
properties: {
alb: {
type: 'object',
properties: {
targetGroupPrefix: { type: 'string', maxLength: 16 },
authorizers: {
type: 'object',
additionalProperties: {
anyOf: [oidcAlbAuthorizer, cognitoAlbAuthorizer],
},
},
},
additionalProperties: false,
},
apiGateway: {
type: 'object',
properties: {
apiKeys: { $ref: '#/definitions/awsApiGatewayApiKeys' },
apiKeySourceType: {
anyOf: ['HEADER', 'AUTHORIZER'].map(caseInsensitive),
},
binaryMediaTypes: {
type: 'array',
items: { type: 'string', pattern: '^\\S+\\/\\S+$' },
},
description: { type: 'string' },
disableDefaultEndpoint: { type: 'boolean' },
metrics: { type: 'boolean' },
minimumCompressionSize: { type: 'integer', minimum: 0, maximum: 10485760 },
resourcePolicy: { $ref: '#/definitions/awsResourcePolicyStatements' },
restApiId: { $ref: '#/definitions/awsCfInstruction' },
restApiResources: {
anyOf: [
{
type: 'array',
items: {
type: 'object',
properties: {
path: { type: 'string' },
resourceId: { type: 'string' },
},
required: [],
additionalProperties: false,
},
},
{ type: 'object' },
],
},
restApiRootResourceId: { $ref: '#/definitions/awsCfInstruction' },
request: {
type: 'object',
properties: {
schemas: {
type: 'object',
additionalProperties: {
type: 'object',
properties: {
schema: { type: 'object' },
name: { type: 'string' },
description: { type: 'string' },
},
required: ['schema'],
additionalProperties: false,
},
},
},
additionalProperties: false,
},
shouldStartNameWithService: { type: 'boolean' },
stage: { type: 'string' },
timeoutInMillis: { type: 'integer', minimum: 50 },
usagePlan: {
anyOf: [
apiGatewayUsagePlan,
{
type: 'array',
items: {
type: 'object',
additionalProperties: apiGatewayUsagePlan,
maxProperties: 1,
},
},
],
},
websocketApiId: { $ref: '#/definitions/awsCfInstruction' },
},
additionalProperties: false,
},
apiName: { type: 'string' },
architecture: { $ref: '#/definitions/awsLambdaArchitecture' },
cfnRole: { $ref: '#/definitions/awsArn' },
cloudFront: {
type: 'object',
properties: {
cachePolicies: {
type: 'object',
additionalProperties: {
type: 'object',
properties: {
Comment: { type: 'string' },
DefaultTTL: { type: 'integer', minimum: 0 },
MaxTTL: { type: 'integer', minimum: 0 },
MinTTL: { type: 'integer', minimum: 0 },
ParametersInCacheKeyAndForwardedToOrigin: {
type: 'object',
properties: {
CookiesConfig: {
type: 'object',
properties: {
CookieBehavior: { enum: ['none', 'whitelist', 'allExcept', 'all'] },
Cookies: { type: 'array', items: { type: 'string' } },
},
required: ['CookieBehavior'],
additionalProperties: false,
},
EnableAcceptEncodingBrotli: { type: 'boolean' },
EnableAcceptEncodingGzip: { type: 'boolean' },
HeadersConfig: {
type: 'object',
properties: {
HeaderBehavior: { enum: ['none', 'whitelist'] },
Headers: { type: 'array', items: { type: 'string' } },
},
required: ['HeaderBehavior'],
additionalProperties: false,
},
QueryStringsConfig: {
type: 'object',
properties: {
QueryStringBehavior: {
enum: ['none', 'whitelist', 'allExcept', 'all'],
},
QueryStrings: { type: 'array', items: { type: 'string' } },
},
required: ['QueryStringBehavior'],
additionalProperties: false,
},
},
required: [
'CookiesConfig',
'EnableAcceptEncodingGzip',
'HeadersConfig',
'QueryStringsConfig',
],
additionalProperties: false,
},
},
required: [
'DefaultTTL',
'MaxTTL',
'MinTTL',
'ParametersInCacheKeyAndForwardedToOrigin',
],
additionalProperties: false,
},
},
},
additionalProperties: false,
},
deploymentBucket: {
anyOf: [
{ $ref: '#/definitions/awsS3BucketName' },
{
type: 'object',
properties: {
blockPublicAccess: { type: 'boolean' },
skipPolicySetup: { type: 'boolean' },
maxPreviousDeploymentArtifacts: { type: 'integer', minimum: 0 },
name: { $ref: '#/definitions/awsS3BucketName' },
versioning: { type: 'boolean' },
serverSideEncryption: { enum: ['AES256', 'aws:kms'] },
sseCustomerAlgorithim: { type: 'string' },
sseCustomerKey: { type: 'string' },
sseCustomerKeyMD5: { type: 'string' },
sseKMSKeyId: { type: 'string' },
tags: { $ref: '#/definitions/awsResourceTags' },
},
additionalProperties: false,
},
],
},
deploymentPrefix: { type: 'string' },
disableRollback: { type: 'boolean' },
endpointType: {
anyOf: ['REGIONAL', 'EDGE', 'PRIVATE'].map(caseInsensitive),
},
environment: { $ref: '#/definitions/awsLambdaEnvironment' },
eventBridge: {
type: 'object',
properties: {
useCloudFormation: { type: 'boolean' },
},
additionalProperties: false,
},
httpApi: {
type: 'object',
properties: {
authorizers: {
type: 'object',
additionalProperties: {
oneOf: [
{
type: 'object',
properties: {
type: { const: 'jwt' },
name: { type: 'string' },
identitySource: { $ref: '#/definitions/awsCfInstruction' },
issuerUrl: { $ref: '#/definitions/awsCfInstruction' },
audience: {
anyOf: [
{ $ref: '#/definitions/awsCfInstruction' },
{
type: 'array',
items: { $ref: '#/definitions/awsCfInstruction' },
},
],
},
},
required: ['identitySource', 'issuerUrl', 'audience'],
additionalProperties: false,
},
{
type: 'object',
properties: {
type: { const: 'request' },
name: { type: 'string' },
functionName: { type: 'string' },
functionArn: { $ref: '#/definitions/awsCfInstruction' },
managedExternally: { type: 'boolean' },
resultTtlInSeconds: { type: 'integer', minimum: 0, maximum: 3600 },
enableSimpleResponses: { type: 'boolean' },
payloadVersion: { $ref: '#/definitions/awsHttpApiPayload' },
identitySource: {
anyOf: [
{ $ref: '#/definitions/awsCfInstruction' },
{
type: 'array',
items: { $ref: '#/definitions/awsCfInstruction' },
},
],
},
},
required: ['type'],
},
],
},
},
cors: {
anyOf: [
{ type: 'boolean' },
{
type: 'object',
properties: {
allowCredentials: { type: 'boolean' },
allowedHeaders: { type: 'array', items: { type: 'string' } },
allowedMethods: { type: 'array', items: { type: 'string' } },
allowedOrigins: { type: 'array', items: { type: 'string' } },
exposedResponseHeaders: { type: 'array', items: { type: 'string' } },
maxAge: { type: 'integer', minimum: 0 },
},
additionalProperties: false,
},
],
},
id: {
anyOf: [
{ type: 'string' },
{ $ref: '#/definitions/awsCfImportLocallyResolvable' },
],
},
name: { type: 'string' },
description: { type: 'string' },
payload: { type: 'string' },
metrics: { type: 'boolean' },
useProviderTags: { const: true },
disableDefaultEndpoint: { type: 'boolean' },
shouldStartNameWithService: { type: 'boolean' },
},
additionalProperties: false,
},
iam: {
type: 'object',
properties: {
role: {
anyOf: [
{ $ref: '#/definitions/awsLambdaRole' },
{
type: 'object',
properties: {
name: {
type: 'string',
pattern: '^[A-Za-z0-9/_+=,.@-]{1,64}$',
},
path: {
type: 'string',
pattern: '(^\\/$)|(^\\/[\u0021-\u007f]+\\/$)',
},
managedPolicies: {
type: 'array',
items: { $ref: '#/definitions/awsArn' },
},
statements: { $ref: '#/definitions/awsIamPolicyStatements' },
permissionBoundary: { $ref: '#/definitions/awsArn' },
permissionsBoundary: { $ref: '#/definitions/awsArn' },
tags: { $ref: '#/definitions/awsResourceTags' },
},
additionalProperties: false,
},
],
},
deploymentRole: { $ref: '#/definitions/awsArn' },
},
additionalProperties: false,
},
iamManagedPolicies: { type: 'array', items: { $ref: '#/definitions/awsArn' } },
iamRoleStatements: { $ref: '#/definitions/awsIamPolicyStatements' },
ecr: {
type: 'object',
properties: {
scanOnPush: { type: 'boolean' },
images: {
type: 'object',
patternProperties: {
[imageNamePattern]: {
anyOf: [
{
type: 'object',
properties: {
uri: { $ref: '#/definitions/ecrImageUri' },
path: { type: 'string' },
file: { type: 'string' },
buildArgs: { type: 'object', additionalProperties: { type: 'string' } },
cacheFrom: { type: 'array', items: { type: 'string' } },
platform: { type: 'string' },
},
additionalProperties: false,
},
{
type: 'string',
},
],
},
},
},
},
required: ['images'],
additionalProperties: false,
},
kmsKeyArn: { $ref: '#/definitions/awsKmsArn' },
lambdaHashingVersion: {
type: 'string',
enum: ['20200924', '20201221'],
},
layers: { $ref: '#/definitions/awsLambdaLayers' },
logRetentionInDays: {
$ref: '#/definitions/awsLogRetentionInDays',
},
logDataProtectionPolicy: {
$ref: '#/definitions/awsLogDataProtectionPolicy',
},
logs: {
type: 'object',
properties: {
frameworkLambda: { type: 'boolean' },
httpApi: {
anyOf: [
{ type: 'boolean' },
{
type: 'object',
properties: {
format: { type: 'string' },
},
additionalProperties: false,
},
],
},
restApi: {
anyOf: [
{ type: 'boolean' },
{
type: 'object',
properties: {
accessLogging: { type: 'boolean' },
executionLogging: { type: 'boolean' },
format: { type: 'string' },
fullExecutionData: { type: 'boolean' },
level: { enum: ['INFO', 'ERROR'] },
role: { $ref: '#/definitions/awsArn' },
roleManagedExternally: { type: 'boolean' },
},
additionalProperties: false,
},
],
},
websocket: {
anyOf: [
{ type: 'boolean' },
{
type: 'object',
properties: {
accessLogging: { type: 'boolean' },
executionLogging: { type: 'boolean' },
format: { type: 'string' },
fullExecutionData: { type: 'boolean' },
level: { enum: ['INFO', 'ERROR'] },
},
additionalProperties: false,
},
],
},
},
},
memorySize: { $ref: '#/definitions/awsLambdaMemorySize' },
notificationArns: { type: 'array', items: { $ref: '#/definitions/awsArnString' } },
profile: { type: 'string' },
region: {
enum: [
'us-east-1',
'us-east-2',
'us-gov-east-1',
'us-gov-west-1',
'us-iso-east-1',
'us-iso-west-1',
'us-isob-east-1',
'us-west-1',
'us-west-2',
'af-south-1',
'ap-east-1',
'ap-northeast-1',
'ap-northeast-2',
'ap-northeast-3',
'ap-south-1',
'ap-south-2',
'ap-southeast-1',
'ap-southeast-2',
'ap-southeast-3',
'ap-southeast-4',
'ca-central-1',
'cn-north-1',
'cn-northwest-1',
'eu-central-1',
'eu-central-2',
'eu-north-1',
'eu-south-1',
'eu-south-2',
'eu-west-1',
'eu-west-2',
'eu-west-3',
'il-central-1',
'me-central-1',
'me-south-1',
'sa-east-1',
],
},
role: { $ref: '#/definitions/awsLambdaRole' },
rolePermissionsBoundary: { $ref: '#/definitions/awsArnString' },
rollbackConfiguration: {
type: 'object',
properties: {
RollbackTriggers: {
type: 'array',
items: {
type: 'object',
properties: {
Arn: { $ref: '#/definitions/awsArnString' },
Type: { const: 'AWS::CloudWatch::Alarm' },
},
additionalProperties: false,
required: ['Arn', 'Type'],
},
},
MonitoringTimeInMinutes: { type: 'integer', minimum: 0 },
},
additionalProperties: false,
},
runtime: { $ref: '#/definitions/awsLambdaRuntime' },
runtimeManagement: { $ref: '#/definitions/awsLambdaRuntimeManagement' },
deploymentMethod: { enum: ['changesets', 'direct'] },
s3: {
type: 'object',
additionalProperties: require('./package/compile/events/s3/config-schema'),
},
stage: { $ref: '#/definitions/stage' },
stackName: {
type: 'string',
pattern: '^[a-zA-Z][a-zA-Z0-9-]*$',
maxLength: 128,
},
stackParameters: {
type: 'array',
items: {
type: 'object',
properties: {
ParameterKey: { type: 'string' },
ParameterValue: { type: 'string' },
UsePreviousValue: { type: 'boolean' },
ResolvedValue: { type: 'string' },
},
additionalProperties: false,
},
},
stackPolicy: { $ref: '#/definitions/awsIamPolicyStatements' },
stackTags: { $ref: '#/definitions/awsResourceTags' },
tags: { $ref: '#/definitions/awsResourceTags' },
timeout: { $ref: '#/definitions/awsLambdaTimeout' },
tracing: {
type: 'object',
properties: {
apiGateway: { type: 'boolean' },
lambda: { $ref: '#/definitions/awsLambdaTracing' },
},
additionalProperties: false,
},
vpc: { $ref: '#/definitions/awsLambdaVpcConfig' },
vpcEndpointIds: { $ref: '#/definitions/awsCfArrayInstruction' },
versionFunctions: { $ref: '#/definitions/awsLambdaVersioning' },
websocket: {
type: 'object',
properties: {
useProviderTags: { type: 'boolean' },
},
additionalProperties: false,
},
websocketsApiName: { type: 'string' },
kinesis: {
type: 'object',
properties: {
consumerNamingMode: { const: 'serviceSpecific' },
},
additionalProperties: false,
},
websocketsApiRouteSelectionExpression: { type: 'string' },
websocketsDescription: { type: 'string' },
},
},
function: {
properties: {
architecture: { $ref: '#/definitions/awsLambdaArchitecture' },
awsKmsKeyArn: { $ref: '#/definitions/awsKmsArn' },
condition: { $ref: '#/definitions/awsResourceCondition' },
dependsOn: { $ref: '#/definitions/awsResourceDependsOn' },
description: { type: 'string', maxLength: 256 },
destinations: {
type: 'object',
properties: {
onSuccess: {
anyOf: [
{ type: 'string', minLength: 1 },
{
type: 'object',
properties: {
arn: { $ref: '#/definitions/awsCfFunction' },
type: { enum: ['function', 'sns', 'sqs', 'eventBus'] },
},
additionalProperties: false,
required: ['arn', 'type'],
},
],
},
onFailure: {
anyOf: [
{ type: 'string', minLength: 1 },
{
type: 'object',
properties: {
arn: { $ref: '#/definitions/awsCfFunction' },
type: { enum: ['function', 'sns', 'sqs', 'eventBus'] },
},
additionalProperties: false,
required: ['arn', 'type'],
},
],
},
},
additionalProperties: false,
},
disableLogs: { type: 'boolean' },
environment: { $ref: '#/definitions/awsLambdaEnvironment' },
ephemeralStorageSize: { type: 'integer', minimum: 512, maximum: 10240 },
fileSystemConfig: {
type: 'object',
properties: {
arn: {
anyOf: [
{
type: 'string',
pattern:
'^arn:aws[a-zA-Z-]*:elasticfilesystem:[a-z]{2}((-gov)|(-iso(b?)))?-[a-z]+-[1-9]{1}:[0-9]{12}:access-point/fsap-[a-f0-9]{17}$',
},
{ $ref: '#/definitions/awsCfGetAtt' },
{ $ref: '#/definitions/awsCfJoin' },
{ $ref: '#/definitions/awsCfImport' },
],
},
localMountPath: { type: 'string', pattern: '^/mnt/[a-zA-Z0-9-_.]+$' },
},
additionalProperties: false,
required: ['localMountPath', 'arn'],
},
handler: { type: 'string' },
image: {
anyOf: [
{ $ref: '#/definitions/ecrImageUri' },
{
type: 'string',
pattern: imageNamePattern,
},
{
type: 'object',
properties: {
name: {
type: 'string',
pattern: imageNamePattern,
},
uri: { $ref: '#/definitions/ecrImageUri' },
workingDirectory: {
type: 'string',
},
command: {
type: 'array',
items: {
type: 'string',
},
},
entryPoint: {
type: 'array',
items: {
type: 'string',
},
},
},
additionalProperties: false,
},
],
},
kmsKeyArn: { $ref: '#/definitions/awsKmsArn' },
snapStart: { type: 'boolean' },
layers: { $ref: '#/definitions/awsLambdaLayers' },
logRetentionInDays: {
$ref: '#/definitions/awsLogRetentionInDays',
},
logDataProtectionPolicy: {
$ref: '#/definitions/awsLogDataProtectionPolicy',
},
maximumEventAge: { type: 'integer', minimum: 60, maximum: 21600 },
maximumRetryAttempts: { type: 'integer', minimum: 0, maximum: 2 },
memorySize: { $ref: '#/definitions/awsLambdaMemorySize' },
onError: {
anyOf: [
{ type: 'string', pattern: '^arn:aws[a-z-]*:sns' },
{ $ref: '#/definitions/awsCfFunction' },
],
},
package: {
type: 'object',
properties: {
artifact: { type: 'string' },
exclude: { type: 'array', items: { type: 'string' } },
include: { type: 'array', items: { type: 'string' } },
individually: { type: 'boolean' },
patterns: { type: 'array', items: { type: 'string' } },
},
additionalProperties: false,
},
provisionedConcurrency: cfValue({ type: 'integer', minimum: 0 }),
reservedConcurrency: cfValue({ type: 'integer', minimum: 0 }),
role: { $ref: '#/definitions/awsLambdaRole' },
runtime: { $ref: '#/definitions/awsLambdaRuntime' },
runtimeManagement: { $ref: '#/definitions/awsLambdaRuntimeManagement' },
tags: { $ref: '#/definitions/awsResourceTags' },
timeout: { $ref: '#/definitions/awsLambdaTimeout' },
tracing: { $ref: '#/definitions/awsLambdaTracing' },
url: {
anyOf: [
{ type: 'boolean' },
{
type: 'object',
properties: {
authorizer: { type: 'string', enum: ['aws_iam'] },
cors: {
anyOf: [
{ type: 'boolean' },
{
type: 'object',
properties: {
allowCredentials: { type: 'boolean' },
allowedHeaders: {
type: 'array',
minItems: 1,
maxItems: 100,
items: { type: 'string' },
},
allowedMethods: {
type: 'array',
minItems: 1,
maxItems: 6,
items: { type: 'string' },
},
allowedOrigins: {
type: 'array',
minItems: 1,
maxItems: 100,
items: { type: 'string' },
},
exposedResponseHeaders: {
type: 'array',
minItems: 1,
maxItems: 100,
items: { type: 'string' },
},
maxAge: { type: 'integer', minimum: 0 },
},
additionalProperties: false,
},
],
},
invokeMode: {
type: 'string',
enum: ['BUFFERED', 'RESPONSE_STREAM'],
},
},
additionalProperties: false,
},
],
},
versionFunction: { $ref: '#/definitions/awsLambdaVersioning' },
vpc: { $ref: '#/definitions/awsLambdaVpcConfig' },
httpApi: {
type: 'object',
properties: {
payload: { $ref: '#/definitions/awsHttpApiPayload' },
},
additionalProperties: false,
},
},
additionalProperties: false,
},
layers: {
type: 'object',
additionalProperties: {
type: 'object',
properties: {
allowedAccounts: {
type: 'array',
items: {
type: 'string',
pattern: '^(\\d{12}|\\*|arn:(aws[a-zA-Z-]*):iam::\\d{12}:root)$',
},
},
compatibleArchitectures: {
type: 'array',
items: { $ref: '#/definitions/awsLambdaArchitecture' },
maxItems: 2,
},
compatibleRuntimes: {
type: 'array',
items: { $ref: '#/definitions/awsLambdaRuntime' },
maxItems: 15,
},
description: { type: 'string', maxLength: 256 },
licenseInfo: { type: 'string', maxLength: 512 },
name: {
type: 'string',
minLength: 1,
maxLength: 140,
pattern:
'^((arn:[a-zA-Z0-9-]+:lambda:[a-zA-Z0-9-]+:\\d{12}:layer:[a-zA-Z0-9-_]+)|[a-zA-Z0-9-_]+)$',
},
package: {
type: 'object',
properties: {
artifact: { type: 'string' },
exclude: { type: 'array', items: { type: 'string' } },
include: { type: 'array', items: { type: 'string' } },
patterns: { type: 'array', items: { type: 'string' } },
},
additionalProperties: false,
},
path: { type: 'string' },
retain: { type: 'boolean' },
},
additionalProperties: false,
},
},
resources: {
type: 'object',
properties: {
AWSTemplateFormatVersion: {
type: 'string',
},
Conditions: {
type: 'object',
},
Description: {
type: 'string',
},
Mappings: {
type: 'object',
},
Metadata: {
type: 'object',
},
// According to https://s3.amazonaws.com/cfn-resource-specifications-us-east-1-prod/schemas/2.15.0/all-spec.json
// `Outputs` is just an "object", though it seems like this is under-specifying that section a bit.
// See also https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html
Outputs: {
type: 'object',
},
Parameters: {
type: 'object',
},
// Not replicating the full JSON schema from https://s3.amazonaws.com/cfn-resource-specifications-us-east-1-prod/schemas/2.15.0/all-spec.json
// as that gets into the specifics for each resource type.
//
// The only required attribute is `Type`; `Properties` and other common attributes are optional.
// See also https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html
Resources: {
type: 'object',
properties: {
'Fn::Transform': {
type: 'object',
properties: {
Name: { type: 'string' },
Parameters: { type: 'object' },
},
required: ['Name'],
additionalProperties: false,
},
},
patternProperties: {
'^[a-zA-Z0-9]{1,255}$': {
type: 'object',
properties: {
Type: { type: 'string' },
Properties: { type: 'object' },
CreationPolicy: { type: 'object' },
DeletionPolicy: { type: 'string' },
DependsOn: { $ref: '#/definitions/awsResourceDependsOn' },
Metadata: { type: 'object' },
UpdatePolicy: { type: 'object' },
UpdateReplacePolicy: { type: 'string' },
Condition: { $ref: '#/definitions/awsResourceCondition' },
},
required: ['Type'],
additionalProperties: false,
},
},
additionalProperties: false,
},
Transform: {
type: 'array',
items: { type: 'string' },
},
extensions: {
type: 'object',
patternProperties: {
// names have the same restrictions as CloudFormation Resources section
'^[a-zA-Z0-9]{1,255}$': {
type: 'object',
// this lists the supported properties, other properties are "Not supported. An error will be thrown
// if you try to extend an unsupported attribute."
// this is different than the above schema for `Resources`, which allows the `Type` attribute.
// extensions are explicitly meant to extend the definition of existing resources.
properties: {
Properties: { type: 'object' },
CreationPolicy: { type: 'object' },
DeletionPolicy: { type: 'string' },
DependsOn: { $ref: '#/definitions/awsResourceDependsOn' },
Metadata: { type: 'object' },
UpdatePolicy: { type: 'object' },
UpdateReplacePolicy: { type: 'string' },
Condition: { $ref: '#/definitions/awsResourceCondition' },
},
additionalProperties: false,
},
},
additionalProperties: false,
},
},
additionalProperties: false,
},
});
}


I think there are three options how we can implement the types:

  1. copy the logic from @serverless/typescript and generate the types via json-schema-to-typescript from a npm script
  2. copy the generated index.d.ts file from @serverless/typescript
    • this would have the advantage than we could fix the type issues that come from the fact that JSON Schema cannot perfectly represent all possible TypeScript types
  3. extract the JSON schema into a separate files like serverless-schema.ts and use the package json-schema-to-ts to infer the types from it like FromSchema<typeof ServerlessSchema>
    • I tried this option, but it doesn't seem to work

@rageycomma
Copy link

rageycomma commented Apr 3, 2025

No worries, will use the package method then and close the PR there 👍 (PR is closed)

@zirkelc
Copy link
Contributor Author

zirkelc commented Apr 3, 2025

@mnapoli what do you think regarding the possible options to include the types? I would lean towards option 2) to simply copy/generate the types once so we can fix any current or future type issues.

@mnapoli
Copy link
Contributor

mnapoli commented Apr 3, 2025

@zirkelc agreed, that sounds simple 👍 I don't expect too many modifications to types, and we can always change back to a script later if needed.

@rageycomma
Copy link

@mnapoli what do you think regarding the possible options to include the types? I would lean towards option 2) to simply copy/generate the types once so we can fix any current or future type issues.

I don't mind fixing the script so it's more up to date.

@mnapoli
Copy link
Contributor

mnapoli commented Apr 3, 2025

Copying the types is simpler.

@zirkelc
Copy link
Contributor Author

zirkelc commented Apr 3, 2025

@mnapoli what do you think regarding the possible options to include the types? I would lean towards option 2) to simply copy/generate the types once so we can fix any current or future type issues.

I don't mind fixing the script so it's more up to date.

The problem is not the script itself, it's that JSON Schema cannot perfectly a describe TypeScript type. See this comment for more information: serverless/typescript#27 (comment)

index.d.ts does includes a mistake at the time being. This is due to the way json-schema-to-typescript handles additionalProperties definition from JSON Schema : while this keyword is used to enforce types on properties not defined in the original list of propertiess, TS enforces usage of index signature on all defined properties. This issue is currently under discussion in bcherny/json-schema-to-typescript#356 (comment) - would you want to help infer in the way of generating union types, your help would be greatly appreciated :)

So copying the type from the v3.38.0 commit should. be a good starting point: serverless/typescript@47fe5fa

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants