effortless-aws 0.9.0 → 0.10.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.
@@ -1,28 +1,67 @@
1
1
  // src/runtime/table-client.ts
2
2
  import { DynamoDB } from "@aws-sdk/client-dynamodb";
3
3
  import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
4
- var createTableClient = (tableName) => {
4
+ var GSI_TAG_PK = "tag-pk-index";
5
+ var marshallKey = (key) => marshall(key, { removeUndefinedValues: true });
6
+ var buildSkCondition = (sk) => {
7
+ const names = { "#sk": "sk" };
8
+ if (typeof sk === "string") {
9
+ return { expression: "AND #sk = :sk", names, values: { ":sk": sk } };
10
+ }
11
+ if ("begins_with" in sk) {
12
+ return { expression: "AND begins_with(#sk, :sk)", names, values: { ":sk": sk.begins_with } };
13
+ }
14
+ if ("gt" in sk) {
15
+ return { expression: "AND #sk > :sk", names, values: { ":sk": sk.gt } };
16
+ }
17
+ if ("gte" in sk) {
18
+ return { expression: "AND #sk >= :sk", names, values: { ":sk": sk.gte } };
19
+ }
20
+ if ("lt" in sk) {
21
+ return { expression: "AND #sk < :sk", names, values: { ":sk": sk.lt } };
22
+ }
23
+ if ("lte" in sk) {
24
+ return { expression: "AND #sk <= :sk", names, values: { ":sk": sk.lte } };
25
+ }
26
+ if ("between" in sk) {
27
+ return { expression: "AND #sk BETWEEN :sk1 AND :sk2", names, values: { ":sk1": sk.between[0], ":sk2": sk.between[1] } };
28
+ }
29
+ return { expression: "", names: {}, values: {} };
30
+ };
31
+ var createTableClient = (tableName, options) => {
5
32
  let client2 = null;
6
33
  const getClient2 = () => client2 ??= new DynamoDB({});
34
+ const tagField = options?.tagField ?? "tag";
7
35
  return {
8
36
  tableName,
9
- async put(item) {
37
+ async put(item, putOptions) {
38
+ const dataObj = item.data;
39
+ const tag = dataObj?.[tagField] || "";
40
+ if (!tag) throw new Error(`tag is required: data must include a "${tagField}" field`);
41
+ const dynamoItem = {
42
+ pk: item.pk,
43
+ sk: item.sk,
44
+ tag,
45
+ data: item.data
46
+ };
47
+ if (item.ttl !== void 0) dynamoItem.ttl = item.ttl;
10
48
  await getClient2().putItem({
11
49
  TableName: tableName,
12
- Item: marshall(item, { removeUndefinedValues: true })
50
+ Item: marshall(dynamoItem, { removeUndefinedValues: true }),
51
+ ...putOptions?.ifNotExists ? { ConditionExpression: "attribute_not_exists(pk)" } : {}
13
52
  });
14
53
  },
15
54
  async get(key) {
16
55
  const result = await getClient2().getItem({
17
56
  TableName: tableName,
18
- Key: marshall(key, { removeUndefinedValues: true })
57
+ Key: marshallKey(key)
19
58
  });
20
59
  return result.Item ? unmarshall(result.Item) : void 0;
21
60
  },
22
61
  async delete(key) {
23
62
  await getClient2().deleteItem({
24
63
  TableName: tableName,
25
- Key: marshall(key, { removeUndefinedValues: true })
64
+ Key: marshallKey(key)
26
65
  });
27
66
  },
28
67
  async update(key, actions) {
@@ -31,63 +70,108 @@ var createTableClient = (tableName) => {
31
70
  const setClauses = [];
32
71
  const removeClauses = [];
33
72
  let counter = 0;
73
+ const DATA_ALIAS = "#data";
74
+ let needsDataAlias = false;
34
75
  if (actions.set) {
35
76
  for (const [attr, val] of Object.entries(actions.set)) {
77
+ needsDataAlias = true;
36
78
  const alias = `#a${counter}`;
37
79
  const valAlias = `:v${counter}`;
38
80
  names[alias] = attr;
39
81
  values[valAlias] = val;
40
- setClauses.push(`${alias} = ${valAlias}`);
82
+ setClauses.push(`${DATA_ALIAS}.${alias} = ${valAlias}`);
41
83
  counter++;
42
84
  }
43
85
  }
44
86
  if (actions.append) {
45
87
  for (const [attr, val] of Object.entries(actions.append)) {
88
+ needsDataAlias = true;
46
89
  const alias = `#a${counter}`;
47
90
  const valAlias = `:v${counter}`;
48
91
  const emptyAlias = `:empty${counter}`;
49
92
  names[alias] = attr;
50
93
  values[valAlias] = val;
51
94
  values[emptyAlias] = [];
52
- setClauses.push(`${alias} = list_append(if_not_exists(${alias}, ${emptyAlias}), ${valAlias})`);
95
+ setClauses.push(`${DATA_ALIAS}.${alias} = list_append(if_not_exists(${DATA_ALIAS}.${alias}, ${emptyAlias}), ${valAlias})`);
53
96
  counter++;
54
97
  }
55
98
  }
56
99
  if (actions.remove) {
57
100
  for (const attr of actions.remove) {
101
+ needsDataAlias = true;
58
102
  const alias = `#a${counter}`;
59
103
  names[alias] = attr;
60
- removeClauses.push(alias);
104
+ removeClauses.push(`${DATA_ALIAS}.${alias}`);
61
105
  counter++;
62
106
  }
63
107
  }
108
+ if (needsDataAlias) {
109
+ names[DATA_ALIAS] = "data";
110
+ }
111
+ if (actions.tag !== void 0) {
112
+ names["#tag"] = "tag";
113
+ values[":tagVal"] = actions.tag;
114
+ setClauses.push("#tag = :tagVal");
115
+ }
116
+ if (actions.ttl !== void 0) {
117
+ names["#ttl"] = "ttl";
118
+ if (actions.ttl === null) {
119
+ removeClauses.push("#ttl");
120
+ } else {
121
+ values[":ttlVal"] = actions.ttl;
122
+ setClauses.push("#ttl = :ttlVal");
123
+ }
124
+ }
64
125
  const parts = [];
65
126
  if (setClauses.length) parts.push(`SET ${setClauses.join(", ")}`);
66
127
  if (removeClauses.length) parts.push(`REMOVE ${removeClauses.join(", ")}`);
67
128
  if (!parts.length) return;
68
129
  await getClient2().updateItem({
69
130
  TableName: tableName,
70
- Key: marshall(key, { removeUndefinedValues: true }),
131
+ Key: marshallKey(key),
71
132
  UpdateExpression: parts.join(" "),
72
133
  ExpressionAttributeNames: names,
73
134
  ...Object.keys(values).length ? { ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }) } : {}
74
135
  });
75
136
  },
76
137
  async query(params) {
77
- const names = { "#pk": params.pk.name };
78
- const values = { ":pk": params.pk.value };
138
+ const names = { "#pk": "pk" };
139
+ const values = { ":pk": params.pk };
79
140
  let keyCondition = "#pk = :pk";
80
- if (params.sk) {
81
- names["#sk"] = params.sk.name;
82
- values[":sk"] = params.sk.value;
83
- if (params.sk.condition === "begins_with") {
84
- keyCondition += " AND begins_with(#sk, :sk)";
85
- } else {
86
- keyCondition += ` AND #sk ${params.sk.condition} :sk`;
141
+ if (params.sk !== void 0) {
142
+ const skCond = buildSkCondition(params.sk);
143
+ keyCondition += ` ${skCond.expression}`;
144
+ Object.assign(names, skCond.names);
145
+ Object.assign(values, skCond.values);
146
+ }
147
+ const result = await getClient2().query({
148
+ TableName: tableName,
149
+ KeyConditionExpression: keyCondition,
150
+ ExpressionAttributeNames: names,
151
+ ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }),
152
+ ...params.limit ? { Limit: params.limit } : {},
153
+ ...params.scanIndexForward !== void 0 ? { ScanIndexForward: params.scanIndexForward } : {}
154
+ });
155
+ return (result.Items ?? []).map((item) => unmarshall(item));
156
+ },
157
+ async queryByTag(params) {
158
+ const names = { "#tag": "tag" };
159
+ const values = { ":tag": params.tag };
160
+ let keyCondition = "#tag = :tag";
161
+ if (params.pk !== void 0) {
162
+ const pkCond = buildSkCondition(params.pk);
163
+ const remapped = pkCond.expression.replace(/#sk/g, "#pk").replace(/:sk/g, ":pk");
164
+ keyCondition += ` ${remapped}`;
165
+ for (const [k, v] of Object.entries(pkCond.names)) {
166
+ names[k === "#sk" ? "#pk" : k] = v === "sk" ? "pk" : v;
167
+ }
168
+ for (const [k, v] of Object.entries(pkCond.values)) {
169
+ values[k.replace(":sk", ":pk")] = v;
87
170
  }
88
171
  }
89
172
  const result = await getClient2().query({
90
173
  TableName: tableName,
174
+ IndexName: GSI_TAG_PK,
91
175
  KeyConditionExpression: keyCondition,
92
176
  ExpressionAttributeNames: names,
93
177
  ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }),
@@ -167,7 +251,7 @@ var buildParams = async (params) => {
167
251
  return result;
168
252
  };
169
253
  var readStatic = (filePath) => readFileSync(join(process.cwd(), filePath), "utf-8");
170
- var createHandlerRuntime = (handler, handlerType, logLevel = "info") => {
254
+ var createHandlerRuntime = (handler, handlerType, logLevel = "info", extraSetupArgs) => {
171
255
  const handlerName = process.env.EFF_HANDLER ?? "unknown";
172
256
  const rank = LOG_RANK[logLevel];
173
257
  let ctx = null;
@@ -187,7 +271,8 @@ var createHandlerRuntime = (handler, handlerType, logLevel = "info") => {
187
271
  const args = {};
188
272
  if (params) args.config = params;
189
273
  if (deps) args.deps = deps;
190
- ctx = Object.keys(args).length > 0 ? await handler.setup(args) : await handler.setup();
274
+ if (extraSetupArgs) Object.assign(args, extraSetupArgs());
275
+ ctx = Object.keys(args).length > 0 || extraSetupArgs ? await handler.setup(args) : await handler.setup();
191
276
  }
192
277
  return ctx;
193
278
  };
package/dist/cli/index.js CHANGED
@@ -70781,6 +70781,7 @@ var ensureLambda = (config2) => Effect_exports.gen(function* () {
70781
70781
  const runtime5 = config2.runtime ?? Runtime2.nodejs24x;
70782
70782
  const layers = config2.layers ?? [];
70783
70783
  const environment2 = config2.environment ?? {};
70784
+ const arch = config2.architecture ?? Architecture.arm64;
70784
70785
  const existingFunction = yield* lambda_exports.make("get_function", {
70785
70786
  FunctionName: functionName
70786
70787
  }).pipe(
@@ -70800,7 +70801,7 @@ var ensureLambda = (config2) => Effect_exports.gen(function* () {
70800
70801
  const envKeys = [.../* @__PURE__ */ new Set([...Object.keys(existingEnv), ...Object.keys(environment2)])].sort();
70801
70802
  const envChanged = envKeys.some((k) => existingEnv[k] !== environment2[k]);
70802
70803
  const existingArch = existingFunction.Architectures?.[0] ?? Architecture.x86_64;
70803
- const archChanged = existingArch !== Architecture.arm64;
70804
+ const archChanged = existingArch !== arch;
70804
70805
  const configChanged = existingFunction.MemorySize !== memory || existingFunction.Timeout !== timeout4 || existingFunction.Handler !== handler || existingFunction.Runtime !== runtime5 || layersChanged || envChanged;
70805
70806
  if (!codeChanged && !archChanged && !configChanged) {
70806
70807
  yield* Effect_exports.logDebug(`Function ${functionName} unchanged, skipping update`);
@@ -70811,7 +70812,7 @@ var ensureLambda = (config2) => Effect_exports.gen(function* () {
70811
70812
  yield* lambda_exports.make("update_function_code", {
70812
70813
  FunctionName: functionName,
70813
70814
  ZipFile: config2.code,
70814
- Architectures: [Architecture.arm64]
70815
+ Architectures: [arch]
70815
70816
  });
70816
70817
  yield* waitForFunctionActive(functionName);
70817
70818
  } else {
@@ -70853,7 +70854,7 @@ var ensureLambda = (config2) => Effect_exports.gen(function* () {
70853
70854
  },
70854
70855
  Handler: handler,
70855
70856
  Runtime: runtime5,
70856
- Architectures: [Architecture.arm64],
70857
+ Architectures: [arch],
70857
70858
  MemorySize: memory,
70858
70859
  Timeout: timeout4,
70859
70860
  Tags: config2.tags,
@@ -70883,6 +70884,16 @@ var waitForFunctionActive = (functionName) => Effect_exports.gen(function* () {
70883
70884
  );
70884
70885
  yield* Effect_exports.logDebug(`Function ${functionName} is active`);
70885
70886
  });
70887
+ var publishVersion = (functionName) => Effect_exports.gen(function* () {
70888
+ yield* Effect_exports.logDebug(`Publishing version for: ${functionName}`);
70889
+ const result = yield* lambda_exports.make("publish_version", {
70890
+ FunctionName: functionName
70891
+ });
70892
+ return {
70893
+ versionArn: result.FunctionArn,
70894
+ version: result.Version
70895
+ };
70896
+ });
70886
70897
  var deleteLambda = (functionName) => Effect_exports.gen(function* () {
70887
70898
  yield* Effect_exports.logDebug(`Deleting Lambda function: ${functionName}`);
70888
70899
  yield* lambda_exports.make("delete_function", {
@@ -70956,6 +70967,18 @@ var LAMBDA_ASSUME_ROLE_POLICY = JSON.stringify({
70956
70967
  }
70957
70968
  ]
70958
70969
  });
70970
+ var EDGE_LAMBDA_ASSUME_ROLE_POLICY = JSON.stringify({
70971
+ Version: "2012-10-17",
70972
+ Statement: [
70973
+ {
70974
+ Effect: "Allow",
70975
+ Principal: {
70976
+ Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"]
70977
+ },
70978
+ Action: "sts:AssumeRole"
70979
+ }
70980
+ ]
70981
+ });
70959
70982
  var BASIC_EXECUTION_POLICY_ARN = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole";
70960
70983
  var ensureRole = (project2, stage, name, additionalActions, tags2) => Effect_exports.gen(function* () {
70961
70984
  const roleName = `${project2}-${stage}-${name}-role`;
@@ -70996,6 +71019,39 @@ var ensureRole = (project2, stage, name, additionalActions, tags2) => Effect_exp
70996
71019
  yield* Effect_exports.sleep("10 seconds");
70997
71020
  return createResult.Role.Arn;
70998
71021
  });
71022
+ var ensureEdgeRole = (project2, stage, name, tags2) => Effect_exports.gen(function* () {
71023
+ const roleName = `${project2}-${stage}-${name}-role`;
71024
+ const existingRole = yield* iam_exports.make("get_role", { RoleName: roleName }).pipe(
71025
+ Effect_exports.map((r) => r.Role),
71026
+ Effect_exports.catchIf(
71027
+ (e) => e._tag === "IAMError" && e.is("NoSuchEntityException"),
71028
+ () => Effect_exports.succeed(void 0)
71029
+ )
71030
+ );
71031
+ if (existingRole) {
71032
+ yield* Effect_exports.logDebug(`Using existing edge role: ${roleName}`);
71033
+ if (tags2) {
71034
+ yield* iam_exports.make("tag_role", {
71035
+ RoleName: roleName,
71036
+ Tags: toAwsTagList(tags2)
71037
+ });
71038
+ }
71039
+ return existingRole.Arn;
71040
+ }
71041
+ yield* Effect_exports.logDebug(`Creating edge role: ${roleName}`);
71042
+ const createResult = yield* iam_exports.make("create_role", {
71043
+ RoleName: roleName,
71044
+ AssumeRolePolicyDocument: EDGE_LAMBDA_ASSUME_ROLE_POLICY,
71045
+ Description: `Execution role for Lambda@Edge function ${name}`,
71046
+ Tags: tags2 ? toAwsTagList(tags2) : void 0
71047
+ });
71048
+ yield* iam_exports.make("attach_role_policy", {
71049
+ RoleName: roleName,
71050
+ PolicyArn: BASIC_EXECUTION_POLICY_ARN
71051
+ });
71052
+ yield* Effect_exports.sleep("10 seconds");
71053
+ return createResult.Role.Arn;
71054
+ });
70999
71055
  var ensureInlinePolicy = (roleName, functionName, actions) => Effect_exports.gen(function* () {
71000
71056
  const policyName = `${functionName}-inline-policy`;
71001
71057
  const policyDocument = JSON.stringify({
@@ -71085,18 +71141,7 @@ var listEffortlessRoles = () => Effect_exports.gen(function* () {
71085
71141
  });
71086
71142
 
71087
71143
  // src/aws/dynamodb.ts
71088
- var keyTypeToDynamoDB = (type2) => {
71089
- switch (type2) {
71090
- case "string":
71091
- return "S";
71092
- case "number":
71093
- return "N";
71094
- case "binary":
71095
- return "B";
71096
- default:
71097
- return type2;
71098
- }
71099
- };
71144
+ var GSI_TAG_PK = "tag-pk-index";
71100
71145
  var streamViewToSpec = (view) => ({
71101
71146
  StreamEnabled: true,
71102
71147
  StreamViewType: view
@@ -71141,7 +71186,7 @@ var ensureTimeToLive = (tableName, attributeName) => Effect_exports.gen(function
71141
71186
  });
71142
71187
  });
71143
71188
  var ensureTable = (input) => Effect_exports.gen(function* () {
71144
- const { name, pk, sk, billingMode = "PAY_PER_REQUEST", streamView = "NEW_AND_OLD_IMAGES", tags: tags2, ttlAttribute } = input;
71189
+ const { name, billingMode = "PAY_PER_REQUEST", streamView = "NEW_AND_OLD_IMAGES", tags: tags2 } = input;
71145
71190
  const existingTable = yield* dynamodb_exports.make("describe_table", { TableName: name }).pipe(
71146
71191
  Effect_exports.map((result2) => result2.Table),
71147
71192
  Effect_exports.catchIf(
@@ -71152,20 +71197,25 @@ var ensureTable = (input) => Effect_exports.gen(function* () {
71152
71197
  let result;
71153
71198
  if (!existingTable) {
71154
71199
  yield* Effect_exports.logInfo(`Creating table ${name}...`);
71155
- const keySchema = [
71156
- { AttributeName: pk.name, KeyType: "HASH" }
71157
- ];
71158
- const attributeDefinitions = [
71159
- { AttributeName: pk.name, AttributeType: keyTypeToDynamoDB(pk.type) }
71160
- ];
71161
- if (sk) {
71162
- keySchema.push({ AttributeName: sk.name, KeyType: "RANGE" });
71163
- attributeDefinitions.push({ AttributeName: sk.name, AttributeType: keyTypeToDynamoDB(sk.type) });
71164
- }
71165
71200
  yield* dynamodb_exports.make("create_table", {
71166
71201
  TableName: name,
71167
- KeySchema: keySchema,
71168
- AttributeDefinitions: attributeDefinitions,
71202
+ KeySchema: [
71203
+ { AttributeName: "pk", KeyType: "HASH" },
71204
+ { AttributeName: "sk", KeyType: "RANGE" }
71205
+ ],
71206
+ AttributeDefinitions: [
71207
+ { AttributeName: "pk", AttributeType: "S" },
71208
+ { AttributeName: "sk", AttributeType: "S" },
71209
+ { AttributeName: "tag", AttributeType: "S" }
71210
+ ],
71211
+ GlobalSecondaryIndexes: [{
71212
+ IndexName: GSI_TAG_PK,
71213
+ KeySchema: [
71214
+ { AttributeName: "tag", KeyType: "HASH" },
71215
+ { AttributeName: "pk", KeyType: "RANGE" }
71216
+ ],
71217
+ Projection: { ProjectionType: "ALL" }
71218
+ }],
71169
71219
  BillingMode: billingMode,
71170
71220
  StreamSpecification: streamViewToSpec(streamView),
71171
71221
  Tags: tags2 ? toAwsTagList(tags2) : void 0
@@ -71189,21 +71239,40 @@ var ensureTable = (input) => Effect_exports.gen(function* () {
71189
71239
  TableName: name,
71190
71240
  StreamSpecification: streamViewToSpec(streamView)
71191
71241
  });
71192
- const table3 = yield* waitForTableActive(name);
71193
- result = {
71194
- tableArn: table3.TableArn,
71195
- streamArn: table3.LatestStreamArn
71196
- };
71197
- } else {
71198
- result = {
71199
- tableArn: existingTable.TableArn,
71200
- streamArn: existingTable.LatestStreamArn
71201
- };
71242
+ yield* waitForTableActive(name);
71202
71243
  }
71244
+ const hasGsi = existingTable.GlobalSecondaryIndexes?.some(
71245
+ (gsi) => gsi.IndexName === GSI_TAG_PK
71246
+ );
71247
+ if (!hasGsi) {
71248
+ yield* Effect_exports.logInfo(`Adding GSI ${GSI_TAG_PK} to table ${name}...`);
71249
+ yield* dynamodb_exports.make("update_table", {
71250
+ TableName: name,
71251
+ AttributeDefinitions: [
71252
+ { AttributeName: "pk", AttributeType: "S" },
71253
+ { AttributeName: "sk", AttributeType: "S" },
71254
+ { AttributeName: "tag", AttributeType: "S" }
71255
+ ],
71256
+ GlobalSecondaryIndexUpdates: [{
71257
+ Create: {
71258
+ IndexName: GSI_TAG_PK,
71259
+ KeySchema: [
71260
+ { AttributeName: "tag", KeyType: "HASH" },
71261
+ { AttributeName: "pk", KeyType: "RANGE" }
71262
+ ],
71263
+ Projection: { ProjectionType: "ALL" }
71264
+ }
71265
+ }]
71266
+ });
71267
+ yield* waitForTableActive(name);
71268
+ }
71269
+ const updated = yield* dynamodb_exports.make("describe_table", { TableName: name });
71270
+ result = {
71271
+ tableArn: updated.Table.TableArn,
71272
+ streamArn: updated.Table.LatestStreamArn
71273
+ };
71203
71274
  }
71204
- if (ttlAttribute) {
71205
- yield* ensureTimeToLive(name, ttlAttribute);
71206
- }
71275
+ yield* ensureTimeToLive(name, "ttl");
71207
71276
  return result;
71208
71277
  });
71209
71278
  var ensureEventSourceMapping = (input) => Effect_exports.gen(function* () {
@@ -72046,14 +72115,15 @@ var ensureViewerRequestFunction = (name, config2) => Effect_exports.gen(function
72046
72115
  });
72047
72116
  var makeDistComment = (project2, stage, handlerName) => `effortless: ${project2}/${stage}/${handlerName}`;
72048
72117
  var ensureDistribution = (input) => Effect_exports.gen(function* () {
72049
- const { project: project2, stage, handlerName, bucketName, bucketRegion, oacId, spa, index, tags: tags2, urlRewriteFunctionArn, aliases, acmCertificateArn } = input;
72118
+ const { project: project2, stage, handlerName, bucketName, bucketRegion, oacId, spa, index, tags: tags2, urlRewriteFunctionArn, lambdaEdgeArn, aliases, acmCertificateArn } = input;
72050
72119
  const aliasesConfig = aliases && aliases.length > 0 ? { Quantity: aliases.length, Items: aliases } : { Quantity: 0, Items: [] };
72051
72120
  const viewerCertificate = acmCertificateArn ? {
72052
72121
  ACMCertificateArn: acmCertificateArn,
72053
72122
  SSLSupportMethod: "sni-only",
72054
72123
  MinimumProtocolVersion: "TLSv1.2_2021"
72055
72124
  } : void 0;
72056
- const functionAssociations = urlRewriteFunctionArn ? { Quantity: 1, Items: [{ FunctionARN: urlRewriteFunctionArn, EventType: "viewer-request" }] } : { Quantity: 0, Items: [] };
72125
+ const functionAssociations = !lambdaEdgeArn && urlRewriteFunctionArn ? { Quantity: 1, Items: [{ FunctionARN: urlRewriteFunctionArn, EventType: "viewer-request" }] } : { Quantity: 0, Items: [] };
72126
+ const lambdaFunctionAssociations = lambdaEdgeArn ? { Quantity: 1, Items: [{ EventType: "viewer-request", LambdaFunctionARN: lambdaEdgeArn, IncludeBody: false }] } : { Quantity: 0, Items: [] };
72057
72127
  const comment = makeDistComment(project2, stage, handlerName);
72058
72128
  const originId = `S3-${bucketName}`;
72059
72129
  const originDomain = `${bucketName}.s3.${bucketRegion}.amazonaws.com`;
@@ -72087,7 +72157,8 @@ var ensureDistribution = (input) => Effect_exports.gen(function* () {
72087
72157
  const desiredAliases = aliases ?? [];
72088
72158
  const aliasesMatch = currentAliases.length === desiredAliases.length && desiredAliases.every((a) => currentAliases.includes(a));
72089
72159
  const certMatch = currentConfig.ViewerCertificate?.ACMCertificateArn === (acmCertificateArn ?? void 0);
72090
- const needsUpdate = currentOrigin?.DomainName !== originDomain || currentOrigin?.OriginAccessControlId !== oacId || currentConfig.DefaultRootObject !== index || currentConfig.DefaultCacheBehavior?.CachePolicyId !== CACHING_OPTIMIZED_POLICY_ID || (currentConfig.CustomErrorResponses?.Quantity ?? 0) !== customErrorResponses.Quantity || (currentConfig.DefaultCacheBehavior?.FunctionAssociations?.Quantity ?? 0) !== functionAssociations.Quantity || currentConfig.DefaultCacheBehavior?.FunctionAssociations?.Items?.[0]?.FunctionARN !== (urlRewriteFunctionArn ?? void 0) || !aliasesMatch || !certMatch;
72160
+ const currentLambdaEdgeArn = currentConfig.DefaultCacheBehavior?.LambdaFunctionAssociations?.Items?.[0]?.LambdaFunctionARN;
72161
+ const needsUpdate = currentOrigin?.DomainName !== originDomain || currentOrigin?.OriginAccessControlId !== oacId || currentConfig.DefaultRootObject !== index || currentConfig.DefaultCacheBehavior?.CachePolicyId !== CACHING_OPTIMIZED_POLICY_ID || (currentConfig.CustomErrorResponses?.Quantity ?? 0) !== customErrorResponses.Quantity || (currentConfig.DefaultCacheBehavior?.FunctionAssociations?.Quantity ?? 0) !== functionAssociations.Quantity || currentConfig.DefaultCacheBehavior?.FunctionAssociations?.Items?.[0]?.FunctionARN !== (urlRewriteFunctionArn ?? void 0) || (currentConfig.DefaultCacheBehavior?.LambdaFunctionAssociations?.Quantity ?? 0) !== lambdaFunctionAssociations.Quantity || currentLambdaEdgeArn !== (lambdaEdgeArn ?? void 0) || !aliasesMatch || !certMatch;
72091
72162
  if (needsUpdate) {
72092
72163
  yield* Effect_exports.logDebug(`CloudFront distribution ${existing.Id} config changed, updating...`);
72093
72164
  const etag = configResult.ETag;
@@ -72122,6 +72193,7 @@ var ensureDistribution = (input) => Effect_exports.gen(function* () {
72122
72193
  Compress: true,
72123
72194
  CachePolicyId: CACHING_OPTIMIZED_POLICY_ID,
72124
72195
  FunctionAssociations: functionAssociations,
72196
+ LambdaFunctionAssociations: lambdaFunctionAssociations,
72125
72197
  ForwardedValues: void 0
72126
72198
  },
72127
72199
  Aliases: aliasesConfig,
@@ -72171,7 +72243,8 @@ var ensureDistribution = (input) => Effect_exports.gen(function* () {
72171
72243
  },
72172
72244
  Compress: true,
72173
72245
  CachePolicyId: CACHING_OPTIMIZED_POLICY_ID,
72174
- FunctionAssociations: functionAssociations
72246
+ FunctionAssociations: functionAssociations,
72247
+ LambdaFunctionAssociations: lambdaFunctionAssociations
72175
72248
  },
72176
72249
  Aliases: aliasesConfig,
72177
72250
  ...viewerCertificate ? { ViewerCertificate: viewerCertificate } : {},
@@ -72443,7 +72516,7 @@ var parseSource = (source) => {
72443
72516
  const project2 = new Project({ useInMemoryFileSystem: true });
72444
72517
  return project2.createSourceFile("input.ts", source);
72445
72518
  };
72446
- var RUNTIME_PROPS = ["onRequest", "onRecord", "onBatchComplete", "onBatch", "onMessage", "setup", "schema", "onError", "deps", "config", "static"];
72519
+ var RUNTIME_PROPS = ["onRequest", "onRecord", "onBatchComplete", "onBatch", "onMessage", "setup", "schema", "onError", "deps", "config", "static", "middleware"];
72447
72520
  var buildConfigWithoutRuntime = (obj) => {
72448
72521
  const props = obj.getProperties().filter((p3) => {
72449
72522
  if (p3.getKind() === SyntaxKind.PropertyAssignment) {
@@ -72557,9 +72630,9 @@ var handlerRegistry = {
72557
72630
  },
72558
72631
  staticSite: {
72559
72632
  defineFn: "defineStaticSite",
72560
- handlerProps: [],
72561
- wrapperFn: "",
72562
- wrapperPath: ""
72633
+ handlerProps: ["middleware"],
72634
+ wrapperFn: "wrapMiddleware",
72635
+ wrapperPath: "~/runtime/wrap-middleware"
72563
72636
  },
72564
72637
  fifoQueue: {
72565
72638
  defineFn: "defineFifoQueue",
@@ -73024,8 +73097,6 @@ var deployTableFunction = ({ input, fn: fn2, layerArn, external, depsEnv, depsPe
73024
73097
  const tableName = `${input.project}-${tagCtx.stage}-${handlerName}`;
73025
73098
  const { tableArn, streamArn } = yield* ensureTable({
73026
73099
  name: tableName,
73027
- pk: config2.pk,
73028
- sk: config2.sk,
73029
73100
  billingMode: config2.billingMode ?? "PAY_PER_REQUEST",
73030
73101
  streamView: config2.streamView ?? "NEW_AND_OLD_IMAGES",
73031
73102
  tags: makeTags(tagCtx, "dynamodb")
@@ -73157,13 +73228,54 @@ var deployAppLambda = ({ input, fn: fn2, layerArn, external, depsEnv, depsPermis
73157
73228
  });
73158
73229
 
73159
73230
  // src/deploy/deploy-static-site.ts
73231
+ import { Architecture as Architecture3 } from "@aws-sdk/client-lambda";
73160
73232
  import { execSync as execSync2 } from "child_process";
73161
73233
  import * as path8 from "path";
73234
+ var deployMiddlewareLambda = (input) => Effect_exports.gen(function* () {
73235
+ const { projectDir, project: project2, stage, handlerName, file: file7, exportName, tagCtx } = input;
73236
+ const middlewareName = `${handlerName}-middleware`;
73237
+ yield* Effect_exports.logDebug(`Deploying middleware Lambda@Edge: ${middlewareName}`);
73238
+ const roleArn = yield* ensureEdgeRole(
73239
+ project2,
73240
+ stage,
73241
+ middlewareName,
73242
+ makeTags(tagCtx, "iam-role")
73243
+ );
73244
+ const bundled = yield* bundle({
73245
+ projectDir,
73246
+ file: file7,
73247
+ exportName,
73248
+ type: "staticSite"
73249
+ });
73250
+ const code2 = yield* zip12({ content: bundled });
73251
+ const { functionArn } = yield* ensureLambda({
73252
+ project: project2,
73253
+ stage,
73254
+ name: middlewareName,
73255
+ region: "us-east-1",
73256
+ roleArn,
73257
+ code: code2,
73258
+ memory: 128,
73259
+ timeout: 5,
73260
+ architecture: Architecture3.x86_64,
73261
+ tags: makeTags(tagCtx, "lambda")
73262
+ }).pipe(
73263
+ Effect_exports.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
73264
+ );
73265
+ const { versionArn } = yield* publishVersion(
73266
+ `${project2}-${stage}-${middlewareName}`
73267
+ ).pipe(
73268
+ Effect_exports.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
73269
+ );
73270
+ yield* Effect_exports.logDebug(`Middleware deployed: ${versionArn}`);
73271
+ return { versionArn };
73272
+ });
73162
73273
  var deployStaticSite = (input) => Effect_exports.gen(function* () {
73163
73274
  const { projectDir, project: project2, region, fn: fn2 } = input;
73164
73275
  const { exportName, config: config2 } = fn2;
73165
73276
  const stage = resolveStage(input.stage);
73166
73277
  const handlerName = config2.name ?? exportName;
73278
+ const hasMiddleware = fn2.hasHandler;
73167
73279
  const tagCtx = { project: project2, stage, handler: handlerName };
73168
73280
  if (config2.build) {
73169
73281
  yield* Effect_exports.logDebug(`Building site: ${config2.build}`);
@@ -73173,7 +73285,7 @@ var deployStaticSite = (input) => Effect_exports.gen(function* () {
73173
73285
  });
73174
73286
  }
73175
73287
  const bucketName = `${project2}-${stage}-${handlerName}-site`.toLowerCase();
73176
- const { bucketArn } = yield* ensureBucket({
73288
+ yield* ensureBucket({
73177
73289
  name: bucketName,
73178
73290
  region,
73179
73291
  tags: makeTags(tagCtx, "s3-bucket")
@@ -73201,16 +73313,32 @@ var deployStaticSite = (input) => Effect_exports.gen(function* () {
73201
73313
  }
73202
73314
  }
73203
73315
  const isSpa = config2.spa ?? false;
73204
- const needsUrlRewrite = !isSpa;
73205
- const needsWwwRedirect = !!wwwDomain;
73206
73316
  let urlRewriteFunctionArn;
73207
- if (needsUrlRewrite || needsWwwRedirect) {
73208
- const fnName = needsWwwRedirect ? `${project2}-${stage}-${handlerName}-viewer-req` : `${project2}-${stage}-url-rewrite`;
73209
- const result = yield* ensureViewerRequestFunction(fnName, {
73210
- rewriteUrls: needsUrlRewrite,
73211
- redirectWwwDomain: wwwDomain
73212
- });
73213
- urlRewriteFunctionArn = result.functionArn;
73317
+ let lambdaEdgeArn;
73318
+ if (hasMiddleware && input.file) {
73319
+ const result = yield* deployMiddlewareLambda({
73320
+ projectDir,
73321
+ project: project2,
73322
+ stage,
73323
+ handlerName,
73324
+ file: input.file,
73325
+ exportName,
73326
+ tagCtx
73327
+ }).pipe(
73328
+ Effect_exports.provide(clients_exports.makeClients({ iam: { region: "us-east-1" } }))
73329
+ );
73330
+ lambdaEdgeArn = result.versionArn;
73331
+ } else {
73332
+ const needsUrlRewrite = !isSpa;
73333
+ const needsWwwRedirect = !!wwwDomain;
73334
+ if (needsUrlRewrite || needsWwwRedirect) {
73335
+ const fnName = needsWwwRedirect ? `${project2}-${stage}-${handlerName}-viewer-req` : `${project2}-${stage}-url-rewrite`;
73336
+ const result = yield* ensureViewerRequestFunction(fnName, {
73337
+ rewriteUrls: needsUrlRewrite,
73338
+ redirectWwwDomain: wwwDomain
73339
+ });
73340
+ urlRewriteFunctionArn = result.functionArn;
73341
+ }
73214
73342
  }
73215
73343
  const index = config2.index ?? "index.html";
73216
73344
  const { distributionId, distributionArn, domainName } = yield* ensureDistribution({
@@ -73224,6 +73352,7 @@ var deployStaticSite = (input) => Effect_exports.gen(function* () {
73224
73352
  index,
73225
73353
  tags: makeTags(tagCtx, "cloudfront-distribution"),
73226
73354
  urlRewriteFunctionArn,
73355
+ lambdaEdgeArn,
73227
73356
  aliases,
73228
73357
  acmCertificateArn
73229
73358
  });
@@ -73555,7 +73684,7 @@ var buildAppTasks = (ctx, handlers, apiId, results) => {
73555
73684
  var buildStaticSiteTasks = (ctx, handlers, results) => {
73556
73685
  const tasks = [];
73557
73686
  const { region } = ctx.input;
73558
- for (const { exports } of handlers) {
73687
+ for (const { file: file7, exports } of handlers) {
73559
73688
  for (const fn2 of exports) {
73560
73689
  tasks.push(
73561
73690
  Effect_exports.gen(function* () {
@@ -73564,7 +73693,8 @@ var buildStaticSiteTasks = (ctx, handlers, results) => {
73564
73693
  project: ctx.input.project,
73565
73694
  stage: ctx.input.stage,
73566
73695
  region,
73567
- fn: fn2
73696
+ fn: fn2,
73697
+ ...fn2.hasHandler ? { file: file7 } : {}
73568
73698
  }).pipe(Effect_exports.provide(clients_exports.makeClients({
73569
73699
  s3: { region },
73570
73700
  cloudfront: { region: "us-east-1" },