effortless-aws 0.2.1 → 0.4.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.
package/README.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # effortless-aws
2
2
 
3
- Code-first AWS Lambda framework. Export handlers, deploy to AWS. No infrastructure files needed.
3
+ AWS serverless is the best way to run production software. Lambda gives you 99.95% availability out of the box, scales to zero, handles thousands of concurrent requests, and you never manage a server. DynamoDB, SQS, S3, EventBridge — these are battle-tested building blocks with guarantees most self-hosted infrastructure can't match. The event-driven model maps naturally to real business logic: an order is placed, a file is uploaded, a record changes.
4
+
5
+ The problem is never AWS itself — it's the tooling. CloudFormation templates, IAM policies, Terraform state files, CDK constructs. You end up spending more time on infrastructure plumbing than on your actual product. And even when infrastructure is sorted out, wiring serverless resources together — connecting a Lambda to a DynamoDB stream, granting cross-service permissions, passing table names between functions — is tedious and error-prone.
6
+
7
+ **Effortless** is a TypeScript framework for developers who build on AWS serverless. It handles three things:
8
+
9
+ 1. **Infrastructure from code.** You write handlers, export them, and deploy. The framework derives Lambda functions, API Gateway routes, DynamoDB tables, streams, and IAM roles directly from your TypeScript exports. No config files.
10
+ 2. **Bundling and packaging.** Your code is automatically bundled with [esbuild](https://esbuild.github.io/) — tree-shaken, split per function, with shared dependencies extracted into a common [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html). No build config to maintain.
11
+ 3. **Typed cross-resource communication.** Reference one handler from another with `deps: { orders }` and get a fully typed client injected at runtime, with IAM permissions wired automatically. Serverless resources talk to each other through code, not through copied ARNs and manual policies.
4
12
 
5
13
  ```bash
6
14
  npm install effortless-aws
@@ -9,32 +17,13 @@ npm install effortless-aws
9
17
  ## What it looks like
10
18
 
11
19
  ```typescript
12
- // src/api.ts
13
- import { defineHttp, defineTable, param } from "effortless-aws";
14
-
15
- // DynamoDB table — just export it, get the table
16
- export const orders = defineTable<Order>({
17
- pk: { name: "id", type: "string" },
18
- onRecord: async ({ record, table }) => {
19
- if (record.eventName === "INSERT") {
20
- await table.put({ ...record.new!, status: "confirmed" });
21
- }
22
- },
23
- });
20
+ import { defineHttp } from "effortless-aws";
24
21
 
25
- // HTTP endpoint creates API Gateway + Lambda + route
26
- export const createOrder = defineHttp({
27
- method: "POST",
28
- path: "/orders",
29
- schema: (input) => parseOrder(input),
30
- deps: { orders },
31
- params: { apiKey: param("stripe-key") },
32
- context: async ({ params }) => ({
33
- stripe: new Stripe(params.apiKey),
34
- }),
35
- onRequest: async ({ data, ctx, deps }) => {
36
- await deps.orders.put({ id: crypto.randomUUID(), ...data });
37
- return { status: 201, body: { ok: true } };
22
+ export const hello = defineHttp({
23
+ method: "GET",
24
+ path: "/hello",
25
+ onRequest: async () => {
26
+ return { status: 200, body: { message: "Hello!" } };
38
27
  },
39
28
  });
40
29
  ```
@@ -43,7 +32,7 @@ export const createOrder = defineHttp({
43
32
  npx eff deploy
44
33
  ```
45
34
 
46
- That's it. No YAML, no CloudFormation, no state files.
35
+ That's it — one export, one command. No YAML, no CloudFormation, no state files.
47
36
 
48
37
  ## Why
49
38
 
@@ -69,34 +58,105 @@ Traditional Lambda development splits infrastructure and code across multiple fi
69
58
 
70
59
  **Cold start caching** — `context` factory runs once per cold start, cached across invocations. Put DB connections, SDK clients, config there.
71
60
 
72
- ## Handler types
61
+ ## Examples
73
62
 
74
- ### HTTP
63
+ ### Path params
75
64
 
76
65
  ```typescript
77
66
  export const getUser = defineHttp({
78
67
  method: "GET",
79
68
  path: "/users/{id}",
80
69
  onRequest: async ({ req }) => {
81
- return { status: 200, body: { id: req.params.id } };
70
+ const user = await findUser(req.params.id);
71
+ return { status: 200, body: user };
72
+ },
73
+ });
74
+ ```
75
+
76
+ > Creates: Lambda function, API Gateway `GET /users/{id}` route, IAM execution role.
77
+
78
+ ### Schema validation
79
+
80
+ Works with any validation library — Zod, Effect Schema, or a plain function.
81
+
82
+ ```typescript
83
+ export const createUser = defineHttp({
84
+ method: "POST",
85
+ path: "/users",
86
+ schema: (input) => parseUser(input),
87
+ onRequest: async ({ data }) => {
88
+ // data is typed from schema return type
89
+ return { status: 201, body: { id: data.id } };
90
+ },
91
+ });
92
+ ```
93
+
94
+ ### Context (cold-start cache)
95
+
96
+ `context` runs once per cold start. Put SDK clients, DB connections, config here.
97
+
98
+ ```typescript
99
+ export const listOrders = defineHttp({
100
+ method: "GET",
101
+ path: "/orders",
102
+ context: () => ({
103
+ db: new DatabaseClient(),
104
+ }),
105
+ onRequest: async ({ ctx }) => {
106
+ const orders = await ctx.db.findAll();
107
+ return { status: 200, body: orders };
82
108
  },
83
109
  });
84
110
  ```
85
111
 
86
- ### DynamoDB Table + Stream
112
+ ### SSM params
113
+
114
+ `param("key")` reads from Parameter Store at cold start. Auto IAM, auto caching.
115
+
116
+ ```typescript
117
+ export const charge = defineHttp({
118
+ method: "POST",
119
+ path: "/charge",
120
+ params: { apiKey: param("stripe-key") },
121
+ context: async ({ params }) => ({
122
+ stripe: new Stripe(params.apiKey),
123
+ }),
124
+ onRequest: async ({ ctx, data }) => {
125
+ await ctx.stripe.charges.create(data);
126
+ return { status: 200, body: { ok: true } };
127
+ },
128
+ });
129
+ ```
130
+
131
+ > Creates: Lambda, API Gateway route, IAM role with `ssm:GetParameter` on `/{project}/{stage}/stripe-key`.
132
+
133
+ ### Cross-handler deps
134
+
135
+ `deps: { orders }` auto-wires IAM and injects a typed `TableClient`.
87
136
 
88
137
  ```typescript
89
- export const users = defineTable({
138
+ type Order = { id: string; name: string };
139
+
140
+ export const orders = defineTable<Order>({
90
141
  pk: { name: "id", type: "string" },
91
- sk: { name: "email", type: "string" },
92
- ttlAttribute: "expiresAt",
93
- onRecord: async ({ record, table }) => {
94
- console.log(record.eventName, record.new);
142
+ });
143
+
144
+ export const createOrder = defineHttp({
145
+ method: "POST",
146
+ path: "/orders",
147
+ deps: { orders },
148
+ onRequest: async ({ deps }) => {
149
+ await deps.orders.put({ id: crypto.randomUUID(), name: "New order" });
150
+ return { status: 201, body: { ok: true } };
95
151
  },
96
152
  });
97
153
  ```
98
154
 
99
- ### Table (resource only, no stream)
155
+ > Creates: DynamoDB table, Lambda, API Gateway route. The Lambda's IAM role gets DynamoDB read/write permissions on the `orders` table automatically. Table name is injected via environment variable.
156
+
157
+ ### DynamoDB table (resource only)
158
+
159
+ Export a table — get the DynamoDB resource. No Lambda, no stream.
100
160
 
101
161
  ```typescript
102
162
  export const sessions = defineTable({
@@ -105,6 +165,61 @@ export const sessions = defineTable({
105
165
  });
106
166
  ```
107
167
 
168
+ > Creates: DynamoDB table with TTL enabled. No Lambda, no stream — just the table.
169
+
170
+ ### DynamoDB table with stream
171
+
172
+ Add `onRecord` to process changes. Each record is handled individually with automatic partial batch failure reporting.
173
+
174
+ ```typescript
175
+ type User = { id: string; email: string; name: string };
176
+
177
+ export const users = defineTable<User>({
178
+ pk: { name: "id", type: "string" },
179
+ sk: { name: "email", type: "string" },
180
+ onRecord: async ({ record }) => {
181
+ if (record.eventName === "INSERT") {
182
+ await sendWelcomeEmail(record.new!.email);
183
+ }
184
+ },
185
+ });
186
+ ```
187
+
188
+ > Creates: DynamoDB table with stream enabled, Lambda for stream processing, event source mapping between them, IAM role with DynamoDB read/write permissions. Failed records are reported individually via [partial batch responses](https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-batchfailurereporting).
189
+
190
+ ### Full example
191
+
192
+ Everything together — table, HTTP handler with validation, deps, params, and context.
193
+
194
+ ```typescript
195
+ type Order = { id: string; total: number; chargeId?: string };
196
+
197
+ export const orders = defineTable<Order>({
198
+ pk: { name: "id", type: "string" },
199
+ onRecord: async ({ record }) => {
200
+ if (record.eventName === "INSERT") {
201
+ await notifyWarehouse(record.new!);
202
+ }
203
+ },
204
+ });
205
+
206
+ export const createOrder = defineHttp({
207
+ method: "POST",
208
+ path: "/orders",
209
+ schema: (input) => parseOrder(input),
210
+ deps: { orders },
211
+ params: { apiKey: param("stripe-key") },
212
+ context: async ({ params }) => ({
213
+ stripe: new Stripe(params.apiKey),
214
+ }),
215
+ onRequest: async ({ data, ctx, deps }) => {
216
+ const charge = await ctx.stripe.charges.create({ amount: data.total });
217
+ await deps.orders.put({ id: crypto.randomUUID(), ...data, chargeId: charge.id });
218
+ return { status: 201, body: { ok: true } };
219
+ },
220
+ });
221
+ ```
222
+
108
223
  ## Configuration
109
224
 
110
225
  ```typescript
@@ -113,6 +113,8 @@ var computeTtl = (ttlSeconds = DEFAULT_TTL_SECONDS) => Math.floor(Date.now() / 1
113
113
 
114
114
  // src/runtime/handler-utils.ts
115
115
  import { randomUUID } from "crypto";
116
+ import { readFileSync } from "fs";
117
+ import { join } from "path";
116
118
 
117
119
  // src/runtime/ssm-client.ts
118
120
  import { SSM } from "@aws-sdk/client-ssm";
@@ -253,6 +255,7 @@ var buildParams = async (params) => {
253
255
  }
254
256
  return result;
255
257
  };
258
+ var readStatic = (filePath) => readFileSync(join(process.cwd(), filePath), "utf-8");
256
259
  var createHandlerRuntime = (handler, handlerType) => {
257
260
  const platform = createPlatformClient();
258
261
  const handlerName = process.env.EFF_HANDLER ?? "unknown";
@@ -280,6 +283,7 @@ var createHandlerRuntime = (handler, handlerType) => {
280
283
  if (deps) args.deps = deps;
281
284
  const params = await getParams();
282
285
  if (params) args.params = params;
286
+ if (handler.static) args.readStatic = readStatic;
283
287
  return args;
284
288
  };
285
289
  const logExecution = (startTime, input, output) => {
package/dist/cli/index.js CHANGED
@@ -70498,7 +70498,7 @@ var parseSource = (source) => {
70498
70498
  const project2 = new Project({ useInMemoryFileSystem: true });
70499
70499
  return project2.createSourceFile("input.ts", source);
70500
70500
  };
70501
- var RUNTIME_PROPS = ["onRequest", "onRecord", "onBatchComplete", "onBatch", "context", "schema", "onError", "deps", "params"];
70501
+ var RUNTIME_PROPS = ["onRequest", "onRecord", "onBatchComplete", "onBatch", "context", "schema", "onError", "deps", "params", "static"];
70502
70502
  var buildConfigWithoutRuntime = (obj) => {
70503
70503
  const props = obj.getProperties().filter((p3) => {
70504
70504
  if (p3.getKind() === SyntaxKind.PropertyAssignment) {
@@ -70572,6 +70572,19 @@ var extractParamEntries = (obj) => {
70572
70572
  }
70573
70573
  return entries2;
70574
70574
  };
70575
+ var extractStaticGlobs = (obj) => {
70576
+ const staticProp = obj.getProperties().find((p3) => {
70577
+ if (p3.getKind() === SyntaxKind.PropertyAssignment) {
70578
+ return p3.getName() === "static";
70579
+ }
70580
+ return false;
70581
+ });
70582
+ if (!staticProp || staticProp.getKind() !== SyntaxKind.PropertyAssignment) return [];
70583
+ const init = staticProp.getInitializer();
70584
+ if (!init || init.getKind() !== SyntaxKind.ArrayLiteralExpression) return [];
70585
+ const arrayLiteral = init;
70586
+ return arrayLiteral.getElements().filter((e) => e.getKind() === SyntaxKind.StringLiteral).map((e) => e.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue());
70587
+ };
70575
70588
  var handlerRegistry = {
70576
70589
  http: {
70577
70590
  defineFn: "defineHttp",
@@ -70584,6 +70597,12 @@ var handlerRegistry = {
70584
70597
  handlerProps: ["onRecord", "onBatch"],
70585
70598
  wrapperFn: "wrapTableStream",
70586
70599
  wrapperPath: "~/runtime/wrap-table-stream"
70600
+ },
70601
+ site: {
70602
+ defineFn: "defineSite",
70603
+ handlerProps: [],
70604
+ wrapperFn: "wrapSite",
70605
+ wrapperPath: "~/runtime/wrap-site"
70587
70606
  }
70588
70607
  };
70589
70608
  var extractHandlerConfigs = (source, type2) => {
@@ -70605,7 +70624,8 @@ var extractHandlerConfigs = (source, type2) => {
70605
70624
  const hasHandler = handlerProps.some((p3) => extractPropertyFromObject(objLiteral, p3) !== void 0);
70606
70625
  const depsKeys = extractDepsKeys(objLiteral);
70607
70626
  const paramEntries = extractParamEntries(objLiteral);
70608
- results.push({ exportName: "default", config: configObj, hasHandler, depsKeys, paramEntries });
70627
+ const staticGlobs = extractStaticGlobs(objLiteral);
70628
+ results.push({ exportName: "default", config: configObj, hasHandler, depsKeys, paramEntries, staticGlobs });
70609
70629
  }
70610
70630
  }
70611
70631
  }
@@ -70626,7 +70646,8 @@ var extractHandlerConfigs = (source, type2) => {
70626
70646
  const hasHandler = handlerProps.some((p3) => extractPropertyFromObject(objLiteral, p3) !== void 0);
70627
70647
  const depsKeys = extractDepsKeys(objLiteral);
70628
70648
  const paramEntries = extractParamEntries(objLiteral);
70629
- results.push({ exportName: decl.getName(), config: configObj, hasHandler, depsKeys, paramEntries });
70649
+ const staticGlobs = extractStaticGlobs(objLiteral);
70650
+ results.push({ exportName: decl.getName(), config: configObj, hasHandler, depsKeys, paramEntries, staticGlobs });
70630
70651
  }
70631
70652
  });
70632
70653
  });
@@ -70646,6 +70667,7 @@ export const handler = ${wrapperFn}(${importName});
70646
70667
  // src/build/bundle.ts
70647
70668
  var extractConfigs = (source) => extractHandlerConfigs(source, "http");
70648
70669
  var extractTableConfigs = (source) => extractHandlerConfigs(source, "table");
70670
+ var extractSiteConfigs = (source) => extractHandlerConfigs(source, "site");
70649
70671
  var runtimeDir = path5.resolve(path5.dirname(fileURLToPath2(import.meta.url)), "../../dist/runtime");
70650
70672
  var bundle = (input) => Effect_exports.gen(function* () {
70651
70673
  const exportName = input.exportName ?? "default";
@@ -70687,8 +70709,27 @@ var zip12 = (input) => Effect_exports.async((resume2) => {
70687
70709
  archive.on("end", () => resume2(Effect_exports.succeed(Buffer.concat(chunks2))));
70688
70710
  archive.on("error", (err) => resume2(Effect_exports.fail(err)));
70689
70711
  archive.append(input.content, { name: input.filename ?? "index.mjs", date: FIXED_DATE2 });
70712
+ if (input.staticFiles) {
70713
+ for (const file6 of input.staticFiles) {
70714
+ archive.append(file6.content, { name: file6.zipPath, date: FIXED_DATE2 });
70715
+ }
70716
+ }
70690
70717
  archive.finalize();
70691
70718
  });
70719
+ var resolveStaticFiles = (globs, projectDir) => {
70720
+ const files = [];
70721
+ for (const pattern2 of globs) {
70722
+ const matches = globSync(pattern2, { cwd: projectDir });
70723
+ for (const match18 of matches) {
70724
+ const absPath = path5.join(projectDir, match18);
70725
+ files.push({
70726
+ content: fsSync2.readFileSync(absPath),
70727
+ zipPath: match18
70728
+ });
70729
+ }
70730
+ }
70731
+ return files;
70732
+ };
70692
70733
  var findHandlerFiles = (patterns, cwd) => {
70693
70734
  const files = /* @__PURE__ */ new Set();
70694
70735
  for (const pattern2 of patterns) {
@@ -70700,15 +70741,18 @@ var findHandlerFiles = (patterns, cwd) => {
70700
70741
  var discoverHandlers = (files) => {
70701
70742
  const httpHandlers = [];
70702
70743
  const tableHandlers = [];
70744
+ const siteHandlers = [];
70703
70745
  for (const file6 of files) {
70704
70746
  if (!fsSync2.statSync(file6).isFile()) continue;
70705
70747
  const source = fsSync2.readFileSync(file6, "utf-8");
70706
70748
  const http = extractConfigs(source);
70707
70749
  const table3 = extractTableConfigs(source);
70750
+ const site = extractSiteConfigs(source);
70708
70751
  if (http.length > 0) httpHandlers.push({ file: file6, exports: http });
70709
70752
  if (table3.length > 0) tableHandlers.push({ file: file6, exports: table3 });
70753
+ if (site.length > 0) siteHandlers.push({ file: file6, exports: site });
70710
70754
  }
70711
- return { httpHandlers, tableHandlers };
70755
+ return { httpHandlers, tableHandlers, siteHandlers };
70712
70756
  };
70713
70757
 
70714
70758
  // src/deploy/shared.ts
@@ -70750,7 +70794,8 @@ var deployCoreLambda = ({
70750
70794
  layerArn,
70751
70795
  external,
70752
70796
  depsEnv,
70753
- depsPermissions
70797
+ depsPermissions,
70798
+ staticGlobs
70754
70799
  }) => Effect_exports.gen(function* () {
70755
70800
  const tagCtx = {
70756
70801
  project: input.project,
@@ -70779,7 +70824,8 @@ var deployCoreLambda = ({
70779
70824
  ...bundleType ? { type: bundleType } : {},
70780
70825
  ...external && external.length > 0 ? { external } : {}
70781
70826
  });
70782
- const code2 = yield* zip12({ content: bundled });
70827
+ const staticFiles = staticGlobs && staticGlobs.length > 0 ? resolveStaticFiles(staticGlobs, input.projectDir) : void 0;
70828
+ const code2 = yield* zip12({ content: bundled, staticFiles });
70783
70829
  const environment2 = {
70784
70830
  EFF_PROJECT: input.project,
70785
70831
  EFF_STAGE: tagCtx.stage,
@@ -70803,7 +70849,7 @@ var deployCoreLambda = ({
70803
70849
  });
70804
70850
 
70805
70851
  // src/deploy/deploy-http.ts
70806
- var deployLambda = ({ input, fn: fn2, layerArn, external, depsEnv, depsPermissions }) => Effect_exports.gen(function* () {
70852
+ var deployLambda = ({ input, fn: fn2, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect_exports.gen(function* () {
70807
70853
  const { exportName, config: config2 } = fn2;
70808
70854
  const handlerName = config2.name ?? exportName;
70809
70855
  const { functionArn } = yield* deployCoreLambda({
@@ -70817,7 +70863,8 @@ var deployLambda = ({ input, fn: fn2, layerArn, external, depsEnv, depsPermissio
70817
70863
  ...layerArn ? { layerArn } : {},
70818
70864
  ...external ? { external } : {},
70819
70865
  ...depsEnv ? { depsEnv } : {},
70820
- ...depsPermissions ? { depsPermissions } : {}
70866
+ ...depsPermissions ? { depsPermissions } : {},
70867
+ ...staticGlobs && staticGlobs.length > 0 ? { staticGlobs } : {}
70821
70868
  });
70822
70869
  return { exportName, functionArn, config: config2, handlerName };
70823
70870
  });
@@ -70940,7 +70987,7 @@ var deployAll = (input) => Effect_exports.gen(function* () {
70940
70987
 
70941
70988
  // src/deploy/deploy-table.ts
70942
70989
  var TABLE_DEFAULT_PERMISSIONS = ["dynamodb:*", "logs:*"];
70943
- var deployTableFunction = ({ input, fn: fn2, layerArn, external, depsEnv, depsPermissions }) => Effect_exports.gen(function* () {
70990
+ var deployTableFunction = ({ input, fn: fn2, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect_exports.gen(function* () {
70944
70991
  const { exportName, config: config2 } = fn2;
70945
70992
  const handlerName = config2.name ?? exportName;
70946
70993
  const tagCtx = {
@@ -70971,7 +71018,8 @@ var deployTableFunction = ({ input, fn: fn2, layerArn, external, depsEnv, depsPe
70971
71018
  ...layerArn ? { layerArn } : {},
70972
71019
  ...external ? { external } : {},
70973
71020
  depsEnv: selfEnv,
70974
- ...depsPermissions ? { depsPermissions } : {}
71021
+ ...depsPermissions ? { depsPermissions } : {},
71022
+ ...staticGlobs && staticGlobs.length > 0 ? { staticGlobs } : {}
70975
71023
  });
70976
71024
  yield* Effect_exports.logInfo("Setting up event source mapping...");
70977
71025
  yield* ensureEventSourceMapping({
@@ -71053,6 +71101,35 @@ var deployAllTables = (input) => Effect_exports.gen(function* () {
71053
71101
  )
71054
71102
  );
71055
71103
 
71104
+ // src/deploy/deploy-site.ts
71105
+ import { execSync } from "child_process";
71106
+ var deploySiteLambda = ({ input, fn: fn2, layerArn, external, depsEnv, depsPermissions }) => Effect_exports.gen(function* () {
71107
+ const { exportName, config: config2 } = fn2;
71108
+ const handlerName = config2.name ?? exportName;
71109
+ if (config2.build) {
71110
+ yield* Effect_exports.logInfo(`Building site: ${config2.build}`);
71111
+ yield* Effect_exports.try({
71112
+ try: () => execSync(config2.build, { cwd: input.projectDir, stdio: "inherit" }),
71113
+ catch: (error4) => new Error(`Site build failed: ${error4}`)
71114
+ });
71115
+ }
71116
+ const staticGlobs = [`${config2.dir}/**/*`];
71117
+ const { functionArn } = yield* deployCoreLambda({
71118
+ input,
71119
+ exportName,
71120
+ handlerName,
71121
+ bundleType: "site",
71122
+ ...config2.memory ? { memory: config2.memory } : {},
71123
+ timeout: config2.timeout ?? 5,
71124
+ ...layerArn ? { layerArn } : {},
71125
+ ...external ? { external } : {},
71126
+ ...depsEnv ? { depsEnv } : {},
71127
+ ...depsPermissions ? { depsPermissions } : {},
71128
+ staticGlobs
71129
+ });
71130
+ return { exportName, functionArn, config: config2, handlerName };
71131
+ });
71132
+
71056
71133
  // src/deploy/deploy.ts
71057
71134
  var prepareLayer = (input) => Effect_exports.gen(function* () {
71058
71135
  const layerResult = yield* ensureLayer({
@@ -71184,7 +71261,8 @@ var deployHttpHandlers = (ctx) => Effect_exports.gen(function* () {
71184
71261
  ...ctx.layerArn ? { layerArn: ctx.layerArn } : {},
71185
71262
  ...ctx.external.length > 0 ? { external: ctx.external } : {},
71186
71263
  depsEnv: withPlatform.depsEnv,
71187
- depsPermissions: withPlatform.depsPermissions
71264
+ depsPermissions: withPlatform.depsPermissions,
71265
+ ...fn2.staticGlobs.length > 0 ? { staticGlobs: fn2.staticGlobs } : {}
71188
71266
  }).pipe(
71189
71267
  Effect_exports.provide(
71190
71268
  clients_exports.makeClients({
@@ -71240,7 +71318,8 @@ var deployTableHandlers = (ctx) => Effect_exports.gen(function* () {
71240
71318
  ...ctx.layerArn ? { layerArn: ctx.layerArn } : {},
71241
71319
  ...ctx.external.length > 0 ? { external: ctx.external } : {},
71242
71320
  depsEnv: withPlatform.depsEnv,
71243
- depsPermissions: withPlatform.depsPermissions
71321
+ depsPermissions: withPlatform.depsPermissions,
71322
+ ...fn2.staticGlobs.length > 0 ? { staticGlobs: fn2.staticGlobs } : {}
71244
71323
  }).pipe(
71245
71324
  Effect_exports.provide(
71246
71325
  clients_exports.makeClients({
@@ -71255,19 +71334,86 @@ var deployTableHandlers = (ctx) => Effect_exports.gen(function* () {
71255
71334
  }
71256
71335
  return results;
71257
71336
  });
71337
+ var deploySiteHandlers = (ctx) => Effect_exports.gen(function* () {
71338
+ const results = [];
71339
+ for (const { file: file6, exports } of ctx.handlers) {
71340
+ yield* Effect_exports.logInfo(`Processing ${path7.basename(file6)} (${exports.length} site handler(s))`);
71341
+ const deployInput = {
71342
+ projectDir: ctx.input.projectDir,
71343
+ file: file6,
71344
+ project: ctx.input.project,
71345
+ region: ctx.input.region
71346
+ };
71347
+ if (ctx.input.stage) deployInput.stage = ctx.input.stage;
71348
+ for (const fn2 of exports) {
71349
+ const withPlatform = {
71350
+ depsEnv: { ...ctx.platformEnv },
71351
+ depsPermissions: [...ctx.platformPermissions]
71352
+ };
71353
+ const { exportName, functionArn, config: config2, handlerName } = yield* deploySiteLambda({
71354
+ input: deployInput,
71355
+ fn: fn2,
71356
+ ...ctx.layerArn ? { layerArn: ctx.layerArn } : {},
71357
+ ...ctx.external.length > 0 ? { external: ctx.external } : {},
71358
+ depsEnv: withPlatform.depsEnv,
71359
+ depsPermissions: withPlatform.depsPermissions
71360
+ }).pipe(
71361
+ Effect_exports.provide(
71362
+ clients_exports.makeClients({
71363
+ lambda: { region: ctx.input.region },
71364
+ iam: { region: ctx.input.region }
71365
+ })
71366
+ )
71367
+ );
71368
+ const basePath = config2.path.replace(/\/+$/, "") || "/";
71369
+ const { apiUrl: rootUrl } = yield* addRouteToApi({
71370
+ apiId: ctx.apiId,
71371
+ region: ctx.input.region,
71372
+ functionArn,
71373
+ method: "GET",
71374
+ path: basePath
71375
+ }).pipe(
71376
+ Effect_exports.provide(
71377
+ clients_exports.makeClients({
71378
+ lambda: { region: ctx.input.region },
71379
+ apigatewayv2: { region: ctx.input.region }
71380
+ })
71381
+ )
71382
+ );
71383
+ yield* addRouteToApi({
71384
+ apiId: ctx.apiId,
71385
+ region: ctx.input.region,
71386
+ functionArn,
71387
+ method: "GET",
71388
+ path: `${basePath}/{file+}`
71389
+ }).pipe(
71390
+ Effect_exports.provide(
71391
+ clients_exports.makeClients({
71392
+ lambda: { region: ctx.input.region },
71393
+ apigatewayv2: { region: ctx.input.region }
71394
+ })
71395
+ )
71396
+ );
71397
+ results.push({ exportName, url: rootUrl, functionArn });
71398
+ yield* Effect_exports.logInfo(` GET ${basePath} \u2192 ${handlerName} (site)`);
71399
+ }
71400
+ }
71401
+ return results;
71402
+ });
71258
71403
  var deployProject = (input) => Effect_exports.gen(function* () {
71259
71404
  const files = findHandlerFiles(input.patterns, input.projectDir);
71260
71405
  if (files.length === 0) {
71261
71406
  return yield* Effect_exports.fail(new Error(`No files match patterns: ${input.patterns.join(", ")}`));
71262
71407
  }
71263
71408
  yield* Effect_exports.logInfo(`Found ${files.length} file(s) matching patterns`);
71264
- const { httpHandlers, tableHandlers } = discoverHandlers(files);
71409
+ const { httpHandlers, tableHandlers, siteHandlers } = discoverHandlers(files);
71265
71410
  const totalHttpHandlers = httpHandlers.reduce((acc, h) => acc + h.exports.length, 0);
71266
71411
  const totalTableHandlers = tableHandlers.reduce((acc, h) => acc + h.exports.length, 0);
71267
- if (totalHttpHandlers === 0 && totalTableHandlers === 0) {
71412
+ const totalSiteHandlers = siteHandlers.reduce((acc, h) => acc + h.exports.length, 0);
71413
+ if (totalHttpHandlers === 0 && totalTableHandlers === 0 && totalSiteHandlers === 0) {
71268
71414
  return yield* Effect_exports.fail(new Error("No handlers found in matched files"));
71269
71415
  }
71270
- yield* Effect_exports.logInfo(`Discovered ${totalHttpHandlers} HTTP handler(s) and ${totalTableHandlers} table handler(s)`);
71416
+ yield* Effect_exports.logInfo(`Discovered ${totalHttpHandlers} HTTP, ${totalTableHandlers} table, ${totalSiteHandlers} site handler(s)`);
71271
71417
  const tableNameMap = buildTableNameMap(tableHandlers, input.project, resolveStage(input.stage));
71272
71418
  const { layerArn, external } = yield* prepareLayer({
71273
71419
  project: input.project,
@@ -71280,7 +71426,7 @@ var deployProject = (input) => Effect_exports.gen(function* () {
71280
71426
  const platformEnv = { EFF_PLATFORM_TABLE: platformTableName };
71281
71427
  let apiId;
71282
71428
  let apiUrl;
71283
- if (totalHttpHandlers > 0) {
71429
+ if (totalHttpHandlers > 0 || totalSiteHandlers > 0) {
71284
71430
  const tagCtx = {
71285
71431
  project: input.project,
71286
71432
  stage: resolveStage(input.stage),
@@ -71321,10 +71467,19 @@ var deployProject = (input) => Effect_exports.gen(function* () {
71321
71467
  platformEnv,
71322
71468
  platformPermissions: PLATFORM_PERMISSIONS
71323
71469
  });
71470
+ const siteResults = apiId ? yield* deploySiteHandlers({
71471
+ handlers: siteHandlers,
71472
+ apiId,
71473
+ input,
71474
+ layerArn,
71475
+ external,
71476
+ platformEnv,
71477
+ platformPermissions: PLATFORM_PERMISSIONS
71478
+ }) : [];
71324
71479
  if (apiUrl) {
71325
71480
  yield* Effect_exports.logInfo(`Deployment complete! API: ${apiUrl}`);
71326
71481
  }
71327
- return { apiId, apiUrl, httpResults, tableResults };
71482
+ return { apiId, apiUrl, httpResults, tableResults, siteResults };
71328
71483
  });
71329
71484
 
71330
71485
  // src/cli/config.ts
@@ -71443,7 +71598,7 @@ var deployCommand = Command_exports.make(
71443
71598
  stage: finalStage,
71444
71599
  region: finalRegion
71445
71600
  });
71446
- const total = results.httpResults.length + results.tableResults.length;
71601
+ const total = results.httpResults.length + results.tableResults.length + results.siteResults.length;
71447
71602
  yield* Console_exports.log(`
71448
71603
  Deployed ${total} handler(s):`);
71449
71604
  for (const r of results.httpResults) {
@@ -71452,6 +71607,9 @@ Deployed ${total} handler(s):`);
71452
71607
  for (const r of results.tableResults) {
71453
71608
  yield* Console_exports.log(` [table] ${r.exportName}: ${r.tableArn}`);
71454
71609
  }
71610
+ for (const r of results.siteResults) {
71611
+ yield* Console_exports.log(` [site] ${r.exportName}: ${r.url}`);
71612
+ }
71455
71613
  }),
71456
71614
  onSome: (targetValue) => Effect_exports.gen(function* () {
71457
71615
  if (isFilePath(targetValue)) {
@@ -71504,7 +71662,7 @@ Deployed ${tableResults.length} table handler(s):`);
71504
71662
  const discovered = discoverHandlers(files);
71505
71663
  let foundFile = null;
71506
71664
  let foundExport = null;
71507
- let isTableHandler = false;
71665
+ let handlerType = "http";
71508
71666
  for (const { file: file6, exports } of discovered.httpHandlers) {
71509
71667
  for (const { exportName, config: handlerConfig } of exports) {
71510
71668
  if (handlerConfig.name === targetValue) {
@@ -71521,7 +71679,20 @@ Deployed ${tableResults.length} table handler(s):`);
71521
71679
  if (handlerConfig.name === targetValue) {
71522
71680
  foundFile = file6;
71523
71681
  foundExport = exportName;
71524
- isTableHandler = true;
71682
+ handlerType = "table";
71683
+ break;
71684
+ }
71685
+ }
71686
+ if (foundFile) break;
71687
+ }
71688
+ }
71689
+ if (!foundFile) {
71690
+ for (const { file: file6, exports } of discovered.siteHandlers) {
71691
+ for (const { exportName, config: handlerConfig } of exports) {
71692
+ if (handlerConfig.name === targetValue) {
71693
+ foundFile = file6;
71694
+ foundExport = exportName;
71695
+ handlerType = "site";
71525
71696
  break;
71526
71697
  }
71527
71698
  }
@@ -71541,6 +71712,11 @@ Deployed ${tableResults.length} table handler(s):`);
71541
71712
  yield* Console_exports.log(` [table] ${c.name}`);
71542
71713
  }
71543
71714
  }
71715
+ for (const { exports } of discovered.siteHandlers) {
71716
+ for (const { config: c } of exports) {
71717
+ yield* Console_exports.log(` [site] ${c.name}`);
71718
+ }
71719
+ }
71544
71720
  return;
71545
71721
  }
71546
71722
  yield* Console_exports.log(`Found handler "${targetValue}" in ${path9.relative(projectDir, foundFile)}`);
@@ -71552,7 +71728,7 @@ Deployed ${tableResults.length} table handler(s):`);
71552
71728
  region: finalRegion,
71553
71729
  exportName: foundExport
71554
71730
  };
71555
- if (isTableHandler) {
71731
+ if (handlerType === "table") {
71556
71732
  const result = yield* deployTable(input);
71557
71733
  yield* Console_exports.log(`
71558
71734
  Table deployed: ${result.tableArn}`);