effortless-aws 0.16.1 → 0.17.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,9 +1,8 @@
1
1
  # effortless-aws
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/effortless-aws)](https://www.npmjs.com/package/effortless-aws)
4
- [![npm downloads](https://img.shields.io/npm/dw/effortless-aws)](https://www.npmjs.com/package/effortless-aws)
5
4
 
6
- TypeScript framework for AWS serverless. Export handlers, deploy with one command. No YAML, no CloudFormation, no state files.
5
+ Code-first AWS Lambda framework. Export handlers, deploy with one command. No YAML, no CloudFormation, no state files.
7
6
 
8
7
  ```bash
9
8
  npm install effortless-aws
@@ -23,26 +22,33 @@ export const hello = defineHttp({
23
22
  });
24
23
  ```
25
24
 
26
- ```bash
27
- npx eff deploy
28
- ```
25
+ ## Handlers
29
26
 
30
- One export, one command. Lambda, API Gateway route, and IAM role created automatically.
27
+ | Handler | Description |
28
+ |---------|-------------|
29
+ | `defineHttp` | HTTP endpoint via API Gateway |
30
+ | `defineApi` | REST API with typed GET/POST routes |
31
+ | `defineApp` | Generic Lambda (cron, custom events) |
32
+ | `defineTable` | DynamoDB table with stream processing |
33
+ | `defineFifoQueue` | SQS FIFO queue consumer |
34
+ | `defineBucket` | S3 bucket with event triggers |
35
+ | `defineMailer` | SES email sending |
36
+ | `defineStaticSite` | CloudFront + S3 static site with optional middleware |
31
37
 
32
38
  ## Features
33
39
 
34
- - **Infrastructure from code** — export a handler, get the AWS resources. No config files.
35
- - **Typed everything** — `defineTable<Order>` gives you typed `put()`, typed `deps.orders.get()`, typed `record.new`.
36
- - **Direct AWS SDK deploys** — no CloudFormation. Deploy in ~5-10s, not minutes.
37
- - **No state files** — AWS resource tags are the source of truth.
38
- - **Cross-handler deps** — `deps: { orders }` auto-wires IAM and injects a typed `TableClient`.
39
- - **SSM params** — `param("stripe-key")` fetches from Parameter Store at cold start. Auto IAM, auto caching.
40
- - **Partial batch failures** — DynamoDB stream processing reports failed records individually.
41
- - **Cold start caching** — `context` factory runs once per cold start, cached across invocations.
40
+ - **Infrastructure from code** — export a handler, get the AWS resources
41
+ - **Typed everything** — `defineTable<Order>` gives you typed `put()`, typed `deps.orders.get()`, typed `record.new`
42
+ - **Cross-handler deps** — `deps: { orders }` auto-wires IAM and injects a typed `TableClient`
43
+ - **SSM params** — `param("stripe-key")` fetches from Parameter Store at cold start
44
+ - **Static files** — `static: ["templates/*.ejs"]` bundles files into the Lambda ZIP
45
+ - **Cold start caching** — `setup` factory runs once per cold start, cached across invocations
46
+
47
+ Deploy with [`@effortless-aws/cli`](https://www.npmjs.com/package/@effortless-aws/cli).
42
48
 
43
49
  ## Documentation
44
50
 
45
- Full docs, examples, and API reference: **[effortless-aws docs](https://effortless-aws.website)**
51
+ Full docs, examples, and API reference: **[effortless-aws.website](https://effortless-aws.website)**
46
52
 
47
53
  ## License
48
54
 
package/dist/index.d.ts CHANGED
@@ -415,7 +415,7 @@ type FailedRecord<T = Record<string, unknown>> = {
415
415
  * Always receives `table: TableClient<T>` (self-client for the handler's own table).
416
416
  * Also receives `deps` and/or `config` when declared.
417
417
  */
418
- type SetupFactory$3<C, T, D, P, S extends string[] | undefined = undefined> = (args: {
418
+ type SetupFactory$4<C, T, D, P, S extends string[] | undefined = undefined> = (args: {
419
419
  table: TableClient<T>;
420
420
  } & ([D] extends [undefined] ? {} : {
421
421
  deps: ResolveDeps<D>;
@@ -491,7 +491,7 @@ type DefineTableBase<T = Record<string, unknown>, C = undefined, D = undefined,
491
491
  * When deps/params are declared, receives them as argument.
492
492
  * Supports both sync and async return values.
493
493
  */
494
- setup?: SetupFactory$3<C, T, D, P, S>;
494
+ setup?: SetupFactory$4<C, T, D, P, S>;
495
495
  /**
496
496
  * Dependencies on other handlers (tables, queues, etc.).
497
497
  * Typed clients are injected into the handler via the `deps` argument.
@@ -661,7 +661,7 @@ type BucketObjectRemovedFn<C = undefined, D = undefined, P = undefined, S extend
661
661
  * Always receives `bucket: BucketClient` (self-client for the handler's own bucket).
662
662
  * Also receives `deps` and/or `config` when declared.
663
663
  */
664
- type SetupFactory$2<C, D, P, S extends string[] | undefined = undefined> = (args: {
664
+ type SetupFactory$3<C, D, P, S extends string[] | undefined = undefined> = (args: {
665
665
  bucket: BucketClient;
666
666
  } & ([D] extends [undefined] ? {} : {
667
667
  deps: ResolveDeps<D>;
@@ -683,7 +683,7 @@ type DefineBucketBase<C = undefined, D = undefined, P = undefined, S extends str
683
683
  * Always receives `bucket: BucketClient` (self-client). When deps/config
684
684
  * are declared, receives them as well.
685
685
  */
686
- setup?: SetupFactory$2<C, D, P, S>;
686
+ setup?: SetupFactory$3<C, D, P, S>;
687
687
  /**
688
688
  * Dependencies on other handlers (tables, buckets, etc.).
689
689
  * Typed clients are injected into the handler via the `deps` argument.
@@ -853,7 +853,7 @@ type ResolveDeps<D> = {
853
853
  };
854
854
 
855
855
  /** HTTP methods supported by API Gateway */
856
- type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
856
+ type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "ANY";
857
857
  /** Short content-type aliases for common response formats */
858
858
  type ContentType = "json" | "html" | "text" | "css" | "js" | "xml" | "csv" | "svg";
859
859
  /**
@@ -932,7 +932,7 @@ type HttpHandlerFn<T = undefined, C = undefined, D = undefined, P = undefined, S
932
932
  * Setup factory type — always receives an args object.
933
933
  * Args include `deps` and/or `config` when declared (empty `{}` otherwise).
934
934
  */
935
- type SetupFactory$1<C, D, P, S extends string[] | undefined = undefined> = (args: ([D] extends [undefined] ? {} : {
935
+ type SetupFactory$2<C, D, P, S extends string[] | undefined = undefined> = (args: ([D] extends [undefined] ? {} : {
936
936
  deps: ResolveDeps<D>;
937
937
  }) & ([P] extends [undefined] ? {} : {
938
938
  config: ResolveConfig<P & {}>;
@@ -970,7 +970,7 @@ type DefineHttpOptions<T = undefined, C = undefined, D extends Record<string, An
970
970
  * When deps/params are declared, receives them as argument.
971
971
  * Supports both sync and async return values.
972
972
  */
973
- setup?: SetupFactory$1<C, D, P, S>;
973
+ setup?: SetupFactory$2<C, D, P, S>;
974
974
  /**
975
975
  * Dependencies on other handlers (tables, queues, etc.).
976
976
  * Typed clients are injected into the handler via the `deps` argument.
@@ -1046,23 +1046,27 @@ type HttpHandler<T = undefined, C = undefined, D = undefined, P = undefined, S e
1046
1046
  * })
1047
1047
  * });
1048
1048
  * ```
1049
+ *
1050
+ * @see {@link defineApi} for multi-route endpoints with typed GET routes and POST command handling (CQRS pattern)
1049
1051
  */
1050
1052
  declare const defineHttp: <T = undefined, C = undefined, D extends Record<string, AnyDepHandler> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined, S extends string[] | undefined = undefined>(options: DefineHttpOptions<T, C, D, P, S>) => HttpHandler<T, C, D, P, S>;
1051
1053
 
1052
1054
  /**
1053
- * Configuration for a Lambda-served static site (API Gateway + Lambda)
1055
+ * Configuration for deploying an SSR framework (Nuxt, Astro, etc.)
1056
+ * via CloudFront + S3 (static assets) + Lambda Function URL (server-side rendering).
1054
1057
  */
1055
- type AppConfig = LambdaConfig & {
1056
- /** Base URL path the site is served under (e.g., "/app") */
1058
+ type AppConfig = LambdaWithPermissions & {
1059
+ /** Directory containing the Lambda server handler (e.g., ".output/server").
1060
+ * Must contain an `index.mjs` (or `index.js`) that exports a `handler` function. */
1061
+ server: string;
1062
+ /** Directory containing static assets for S3 (e.g., ".output/public") */
1063
+ assets: string;
1064
+ /** Base URL path (default: "/") */
1057
1065
  path?: string;
1058
- /** Directory containing the static site files, relative to project root */
1059
- dir: string;
1060
- /** Default file for directory requests (default: "index.html") */
1061
- index?: string;
1062
- /** SPA mode: serve index.html for all paths that don't match a file (default: false) */
1063
- spa?: boolean;
1064
- /** Shell command to run before deploy to generate site content (e.g., "npx astro build") */
1066
+ /** Shell command to build the framework output (e.g., "nuxt build") */
1065
1067
  build?: string;
1068
+ /** Custom domain name. String or stage-keyed record (e.g., { prod: "app.example.com" }). */
1069
+ domain?: string | Record<string, string>;
1066
1070
  };
1067
1071
  /**
1068
1072
  * Internal handler object created by defineApp
@@ -1073,19 +1077,24 @@ type AppHandler = {
1073
1077
  readonly __spec: AppConfig;
1074
1078
  };
1075
1079
  /**
1076
- * Deploy a static site via Lambda + API Gateway.
1077
- * Files are bundled into the Lambda ZIP and served with auto-detected content types.
1080
+ * Deploy an SSR framework application via CloudFront + Lambda Function URL.
1081
+ *
1082
+ * Static assets from the `assets` directory are served via S3 + CloudFront CDN.
1083
+ * Server-rendered pages are handled by a Lambda function using the framework's
1084
+ * built output from the `server` directory.
1078
1085
  *
1079
- * For CDN-backed sites (S3 + CloudFront), use {@link defineStaticSite} instead.
1086
+ * For static-only sites (no SSR), use {@link defineStaticSite} instead.
1080
1087
  *
1081
- * @param options - Site configuration: path, directory, optional SPA mode
1088
+ * @param options - App configuration: server directory, assets directory, optional build command
1082
1089
  * @returns Handler object used by the deployment system
1083
1090
  *
1084
- * @example Basic static site
1091
+ * @example Nuxt SSR
1085
1092
  * ```typescript
1086
1093
  * export const app = defineApp({
1087
- * path: "/app",
1088
- * dir: "src/webapp",
1094
+ * build: "nuxt build",
1095
+ * server: ".output/server",
1096
+ * assets: ".output/public",
1097
+ * memory: 1024,
1089
1098
  * });
1090
1099
  * ```
1091
1100
  */
@@ -1234,7 +1243,7 @@ type FifoQueueConfig = LambdaWithPermissions & {
1234
1243
  * Setup factory type — always receives an args object.
1235
1244
  * Args include `deps` and/or `config` when declared (empty `{}` otherwise).
1236
1245
  */
1237
- type SetupFactory<C, D, P, S extends string[] | undefined = undefined> = (args: ([D] extends [undefined] ? {} : {
1246
+ type SetupFactory$1<C, D, P, S extends string[] | undefined = undefined> = (args: ([D] extends [undefined] ? {} : {
1238
1247
  deps: ResolveDeps<D>;
1239
1248
  }) & ([P] extends [undefined] ? {} : {
1240
1249
  config: ResolveConfig<P & {}>;
@@ -1288,7 +1297,7 @@ type DefineFifoQueueBase<T = unknown, C = undefined, D = undefined, P = undefine
1288
1297
  * Called once on cold start, result is cached and reused across invocations.
1289
1298
  * When deps/params are declared, receives them as argument.
1290
1299
  */
1291
- setup?: SetupFactory<C, D, P, S>;
1300
+ setup?: SetupFactory$1<C, D, P, S>;
1292
1301
  /**
1293
1302
  * Dependencies on other handlers (tables, queues, etc.).
1294
1303
  * Typed clients are injected into the handler via the `deps` argument.
@@ -1364,4 +1373,127 @@ type FifoQueueHandler<T = unknown, C = undefined, D = undefined, P = undefined,
1364
1373
  */
1365
1374
  declare const defineFifoQueue: <T = unknown, C = undefined, D extends Record<string, AnyDepHandler> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined, S extends string[] | undefined = undefined>(options: DefineFifoQueueOptions<T, C, D, P, S>) => FifoQueueHandler<T, C, D, P, S>;
1366
1375
 
1367
- export { type AppConfig, type AppHandler, type BucketClient, type BucketConfig, type BucketEvent, type BucketHandler, type BucketObjectCreatedFn, type BucketObjectRemovedFn, type ContentType, type DefineBucketOptions, type DefineFifoQueueOptions, type DefineHttpOptions, type DefineTableOptions, type EffortlessConfig, type EmailClient, type FailedRecord, type FifoQueueBatchFn, type FifoQueueConfig, type FifoQueueHandler, type FifoQueueMessage, type FifoQueueMessageFn, type HttpConfig, type HttpHandler, type HttpHandlerFn, type HttpMethod, type HttpRequest, type HttpResponse, type LambdaConfig, type LambdaWithPermissions, type LogLevel, type MailerConfig, type MailerHandler, type MiddlewareDeny, type MiddlewareHandler, type MiddlewareRedirect, type MiddlewareRequest, type MiddlewareResult, type ParamRef, type PutInput, type PutOptions, type QueryByTagParams, type QueryParams, type ResolveConfig, type ResolveDeps, type SendEmailOptions, type SkCondition, type StaticFiles, type StaticSiteConfig, type StaticSiteHandler, type StreamView, type TableBatchCompleteFn, type TableBatchFn, type TableClient, type TableConfig, type TableHandler, type TableItem, type TableKey, type TableRecord, type TableRecordFn, type UpdateActions, defineApp, defineBucket, defineConfig, defineFifoQueue, defineHttp, defineMailer, defineStaticSite, defineTable, param, typed };
1376
+ /** GET route handler no schema, no data */
1377
+ type ApiGetHandlerFn<C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = (args: {
1378
+ req: HttpRequest;
1379
+ } & ([C] extends [undefined] ? {} : {
1380
+ ctx: C;
1381
+ }) & ([D] extends [undefined] ? {} : {
1382
+ deps: ResolveDeps<D>;
1383
+ }) & ([P] extends [undefined] ? {} : {
1384
+ config: ResolveConfig<P>;
1385
+ }) & ([S] extends [undefined] ? {} : {
1386
+ files: StaticFiles;
1387
+ })) => Promise<HttpResponse> | HttpResponse;
1388
+ /** POST handler — with typed data from schema */
1389
+ type ApiPostHandlerFn<T = undefined, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = (args: {
1390
+ req: HttpRequest;
1391
+ } & ([T] extends [undefined] ? {} : {
1392
+ data: T;
1393
+ }) & ([C] extends [undefined] ? {} : {
1394
+ ctx: C;
1395
+ }) & ([D] extends [undefined] ? {} : {
1396
+ deps: ResolveDeps<D>;
1397
+ }) & ([P] extends [undefined] ? {} : {
1398
+ config: ResolveConfig<P>;
1399
+ }) & ([S] extends [undefined] ? {} : {
1400
+ files: StaticFiles;
1401
+ })) => Promise<HttpResponse> | HttpResponse;
1402
+ /** Setup factory — receives deps/config/files when declared */
1403
+ type SetupFactory<C, D, P, S extends string[] | undefined = undefined> = (args: ([D] extends [undefined] ? {} : {
1404
+ deps: ResolveDeps<D>;
1405
+ }) & ([P] extends [undefined] ? {} : {
1406
+ config: ResolveConfig<P & {}>;
1407
+ }) & ([S] extends [undefined] ? {} : {
1408
+ files: StaticFiles;
1409
+ })) => C | Promise<C>;
1410
+ /** Static config extracted by AST (no runtime callbacks) */
1411
+ type ApiConfig = LambdaWithPermissions & {
1412
+ /** Base path prefix for all routes (e.g., "/api") */
1413
+ basePath: string;
1414
+ };
1415
+ /**
1416
+ * Options for defining a CQRS-style API endpoint.
1417
+ *
1418
+ * - `get` routes handle queries (path-based routing, no body)
1419
+ * - `post` handles commands (single entry point, discriminated union via `schema`)
1420
+ */
1421
+ type DefineApiOptions<T = undefined, C = undefined, D extends Record<string, AnyDepHandler> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined, S extends string[] | undefined = undefined> = LambdaWithPermissions & {
1422
+ /** Base path prefix for all routes (e.g., "/api") */
1423
+ basePath: string;
1424
+ /**
1425
+ * Factory function to initialize shared state.
1426
+ * Called once on cold start, result is cached and reused across invocations.
1427
+ */
1428
+ setup?: SetupFactory<C, D, P, S>;
1429
+ /** Dependencies on other handlers (tables, queues, etc.) */
1430
+ deps?: D;
1431
+ /** SSM Parameter Store parameters */
1432
+ config?: P;
1433
+ /** Static file glob patterns to bundle into the Lambda ZIP */
1434
+ static?: S;
1435
+ /** Error handler called when schema validation or handler throws */
1436
+ onError?: (error: unknown, req: HttpRequest) => HttpResponse;
1437
+ /** GET routes — query handlers keyed by relative path (e.g., "/users/{id}") */
1438
+ get?: Record<string, ApiGetHandlerFn<C, D, P, S>>;
1439
+ /**
1440
+ * Schema for POST body validation. Use with discriminated unions:
1441
+ * ```typescript
1442
+ * schema: Action.parse,
1443
+ * post: async ({ data }) => { switch (data.action) { ... } }
1444
+ * ```
1445
+ */
1446
+ schema?: (input: unknown) => T;
1447
+ /** POST handler — single entry point for commands */
1448
+ post?: ApiPostHandlerFn<T, C, D, P, S>;
1449
+ };
1450
+ /** Internal handler object created by defineApi */
1451
+ type ApiHandler<T = undefined, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = {
1452
+ readonly __brand: "effortless-api";
1453
+ readonly __spec: ApiConfig;
1454
+ readonly schema?: (input: unknown) => T;
1455
+ readonly onError?: (error: unknown, req: HttpRequest) => HttpResponse;
1456
+ readonly setup?: (...args: any[]) => C | Promise<C>;
1457
+ readonly deps?: D;
1458
+ readonly config?: P;
1459
+ readonly static?: string[];
1460
+ readonly get?: Record<string, ApiGetHandlerFn<C, D, P, S>>;
1461
+ readonly post?: ApiPostHandlerFn<T, C, D, P, S>;
1462
+ };
1463
+ /**
1464
+ * Define a CQRS-style API with typed GET routes and POST commands.
1465
+ *
1466
+ * GET routes handle queries — path-based routing, no request body.
1467
+ * POST handles commands — single entry point with discriminated union schema.
1468
+ * Deploys as a single Lambda (fat Lambda) with one API Gateway catch-all route.
1469
+ *
1470
+ * @example
1471
+ * ```typescript
1472
+ * export default defineApi({
1473
+ * basePath: "/api",
1474
+ * deps: { users },
1475
+ *
1476
+ * get: {
1477
+ * "/users": async ({ req, deps }) => ({
1478
+ * status: 200,
1479
+ * body: await deps.users.scan()
1480
+ * }),
1481
+ * "/users/{id}": async ({ req, deps }) => ({
1482
+ * status: 200,
1483
+ * body: await deps.users.get(req.params.id)
1484
+ * }),
1485
+ * },
1486
+ *
1487
+ * schema: Action.parse,
1488
+ * post: async ({ data, deps }) => {
1489
+ * switch (data.action) {
1490
+ * case "create": return { status: 201, body: await deps.users.put(data) }
1491
+ * case "delete": return { status: 200, body: await deps.users.delete(data.id) }
1492
+ * }
1493
+ * },
1494
+ * })
1495
+ * ```
1496
+ */
1497
+ declare const defineApi: <T = undefined, C = undefined, D extends Record<string, AnyDepHandler> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined, S extends string[] | undefined = undefined>(options: DefineApiOptions<T, C, D, P, S>) => ApiHandler<T, C, D, P, S>;
1498
+
1499
+ export { type AnyParamRef, type ApiConfig, type ApiGetHandlerFn, type ApiHandler, type ApiPostHandlerFn, type AppConfig, type AppHandler, type BucketClient, type BucketConfig, type BucketEvent, type BucketHandler, type BucketObjectCreatedFn, type BucketObjectRemovedFn, type ContentType, type DefineApiOptions, type DefineBucketOptions, type DefineFifoQueueOptions, type DefineHttpOptions, type DefineTableOptions, type EffortlessConfig, type EmailClient, type FailedRecord, type FifoQueueBatchFn, type FifoQueueConfig, type FifoQueueHandler, type FifoQueueMessage, type FifoQueueMessageFn, type HttpConfig, type HttpHandler, type HttpHandlerFn, type HttpMethod, type HttpRequest, type HttpResponse, type LambdaConfig, type LambdaWithPermissions, type LogLevel, type MailerConfig, type MailerHandler, type MiddlewareDeny, type MiddlewareHandler, type MiddlewareRedirect, type MiddlewareRequest, type MiddlewareResult, type ParamRef, type Permission, type PutInput, type PutOptions, type QueryByTagParams, type QueryParams, type ResolveConfig, type ResolveDeps, type SendEmailOptions, type SkCondition, type StaticFiles, type StaticSiteConfig, type StaticSiteHandler, type StreamView, type TableBatchCompleteFn, type TableBatchFn, type TableClient, type TableConfig, type TableHandler, type TableItem, type TableKey, type TableRecord, type TableRecordFn, type UpdateActions, defineApi, defineApp, defineBucket, defineConfig, defineFifoQueue, defineHttp, defineMailer, defineStaticSite, defineTable, param, typed };
package/dist/index.js CHANGED
@@ -86,6 +86,23 @@ var defineMailer = (options) => ({
86
86
  __spec: options
87
87
  });
88
88
 
89
+ // src/handlers/define-api.ts
90
+ var defineApi = (options) => {
91
+ const { get, post, schema, onError, setup, deps, config, static: staticFiles, ...__spec } = options;
92
+ return {
93
+ __brand: "effortless-api",
94
+ __spec,
95
+ ...get ? { get } : {},
96
+ ...post ? { post } : {},
97
+ ...schema ? { schema } : {},
98
+ ...onError ? { onError } : {},
99
+ ...setup ? { setup } : {},
100
+ ...deps ? { deps } : {},
101
+ ...config ? { config } : {},
102
+ ...staticFiles ? { static: staticFiles } : {}
103
+ };
104
+ };
105
+
89
106
  // src/handlers/handler-options.ts
90
107
  function param(key, transform) {
91
108
  return {
@@ -98,6 +115,7 @@ function typed() {
98
115
  return (input) => input;
99
116
  }
100
117
  export {
118
+ defineApi,
101
119
  defineApp,
102
120
  defineBucket,
103
121
  defineConfig,