@certik/serverless-api 1.0.12 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +0 -1
  3. package/dist/deploy.d.ts +20 -0
  4. package/dist/deploy.d.ts.map +1 -0
  5. package/dist/deploy.js +17 -0
  6. package/dist/dev.d.ts +8 -0
  7. package/dist/dev.d.ts.map +1 -0
  8. package/dist/dev.js +8 -0
  9. package/dist/entrypoint.d.ts +26 -0
  10. package/dist/entrypoint.d.ts.map +1 -0
  11. package/dist/entrypoint.js +78 -0
  12. package/dist/handler.d.ts +2 -0
  13. package/dist/handler.d.ts.map +1 -0
  14. package/{index.js → dist/handler.js} +1 -0
  15. package/dist/index.d.ts +14 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +13 -0
  18. package/dist/lib/app.d.ts +39 -0
  19. package/dist/lib/app.d.ts.map +1 -0
  20. package/dist/lib/app.js +139 -0
  21. package/dist/lib/const.d.ts +12 -0
  22. package/dist/lib/const.d.ts.map +1 -0
  23. package/dist/lib/const.js +12 -0
  24. package/dist/lib/cors.d.ts +19 -0
  25. package/dist/lib/cors.d.ts.map +1 -0
  26. package/dist/lib/cors.js +38 -0
  27. package/dist/lib/dev.d.ts +38 -0
  28. package/dist/lib/dev.d.ts.map +1 -0
  29. package/dist/lib/dev.js +78 -0
  30. package/dist/lib/domain.d.ts +16 -0
  31. package/dist/lib/domain.d.ts.map +1 -0
  32. package/dist/lib/domain.js +19 -0
  33. package/dist/lib/env.d.ts +14 -0
  34. package/dist/lib/env.d.ts.map +1 -0
  35. package/dist/lib/env.js +37 -0
  36. package/dist/lib/pack.d.ts +29 -0
  37. package/dist/lib/pack.d.ts.map +1 -0
  38. package/dist/lib/pack.js +93 -0
  39. package/dist/lib/path.d.ts +22 -0
  40. package/dist/lib/path.d.ts.map +1 -0
  41. package/dist/lib/path.js +26 -0
  42. package/dist/lib/routes.d.ts +32 -0
  43. package/dist/lib/routes.d.ts.map +1 -0
  44. package/dist/lib/routes.js +76 -0
  45. package/dist/lib/types.d.ts +125 -0
  46. package/dist/lib/types.d.ts.map +1 -0
  47. package/dist/lib/types.js +7 -0
  48. package/package.json +58 -44
  49. package/dev.js +0 -4
  50. package/entrypoint.js +0 -68
  51. package/lib/app.js +0 -162
  52. package/lib/const.js +0 -4
  53. package/lib/cors.js +0 -21
  54. package/lib/dev.js +0 -54
  55. package/lib/env.js +0 -24
  56. package/lib/pack.js +0 -83
  57. package/lib/path.js +0 -6
  58. package/lib/routes.js +0 -39
