set
plugins
set
plugins generate various kinds of resources for Architect projects. Resource setters are small, synchronous methods that can create many common kinds of resources, from HTTP routes, to environment variables, to custom runtimes.
Any Lambdas or resources defined by a set
plugin is treated by Architect as a first-class primitive. For example: if you build a plugin that creates a route with set.http
, Architect would treat that route as though the user had actually added it to their project manifest’s @http
pragma.
Caveats
Unlike workflow lifecycle plugins, which execute only when needed, all set
plugins must execute every time Architect (or any of its modules) run. Thus, they are expected to be synchronous and fast.
Because of this, we advise against such things as filesystem reads and writes from within set
plugins. Definitely do not start services, or run large, CPU intensive operations from within set
plugins.
Additionally, it is worth noting that set
plugins are among the very first things to run in any Architect execution. Because they are run before the rest of the project been enumerated, they are not passed a complete Inventory object. If your plugin requires knowledge of your project, please access the arc
property.
Plugin parameters
All set
methods are synchronous functions, and receive a single argument, which is an object containing the following properties:
Property | Type | Description |
---|---|---|
arc |
object | Raw Architect project object |
inventory |
object | Partial Inventory object |
Valid returns
All set
methods can return a single resource object, and those that create resources (like set.http
) can return an array of resource objects.
There is no limit to the number of resources a set
plugin can return, however AWS does have limits on the number of resources in a CloudFormation deployment (and a hard cap on the size of a given CloudFormation document).
Generated resources that require a src
property accept an absolute or relative file path. Additionally, file paths will be automatically platform normalized (so you do not have to use path.join()
for other platforms if you’re publicly publishing your plugin).
By default, Lambdas created by the set
API are assumed to run the latest version of Node.js (unless configured otherwise)
Pragmas
set.events
Register async events (as in the @events
pragma). Return a single object or an array of objects with the following properties:
Property | Type | Description |
---|---|---|
name |
string | Event name (follows @events syntax) |
src |
string | Absolute or relative file path to the handler |
required |
boolean | Fail if plugin conflicts with project manifest (defaults to false ) |
Example:
// Return a single async event
module.exports = {
set: {
events ({ arc, inventory }) {
return {
name: 'an-async-event',
src: __dirname + '/handler' // Points to a handler dir inside the plugin
}
}
}
}
set.http
Register HTTP routes (as in the [@http
][htp] pragma). Return a single object or an array of objects with the following properties:
Property | Type | Description |
---|---|---|
method |
string | HTTP method (follows @http syntax) |
path |
string | HTTP path (follows @http syntax) |
src |
string | Absolute or relative file path to the handler |
required |
boolean | Fail if plugin conflicts with project manifest (defaults to false ) |
Example:
// Return multiple HTTP routes
module.exports = {
set: {
http ({ arc, inventory }) {
let src = __dirname + '/handler'
// Multiple Lambdas can use the same handler
return [
{ method: 'get', path: '/foo', src },
{ method: 'put', name: '/bar', src }
]
}
}
}
set.proxy
Set URLs for API Gateway to forward all requests by default; individual routes can be overridden by @http
/ set.http
. Return a single object with the following properties:
Property | Type | Description |
---|---|---|
testing |
string | URL to forward requests to from the testing environment |
staging |
string | … the same, but for the staging environment |
production |
string | … the same, but for the production environment |
Example:
module.exports = {
set: {
proxy ({ arc, inventory }) {
return {
testing: 'https://testing-url.com',
staging: 'https://staging-url.com',
production: 'https://production-url.com',
}
}
}
}
set.queues
Register async event queues (as in the @queues
pragma). Return a single object or an array of objects with the following properties:
Property | Type | Description |
---|---|---|
name |
string | Event name (follows @queues syntax) |
src |
string | Absolute or relative file path to the handler |
required |
boolean | Fail if plugin conflicts with project manifest (defaults to false ) |
Example:
// Return a single async event queue
module.exports = {
set: {
queues ({ arc, inventory }) {
return {
name: 'a-queue',
src: __dirname + '/handler' // Points to a handler dir inside the plugin
}
}
}
}
set.scheduled
Register scheduled event (as in the @scheduled
pragma). Return a single object or an array of objects with the following properties:
Property | Type | Description |
---|---|---|
name |
string | Event name (follows @scheduled syntax) |
rate |
string | Rate expression, cannot be used with cron property |
cron |
string | Cron expression, cannot be used with rate property |
src |
string | Absolute or relative file path to the handler |
required |
boolean | Fail if plugin conflicts with project manifest (defaults to false ) |
Note: unlike in
@scheduled
pragma use,rate
+cron
properties should not be returned in parenthesis.
Example:
// Return two scheduled events: one using rate syntax, and one using cron syntax
module.exports = {
set: {
scheduled ({ arc, inventory }) {
let src = __dirname + '/handler'
return [
{
name: 'scheduled-using-rate',
rate: '1 day',
src,
},
{
name: 'scheduled-using-cron',
cron: '15 10 * * ? *',
src,
}
]
}
}
}
set.shared
Set a custom source path for Architect’s code sharing system (@shared
). Return a single object with the following property:
Property | Type | Description |
---|---|---|
src |
string | Absolute or relative file path to the shared code path |
required |
boolean | Enforce the existence of src folder (defaults to false ) |
Example:
module.exports = {
set: {
shared ({ arc, inventory }) {
return {
src: __dirname + '/shared-libs'
}
}
}
}
set.static
Modify settings for static asset handling. Return a single object with the following properties:
Property | Type | Description |
---|---|---|
compression |
string | Enable static asset compression; true or br compresses with brotli, or gzip compresses with gzip |
fingerprint |
boolean | Enable Architect’s static asset fingerprinting; also accepts external (string) to assume assets have filenames fingerprinted by another tool or system |
folder |
string | Relative file path of static asset dir (defaults to public ) |
ignore |
array | File names or paths within folder to ignore during deployment |
prefix |
string | Path prefix for publishing assets to S3 |
prune |
boolean | Enable Architect to prune S3 files not found within folder |
spa |
boolean | Enable single page app (SPA) mode (defaults to false ) |
Example:
module.exports = {
set: {
static ({ arc, inventory }) {
return {
fingerprint: true,
folder: 'static-assets',
}
}
}
}
set.tables
Register DynamoDB tables (as in the @tables
) pragma). Return a single object or an array of objects with the following properties:
Property | Type | Required | Description |
---|---|---|---|
name |
string | Yes | Table name (follows @tables syntax) |
partitionKey |
string | Yes | Partition key name |
partitionKeyType |
string | Yes | Partition key type (string or number ) |
sortKey |
string | No | Sort key name |
sortKeyType |
string | No | Sort key type (string or number ) |
stream |
boolean | No | Enable DynamoDB stream (see @tables-streams ) |
ttl |
string | No | Time-to-live timestamp column to expire records |
encrypt |
boolean | No | Enable server-side encryption with AWS KMS |
pitr |
boolean | No | Enable point-in-time recovery |
Example:
// Return a single table
module.exports = {
set: {
tables ({ arc, inventory }) {
return {
name: 'a-table',
partitionKey: 'id',
partitionKeyType: 'string',
// These are all optional
sortKey: 'ts',
sortKeyType: 'number',
pitr: true,
}
}
}
}
set['tables-indexes']
Register DynamoDB table indexes (as in the @tables-indexes
) pragma). Return a single object or an array of objects with the following properties:
Property | Type | Required | Description |
---|---|---|---|
name |
string | Yes | Table name (follows @tables syntax) |
partitionKey |
string | Yes | Partition key name |
partitionKeyType |
string | Yes | Partition key type (string or number ) |
sortKey |
string | No | Sort key name |
sortKeyType |
string | No | Sort key type (string or number ) |
indexName |
boolean | No | Custom index name |
Example:
// Return a single table index
module.exports = {
set: {
'tables-indexes': ({ arc, inventory }) => {
return {
name: 'a-table',
partitionKey: 'secondary-index',
partitionKeyType: 'string',
indexName: 'my-custom-index-name', // Optional!
}
}
}
}
set['tables-streams']
Register DynamoDB event streams (as in the @tables-streams
) pragma). Return a single object or an array of objects with the following properties:
Property | Type | Description |
---|---|---|
name |
string | Event name (follows @tables-streams syntax) |
table |
string | Logical DynamoDB table name (as in your project manifest) |
src |
string | Absolute or relative file path to the handler |
required |
boolean | Fail if plugin conflicts with project manifest (defaults to false ) |
Example:
// Return a single table stream
module.exports = {
set: {
'tables-streams': ({ arc, inventory }) => {
return {
name: 'a-table-stream-event',
table: 'my-logical-table-name',
src: __dirname + '/handler' // Points to a handler dir inside the plugin
}
}
}
}
set.views
Set a custom source path for Architect’s frontend views code sharing system (@views
). Return a single object with the following property:
Property | Type | Description |
---|---|---|
src |
string | Absolute or relative file path to the views code path |
required |
boolean | Enforce the existence of src folder (defaults to false ) |
Example:
module.exports = {
set: {
views ({ arc, inventory }) {
return {
src: 'app/views'
}
}
}
}
set.ws
Register WebSocket routes (as in the @ws
pragma). Return a single object or an array of objects with the following properties:
Property | Type | Description |
---|---|---|
name |
string | Route name (follows @ws syntax) |
src |
string | Absolute or relative file path to the handler |
required |
boolean | Fail if plugin conflicts with project manifest (defaults to false ) |
Note: WebSockets is required to have three default routes (
$connect
,$disconnect
,$default
), which Architect populates with the addition of the@ws
pragma. If the consumer of your plugin does not specify@ws
in their manifest, usingset.ws
will infer it for them; you should not attempt to return any of the default routes in yourset.ws
plugin.
Example:
// Return a single WebSocket route
module.exports = {
set: {
ws ({ arc, inventory }) {
return {
name: 'refresh',
src: __dirname + '/handler' // Points to a handler dir inside the plugin
}
}
}
}
Resources
set.env
Register environment variables for all Lambdas. To create an environment variable for all Lambdas, return an object with names and values. To create an environment variable specific to Architect’s built in testing
, staging
, and production
environments, return an object containing one or more of those properties, each containing an object with names and value.
Note: if an object or array is passed as a value, the
set.env
API will automatically JSON-serialize it into your environment variable.
Examples:
// Return an environment variable for all Lambdas
module.exports = {
set: {
env ({ arc, inventory }) {
return {
API_SECRET: process.env.API_SECRET // Handy for exporting secrets in CI/CD
}
}
}
}
// Return a different environment variables for different stages
module.exports = {
set: {
env ({ arc, inventory }) {
return {
testing: {
API_SECRET: 'sample-key'
},
staging: {
API_SECRET: process.env.API_SECRET
},
production: {
API_SECRET: process.env.API_SECRET
},
}
}
}
}
set.customLambdas
Register bare Lambdas without a pre-associated event source. set.customLambdas
pairs nicely with deploy.start
, where you can customize a custom Lambda’s event source in CloudFormation. Return a single object or an array of objects with the following properties:
Property | Type | Description |
---|---|---|
name |
string | Bare Lambda name |
src |
string | Absolute or relative file path to the handler |
Example:
// Return a single async event
module.exports = { set: {
customLambdas: ({ arc, inventory }) => {
return {
name: 'a-custom-lambda',
src: __dirname + '/handler' // Points to a handler dir inside the plugin
}
}
} }
set.runtimes
Register custom runtimes for Lambdas. Return a single object or an array of objects. Each runtime type has its own set of requirements; transpiled
and compiled
are currently supported, interpeted
is coming soon.
No matter which runtime is designated, the source for each Lambda lives within the typical Architect project structure, whether using Architect conventions (e.g. src/http/$method-$path
) or custom src
paths.
Note: Architect’s built in shared code affordances are permanently disabled for transpiled and compiled handlers and their build artifacts.
transpiled
Lambdas with a transpiled
runtime (such as those using TypeScript) undergo a transpilation step that publishes build artifacts to separate dist folder; artifacts in the dist folder are, as one would expect, composed in a corresponding interpreted language (e.g. JavaScript).
For example, using Architect TypeScript, the get /foo
handler is authored in src/http/get-foo/index.ts
, and automatically transpiled to (and run / deployed from) .build/http/get-foo/index.js
.
A plugin setting a transpiled
runtime must return an object (or array of objects) containing the following properties:
Property | Type | Required | Description |
---|---|---|---|
name |
string | Yes | Custom runtime name |
type |
string | Yes | Must be transpiled |
baseRuntime |
string | Yes | Lambda runtime identifier or alias |
build |
string | No | Relative build dir path; defaults to build |
Note: Architect’s built in shared code affordances are permanently disabled for transpiled output.
Example:
// Enable a custom build directory with a custom runtime pragma (`@typescript`)
module.exports = {
set: {
runtimes ({ arc, inventory }) {
let { arc } = inventory.inv._project
let build = '.build'
if (arc.typescript) {
arc.typescript.forEach(s => {
if (Array.isArray(s) && s[0] === 'build' && typeof s[1] === 'string') {
build = s[1]
}
})
}
return {
name: 'typescript',
type: 'transpiled',
baseRuntime: 'nodejs18.x',
build,
}
}
}
}
compiled
Lambdas with a compiled
runtime (such as those using Rust, Go, Java, etc.) undergo a compilation step that publishes build artifacts to separate dist folder. Per Lambda conventions, this folder is expected to contain a binary artifact called bootstrap
that will serve as the Lambda handler (reference).
Note: When running in Sandbox, your plugin should account for compiling the
bootstrap
for your local system architecture; likewise, during deployment the plugin should ensure the compile target is Amazon Linux 2 (akaAL2
). A live AWS deployment is indicated by ainventory.inv._arc.deployStage
value.
For example, using Architect Rust, the get /foo
handler is authored in src/http/get-foo/src/main.rs
, and automatically compiled to (and run / deployed from) .build/http/get-foo/debug/bootstrap
. Remember, during production deploys that dist path may automatically change per your compiler’s behavior.
Because cargo
’s build tooling automatically compiles the bootstrap
file to a subfolder called debug
, this plugin makes use of the buildSubpath
property. (Without it, Architect would look for (and fail to find) your handler at .build/http/get-foo/bootstrap
.)
A plugin setting a compiled
runtime must return an object (or array of objects) containing the following properties:
Property | Type | Required | Description |
---|---|---|---|
name |
string | Yes | Custom runtime name |
type |
string | Yes | Must be compiled |
baseRuntime |
string | No | Lambda runtime identifier or alias |
build |
string | No | Relative build dir path; defaults to build |
buildSubpath |
string | No | Optional subpath conforming to compiler output |
Example:
// Set `buildSubpath` based on whether the compiler is building locally or for AWS release
let { join } = require('path')
module.exports = {
set: {
runtimes ({ arc, inventory }) {
let { deployStage } = inventory.inv._arc
let buildSubpath = deployStage ? 'release' : 'debug'
return {
name: 'rust',
type: 'compiled',
build,
buildSubpath,
}
}
}
}
Advanced usage
lambda.config
Lambdas created by set
plugins are treated like any other Lambda in the system, which means they are subject to project defaults. This may be convenient: if the project using your Node.js handler created by a set
plugin is also by default Node.js, you don’t have to do anything.
However, if you are publishing your project publicly, you cannot assume all consumers of your plugin are running the same runtime as you. In this case, and other cases where greater customization and specificity is required, you should include a config
property in your set
Lambdas.
Any of the set
APIs that create Lambdas (events
, http
, customLambdas
, etc.) accept an optional config
object with named properties (and values) that are the same as those found in function config.
These include: runtime
, memory
, timeout
, concurrency
, architecture
, and more, and are subject to the same limitations as any other Lambda (e.g. if specifying layers
, only 5 may be specified, and they must be in the same region as the app is deployed).
Example:
// Returning this event Lambda assumes user project defaults > Architect defaults
// If the project specifies `@aws runtime python3.9`, and your handler is JS, it will not run
module.exports = {
set: {
events ({ arc, inventory }) {
return {
name: 'an-async-event',
src: __dirname + '/handler'
}
}
}
}
// Returning a `config` property provides control over the configuration of the returned Lambda
module.exports = {
set: {
events ({ arc, inventory }) {
return {
name: 'an-async-event',
src: __dirname + '/handler',
config: {
runtime: 'nodejs14.x',
memory: 3008, // in MB
timeout: 10, // in seconds
}
}
}
}
}
Where set
Lambdas can live
Like those created by modifying a project manifest, Lambdas (and their handlers) specified by set
plugins can live in the user’s project. We’ll call those userland Lambdas.
However, set
plugins can also point to prepackaged functions and live inside a published plugin that do not allow for customization, because they’re intended to do a specific job on behalf of the plugin consumer. We’ll call those pluginland Lambdas.
Let’s take a look at some examples of how this might work.
Userland Lambdas
Say you’re writing a plugin called local-s3
, which attaches Lambdas to specific S3 events. Your users need to be able to define and maintain the logic executed by S3 event Lambdas provisioned by your plugin. You may ask your user to update their project to define some handlers in a new custom pragma (@local-s3
):
@plugins
local-s3
@local-s3
create
update
delete
Since these Lambdas live in userland, set.customLambdas
method might look something like this:
module.exports = {
set: {
customLambdas ({ arc, inventory }) {
let localS3 = arc['local-s3']
if (!localS3 || !Array.isArray(localS3)) return
// Create an abritrary number of plugins from the Arc manifest
let lambdas = localS3.map((item) => {
let name = item[0]
return {
name,
src: `src/local-s3/${name}`
}
})
return lambdas
}
}
}
This approach puts the Lambdas squarely in the realm of your plugin consumers, and empowers them to make customize the resources you’re managing with the plugin.
Pluginland Lambdas
Now let’s say you’re writing a plugin called autobundle
to accomplish a specific task: automatically bundling JS from your project’s views directory at get /_bundle/:entry
. Your users expect to consume this plugin like any other dependency, having it dropped right in and fully maintained by the plugin author.
Assuming you published your project as arc-plugin-autobundle
, you might want your set
plugin to look something like this:
// node_modules/arc-plugin-autobundle/index.js
module.exports = {
set: {
http ({ arc, inventory }) {
return {
method: 'get',
path: 'get /_bundle/:entry',
// Assuming a handler at `node_modules/arc-plugin-autobundle/handler/index.js`
src: __dirname + '/handler'
}
}
}
}
Returning the Lambda above, Architect will look for your autobundle
Lambda handler at node_modules/arc-plugin-autobundle/handler/index.js
. Should your handler have its own dependencies, you must declare them in your plugin’s package.json
file.