Security News
Fluent Assertions Faces Backlash After Abandoning Open Source Licensing
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
@serverless-contracts/core
Advanced tools
Generate and use type-safe contracts between your Serverless services.
Generate and use type-safe contracts between your Serverless services.
This package is part of the serverless-contracts project. See its documentation for more insights.
npm install @serverless-contracts/core
or if using yarn
yarn add @serverless-contracts/core
ApiGateway is an AWS service that makes it possible to trigger lambda functions through HTTP. There are two types of ApiGateways (for more details, see AWS documentation):
In our examples, we will use and HTTP API, but it is completely equivalent for REST APIs in terms of contracts.
Let's create our first HttpApi contract. First we will need to define the subschemas for each part of our contract:
id
serves to uniquely identify the contract among all stacks. Please note that this id MUST be unique among all stacks. Use a convention to ensure unicity.path
and the http method
which will trigger the lambdaintegrationType
: "httpApi"
or "restApi"
pathParametersSchema
, which must correspond to a Record<string, string>
queryStringParametersSchema
, which must respect the same constraintheadersSchema
, with the same constraintbodySchema
which is an unconstrained JSON schemaoutputSchema
in order to be able to validate the output of the lambda. It is also an unconstrained JSON schema.const pathParametersSchema = {
type: 'object',
properties: { userId: { type: 'string' }, pageNumber: { type: 'string' } },
required: ['userId', 'pageNumber'],
additionalProperties: false,
} as const;
const queryStringParametersSchema = {
type: 'object',
properties: { testId: { type: 'string' } },
required: ['testId'],
additionalProperties: false,
} as const;
const headersSchema = {
type: 'object',
properties: { myHeader: { type: 'string' } },
required: ['myHeader'],
} as const;
const bodySchema = {
type: 'object',
properties: { foo: { type: 'string' } },
required: ['foo'],
} as const;
const outputSchema = {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
},
required: ['id', 'name'],
} as const;
const myContract = new ApiGatewayContract({
id: 'my-unique-id',
path: '/users/{userId}',
method: 'GET',
integrationType: 'httpApi',
pathParametersSchema,
queryStringParametersSchema,
headersSchema,
bodySchema,
outputSchema,
});
Please note: In order to properly use Typescript's type inference:
as const
directive. For more information, see json-schema-to-tsundefined
in the contract. For example, in order to define a contract without headers, we need to create it with:const myContract = new ApiGatewayContract({
id: 'my-unique-id',
path: '/users/{userId}',
method: 'GET',
integrationType: 'httpApi',
pathParametersSchema,
queryStringParametersSchema,
headersSchema: undefined,
bodySchema,
outputSchema,
});
In the config.ts
file of our lambda, in the events
section, we need to use the generated trigger to define the path and method that will trigger the lambda:
export default {
environment: {},
handler: getHandlerPath(__dirname),
events: [myContract.trigger],
};
This will only output the method
and path
. However, if you need a more fine-grained configuration for your lambda (such as defining an authorizer), you can use the getCompleteTrigger
method.
export default {
environment: {},
handler: getHandlerPath(__dirname),
events: [myContract.getCompleteTrigger({ authorizer: 'arn::aws...' })],
};
The static typing helps here to prevent accidental overloading of path
and method
:
export default {
environment: {},
handler: getHandlerPath(__dirname),
events: [
myContract.getCompleteTrigger({
method: 'delete', // typescript will throw an error
}),
],
};
JSON Schemas are compatible with ajv
and @middy/validator
. You can use
myContract.inputSchema;
and
myContract.outputSchema;
in order to validate the input and/or the output of your lambda.
On the handler side, you can use the handler
method on the contract to correctly infer the input and output types from the schema.
const handler = myContract.handler(async event => {
event.pathParameters.userId; // will have type 'string'
event.toto; // will fail typing
event.pathParameters.toto; // will also fail
return { id: 'coucou', name: 'coucou' }; // also type-safe!
});
Simply call the axiosRequest
method on the schema.
await myContract.axiosRequest('https://my-site.com', {
pathParameters: { userId: '15', pageNumber: '45' },
headers: {
myHeader: 'hello',
},
queryStringParameters: { testId: 'plop' },
body: { foo: 'bar' },
});
All parameter types will be inferred from the schemas.
The return type will be an axios response of the type inferred from the outputSchema
.
If you do not wish to use axios
, you can use the type inference to generate request parameters with:
myContract.getRequestParameters({
pathParameters: { userId: '15', pageNumber: '45' },
headers: {
myHeader: 'hello',
},
queryStringParameters: { testId: 'plop' },
body: { foo: 'bar' },
});
and then use them in your request.
AWS CloudFormation is used by the Serverless Framework to manage resources. In certain cases, it may be necessary to share these resources between services. For example, authentication may be handled by a common authorizer, which should not be reimplemented on each service.
The CloudFormation import/export syntax is very specific, but only one information is truly useful: the name of the export. This must be unique across CloudFormation stacks and serves as a global variable name for the related value.
import { CloudFormationContract } from '@serverless-contracts/core';
const myCloudFormationContract = new CloudFormationContract({
name: 'mySuperExport',
});
Please note that here the export name is 'mySuperExport'
, and this value must be unique across stacks.
In the provider serverless.ts
, add an Outputs
key
const serverlessConfiguration = {
service: "my-provider-service",
provider: {...},
functions: {...},
resources: {
Resources: {...}
Outputs: {
MyAwesomeExport: myCloudFormationContract.exportValue({
description: 'A nice description',
value: { Ref: 'MyResourceLogicalId' },
}),
},
},
};
Please note:
Ref
function is here an example, the CloudFormationContract
is compatible with all CloudFormation functions. Please refer to the documentation for more examplesMyAwesomeExport
key has no importance and is not taken into account for the exportIn the consumer serverless.ts
, you can use the import with:
const serverlessConfiguration = {
service: 'my-consumer-service',
functions: {...},
custom: {
myImportedValue: myCloudFormationContract.importValue,
},
};
The resolved imported value will be available as ${self:custom.myImportedValue}
in your serverless files. See the Serverless variables documentation.
TODO
FAQs
Generate and use type-safe contracts between your Serverless services.
The npm package @serverless-contracts/core receives a total of 1 weekly downloads. As such, @serverless-contracts/core popularity was classified as not popular.
We found that @serverless-contracts/core demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.