package/entrypoint.js DELETED
@@ -1,68 +0,0 @@
1
- /*
2
- * All handlers will share this same entrypoint
3
- * the entrypoint will be responsible for routing the request to the correct handler
4
- * and also for applying middlewares
5
- * */
6
- import pathModule from "node:path";
7
- import { getRoutes } from "./lib/routes.js";
8
-
9
- export const handler = async (event, context) => {
10
- const routes = await getRoutes(pathModule.resolve("routes"));
11
-
12
- const currentRoute = routes.find((route) => {
13
- return (
14
- route.path === event.path &&
15
- route.method.toLowerCase() === event.httpMethod.toLowerCase()
16
- );
17
- });
18
-
19
- if (!currentRoute) {
20
- return {
21
- statusCode: 404,
22
- body: "Not Found",
23
- };
24
- }
25
-
26
- const extraHeaders = {};
27
-
28
- if (currentRoute.corsOrigin) {
29
- extraHeaders["Access-Control-Allow-Origin"] = currentRoute.corsOrigin;
30
- }
31
-
32
- const response = await Promise.race([
33
- (async () => {
34
- let body = event.body ? event.body : null;
35
-
36
- if (event.isBase64Encoded && body) {
37
- body = Buffer.from(body, "base64").toString("utf-8");
38
- }
39
-
40
- const rp = await currentRoute.handler(
41
- {
42
- ...event,
43
- body,
44
- isBase64Encoded: false,
45
- },
46
- context,
47
- );
48
-
49
- return rp;
50
- })(),
51
- new Promise(function timeoutResponse(resolve) {
52
- setTimeout(() => {
53
- resolve({
54
- statusCode: 408,
55
- body: `Request Timeout after ${currentRoute.timeout} seconds`,
56
- });
57
- }, currentRoute.timeout * 1000);
58
- }),
59
- ]);
60
-
61
- return {
62
- ...response,
63
- headers: {
64
- ...response.headers,
65
- ...extraHeaders,
66
- },
67
- };
68
- };
package/lib/app.js DELETED
@@ -1,162 +0,0 @@
1
- import * as pulumi from "@pulumi/pulumi";
2
- import * as aws from "@pulumi/aws";
3
- import * as apigateway from "@pulumi/aws-apigateway";
4
- import { readFile } from "node:fs/promises";
5
- import { isReservedEnvName } from "./env.js";
6
- import { packAndZip } from "./pack.js";
7
-
8
- export function getWildcardCertificateName(domainName) {
9
- const parts = domainName.split(".");
10
- return ["*", ...parts.slice(1)].join(".");
11
- }
12
-
13
- export async function createPulumiAPIApp({
14
- routes,
15
- bundleDevDependencies = false,
16
- }) {
17
- const { zipFileName, zipFileHash } = await packAndZip({
18
- bundleDevDependencies,
19
- });
20
-
21
- const projectName = pulumi.getProject();
22
- const stackName = pulumi.getStack();
23
-
24
- const namespace = `wf-${stackName}-${projectName}-${stackName}`;
25
-
26
- const config = new pulumi.Config();
27
- const sourceBucketName = config.require("sourceBucket");
28
- const hostedZone = config.require("hostedZone");
29
- const runtime = config.require("runtime");
30
- // we can use subdomain config option to specify a custom name
31
- let subdomain = config.get("subdomain");
32
- if (!subdomain) {
33
- // default subdomain will be generated from pulumi project name
34
- subdomain = projectName.startsWith("api-")
35
- ? projectName.slice(4)
36
- : projectName;
37
- }
38
- const domainName = `${subdomain}.${hostedZone}`;
39
- // this bases on a naming convention
40
- const wildcardCertificateName = getWildcardCertificateName(domainName);
41
- const lambdaRoleName = config.get("lambdaRole");
42
-
43
- const role = aws.iam.getRole({ name: lambdaRoleName });
44
-
45
- const sourceBucket = aws.s3.getBucket({ bucket: sourceBucketName });
46
- const sourceKey = `${namespace}/sourcecode.zip`;
47
-
48
- // write to s3 bucket
49
- // eslint-disable-next-line no-new
50
- const sourceS3File = new aws.s3.BucketObject(`${namespace}-s3-sourcecode`, {
51
- bucket: sourceBucket.then((b) => b.id),
52
- key: sourceKey,
53
- source: new pulumi.asset.FileArchive(zipFileName),
54
- sourceHash: zipFileHash,
55
- });
56
-
57
- // Create an API endpoint
58
- const site = new apigateway.RestAPI(namespace, {
59
- routes: routes.map(
60
- ({ name, path, method, timeout, memorySize, environmentVariables }) => {
61
- const variables = environmentVariables
62
- ? environmentVariables.reduce((acc, envName) => {
63
- if (isReservedEnvName(envName)) {
64
- // for reserved env names, we don't need to set them
65
- return acc;
66
- }
67
-
68
- if (process.env[envName]) {
69
- acc[envName] = process.env[envName];
70
- } else {
71
- console.warn(
72
- `Environment variable not set: process.env.${envName}`,
73
- );
74
- }
75
-
76
- return acc;
77
- }, {})
78
- : {};
79
-
80
- return {
81
- path,
82
- method,
83
- eventHandler: new aws.lambda.Function(
84
- // maximum length of lambda function name is 64, pulumi will append 8 random characters to the name
85
- `${namespace}-${name}`.slice(0, 56),
86
- {
87
- name: `${namespace}-${name}`.slice(0, 56),
88
- role: role.then((r) => r.arn),
89
- runtime,
90
- s3Bucket: sourceBucketName,
91
- s3Key: sourceS3File.key, // wait until the source code is uploaded
92
- handler: "entrypoint.handler",
93
- sourceCodeHash: zipFileHash,
94
- timeout: timeout + 10, // add a few more second for buffer so entrypoint can handle 408 case
95
- memorySize,
96
- environment: { variables },
97
- },
98
- ),
99
- };
100
- },
101
- ),
102
- });
103
-
104
- // Get certificate for desired endpoint
105
- const certificate = await aws.acm.getCertificate({
106
- domain: wildcardCertificateName,
107
- statuses: ["ISSUED"],
108
- });
109
-
110
- // Get the zone we're going to use
111
- const zone = await aws.route53.getZone({ name: hostedZone }, { async: true });
112
-
113
- // API Gateway requires register the domain with it first
114
- const domain = new aws.apigateway.DomainName(namespace, {
115
- certificateArn: certificate.arn,
116
- domainName,
117
- });
118
-
119
- // eslint-disable-next-line no-new
120
- new aws.apigateway.BasePathMapping(namespace, {
121
- restApi: site.api,
122
- stageName: site.stage.stageName,
123
- domainName: domain.domainName,
124
- });
125
-
126
- // a DNS record to point at the API Gateway
127
- // eslint-disable-next-line no-new
128
- new aws.route53.Record(namespace, {
129
- type: "A",
130
- zoneId: zone.zoneId,
131
- name: domainName,
132
- aliases: [
133
- {
134
- // APIGateway provides it's own CloudFront distribution we can point at...
135
- name: domain.cloudfrontDomainName,
136
- zoneId: domain.cloudfrontZoneId,
137
- evaluateTargetHealth: false,
138
- },
139
- ],
140
- });
141
-
142
- const routesInfo = routes.reduce((acc, { name, method, timeout, path }) => {
143
- acc[name] = {
144
- method,
145
- timeout,
146
- url: `https://${domainName}${path}`,
147
- };
148
- return acc;
149
- }, {});
150
-
151
- const readme = (await readFile("./README.md")).toString();
152
-
153
- return {
154
- zipFileName,
155
- zipFileHash,
156
- routes: routesInfo,
157
- runtime,
158
- hostedZone,
159
- domainName,
160
- readme,
161
- };
162
- }
package/lib/const.js DELETED
@@ -1,4 +0,0 @@
1
- export const DEFAULT_TIMEOUT = 30; // seconds
2
- export const MAX_TIMEOUT = 90; // seconds
3
-
4
- export const DEFAULT_MEMORY_SIZE = 128; // MB
package/lib/cors.js DELETED
@@ -1,21 +0,0 @@
1
- export function corsPreflight(name, path) {
2
- return {
3
- name,
4
- path,
5
- method: "OPTIONS",
6
- timeout: 30,
7
- memorySize: 128,
8
- handler: async () => {
9
- return {
10
- body: "",
11
- statusCode: 200,
12
- headers: {
13
- "Access-Control-Allow-Origin": "*",
14
- "Access-Control-Allow-Methods":
15
- "GET, POST, OPTIONS, PUT, PATCH, DELETE",
16
- "Access-Control-Allow-Headers": "*",
17
- },
18
- };
19
- },
20
- };
21
- }
package/lib/dev.js DELETED
@@ -1,54 +0,0 @@
1
- import express from "express";
2
-
3
- export function mapRequestToEvent(req) {
4
- // a custom simulator
5
- return {
6
- resource: req.path,
7
- path: req.path,
8
- httpMethod: req.method,
9
- requestContext: {
10
- resourcePath: req.path,
11
- httpMethod: req.method,
12
- path: req.path,
13
- },
14
- headers: req.headers,
15
- multiValueHeaders: {},
16
- queryStringParameters: req.query,
17
- body: Buffer.isBuffer(req.body) ? req.body.toString("base64") : "",
18
- isBase64Encoded: true,
19
- };
20
- }
21
-
22
- export async function startLocalApp({ routes, handler }) {
23
- const app = express();
24
-
25
- app.use(
26
- express.raw({
27
- inflate: true,
28
- limit: "10mb",
29
- type: () => true, // this matches all content types
30
- }),
31
- );
32
-
33
- routes.forEach(({ path, method }) => {
34
- const m = method || "GET";
35
- console.log(`registering ${m} ${path}`);
36
- const callback = async (req, res) => {
37
- const context = {};
38
- const result = await handler(mapRequestToEvent(req), context);
39
-
40
- for (const [key, value] of Object.entries(result.headers || {})) {
41
- res.set(key, value);
42
- }
43
- res.status(result.statusCode);
44
- res.send(result.body);
45
- };
46
- app[m.toLowerCase()](path, callback);
47
- // keep compatibility with serverless-offline
48
- app[m.toLowerCase()](`/dev${path}`, callback);
49
- });
50
-
51
- console.log("listening on http://localhost:4000");
52
-
53
- app.listen(4000);
54
- }
package/lib/env.js DELETED
@@ -1,24 +0,0 @@
1
- const RESERVED_ENV_NAMES = [
2
- "_HANDLER",
3
- "_X_AMZN_TRACE_ID",
4
- "AWS_DEFAULT_REGION",
5
- "AWS_REGION",
6
- "AWS_EXECUTION_ENV",
7
- "AWS_LAMBDA_FUNCTION_NAME",
8
- "AWS_LAMBDA_FUNCTION_MEMORY_SIZE",
9
- "AWS_LAMBDA_FUNCTION_VERSION",
10
- "AWS_LAMBDA_INITIALIZATION_TYPE",
11
- "AWS_LAMBDA_LOG_GROUP_NAME",
12
- "AWS_LAMBDA_LOG_STREAM_NAME",
13
- "AWS_ACCESS_KEY",
14
- "AWS_ACCESS_KEY_ID",
15
- "AWS_SECRET_ACCESS_KEY",
16
- "AWS_SESSION_TOKEN",
17
- "AWS_LAMBDA_RUNTIME_API",
18
- "LAMBDA_TASK_ROOT",
19
- "LAMBDA_RUNTIME_DIR",
20
- ];
21
-
22
- export function isReservedEnvName(name) {
23
- return RESERVED_ENV_NAMES.includes(name);
24
- }
package/lib/pack.js DELETED
@@ -1,83 +0,0 @@
1
- import fsModule from "node:fs";
2
- import pathModule from "node:path";
3
- import cryptoModule from "node:crypto";
4
- import { promisify } from "node:util";
5
- import { exec } from "node:child_process";
6
- import archiver from "archiver";
7
-
8
- const execPromise = promisify(exec);
9
- const rmPromise = promisify(fsModule.rm);
10
- const mkdirPromise = promisify(fsModule.mkdir);
11
- const readdirPromise = promisify(fsModule.readdir);
12
- const cpPromise = promisify(fsModule.cp);
13
-
14
- const PACK_DIR = ".serverless-pack";
15
- const ZIP_FILE = "all-code.zip";
16
- const EXCLUDED_FILES = [
17
- "node_modules",
18
- "test",
19
- "bun.lockb",
20
- ZIP_FILE,
21
- PACK_DIR,
22
- ];
23
-
24
- function zipDirectory(sourceDir, outPath) {
25
- const archive = archiver("zip", { zlib: { level: 9 } });
26
- const stream = fsModule.createWriteStream(outPath);
27
-
28
- return new Promise((resolve, reject) => {
29
- archive
30
- .directory(sourceDir, false)
31
- .on("error", (err) => reject(err))
32
- .pipe(stream);
33
-
34
- stream.on("close", () => resolve());
35
- archive.finalize();
36
- });
37
- }
38
-
39
- async function getHash(path) {
40
- return new Promise((resolve, reject) => {
41
- const hash = cryptoModule.createHash("sha256");
42
- const rs = fsModule.createReadStream(path);
43
- rs.on("error", reject);
44
- rs.on("data", (chunk) => hash.update(chunk));
45
- rs.on("end", () => resolve(hash.digest("hex")));
46
- });
47
- }
48
-
49
- export async function packAndZip({ bundleDevDependencies } = {}) {
50
- const packDirAbsolute = pathModule.resolve(PACK_DIR);
51
-
52
- // remove directory for packing
53
- await rmPromise(packDirAbsolute, { recursive: true, force: true });
54
- // make new directory for packing
55
- await mkdirPromise(packDirAbsolute);
56
- // copy files to new directory, ignore node_modules, test and all-code.zip
57
- const rootLevelFiles = await readdirPromise(".");
58
-
59
- for (const file of rootLevelFiles) {
60
- if (!EXCLUDED_FILES.includes(file)) {
61
- await cpPromise(file, pathModule.join(packDirAbsolute, file), {
62
- recursive: true,
63
- });
64
- }
65
- }
66
-
67
- // assume bun exist
68
- // run bun install --production
69
- if (bundleDevDependencies) {
70
- await execPromise("bun install", { cwd: packDirAbsolute });
71
- } else {
72
- // install only production dependencies
73
- await execPromise("bun install --production", { cwd: packDirAbsolute });
74
- }
75
-
76
- // zip all files in directory
77
- // create a file to stream archive data to.
78
- await zipDirectory(packDirAbsolute, pathModule.resolve(ZIP_FILE));
79
-
80
- const zipFileHash = await getHash(pathModule.resolve(ZIP_FILE));
81
-
82
- return { zipFileName: ZIP_FILE, zipFileHash };
83
- }
package/lib/path.js DELETED
@@ -1,6 +0,0 @@
1
- import pathModule from "node:path";
2
- import { fileURLToPath } from "node:url";
3
-
4
- export function dirname(importMetaUrl) {
5
- return fileURLToPath(new URL(pathModule.join("."), importMetaUrl));
6
- }
package/lib/routes.js DELETED
@@ -1,39 +0,0 @@
1
- import pathModule from "node:path";
2
- import { readdir } from "node:fs/promises";
3
- import { corsPreflight } from "./cors.js";
4
- import { DEFAULT_TIMEOUT, MAX_TIMEOUT, DEFAULT_MEMORY_SIZE } from "./const.js";
5
-
6
- export async function getRoutes(routeDirectory) {
7
- const routeFiles = await readdir(routeDirectory);
8
-
9
- const routesFromFiles = await Promise.all(
10
- routeFiles.map(async (routeFile) => {
11
- const routeFilePath = pathModule.join(routeDirectory, routeFile);
12
- const route = await import(`file://${routeFilePath}`);
13
- const routeName = pathModule.basename(routeFile, ".js");
14
- const methods = route.methods || ["GET"];
15
- const environmentVariables = route.environmentVariables || [];
16
-
17
- const methodRoutes = methods.map((method) => ({
18
- name: `${routeName}-${method}`,
19
- path: `/${routeName}`,
20
- method,
21
- corsOrigin: route.corsOrigin,
22
- timeout: Math.min(route.timeout || DEFAULT_TIMEOUT, MAX_TIMEOUT),
23
- handler: route.default,
24
- memorySize: route.memorySize || DEFAULT_MEMORY_SIZE,
25
- environmentVariables,
26
- }));
27
-
28
- if (route.corsOrigin) {
29
- methodRoutes.push(
30
- corsPreflight(`${routeName}-OPTIONS`, `/${routeName}`),
31
- );
32
- }
33
-
34
- return methodRoutes;
35
- }),
36
- );
37
-
38
- return routesFromFiles.reduce((acc, rs) => acc.concat(rs), []);
39
- }