@awsless/cli 0.0.46-next.1 → 0.0.46-next.10

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.
@@ -237,7 +237,7 @@ var CodeSchema = z10.union([
237
237
  var FnSchema = z10.object({
238
238
  code: CodeSchema,
239
239
  handler: HandlerSchema.optional()
240
- });
240
+ }).strict();
241
241
  var FunctionSchema = z10.union([
242
242
  LocalFileSchema.transform((code) => ({
243
243
  code
@@ -483,6 +483,70 @@ var InstanceDefaultSchema = z15.object({
483
483
  // src/feature/router/schema.ts
484
484
  import { days as days3, minutes as minutes2, parse as parse3 } from "@awsless/duration";
485
485
  import { z as z16 } from "zod";
486
+
487
+ // src/error.ts
488
+ var ExpectedError = class extends Error {
489
+ };
490
+
491
+ // src/feature/router/pattern.ts
492
+ var PARAM_TOKEN = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}|\*/g;
493
+ var escapeRegex = (value) => {
494
+ return value.replace(/[|\\{}()[\]^$+*?.\-]/g, "\\$&");
495
+ };
496
+ var compileRoutePattern = (pattern) => {
497
+ if (!pattern.startsWith("/")) {
498
+ throw new ExpectedError(`Route pattern "${pattern}" must start with a slash (/)`);
499
+ }
500
+ if (pattern === "/*") {
501
+ return { key: pattern };
502
+ }
503
+ const params = [];
504
+ let regex = "";
505
+ let stars = 0;
506
+ let last = 0;
507
+ let token;
508
+ PARAM_TOKEN.lastIndex = 0;
509
+ while (token = PARAM_TOKEN.exec(pattern)) {
510
+ regex += escapeRegex(pattern.slice(last, token.index));
511
+ const param = token[1];
512
+ if (param) {
513
+ if (params.includes(param)) {
514
+ throw new ExpectedError(`Duplicate param "${param}" in route pattern "${pattern}"`);
515
+ }
516
+ params.push(param);
517
+ regex += "([^/]+)";
518
+ } else {
519
+ stars++;
520
+ regex += ".*";
521
+ }
522
+ last = PARAM_TOKEN.lastIndex;
523
+ }
524
+ if (params.length === 0 && stars === 0) {
525
+ return { key: pattern };
526
+ }
527
+ regex += escapeRegex(pattern.slice(last));
528
+ const root = pattern.split("/")[1] ?? "";
529
+ if (root === "" || root.includes("*") || root.includes("{")) {
530
+ throw new ExpectedError(
531
+ `The first path segment of route pattern "${pattern}" must be static when the pattern contains params or wildcards.`
532
+ );
533
+ }
534
+ if (root.includes(".")) {
535
+ throw new ExpectedError(
536
+ `The first path segment of route pattern "${pattern}" can't contain a dot when the pattern contains params or wildcards.`
537
+ );
538
+ }
539
+ if (params.length === 0 && pattern === `/${root}/*`) {
540
+ return { key: pattern };
541
+ }
542
+ return {
543
+ key: `/${root}/*`,
544
+ match: `^${regex}$`,
545
+ params: params.length > 0 ? params : void 0
546
+ };
547
+ };
548
+
549
+ // src/feature/router/schema.ts
486
550
  var ErrorResponsePathSchema = z16.string().describe(
487
551
  [
488
552
  "The path to the custom error page that you want to return to the viewer when your origin returns the HTTP status code specified.",
@@ -511,7 +575,29 @@ var ErrorResponseSchema = z16.union([
511
575
  minTTL: MinTTLSchema.optional()
512
576
  })
513
577
  ]).optional();
514
- var RouteSchema = z16.string().regex(/^\//, "Route must start with a slash (/)");
578
+ var RouteSchema = z16.string().regex(/^\//, "Route must start with a slash (/)").regex(/^\/([^/*.]+)?$/, 'Router paths mount a single segment without dots, like "/api".');
579
+ var RoutesSchema = z16.record(
580
+ ResourceIdSchema.describe("The router id to add your routes to."),
581
+ z16.record(z16.string().regex(/^\//, "Route must start with a slash (/)"), FunctionSchema).superRefine((routes, ctx) => {
582
+ for (const pattern of Object.keys(routes)) {
583
+ try {
584
+ compileRoutePattern(pattern);
585
+ } catch (error) {
586
+ ctx.addIssue({
587
+ code: z16.ZodIssueCode.custom,
588
+ path: [pattern],
589
+ message: error instanceof Error ? error.message : `Invalid route pattern: ${pattern}`
590
+ });
591
+ }
592
+ }
593
+ }).describe(
594
+ [
595
+ "Define the routes and the lambda function that should handle them.",
596
+ 'Routes can be an exact path like "/sitemap.xml", a wildcard like "/sitemap/*", or contain params like "/sitemap/{locale}/{page}.xml".',
597
+ 'Param values are passed to the function as "x-param-[NAME]" request headers.'
598
+ ].join("\n")
599
+ )
600
+ ).optional().describe("Add routes to your global Router that link a path pattern to a lambda function.");
515
601
  var VisibilitySchema = z16.boolean().default(false).describe("Whether to enable CloudWatch metrics for the WAF rule.");
516
602
  var WafSettingsSchema = z16.object({
517
603
  rateLimiter: z16.object({
@@ -1009,8 +1095,8 @@ var AppSchema = z25.object({
1009
1095
  layers: LayerSchema,
1010
1096
  router: RouterDefaultSchema
1011
1097
  // dataRetention: z.boolean().describe('Configure how your resources are handled on delete.').default(false),
1012
- }).default({}).describe("Default properties")
1013
- });
1098
+ }).strict().default({}).describe("Default properties")
1099
+ }).strict();
1014
1100
 
1015
1101
  // src/config/stack.ts
1016
1102
  import { z as z41 } from "zod";
@@ -1066,7 +1152,7 @@ var CommandsSchema = z27.record(ResourceIdSchema, CommandSchema).optional().desc
1066
1152
 
1067
1153
  // src/feature/config/schema.ts
1068
1154
  import { z as z28 } from "zod";
1069
- var ConfigNameSchema = z28.string().regex(/[a-z0-9\-]/g, "Invalid config name");
1155
+ var ConfigNameSchema = z28.string().regex(/^[a-z0-9-]+$/, "Invalid config name");
1070
1156
  var ConfigsSchema = z28.array(ConfigNameSchema).optional().describe("Define the config values for your stack.");
1071
1157
 
1072
1158
  // src/feature/cron/schema/index.ts
@@ -1619,14 +1705,13 @@ var TestsSchema = z40.union([
1619
1705
  ]).describe("Define the location of your tests for your stack.").optional();
1620
1706
 
1621
1707
  // src/config/stack.ts
1622
- var DependsSchema = ResourceIdSchema.array().optional().describe("Define the stacks that this stack is depended on.");
1623
1708
  var NameSchema = ResourceIdSchema.refine((name) => !["base", "hostedzones"].includes(name), {
1624
1709
  message: `Stack name can't be a reserved name.`
1625
1710
  }).describe("Stack name.");
1626
1711
  var StackSchema = z41.object({
1627
1712
  $schema: z41.string().optional(),
1628
1713
  name: NameSchema,
1629
- depends: DependsSchema,
1714
+ routes: RoutesSchema,
1630
1715
  commands: CommandsSchema,
1631
1716
  // auth: AuthSchema,
1632
1717
  // http: HttpSchema,
@@ -1652,7 +1737,7 @@ var StackSchema = z41.object({
1652
1737
  images: ImagesSchema,
1653
1738
  icons: IconsSchema,
1654
1739
  metrics: MetricsSchema
1655
- });
1740
+ }).strict();
1656
1741
 
1657
1742
  // src/config/stage-patch-json-schema.ts
1658
1743
  var clone = (value) => {
@@ -3,128 +3,6 @@ import { patch, unpatch } from "@awsless/json";
3
3
  import { ExpectedError, invoke, isErrorResponse } from "@awsless/lambda";
4
4
  import { formatRoutePayload, getCurrentRoute, withRoute } from "awsless";
5
5
 
6
- // src/feature/bundle/server/preview.ts
7
- import { GetObjectCommand, NoSuchKey } from "@aws-sdk/client-s3";
8
- import { s3Client } from "@awsless/s3";
9
- var getPossibleRouteKeys = (path) => {
10
- if (path === "" || path === "/") {
11
- return ["/", "/*"];
12
- }
13
- const parts = path.split("/");
14
- const root = path.startsWith("/") ? parts[1] : parts[0];
15
- const file = parts[parts.length - 1].includes(".");
16
- if (root.includes(".")) {
17
- return [path, "/*.", "/*"];
18
- }
19
- if (file) {
20
- return [path, "/" + root + "/*.", "/" + root + "/*", "/*.", "/*"];
21
- }
22
- return [path, "/" + root + "/*", "/*"];
23
- };
24
- var findRoute = (props, path, method) => {
25
- for (const key of getPossibleRouteKeys(path)) {
26
- const route = props.routes[`${props.router}:${key}`];
27
- if (!route) {
28
- continue;
29
- }
30
- if (route.type === "s3" && method !== "GET" && method !== "HEAD") {
31
- continue;
32
- }
33
- return route;
34
- }
35
- return;
36
- };
37
- var rewritePath = (route, path) => {
38
- if (!route.rewrite) {
39
- return path;
40
- }
41
- if (route.rewrite.regex) {
42
- return path.replace(new RegExp(route.rewrite.regex), route.rewrite.to);
43
- }
44
- return route.rewrite.to;
45
- };
46
- var serveObject = async (route, path, method) => {
47
- const bucket = route.domainName.split(".s3")[0];
48
- const key = rewritePath(route, path).replace(/^\//, "");
49
- let result;
50
- try {
51
- result = await s3Client().send(new GetObjectCommand({
52
- Bucket: bucket,
53
- Key: key
54
- }));
55
- } catch (error) {
56
- if (error instanceof NoSuchKey) {
57
- return {
58
- statusCode: 404
59
- };
60
- }
61
- throw error;
62
- }
63
- const headers = {};
64
- if (result.ContentType) {
65
- headers["content-type"] = result.ContentType;
66
- }
67
- if (result.CacheControl) {
68
- headers["cache-control"] = result.CacheControl;
69
- }
70
- if (result.ETag) {
71
- headers["etag"] = result.ETag;
72
- }
73
- if (method === "HEAD") {
74
- return { statusCode: 200, headers };
75
- }
76
- return {
77
- statusCode: 200,
78
- headers,
79
- body: await result.Body.transformToString("base64"),
80
- isBase64Encoded: true
81
- };
82
- };
83
- var createPreviewHandler = (props) => {
84
- return async (event) => {
85
- const method = event.requestContext.http.method;
86
- const headers = event.headers ?? {};
87
- let path = event.rawPath;
88
- try {
89
- path = decodeURIComponent(path);
90
- } catch {}
91
- if (method === "OPTIONS") {
92
- return {
93
- statusCode: 204,
94
- headers: {
95
- "access-control-allow-origin": "*",
96
- "access-control-allow-methods": "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS",
97
- "access-control-allow-headers": "*",
98
- "access-control-max-age": "86400"
99
- }
100
- };
101
- }
102
- const route = findRoute(props, path, method);
103
- if (!route) {
104
- return {
105
- statusCode: 404
106
- };
107
- }
108
- if (route.type === "s3") {
109
- return serveObject(route, path, method);
110
- }
111
- for (const [name, value] of Object.entries(route.requestHeaders ?? {})) {
112
- headers[name] = value;
113
- }
114
- if (headers.authorization) {
115
- headers["x-awsless-authorization"] = headers.authorization;
116
- } else {
117
- delete headers["x-awsless-authorization"];
118
- }
119
- if (route.forwardHost && headers.host) {
120
- headers["x-forwarded-host"] = headers.host;
121
- }
122
- event.headers = headers;
123
- event.rawPath = rewritePath(route, path);
124
- return props.dispatch(event);
125
- };
126
- };
127
-
128
6
  // src/feature/bundle/server/resource/util.ts
129
7
  var asyncRoute = (key, payload) => {
130
8
  process.env.THROW_EXPECTED_ERRORS = "1";
@@ -434,7 +312,6 @@ var topicHandler = (event, routes) => {
434
312
  // src/feature/bundle/server/handle.ts
435
313
  var createBundle = (handlers) => {
436
314
  const routes = Object.keys(handlers);
437
- let previewConfig;
438
315
  const matchers = [
439
316
  functionHandler,
440
317
  cronHandler,
@@ -473,7 +350,8 @@ var createBundle = (handlers) => {
473
350
  process.env.AWSLESS_ROUTE = match2.key;
474
351
  return withRoute(match2.key, invokeRoute, async () => {
475
352
  const handle = await load();
476
- return handle(match2.payload ?? {}, context);
353
+ const routedContext = { ...context, route: match2.key };
354
+ return handle(match2.payload ?? {}, routedContext);
477
355
  });
478
356
  };
479
357
  const invokeRoute = async (key, payload) => {
@@ -502,20 +380,6 @@ var createBundle = (handlers) => {
502
380
  }
503
381
  return response;
504
382
  };
505
- const raw = event;
506
- if (process.env.AWSLESS_PREVIEW && raw?.requestContext?.http && !event?.headers?.["x-awsless-route"]) {
507
- previewConfig ??= JSON.parse(process.env.AWSLESS_PREVIEW);
508
- return createPreviewHandler({
509
- ...previewConfig,
510
- dispatch: async (event2) => {
511
- const match2 = matchRoute(event2);
512
- if (Array.isArray(match2)) {
513
- throw new Error("Unknown bundle route");
514
- }
515
- return handleRoute(match2);
516
- }
517
- })(event);
518
- }
519
383
  const match = matchRoute(event);
520
384
  if (Array.isArray(match)) {
521
385
  const name = `${process.env.AWS_LAMBDA_FUNCTION_NAME}:${process.env.AWS_LAMBDA_FUNCTION_VERSION}`;
@@ -1,8 +1,81 @@
1
1
  // src/feature/on-failure/server/handle.ts
2
- import { parse, patch } from "@awsless/json";
2
+ import { parse as parse2, patch } from "@awsless/json";
3
3
  import { invoke } from "@awsless/lambda";
4
4
  import { deleteObject, getObject } from "@awsless/s3";
5
5
  import { formatRoutePayload, getRouteEnv } from "awsless";
6
+
7
+ // src/feature/on-failure/server/util.ts
8
+ import { parse } from "@awsless/json";
9
+ var isDynamoDBFailureEvent = (event) => {
10
+ return "DDBStreamBatchInfo" in event;
11
+ };
12
+ var logicalResourceName = (physical) => {
13
+ const segments = physical.replace(/\.fifo$/, "").split("--");
14
+ if (segments.length === 4) {
15
+ return `${segments[1]}:${segments[2]}:${segments[3]}`;
16
+ }
17
+ if (segments.length === 3) {
18
+ return `${segments[1]}:${segments[2]}`;
19
+ }
20
+ return physical;
21
+ };
22
+ var getFailureSource = (payload) => {
23
+ const record = getDeliveryRecord(payload);
24
+ if (!record) {
25
+ return;
26
+ }
27
+ if (isTopicRecord(record)) {
28
+ return {
29
+ resource: record.Sns.TopicArn ? logicalResourceName(lastArnSegment(record.Sns.TopicArn)) : "topic",
30
+ event: parseEvent(record.Sns.Message)
31
+ };
32
+ }
33
+ if (isStreamRecord(record)) {
34
+ const table = record.eventSourceARN.split("/")[1];
35
+ return {
36
+ resource: table ? logicalResourceName(table) : "table-stream"
37
+ };
38
+ }
39
+ if (isQueueRecord(record)) {
40
+ return {
41
+ resource: logicalResourceName(lastArnSegment(record.eventSourceARN)),
42
+ event: parseEvent(record.body)
43
+ };
44
+ }
45
+ return;
46
+ };
47
+ var getDeliveryRecord = (payload) => {
48
+ if (!payload || typeof payload !== "object") {
49
+ return;
50
+ }
51
+ const records = payload.Records;
52
+ const record = Array.isArray(records) ? records[0] : undefined;
53
+ return record && typeof record === "object" ? record : undefined;
54
+ };
55
+ var isTopicRecord = (record) => {
56
+ return "Sns" in record && typeof record.Sns === "object" && record.Sns !== null;
57
+ };
58
+ var isStreamRecord = (record) => {
59
+ return "eventSource" in record && record.eventSource === "aws:dynamodb" && "eventSourceARN" in record;
60
+ };
61
+ var isQueueRecord = (record) => {
62
+ return "eventSource" in record && record.eventSource === "aws:sqs" && "eventSourceARN" in record;
63
+ };
64
+ var lastArnSegment = (arn) => {
65
+ return arn.split(":").at(-1);
66
+ };
67
+ var parseEvent = (value) => {
68
+ if (typeof value !== "string") {
69
+ return value;
70
+ }
71
+ try {
72
+ return parse(value);
73
+ } catch {
74
+ return value;
75
+ }
76
+ };
77
+
78
+ // src/feature/on-failure/server/handle.ts
6
79
  var handle_default = async (event, context) => {
7
80
  if (!Array.isArray(event.Records)) {
8
81
  throw new TypeError(`Unknown Event Type: ${JSON.stringify(event)}`);
@@ -28,13 +101,16 @@ var sqsRecord = async (record, context) => {
28
101
  await Promise.all(s3Records.map((record2) => s3Record(record2, context)));
29
102
  return;
30
103
  }
104
+ const queueName = record.messageAttributes.queueName?.stringValue;
105
+ const body = parsePayload(record.body);
31
106
  const payload = {
32
107
  type: "queue",
33
108
  id: record.messageId,
34
109
  date: new Date(Number(record.attributes.SentTimestamp)),
35
- payload: parsePayload(record.body),
110
+ payload: body,
111
+ source: queueName ? { resource: logicalResourceName(queueName), event: body } : undefined,
36
112
  queue: {
37
- name: record.messageAttributes.queueName?.stringValue,
113
+ name: queueName,
38
114
  url: record.messageAttributes.queueUrl?.stringValue
39
115
  }
40
116
  };
@@ -70,9 +146,6 @@ var s3Record = async (record, context) => {
70
146
  key
71
147
  });
72
148
  };
73
- var isDynamoDBFailureEvent = (event) => {
74
- return "DDBStreamBatchInfo" in event;
75
- };
76
149
  var formatUnknownFailureEvent = (event) => {
77
150
  if (isDynamoDBFailureEvent(event)) {
78
151
  return formatDynamoDBStreamFailureEvent(event);
@@ -90,6 +163,7 @@ var formatAsyncLambdaFailureEvent = (event) => {
90
163
  name: typeof route === "string" ? route : event.requestContext.functionArn.split(":")[6]
91
164
  },
92
165
  payload: typeof route === "string" ? payload.event ?? {} : payload,
166
+ source: typeof route === "string" ? { resource: route, event: payload.event ?? {} } : getFailureSource(payload),
93
167
  error: {
94
168
  type: event.responsePayload.errorType,
95
169
  message: event.responsePayload.errorMessage,
@@ -98,6 +172,9 @@ var formatAsyncLambdaFailureEvent = (event) => {
98
172
  };
99
173
  };
100
174
  var formatDynamoDBStreamFailureEvent = (event) => {
175
+ const payload = parsePayload(event.payload);
176
+ const streamArn = event.DDBStreamBatchInfo?.streamArn;
177
+ const table = streamArn?.split("/")[1];
101
178
  return {
102
179
  type: "dynamodb-stream",
103
180
  date: new Date(event.timestamp),
@@ -105,12 +182,13 @@ var formatDynamoDBStreamFailureEvent = (event) => {
105
182
  function: {
106
183
  name: event.requestContext.functionArn.split(":")[6]
107
184
  },
108
- payload: parsePayload(event.payload)
185
+ payload,
186
+ source: table ? { resource: logicalResourceName(table) } : getFailureSource(payload)
109
187
  };
110
188
  };
111
189
  var parsePayload = (payload) => {
112
190
  try {
113
- return parse(payload);
191
+ return parse2(payload);
114
192
  } catch {
115
193
  return payload;
116
194
  }