effortless-aws 0.38.0 → 0.39.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.
@@ -112,7 +112,7 @@ var buildParams = async (params) => {
112
112
  var resolvePath = (filePath) => join(process.cwd(), filePath);
113
113
  var staticFiles = {
114
114
  read: (filePath) => readFileSync(resolvePath(filePath), "utf-8"),
115
- readBuffer: (filePath) => readFileSync(resolvePath(filePath)),
115
+ readBytes: (filePath) => readFileSync(resolvePath(filePath)),
116
116
  path: resolvePath
117
117
  };
118
118
  var createHandlerRuntime = (handler, handlerType, logLevel = "info", extraSetupArgs) => {
package/dist/index.d.ts CHANGED
@@ -249,7 +249,12 @@ type HttpRequest = {
249
249
  type HttpResponse = {
250
250
  /** HTTP status code (e.g., 200, 404, 500) */
251
251
  status: number;
252
- /** Response body — JSON-serialized by default, or sent as string when contentType is set */
252
+ /**
253
+ * Response body. Type determines serialization:
254
+ * - `Uint8Array` (incl. `Buffer`) — sent as binary, base64-encoded
255
+ * - `Blob` — sent as binary, base64-encoded; `Blob.type` is used as Content-Type when not set explicitly
256
+ * - other values — JSON-serialized by default, or sent as string when `contentType` is set
257
+ */
253
258
  body?: unknown;
254
259
  /**
255
260
  * Short content-type alias. Resolves to full MIME type automatically:
@@ -272,10 +277,14 @@ type HttpResponse = {
272
277
  */
273
278
  cookies?: string[];
274
279
  /**
275
- * Set to `true` to return binary data.
276
- * When enabled, `body` must be a base64-encoded string and the response
277
- * will include `isBase64Encoded: true` so Lambda Function URLs / API Gateway
278
- * decode it back to binary for the client.
280
+ * Force the client to download the response as a file with the given filename.
281
+ * Sets `Content-Disposition: attachment; filename="<value>"`. Works with any body type
282
+ * (JSON, text, binary). An explicit `Content-Disposition` header overrides this.
283
+ */
284
+ downloadAs?: string;
285
+ /**
286
+ * Set to `true` to return binary data when `body` is already a base64-encoded string.
287
+ * Prefer returning `Uint8Array` / `Buffer` / `Blob` directly — the runtime detects them automatically.
279
288
  */
280
289
  binary?: boolean;
281
290
  };
@@ -294,8 +303,8 @@ type ResponseStream = {
294
303
  type StaticFiles = {
295
304
  /** Read file as UTF-8 string */
296
305
  read(path: string): string;
297
- /** Read file as Buffer (for binary content) */
298
- readBuffer(path: string): Buffer;
306
+ /** Read file as raw bytes (for binary content) */
307
+ readBytes(path: string): Uint8Array;
299
308
  /** Resolve absolute path to the bundled file */
300
309
  path(path: string): string;
301
310
  };
@@ -480,14 +489,6 @@ interface BucketBuilder<D = undefined, P = undefined, C = undefined, HasFiles ex
480
489
  * Define an S3 bucket with optional event handlers.
481
490
  *
482
491
  * @see {@link https://effortless-aws.website/use-cases/storage | Storage guide}
483
- *
484
- * @example
485
- * ```typescript
486
- * export const uploads = defineBucket({ prefix: "images/", suffix: ".jpg" })
487
- * .onObjectCreated(async ({ event, bucket }) => {
488
- * console.log("New upload:", event.key);
489
- * })
490
- * ```
491
492
  */
492
493
  declare function defineBucket(): BucketBuilder;
493
494
  declare function defineBucket(options: BucketOptions): BucketBuilder;
@@ -517,28 +518,6 @@ type MailerHandler = {
517
518
  * Add them to your DNS provider to verify the domain.
518
519
  *
519
520
  * @see {@link https://effortless-aws.website/use-cases/email | Email guide}
520
- *
521
- * @param options - Mailer configuration with the domain to send from
522
- * @returns Handler object used by the deployment system and as a `deps` value
523
- *
524
- * @example Basic mailer with HTTP handler
525
- * ```typescript
526
- * export const mailer = defineMailer({ domain: "myapp.com" });
527
- *
528
- * export const api = defineApi({
529
- * basePath: "/signup",
530
- * deps: { mailer },
531
- * post: async ({ req, deps }) => {
532
- * await deps.mailer.send({
533
- * from: "hello@myapp.com",
534
- * to: req.body.email,
535
- * subject: "Welcome!",
536
- * html: "<h1>Hi!</h1>",
537
- * });
538
- * return { status: 200, body: { ok: true } };
539
- * },
540
- * });
541
- * ```
542
521
  */
543
522
  declare const defineMailer: () => (options: MailerConfig) => MailerHandler;
544
523
 
@@ -714,15 +693,6 @@ interface QueueBuilder<T = unknown, D = undefined, P = undefined, C = undefined,
714
693
  * object.
715
694
  *
716
695
  * @see {@link https://effortless-aws.website/use-cases/queue | Queue guide}
717
- *
718
- * @example
719
- * ```typescript
720
- * export const notifications = defineQueue<Notif>({ fifo: true })
721
- * .poller({ batchSize: 5, batchWindow: "2s" })
722
- * .onMessageBatch(async ({ messages }) => {
723
- * await sendAll(messages.map(m => m.body));
724
- * })
725
- * ```
726
696
  */
727
697
  declare function defineQueue<T = unknown>(): QueueBuilder<T>;
728
698
  declare function defineQueue<T = unknown>(options: QueueOptions<T>): QueueBuilder<T>;
@@ -809,17 +779,7 @@ interface WorkerBuilder<T, D = undefined, P = undefined, C = undefined, HasFiles
809
779
  *
810
780
  * @typeParam T - Type of messages the worker receives via its queue
811
781
  *
812
- * @example
813
- * ```typescript
814
- * type Job = { type: "export"; userId: string }
815
- *
816
- * export const worker = defineWorker<Job>({ memory: 2048, concurrency: 5 })
817
- * .deps(() => ({ orders }))
818
- * .setup(async ({ deps }) => ({ db: deps.orders }))
819
- * .onMessage(async (msg, { db }) => {
820
- * await processJob(msg, db)
821
- * })
822
- * ```
782
+ * @see {@link https://effortless-aws.website/definitions#defineworker | Worker reference}
823
783
  */
824
784
  declare function defineWorker<T = unknown>(options?: WorkerOptions): WorkerBuilder<T>;
825
785
 
@@ -1172,18 +1132,6 @@ interface TableBuilder<T = Record<string, unknown>, D = undefined, P = undefined
1172
1132
  * `data (M)`, and `ttl (N)` attributes. TTL is always enabled.
1173
1133
  *
1174
1134
  * @see {@link https://effortless-aws.website/use-cases/database | Database guide}
1175
- *
1176
- * @example
1177
- * ```typescript
1178
- * export const orders = defineTable<OrderData>()
1179
- * .stream({ batchSize: 10, concurrency: 5, maxRetries: 3 })
1180
- * .setup(({ table }) => ({ table }))
1181
- * .onRecord(async ({ record, table }) => {
1182
- * if (record.eventName === "INSERT") {
1183
- * console.log("New order:", record.new?.data);
1184
- * }
1185
- * })
1186
- * ```
1187
1135
  */
1188
1136
  declare function defineTable<T = Record<string, unknown>>(): TableBuilder<T>;
1189
1137
  declare function defineTable<T = Record<string, unknown>>(options: TableOptions<T>): TableBuilder<T>;
@@ -1538,16 +1486,6 @@ interface ApiBuilder<D = undefined, P = undefined, C = undefined, ST extends boo
1538
1486
  * Define an API with typed routes using a builder pattern.
1539
1487
  *
1540
1488
  * @see {@link https://effortless-aws.website/use-cases/http-api | HTTP API guide}
1541
- *
1542
- * @example
1543
- * ```typescript
1544
- * export const api = defineApi({ basePath: "/api" })
1545
- * .deps(() => ({ users }))
1546
- * .config(({ defineSecret }) => ({ authSecret: defineSecret() }))
1547
- * .auth<Session>(({ config }) => ({ secret: config.authSecret, expiresIn: "1h" }))
1548
- * .get({ path: "/me" }, async ({ users, auth, ok }) => ok(auth.session))
1549
- * .post({ path: "/login", public: true }, async ({ auth, ok }) => ok(await auth.createSession()))
1550
- * ```
1551
1489
  */
1552
1490
  declare function defineApi<const O extends ApiOptions>(options: O): ApiBuilder<undefined, undefined, undefined, O["stream"] extends true ? true : false, false>;
1553
1491
 
@@ -1653,19 +1591,7 @@ interface CronBuilder<D = undefined, P = undefined, C = undefined, HasFiles exte
1653
1591
  /**
1654
1592
  * Define a cron job — scheduled Lambda invocation via EventBridge Scheduler.
1655
1593
  *
1656
- * @example
1657
- * ```typescript
1658
- * export const sync = defineCron({ schedule: "cron(0 9 * * ? *)" })
1659
- * .deps(() => ({ orders }))
1660
- * .config(({ defineSecret }) => ({ apiKey: defineSecret() }))
1661
- * .include("templates/*.html")
1662
- * .setup(async ({ deps, config, files }) => ({
1663
- * db: deps.orders, key: config.apiKey, tpl: files,
1664
- * }), { memory: 512 })
1665
- * .onTick(async ({ db, key, tpl }) => {
1666
- * const html = tpl.read("templates/report.html")
1667
- * })
1668
- * ```
1594
+ * @see {@link https://effortless-aws.website/definitions#definecron | Cron reference}
1669
1595
  */
1670
1596
  declare function defineCron(options: CronOptions): CronBuilder;
1671
1597
 
@@ -1921,8 +1847,8 @@ interface McpBuilder<D = undefined, P = undefined, C = undefined, HasFiles exten
1921
1847
  * Creates a Lambda-backed MCP server that exposes tools, resources, and prompts
1922
1848
  * for AI models and MCP-compatible clients via Streamable HTTP (JSON-RPC over POST).
1923
1849
  *
1924
- * @see https://modelcontextprotocol.io/specification/2025-03-26 MCP specification
1925
- * @see https://effortless-aws.com/use-cases/mcp-server/ full documentation with examples
1850
+ * @see {@link https://effortless-aws.website/use-cases/mcp-server | MCP Server guide}
1851
+ * @see {@link https://modelcontextprotocol.io/specification/2025-03-26 | MCP specification}
1926
1852
  */
1927
1853
  declare function defineMcp(options: McpOptions): McpBuilder;
1928
1854
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/handlers/handler-options.ts","../src/handlers/define-table.ts","../src/handlers/define-app.ts","../src/handlers/define-static-site.ts","../src/handlers/define-queue.ts","../src/handlers/define-bucket.ts","../src/handlers/define-mailer.ts","../src/handlers/define-api.ts","../src/handlers/define-cron.ts","../src/handlers/define-worker.ts","../src/handlers/define-mcp.ts"],"sourcesContent":["/**\n * Configuration for an Effortless project.\n *\n * @see {@link https://effortless-aws.website/configuration | Configuration guide}\n */\nexport type EffortlessConfig = {\n /**\n * Project name used for resource naming and tagging.\n * This becomes part of Lambda function names, IAM roles, etc.\n */\n name: string;\n\n /**\n * Default AWS region for all handlers.\n * Can be overridden per-handler or via CLI `--region` flag.\n * @default \"eu-central-1\"\n */\n region?: string;\n\n /**\n * Deployment stage (e.g., \"dev\", \"staging\", \"prod\").\n * Used for resource isolation and tagging.\n * @default \"dev\"\n */\n stage?: string;\n\n /**\n * Glob patterns or directory paths to scan for handlers.\n * Used by `eff deploy` (without file argument) to auto-discover handlers.\n *\n * @example\n * ```typescript\n * // Single directory - scans for all .ts files\n * handlers: \"src\"\n *\n * // Glob patterns\n * handlers: [\"src/**\\/*.ts\", \"lib/**\\/*.handler.ts\"]\n * ```\n */\n handlers?: string | string[];\n\n /**\n * Default Lambda settings applied to all handlers unless overridden.\n *\n * All Lambdas run on ARM64 (Graviton2) architecture — ~20% cheaper than x86_64\n * with better price-performance for most workloads.\n */\n lambda?: {\n /**\n * Lambda memory in MB. AWS allocates proportional CPU —\n * 1769 MB gives one full vCPU.\n * @default 256\n */\n memory?: number;\n\n /**\n * Lambda timeout as a human-readable string.\n * AWS maximum is 15 minutes.\n * @example \"30 seconds\", \"5 minutes\"\n */\n timeout?: string;\n\n /**\n * Node.js Lambda runtime version.\n * @default \"nodejs24.x\"\n */\n runtime?: string;\n };\n\n};\n\n/** Helper function for type-safe configuration with TypeScript autocompletion. */\nexport const defineConfig = (config: EffortlessConfig): EffortlessConfig => config;\n","// Public helpers — this file must have ZERO heavy imports (no effect, no AWS SDK, no deploy code).\n// It is the source of truth for param() and related types used by the public API.\n\n// ============ Generate spec ============\n\n/** Generator spec for auto-creating secrets at deploy time. */\nexport type GenerateSpec = `hex:${number}` | `base64:${number}` | \"uuid\";\n\n// ============ Permissions ============\n\ntype AwsService =\n | \"dynamodb\"\n | \"s3\"\n | \"sqs\"\n | \"sns\"\n | \"ses\"\n | \"ssm\"\n | \"lambda\"\n | \"events\"\n | \"secretsmanager\"\n | \"cognito-idp\"\n | \"logs\";\n\nexport type Permission = `${AwsService}:${string}` | (string & {});\n\n// ============ Duration ============\n\n/**\n * Human-readable duration. Accepts a plain number (seconds) or a string\n * with a unit suffix: `\"30s\"`, `\"5m\"`, `\"1h\"`, `\"2d\"`.\n *\n * @example\n * ```typescript\n * timeout: 30 // 30 seconds\n * timeout: \"30s\" // 30 seconds\n * timeout: \"5m\" // 300 seconds\n * timeout: \"1h\" // 3600 seconds\n * retentionPeriod: \"4d\" // 345600 seconds\n * ```\n */\nexport type Duration = number | `${number}s` | `${number}m` | `${number}h` | `${number}d`;\n\n/** Convert a Duration to seconds. */\nexport const toSeconds = (d: Duration): number => {\n if (typeof d === \"number\") return d;\n const match = d.match(/^(\\d+(?:\\.\\d+)?)(s|m|h|d)$/);\n if (!match) throw new Error(`Invalid duration: \"${d}\"`);\n const n = Number(match[1]);\n const unit = match[2];\n if (unit === \"d\") return n * 86400;\n if (unit === \"h\") return n * 3600;\n if (unit === \"m\") return n * 60;\n return n;\n};\n\n// ============ Lambda config ============\n\n/** Logging verbosity level for Lambda handlers */\nexport type LogLevel = \"error\" | \"info\" | \"debug\";\n\n/**\n * Common Lambda configuration shared by all handler types.\n */\nexport type LambdaConfig = {\n /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout (default: 30s). Accepts seconds or duration string: `\"30s\"`, `\"5m\"` */\n timeout?: Duration;\n /** Logging verbosity: \"error\" (errors only), \"info\" (+ execution summary), \"debug\" (+ input/output). Default: \"info\" */\n logLevel?: LogLevel;\n};\n\n/**\n * Lambda configuration with additional IAM permissions.\n * Used by handler types that support custom permissions (http, table, queue).\n */\nexport type LambdaWithPermissions = LambdaConfig & {\n /** Additional IAM permissions for the Lambda */\n permissions?: Permission[];\n};\n\n// ============ Lambda options (for .setup()) ============\n\n/**\n * Lambda configuration passed as argument to `.setup()`.\n * Common across all handler types that create a Lambda function.\n */\nexport type LambdaOptions = {\n /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout (default: 30s). Accepts seconds or duration string: `\"30s\"`, `\"5m\"` */\n timeout?: Duration;\n /** Additional IAM permissions for the Lambda */\n permissions?: Permission[];\n /** Logging verbosity: \"error\" (errors only), \"info\" (+ execution summary), \"debug\" (+ input/output). Default: \"info\" */\n logLevel?: LogLevel;\n};\n\n// ============ Secrets (SSM Parameters) ============\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnySecretRef = SecretRef<any>;\n\n/**\n * Reference to an SSM Parameter Store secret.\n *\n * @typeParam T - The resolved type after optional transform (default: string)\n */\nexport type SecretRef<T = string> = {\n readonly __brand: \"effortless-secret\";\n readonly key?: string;\n readonly generate?: GenerateSpec;\n readonly transform?: (raw: string) => T;\n};\n\n/**\n * Maps a config declaration to resolved value types.\n * `SecretRef<T>` resolves to `T`.\n *\n * @typeParam P - Record of config keys to SecretRef instances\n */\nexport type ResolveConfig<P> = {\n [K in keyof P]: P[K] extends SecretRef<infer T> ? T : string;\n};\n\n/** Options for `defineSecret()` without a transform. */\nexport type DefineSecretOptions = {\n /** Custom SSM key (default: derived from config property name in kebab-case) */\n key?: string;\n /** Generator spec for auto-creating the secret at deploy time: `\"hex:32\"`, `\"base64:32\"`, `\"uuid\"` */\n generate?: GenerateSpec;\n};\n\n/** Options for `defineSecret()` with a transform. */\nexport type DefineSecretOptionsWithTransform<T> = DefineSecretOptions & {\n /** Transform the raw SSM string value into a typed value */\n transform: (raw: string) => T;\n};\n\n/** The defineSecret helper function type, injected into config callbacks. */\nexport type DefineSecretFn = {\n (): SecretRef<string>;\n (options: DefineSecretOptions): SecretRef<string>;\n <T>(options: DefineSecretOptionsWithTransform<T>): SecretRef<T>;\n};\n\n/** Helpers injected into the `config` callback. */\nexport type ConfigHelpers = {\n defineSecret: DefineSecretFn;\n};\n\n/** Config factory: a function receiving helpers and returning a record of SecretRefs. */\nexport type ConfigFactory<P> = (helpers: ConfigHelpers) => P;\n\n/** The `defineSecret` implementation, passed to config callbacks. */\nexport const defineSecret: DefineSecretFn = <T = string>(\n options?: DefineSecretOptions | DefineSecretOptionsWithTransform<T>\n): SecretRef<T> => {\n return {\n __brand: \"effortless-secret\",\n ...(options?.key ? { key: options.key } : {}),\n ...(options?.generate ? { generate: options.generate } : {}),\n ...(\"transform\" in (options ?? {}) ? { transform: (options as DefineSecretOptionsWithTransform<T>).transform } : {}),\n } as SecretRef<T>;\n};\n\n/** Internal helpers object passed to config callbacks. */\nexport const configHelpers: ConfigHelpers = { defineSecret };\n\n/** Resolve a config factory to a plain record of SecretRefs. */\nexport const resolveConfigFactory = <P>(config: ConfigFactory<P>): P =>\n config(configHelpers);\n\n// ============ Backwards compatibility ============\n\n/** @deprecated Use `defineSecret()` inside a config callback instead. */\nexport const secret = defineSecret;\n/** @deprecated Use `SecretRef` instead */\nexport type ParamRef<T = string> = SecretRef<T>;\n/** @deprecated Use `AnySecretRef` instead */\nexport type AnyParamRef = AnySecretRef;\n/** @deprecated Use `defineSecret()` instead. */\nexport const param = <T = string>(key: string, transform?: (raw: string) => T): SecretRef<T> => {\n return {\n __brand: \"effortless-secret\",\n key,\n ...(transform ? { transform } : {}),\n } as SecretRef<T>;\n};\n/** @deprecated Use `defineSecret({ generate: \"hex:N\" })` instead. */\nexport const generateHex = (bytes: number) => `hex:${bytes}`;\n/** @deprecated Use `defineSecret({ generate: \"base64:N\" })` instead. */\nexport const generateBase64 = (bytes: number) => `base64:${bytes}`;\n/** @deprecated Use `defineSecret({ generate: \"uuid\" })` instead. */\nexport const generateUuid = () => \"uuid\";\n\n// ============ Single-table types ============\n\n/**\n * DynamoDB table key (always pk + sk strings in single-table design).\n */\nexport type TableKey = { pk: string; sk: string };\n\n/**\n * Full DynamoDB item in the single-table design.\n *\n * Every item has a fixed envelope: `pk`, `sk`, `tag`, `data`, and optional `ttl`.\n * `tag` is stored as a top-level DynamoDB attribute (GSI-ready).\n * If your domain type `T` includes a `tag` field, effortless auto-copies\n * `data.tag` to the top-level `tag` on `put()`, so you don't have to pass it twice.\n *\n * @typeParam T - The domain data type stored in the `data` attribute\n */\nexport type TableItem<T> = {\n pk: string;\n sk: string;\n tag: string;\n data: T;\n ttl?: number;\n};\n\n/**\n * Input type for `TableClient.put()`.\n *\n * Unlike `TableItem<T>`, this does NOT include `tag` — effortless auto-extracts\n * the top-level DynamoDB `tag` attribute from your data using the configured\n * tag field (defaults to `data.tag`, configurable via `defineTable({ tag: \"type\" })`).\n *\n * @typeParam T - The domain data type stored in the `data` attribute\n */\nexport type PutInput<T> = {\n pk: string;\n sk: string;\n data: T;\n ttl?: number;\n};\n\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LogLevel, Permission, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { TableClient } from \"../runtime/table-client\";\nimport type { TableItem } from \"./handler-options\";\nimport type { StaticFiles } from \"./shared\";\n\n/** DynamoDB attribute types for keys */\nexport type KeyType = \"string\" | \"number\" | \"binary\";\n\n/**\n * DynamoDB table key definition\n */\nexport type TableKey = {\n /** Attribute name */\n name: string;\n /** Attribute type */\n type: KeyType;\n};\n\n/** DynamoDB Streams view type - determines what data is captured in stream records */\nexport type StreamView = \"NEW_AND_OLD_IMAGES\" | \"NEW_IMAGE\" | \"OLD_IMAGE\" | \"KEYS_ONLY\";\n\n/**\n * Stream event source mapping configuration.\n * @internal\n */\nexport type TableStreamConfig = {\n /** Stream view type - what data to include in stream records (default: \"NEW_AND_OLD_IMAGES\") */\n streamView?: StreamView;\n /** Number of records to process in each Lambda invocation (1-10000, default: 100) */\n batchSize?: number;\n /** Maximum time to gather records before invoking (default: `\"2s\"`). Accepts `\"5s\"`, `\"1m\"`, etc. */\n batchWindow?: Duration;\n /** Where to start reading the stream (default: \"LATEST\") */\n startingPosition?: \"LATEST\" | \"TRIM_HORIZON\";\n /** Number of records to process concurrently within a batch (default: 1 — sequential) */\n concurrency?: number;\n /** Max retry attempts for failed records before sending to DLQ (default: 1) */\n maxRetries?: number;\n};\n\n/**\n * Configuration options for defineTable (single-table design).\n *\n * Tables always use `pk (S)` + `sk (S)` keys, `tag (S)` discriminator,\n * `data (M)` for domain fields, and `ttl (N)` for optional expiration.\n */\nexport type TableConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: Duration; logLevel?: LogLevel; permissions?: Permission[] };\n /** DynamoDB billing mode (default: \"PAY_PER_REQUEST\") */\n billingMode?: \"PAY_PER_REQUEST\" | \"PROVISIONED\";\n /**\n * Name of the field in `data` that serves as the entity type discriminant.\n * Effortless auto-copies `data[tagField]` to the top-level DynamoDB `tag` attribute on `put()`.\n * Defaults to `\"tag\"`.\n */\n tagField?: string;\n /** Stream event source mapping config — set via `.stream({...})` builder method. */\n stream?: TableStreamConfig;\n};\n\n/**\n * DynamoDB stream record passed to onRecord callback.\n *\n * `new` and `old` are full `TableItem<T>` objects with the single-table envelope.\n *\n * @typeParam T - Type of the domain data (inside `data`)\n */\nexport type TableRecord<T = Record<string, unknown>> = {\n /** Type of modification: INSERT, MODIFY, or REMOVE */\n eventName: \"INSERT\" | \"MODIFY\" | \"REMOVE\";\n /** New item value (present for INSERT and MODIFY) */\n new?: TableItem<T>;\n /** Old item value (present for MODIFY and REMOVE) */\n old?: TableItem<T>;\n /** Primary key of the affected item */\n keys: { pk: string; sk: string };\n /** Sequence number for ordering */\n sequenceNumber?: string;\n /** Approximate timestamp when the modification occurred */\n timestamp?: number;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives table/deps/config/files based on what was declared */\ntype SetupArgs<T, D, P, HasFiles extends boolean> =\n & { table: TableClient<T> }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/**\n * Callback function type for processing a single DynamoDB stream record.\n * Receives the current record and the full batch for context.\n */\nexport type TableRecordFn<T = Record<string, unknown>, C = undefined> =\n (args: { record: TableRecord<T>; batch: readonly TableRecord<T>[] }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Batch handler function for DynamoDB stream records.\n * Called once with all records in the batch.\n * Return `{ failures: string[] }` (sequence numbers) for partial batch failure reporting.\n */\nexport type TableBatchFn<T = Record<string, unknown>, C = undefined> =\n (args: { records: readonly TableRecord<T>[] }\n & SpreadCtx<C>\n ) => Promise<void | { failures: string[] }>;\n\n// ============ Static config ============\n\n/** Static config extracted by AST (no runtime callbacks) */\n// TableConfig is already defined above and used as the extracted config type.\n\n// ============ Handler type (base interface for runtime wrappers and annotations) ============\n\n/**\n * Handler object created by defineTable.\n * Used by runtime wrappers and as type annotation for circular deps.\n * @internal\n */\nexport type TableHandler<T = Record<string, unknown>, C = any> = {\n readonly __brand: \"effortless-table\";\n readonly __spec: TableConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onRecord?: (...args: any[]) => any;\n readonly onRecordBatch?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineTable()` — resource config only. Stream options go in `.stream({...})`. */\ntype TableOptions<T> = {\n /** DynamoDB billing mode (default: \"PAY_PER_REQUEST\") */\n billingMode?: \"PAY_PER_REQUEST\" | \"PROVISIONED\";\n /** Name of the field in `data` that serves as the entity type discriminant (default: \"tag\") */\n tagField?: Extract<keyof T, string>;\n /** Decode/validate function for the `data` portion of stream records */\n schema?: (input: unknown) => T;\n};\n\n// ============ Builder ============\n\ninterface TableBuilder<\n T = Record<string, unknown>,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n HasStream extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): TableBuilder<T, D2, P, C, HasFiles, HasStream>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): TableBuilder<T, D, P2, C, HasFiles, HasStream>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): TableBuilder<T, D, P, C, true, HasStream>;\n\n /** Configure the DynamoDB stream event source mapping (batch size, retries, concurrency, etc.). */\n stream: HasStream extends true\n ? never\n : (opts: TableStreamConfig) => TableBuilder<T, D, P, C, HasFiles, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): TableBuilder<T, D, P, C, HasFiles, HasStream>;\n\n /** Initialize shared state on cold start. Receives table (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<T, D, P, HasFiles>) => C2 | Promise<C2>\n ): TableBuilder<T, D, P, C2, HasFiles, HasStream>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<T, D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): TableBuilder<T, D, P, C2, HasFiles, HasStream>;\n\n /** Handle errors thrown by onRecord/onRecordBatch */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): TableBuilder<T, D, P, C, HasFiles, HasStream>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): TableBuilder<T, D, P, C, HasFiles, HasStream>;\n\n /** Per-record stream handler (terminal — returns finalized handler) */\n onRecord(\n fn: TableRecordFn<T, C>\n ): TableHandler<T, C>;\n\n /** Batch stream handler (terminal — returns finalized handler) */\n onRecordBatch(\n fn: TableBatchFn<T, C>\n ): TableHandler<T, C>;\n\n /** Finalize as resource-only table (no Lambda) */\n build(): TableHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a DynamoDB table with optional stream handler (single-table design).\n *\n * Creates a table with fixed key schema: `pk (S)` + `sk (S)`, plus `tag (S)`,\n * `data (M)`, and `ttl (N)` attributes. TTL is always enabled.\n *\n * @see {@link https://effortless-aws.website/use-cases/database | Database guide}\n *\n * @example\n * ```typescript\n * export const orders = defineTable<OrderData>()\n * .stream({ batchSize: 10, concurrency: 5, maxRetries: 3 })\n * .setup(({ table }) => ({ table }))\n * .onRecord(async ({ record, table }) => {\n * if (record.eventName === \"INSERT\") {\n * console.log(\"New order:\", record.new?.data);\n * }\n * })\n * ```\n */\nexport function defineTable<T = Record<string, unknown>>(): TableBuilder<T>;\nexport function defineTable<T = Record<string, unknown>>(\n options: TableOptions<T>,\n): TableBuilder<T>;\nexport function defineTable<T = Record<string, unknown>>(\n options?: TableOptions<T>,\n): TableBuilder<T> {\n const {\n schema,\n ...tableConfig\n } = options ?? {} as TableOptions<T>;\n\n const spec: TableConfig = { ...tableConfig };\n\n const state: {\n spec: TableConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n schema?: (input: unknown) => T;\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onRecord?: (...args: any[]) => any;\n onRecordBatch?: (...args: any[]) => any;\n } = {\n spec,\n ...(schema ? { schema } : {}),\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): TableHandler<T> => ({\n __brand: \"effortless-table\",\n __spec: state.spec,\n ...(state.schema ? { schema: state.schema } : {}),\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onRecord ? { onRecord: state.onRecord } : {}),\n ...(state.onRecordBatch ? { onRecordBatch: state.onRecordBatch } : {}),\n }) as TableHandler<T>;\n\n const builder: TableBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n stream(opts: TableStreamConfig) {\n state.spec = { ...state.spec, stream: { ...state.spec.stream, ...opts } };\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onRecord(fn) {\n state.onRecord = fn as any;\n return finalize() as any;\n },\n onRecordBatch(fn) {\n state.onRecordBatch = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { LambdaWithPermissions } from \"./handler-options\";\n\n/**\n * Configuration for deploying an SSR framework (Nuxt, Astro, etc.)\n * via CloudFront + S3 (static assets) + Lambda Function URL (server-side rendering).\n */\nexport type AppConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Directory containing the Lambda server handler (e.g., \".output/server\").\n * Must contain an `index.mjs` (or `index.js`) that exports a `handler` function. */\n server: string;\n /** Directory containing static assets for S3 (e.g., \".output/public\") */\n assets: string;\n /** Base URL path (default: \"/\") */\n path?: string;\n /** Shell command to build the framework output (e.g., \"nuxt build\") */\n build?: string;\n /** Custom domain name. String or stage-keyed record (e.g., { prod: \"app.example.com\" }). */\n domain?: string | Record<string, string>;\n /** CloudFront route overrides: path patterns forwarded to API Gateway instead of the SSR Lambda.\n * Keys are CloudFront path patterns (e.g., \"/api/*\"), values are handler references.\n * Example: `routes: { \"/api/*\": api }` */\n routes?: Record<string, { readonly __brand: string }>;\n};\n\n/**\n * Internal handler object created by defineApp\n * @internal\n */\nexport type AppHandler = {\n readonly __brand: \"effortless-app\";\n readonly __spec: AppConfig;\n};\n\n/**\n * Deploy an SSR framework application via CloudFront + Lambda Function URL.\n *\n * Static assets from the `assets` directory are served via S3 + CloudFront CDN.\n * Server-rendered pages are handled by a Lambda function using the framework's\n * built output from the `server` directory.\n *\n * For static-only sites (no SSR), use {@link defineStaticSite} instead.\n *\n * @see {@link https://effortless-aws.website/use-cases/web-app | Web app guide}\n *\n * @param options - App configuration: server directory, assets directory, optional build command\n * @returns Handler object used by the deployment system\n */\nexport const defineApp = () => (options: AppConfig): AppHandler => ({\n __brand: \"effortless-app\",\n __spec: options,\n});\n","/** Any branded handler that deploys to API Gateway or S3 */\ntype AnyRoutableHandler = { readonly __brand: string };\n\n/** Simplified request object passed to middleware */\nexport type MiddlewareRequest = {\n uri: string;\n method: string;\n querystring: string;\n headers: Record<string, string>;\n cookies: Record<string, string>;\n};\n\n/** Redirect the user to another URL */\nexport type MiddlewareRedirect = {\n redirect: string;\n status?: 301 | 302 | 307 | 308;\n};\n\n/** Deny access with a 403 status */\nexport type MiddlewareDeny = {\n status: 403;\n body?: string;\n};\n\n/** Middleware return type: redirect, deny, or void (continue serving) */\nexport type MiddlewareResult = MiddlewareRedirect | MiddlewareDeny | void;\n\n/** Function that runs before serving static files via Lambda@Edge */\nexport type MiddlewareHandler = (\n request: MiddlewareRequest\n) => Promise<MiddlewareResult> | MiddlewareResult;\n\n/** SEO options for auto-generating sitemap.xml, robots.txt, and submitting to Google Indexing API */\nexport type StaticSiteSeo = {\n /** Sitemap filename (e.g. \"sitemap.xml\", \"sitemap-v2.xml\") */\n sitemap: string;\n /** Path to Google service account JSON key file for Indexing API batch submission.\n * Requires adding the service account email as an owner in Google Search Console. */\n googleIndexing?: string;\n};\n\n/**\n * Configuration for a static site (S3 + CloudFront)\n */\nexport type StaticSiteConfig = {\n /** Directory containing the static site files, relative to project root */\n dir: string;\n /** Default file for directory requests (default: \"index.html\") */\n index?: string;\n /** Shell command to run before deploy to generate site content (e.g., \"npx astro build\") */\n build?: string;\n /** Path to a custom error page relative to `dir`.\n * - If set to the same value as `index` (e.g. \"index.html\"), enables SPA mode:\n * all paths that don't match a file are served with `index.html` (HTTP 200), letting the client-side router handle them.\n * - If set to a different file (e.g. \"404.html\"), that file is served with HTTP 404 for missing paths.\n * - If omitted, a default 404 page is auto-generated. */\n errorPage?: string;\n /** Custom domain name. Accepts a string (same domain for all stages) or a Record mapping stage names to domains (e.g., `{ prod: \"example.com\", dev: \"dev.example.com\" }`). Requires an ACM certificate in us-east-1. If the cert also covers www, a 301 redirect from www to non-www is set up automatically. */\n domain?: string | Record<string, string>;\n /** SEO: auto-generate sitemap.xml and robots.txt at deploy time, optionally submit URLs to Google Indexing API */\n seo?: StaticSiteSeo;\n};\n\n/** Route entry stored on the static site handler */\ntype StaticSiteRouteEntry = {\n pattern: string;\n origin: AnyRoutableHandler;\n access?: \"private\" | \"public\";\n};\n\n/**\n * Internal handler object created by defineStaticSite\n * @internal\n */\nexport type StaticSiteHandler = {\n readonly __brand: \"effortless-static-site\";\n readonly __spec: StaticSiteConfig;\n readonly routes: StaticSiteRouteEntry[];\n readonly middleware?: MiddlewareHandler;\n};\n\n/** Builder for configuring a static site before calling `.build()` */\nexport type StaticSiteBuilder = {\n route(pattern: string, origin: AnyRoutableHandler, opts?: { access?: \"private\" | \"public\" }): StaticSiteBuilder;\n middleware(fn: MiddlewareHandler): StaticSiteBuilder;\n build(): StaticSiteHandler;\n};\n\n/**\n * Deploy a static site via S3 + CloudFront CDN, with optional API and bucket route overrides.\n *\n * @see {@link https://effortless-aws.website/use-cases/web-app | Web app guide}\n *\n * @param options - Static site configuration: directory, optional SPA mode, build command, domain\n * @returns Builder with `.route()`, `.middleware()`, and `.build()` methods\n */\nexport function defineStaticSite(options: StaticSiteConfig): StaticSiteBuilder {\n const state = {\n spec: { ...options },\n routes: [] as StaticSiteRouteEntry[],\n middleware: undefined as MiddlewareHandler | undefined,\n };\n\n const builder = {\n route(pattern: string, origin: AnyRoutableHandler, opts?: { access?: \"private\" | \"public\" }) {\n state.routes.push({ pattern, origin, ...(opts?.access ? { access: opts.access } : {}) });\n return builder;\n },\n middleware(fn: MiddlewareHandler) {\n state.middleware = fn;\n return builder;\n },\n build(): StaticSiteHandler {\n return {\n __brand: \"effortless-static-site\",\n __spec: state.spec,\n routes: state.routes,\n ...(state.middleware ? { middleware: state.middleware } : {}),\n };\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LogLevel, Permission, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n/**\n * Parsed SQS message passed to the handler callbacks.\n *\n * @typeParam T - Type of the decoded message body (from schema function)\n */\nexport type QueueMessage<T = unknown> = {\n /** Unique message identifier */\n messageId: string;\n /** Receipt handle for acknowledgement */\n receiptHandle: string;\n /** Parsed message body (JSON-decoded, then optionally schema-validated) */\n body: T;\n /** Raw unparsed message body string */\n rawBody: string;\n /** Message group ID (FIFO ordering key, empty string for standard queues) */\n messageGroupId: string;\n /** Message deduplication ID (FIFO only) */\n messageDeduplicationId?: string;\n /** SQS message attributes */\n messageAttributes: Record<string, { dataType?: string; stringValue?: string }>;\n /** Approximate first receive timestamp */\n approximateFirstReceiveTimestamp?: string;\n /** Approximate receive count */\n approximateReceiveCount?: string;\n /** Sent timestamp */\n sentTimestamp?: string;\n};\n\n/**\n * Event source mapping (poller) configuration — how Lambda consumes the queue.\n */\nexport type QueuePollerConfig = {\n /** Number of messages per Lambda invocation (1-10 for FIFO, default: 10) */\n batchSize?: number;\n /** Maximum time to gather messages before invoking (default: 0). Accepts `\"5s\"`, `\"1m\"`, etc. */\n batchWindow?: Duration;\n};\n\n/**\n * Configuration options for a queue handler.\n */\nexport type QueueConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: Duration; logLevel?: LogLevel; permissions?: Permission[] };\n /**\n * Whether this is a FIFO queue (ordered, exactly-once).\n * Currently only `true` is supported — standard queue support is planned.\n */\n fifo?: boolean;\n /** Visibility timeout (default: max of timeout or 30s). Accepts `\"30s\"`, `\"5m\"`, etc. */\n visibilityTimeout?: Duration;\n /** Message retention period (default: `\"4d\"`). Accepts `\"1h\"`, `\"7d\"`, etc. */\n retentionPeriod?: Duration;\n /** Delivery delay for all messages in the queue (default: 0). Accepts `\"30s\"`, `\"5m\"`, etc. */\n delay?: Duration;\n /** Enable content-based deduplication for FIFO queues (default: true) */\n contentBasedDeduplication?: boolean;\n /** Max number of receives before a message is sent to the dead-letter queue (default: 3) */\n maxReceiveCount?: number;\n /** Event source mapping config — set via `.poller({...})` builder method. */\n poller?: QueuePollerConfig;\n};\n\n// ============ Setup args ============\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/**\n * Per-message handler function.\n * Called once per message in the batch. Failures are reported individually.\n */\nexport type QueueMessageFn<T = unknown, C = undefined> =\n (args: { message: QueueMessage<T> }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Batch handler function.\n * Called once with all messages in the batch.\n * Return `{ failures: string[] }` (messageIds) for partial batch failure reporting.\n */\nexport type QueueBatchFn<T = unknown, C = undefined> =\n (args: { messages: QueueMessage<T>[] }\n & SpreadCtx<C>\n ) => Promise<void | { failures: string[] }>;\n\n// ============ Internal handler object ============\n\n/**\n * Internal handler object created by defineQueue.\n * @internal\n */\nexport type QueueHandler<T = unknown, C = any> = {\n readonly __brand: \"effortless-queue\";\n readonly __spec: QueueConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onMessage?: (...args: any[]) => any;\n readonly onMessageBatch?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineQueue()` — queue resource config */\ntype QueueOptions<T> = {\n /**\n * Whether this is a FIFO queue (ordered, exactly-once).\n * Currently only `true` is supported — standard queue support is planned.\n */\n fifo?: boolean;\n /** Visibility timeout (default: max of timeout or 30s) */\n visibilityTimeout?: Duration;\n /** Message retention period (default: \"4d\") */\n retentionPeriod?: Duration;\n /** Delivery delay for all messages in the queue (default: 0) */\n delay?: Duration;\n /** Enable content-based deduplication for FIFO queues (default: true) */\n contentBasedDeduplication?: boolean;\n /** Max number of receives before DLQ (default: 3) */\n maxReceiveCount?: number;\n /** Decode/validate function for the message body */\n schema?: (input: unknown) => T;\n};\n\n// ============ Builder ============\n\ninterface QueueBuilder<\n T = unknown,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): QueueBuilder<T, D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): QueueBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): QueueBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): QueueBuilder<T, D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): QueueBuilder<T, D, P, C2, HasFiles>;\n\n /**\n * Configure the event source mapping (poller) that delivers messages to the Lambda.\n * Call before the terminal `.onMessage` / `.onMessageBatch`.\n */\n poller(\n options: QueuePollerConfig\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Handle errors thrown by message handlers */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Per-message handler (terminal — returns finalized handler) */\n onMessage(\n fn: QueueMessageFn<T, C>\n ): QueueHandler<T, C>;\n\n /** Batch handler (terminal — returns finalized handler) */\n onMessageBatch(\n fn: QueueBatchFn<T, C>\n ): QueueHandler<T, C>;\n\n /** Finalize as a resource-only queue (no Lambda). Use when the SQS queue is consumed by an external system. */\n build(): QueueHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an SQS queue with a Lambda message handler.\n *\n * Currently only FIFO queues are supported — pass `{ fifo: true }` explicitly\n * (standard queue support is planned). Poller/batch settings live on the\n * `.poller({...})` builder method; queue-level properties stay in the options\n * object.\n *\n * @see {@link https://effortless-aws.website/use-cases/queue | Queue guide}\n *\n * @example\n * ```typescript\n * export const notifications = defineQueue<Notif>({ fifo: true })\n * .poller({ batchSize: 5, batchWindow: \"2s\" })\n * .onMessageBatch(async ({ messages }) => {\n * await sendAll(messages.map(m => m.body));\n * })\n * ```\n */\nexport function defineQueue<T = unknown>(): QueueBuilder<T>;\nexport function defineQueue<T = unknown>(\n options: QueueOptions<T>,\n): QueueBuilder<T>;\nexport function defineQueue<T = unknown>(\n options?: QueueOptions<T>,\n): QueueBuilder<T> {\n const {\n schema,\n ...queueConfig\n } = options ?? {} as QueueOptions<T>;\n\n const spec: QueueConfig = { ...queueConfig };\n\n const state: {\n spec: QueueConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n schema?: (input: unknown) => T;\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onMessage?: (...args: any[]) => any;\n onMessageBatch?: (...args: any[]) => any;\n } = {\n spec,\n ...(schema ? { schema } : {}),\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): QueueHandler<T> => ({\n __brand: \"effortless-queue\",\n __spec: state.spec,\n ...(state.schema ? { schema: state.schema } : {}),\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onMessage ? { onMessage: state.onMessage } : {}),\n ...(state.onMessageBatch ? { onMessageBatch: state.onMessageBatch } : {}),\n }) as QueueHandler<T>;\n\n const builder: QueueBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n poller(options) {\n if (Object.keys(options).length > 0) {\n state.spec = { ...state.spec, poller: { ...state.spec.poller, ...options } };\n }\n return builder;\n },\n onMessage(fn) {\n state.onMessage = fn as any;\n return finalize() as any;\n },\n onMessageBatch(fn) {\n state.onMessageBatch = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\nimport type { BucketClient, BucketClientWithEntities } from \"../runtime/bucket-client\";\n\n/**\n * Per-entity configuration for typed JSON key-value storage within a bucket.\n */\nexport type BucketEntityConfig = {\n /** Cache duration for CloudFront/browser caching (e.g., \"10s\", \"5m\", \"1h\"). No caching if omitted. */\n cache?: Duration;\n};\n\n/**\n * Configuration options for defineBucket.\n */\nexport type BucketConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: import(\"./handler-options\").Duration; logLevel?: import(\"./handler-options\").LogLevel; permissions?: import(\"./handler-options\").Permission[] };\n /** S3 key prefix filter for event notifications (e.g., \"uploads/\") */\n prefix?: string;\n /** S3 key suffix filter for event notifications (e.g., \".jpg\") */\n suffix?: string;\n /** Typed JSON entity definitions for key-value storage */\n entities?: Record<string, BucketEntityConfig>;\n /** Local directory to seed into bucket on deploy (only uploads files that don't already exist) */\n seed?: string;\n /** Local directory to sync into bucket on every deploy (uploads new/changed, deletes removed) */\n sync?: string;\n};\n\n/**\n * S3 event record passed to onObjectCreated/onObjectRemoved callbacks.\n */\nexport type BucketEvent = {\n /** S3 event name (e.g., \"ObjectCreated:Put\", \"ObjectRemoved:Delete\") */\n eventName: string;\n /** Object key (path within the bucket) */\n key: string;\n /** Object size in bytes (present for created events) */\n size?: number;\n /** Object ETag (present for created events) */\n eTag?: string;\n /** ISO 8601 timestamp of when the event occurred */\n eventTime?: string;\n /** S3 bucket name */\n bucketName: string;\n};\n\n// ============ Setup args ============\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/** Setup factory — receives bucket/deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean, Entities extends Record<string, any> = {}> =\n & { bucket: {} extends Entities ? BucketClient : BucketClientWithEntities<Entities> }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/**\n * Callback function type for S3 ObjectCreated events\n */\nexport type BucketObjectCreatedFn<C = undefined> =\n (args: { event: BucketEvent }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Callback function type for S3 ObjectRemoved events\n */\nexport type BucketObjectRemovedFn<C = undefined> =\n (args: { event: BucketEvent }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n// ============ Internal handler object ============\n\n/**\n * Internal handler object created by defineBucket\n * @internal\n */\nexport type BucketHandler<C = any, _Entities extends Record<string, any> = {}> = {\n readonly __brand: \"effortless-bucket\";\n readonly __spec: BucketConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onObjectCreated?: (...args: any[]) => any;\n readonly onObjectRemoved?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineBucket()` — static config */\ntype BucketOptions = {\n /** S3 key prefix filter for event notifications (e.g., \"uploads/\") */\n prefix?: string;\n /** S3 key suffix filter for event notifications (e.g., \".jpg\") */\n suffix?: string;\n /** Local directory to seed into bucket on deploy (only uploads files that don't already exist) */\n seed?: string;\n /** Local directory to sync into bucket on every deploy (uploads new/changed, deletes removed) */\n sync?: string;\n};\n\n// ============ Builder ============\n\ninterface BucketBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n Entities extends Record<string, any> = {},\n> {\n /** Declare handler dependencies */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): BucketBuilder<D2, P, C, HasFiles, Entities>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): BucketBuilder<D, P2, C, HasFiles, Entities>;\n\n /** Include static files in the Lambda ZIP */\n include(glob: string): BucketBuilder<D, P, C, true, Entities>;\n\n /** Register a typed JSON entity stored as `{name}/{id}.json` in the bucket */\n entity<N extends string, T>(\n name: N,\n options?: BucketEntityConfig,\n ): BucketBuilder<D, P, C, HasFiles, Entities & { [K in N]: T }>;\n\n /** Initialize shared state on cold start with lambda options */\n setup(lambda: LambdaOptions): BucketBuilder<D, P, C, HasFiles, Entities>;\n /** Initialize shared state on cold start. Receives bucket (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles, Entities>) => C2 | Promise<C2>\n ): BucketBuilder<D, P, C2, HasFiles, Entities>;\n /** Initialize shared state on cold start with lambda options. Receives bucket (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles, Entities>) => C2 | Promise<C2>,\n lambda: LambdaOptions,\n ): BucketBuilder<D, P, C2, HasFiles, Entities>;\n\n /** Handle errors thrown by callbacks */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): BucketBuilder<D, P, C, HasFiles, Entities>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): BucketBuilder<D, P, C, HasFiles, Entities>;\n\n /** Handle S3 ObjectCreated events (terminal — returns finalized handler) */\n onObjectCreated(\n fn: BucketObjectCreatedFn<C>\n ): BucketHandler<C, Entities>;\n\n /** Handle S3 ObjectRemoved events (terminal — returns finalized handler) */\n onObjectRemoved(\n fn: BucketObjectRemovedFn<C>\n ): BucketHandler<C, Entities>;\n\n /** Finalize as resource-only bucket (no Lambda) */\n build(): BucketHandler<C, Entities>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an S3 bucket with optional event handlers.\n *\n * @see {@link https://effortless-aws.website/use-cases/storage | Storage guide}\n *\n * @example\n * ```typescript\n * export const uploads = defineBucket({ prefix: \"images/\", suffix: \".jpg\" })\n * .onObjectCreated(async ({ event, bucket }) => {\n * console.log(\"New upload:\", event.key);\n * })\n * ```\n */\nexport function defineBucket(): BucketBuilder;\nexport function defineBucket(\n options: BucketOptions,\n): BucketBuilder;\nexport function defineBucket(\n options?: BucketOptions,\n): BucketBuilder {\n const bucketConfig = options ?? {} as BucketOptions;\n\n const spec: BucketConfig = {\n ...bucketConfig,\n };\n\n const state: {\n spec: BucketConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onObjectCreated?: (...args: any[]) => any;\n onObjectRemoved?: (...args: any[]) => any;\n } = {\n spec,\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): BucketHandler => ({\n __brand: \"effortless-bucket\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onObjectCreated ? { onObjectCreated: state.onObjectCreated } : {}),\n ...(state.onObjectRemoved ? { onObjectRemoved: state.onObjectRemoved } : {}),\n }) as BucketHandler;\n\n const builder: BucketBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n entity(name: string, options?: BucketEntityConfig) {\n state.spec = {\n ...state.spec,\n entities: { ...state.spec.entities, [name]: options ?? {} },\n };\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onObjectCreated(fn) {\n state.onObjectCreated = fn as any;\n return finalize() as any;\n },\n onObjectRemoved(fn) {\n state.onObjectRemoved = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","/**\n * Configuration options for defining a mailer (SES email identity)\n */\nexport type MailerConfig = {\n /** Domain to verify and send emails from (e.g., \"myapp.com\") */\n domain: string;\n};\n\n/**\n * Internal handler object created by defineMailer\n * @internal\n */\nexport type MailerHandler = {\n readonly __brand: \"effortless-mailer\";\n readonly __spec: MailerConfig;\n};\n\n/**\n * Define an email sender backed by Amazon SES.\n *\n * Creates an SES Email Identity for the specified domain and provides\n * a typed `EmailClient` to other handlers via `deps`.\n *\n * On first deploy, DKIM DNS records are printed to the console.\n * Add them to your DNS provider to verify the domain.\n *\n * @see {@link https://effortless-aws.website/use-cases/email | Email guide}\n *\n * @param options - Mailer configuration with the domain to send from\n * @returns Handler object used by the deployment system and as a `deps` value\n *\n * @example Basic mailer with HTTP handler\n * ```typescript\n * export const mailer = defineMailer({ domain: \"myapp.com\" });\n *\n * export const api = defineApi({\n * basePath: \"/signup\",\n * deps: { mailer },\n * post: async ({ req, deps }) => {\n * await deps.mailer.send({\n * from: \"hello@myapp.com\",\n * to: req.body.email,\n * subject: \"Welcome!\",\n * html: \"<h1>Hi!</h1>\",\n * });\n * return { status: 200, body: { ok: true } };\n * },\n * });\n * ```\n */\nexport const defineMailer = () => (options: MailerConfig): MailerHandler => ({\n __brand: \"effortless-mailer\",\n __spec: options,\n});\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { LambdaWithPermissions, AnySecretRef, ResolveConfig, Duration, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory, toSeconds } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles, ResponseStream } from \"./shared\";\nimport type { HttpRequest, HttpResponse } from \"./shared\";\nimport type { AuthHelpers } from \"./auth\";\n\n// ============ Auth types ============\n\n/** Auth config options (user-facing) */\nexport type AuthOptions<A = unknown> = {\n /** HMAC secret for signing session cookies. Use `secret()` or `param()` in config. */\n secret: string;\n /** Default session lifetime (default: \"7d\"). */\n expiresIn?: Duration;\n /** Optional API token strategy for external clients. */\n apiToken?: {\n /** HTTP header to read the token from. Default: \"authorization\" (strips \"Bearer \" prefix). */\n header?: string;\n /** Verify the token value and return session data, or null if invalid. */\n verify: (value: string) => A | null | Promise<A | null>;\n /** Cache verified token results for this duration. */\n cacheTtl?: Duration;\n };\n};\n\n/** Property names reserved by the framework — cannot be used in setup return */\ntype ReservedKeys = 'req' | 'input' | 'stream' | 'ok' | 'fail';\n\n// ============ Response helpers ============\n\n/** Success response helper: `ok({ data })` → `{ status: 200, body: { data } }` */\nexport type OkHelper = (body?: unknown, status?: number) => HttpResponse;\n/** Error response helper: `fail(\"message\")` → `{ status: 400, body: { error: \"message\" } }` */\nexport type FailHelper = (message: string, status?: number) => HttpResponse;\n\n// ============ Route types ============\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/** Cache options for a GET route. Duration shorthand (e.g. \"30s\", \"5m\") or object for fine-grained control. */\nexport type CacheOptions = Duration | {\n ttl: Duration;\n swr?: Duration;\n scope?: \"public\" | \"private\";\n};\n\n/** Resolved cache config with numeric seconds */\ntype ResolvedCache =\n | { private?: false; ttl: number; swr: number }\n | { private: true; ttl: number };\n\n/** Resolve CacheOptions into numeric seconds. Shorthand = public with swr = ttl * 2. */\nconst resolveCache = (cache: CacheOptions): ResolvedCache => {\n if (typeof cache === \"number\" || typeof cache === \"string\") {\n const ttl = toSeconds(cache);\n return { ttl, swr: ttl * 2 };\n }\n const ttl = toSeconds(cache.ttl);\n if (cache.scope === \"private\") {\n return { private: true, ttl };\n }\n return {\n ttl,\n swr: cache.swr != null ? toSeconds(cache.swr) : ttl * 2,\n };\n};\n\n/** Parsed route definition stored at runtime */\nexport type RouteEntry = {\n method: HttpMethod;\n path: string;\n onRequest: (...args: any[]) => any;\n schema?: unknown;\n public?: boolean;\n cache?: ResolvedCache;\n};\n\n/** Spread ctx into route args, add AuthHelpers if A is set */\ntype SpreadCtx<C, A = undefined> =\n & ([C] extends [undefined] ? {} : C & {})\n & ([A] extends [undefined] ? {} : { auth: AuthHelpers<A> });\n\n/** Infer validated output type from a Standard Schema, or fall back to unknown */\ntype InferInput<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : unknown;\n\n/** Callback args available inside each route — ctx is spread into args */\ntype RouteArgs<C, ST, A = undefined, S = undefined> =\n & SpreadCtx<C, A>\n & { req: HttpRequest; input: InferInput<S>; ok: OkHelper; fail: FailHelper }\n & ([ST] extends [true] ? { stream: ResponseStream } : {});\n\n/** Route handler function */\ntype RouteHandler<C, ST, A = undefined, S = undefined> = (args: RouteArgs<C, ST, A, S>) => Promise<HttpResponse | void> | HttpResponse | void;\n\n/** Route definition — pass `input` for typed schema validation */\ntype RouteDef<S extends StandardSchemaV1 | undefined = undefined> = {\n path: `/${string}`;\n input?: S;\n public?: boolean;\n cache?: CacheOptions;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & { ok: OkHelper; fail: FailHelper }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Auth factory args — receives config/deps based on what was declared */\ntype AuthArgs<D, P> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> });\n\n/** Validate that setup return type does not use reserved property names */\ntype ValidateSetupReturn<C> = C & { [K in ReservedKeys]?: never };\n\n// ============ Static config ============\n\n/** Static config extracted by AST (no runtime callbacks) */\nexport type ApiConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Base path prefix for all routes (e.g., \"/api\") */\n basePath: `/${string}`;\n /** Enable response streaming. When true, the Lambda Function URL uses RESPONSE_STREAM invoke mode. */\n stream?: boolean;\n};\n\n// ============ Internal handler object ============\n\n/** Internal handler object created by defineApi */\nexport type ApiHandler<C = undefined> = {\n readonly __brand: \"effortless-api\";\n readonly __spec: ApiConfig;\n readonly onError?: (...args: any[]) => any | Promise<any>;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly routes?: RouteEntry[];\n};\n\n// ============ Builder options (plain values, no inference) ============\n\n/** Options passed to `defineApi()` */\ntype ApiOptions = {\n /** Base path prefix for all routes (e.g., \"/api\") */\n basePath: `/${string}`;\n /** Enable response streaming. When true, routes receive a `stream` arg for SSE. */\n stream?: boolean;\n};\n\n// ============ ApiRoutes — returned after first route method ============\n\n/**\n * Finalized API handler with route-adding methods.\n * Has `__brand` so CLI discovers it. Each `.get()/.post()` adds a route and returns self.\n */\nexport interface ApiRoutes<C = undefined, ST extends boolean = false, A = undefined> extends ApiHandler<C> {\n get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n}\n\n// ============ Builder ============\n\n/**\n * Builder interface for defining API handlers.\n *\n * Each method sets exactly one generic, so inference happens one step at a time.\n * This prevents cascading type errors when one property has a mistake.\n */\ninterface ApiBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n ST extends boolean = false,\n HasFiles extends boolean = false,\n A = undefined,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): ApiBuilder<D2, P, C, ST, HasFiles, A>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): ApiBuilder<D, P2, C, ST, HasFiles, A>;\n\n /** Include static files by glob pattern */\n include(glob: string): ApiBuilder<D, P, C, ST, true, A>;\n\n /** Configure session-based authentication. Receives resolved config/deps. */\n auth<A2>(\n fn: (args: AuthArgs<D, P>) => AuthOptions<A2> | Promise<AuthOptions<A2>>\n ): ApiBuilder<D, P, C, ST, HasFiles, A2>;\n\n /** Configure Lambda settings only (no init function) */\n setup(lambda: LambdaOptions): ApiBuilder<D, P, C, ST, HasFiles, A>;\n /** Initialize shared state on cold start. Receives deps/config/files based on what was declared. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => ValidateSetupReturn<C2> | Promise<ValidateSetupReturn<C2>>\n ): ApiBuilder<D, P, C2, ST, HasFiles, A>;\n /** Initialize shared state on cold start with Lambda config. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => ValidateSetupReturn<C2> | Promise<ValidateSetupReturn<C2>>,\n lambda: LambdaOptions,\n ): ApiBuilder<D, P, C2, ST, HasFiles, A>;\n\n /** Handle errors thrown by routes */\n onError(\n fn: (args: { error: unknown; req: HttpRequest; ok: OkHelper; fail: FailHelper } & SpreadCtx<C, A>) => HttpResponse | Promise<HttpResponse>\n ): ApiBuilder<D, P, C, ST, HasFiles, A>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C, A>) => void | Promise<void>\n ): ApiBuilder<D, P, C, ST, HasFiles, A>;\n\n get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an API with typed routes using a builder pattern.\n *\n * @see {@link https://effortless-aws.website/use-cases/http-api | HTTP API guide}\n *\n * @example\n * ```typescript\n * export const api = defineApi({ basePath: \"/api\" })\n * .deps(() => ({ users }))\n * .config(({ defineSecret }) => ({ authSecret: defineSecret() }))\n * .auth<Session>(({ config }) => ({ secret: config.authSecret, expiresIn: \"1h\" }))\n * .get({ path: \"/me\" }, async ({ users, auth, ok }) => ok(auth.session))\n * .post({ path: \"/login\", public: true }, async ({ auth, ok }) => ok(await auth.createSession()))\n * ```\n */\nexport function defineApi<const O extends ApiOptions>(\n options: O,\n): ApiBuilder<undefined, undefined, undefined, O[\"stream\"] extends true ? true : false, false>;\nexport function defineApi(\n options: ApiOptions,\n): ApiBuilder {\n const { basePath, stream } = options;\n\n const state: {\n spec: ApiConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n authFn?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n routes: RouteEntry[];\n } = {\n spec: {\n basePath,\n ...(stream ? { stream } : {}),\n },\n routes: [],\n };\n\n const addRoute = (method: HttpMethod, def: { path: string; input?: unknown; public?: boolean; cache?: CacheOptions }, handler: Function) => {\n const routeCache = def.cache != null\n ? resolveCache(def.cache)\n : undefined;\n\n state.routes.push({\n method,\n path: def.path,\n onRequest: handler as any,\n ...(def.input ? { schema: def.input } : {}),\n ...(def.public ? { public: true } : {}),\n ...(routeCache ? { cache: routeCache } : {}),\n });\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): ApiRoutes => {\n const handler: any = {\n __brand: \"effortless-api\",\n __spec: state.spec,\n routes: state.routes,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.authFn ? { authFn: state.authFn } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n };\n\n // Add route methods to the finalized handler\n for (const m of [\"get\", \"post\", \"put\", \"patch\", \"delete\"] as const) {\n handler[m] = (def: any, fn: any) => {\n addRoute(m.toUpperCase() as HttpMethod, def, fn);\n handler.routes = state.routes;\n return handler;\n };\n }\n\n return handler as ApiRoutes;\n };\n\n const builder: ApiBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n auth(fn: any) {\n state.authFn = fn;\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n get(def: any, fn: any) { addRoute(\"GET\", def, fn); return finalize() as any; },\n post(def: any, fn: any) { addRoute(\"POST\", def, fn); return finalize() as any; },\n put(def: any, fn: any) { addRoute(\"PUT\", def, fn); return finalize() as any; },\n patch(def: any, fn: any) { addRoute(\"PATCH\", def, fn); return finalize() as any; },\n delete(def: any, fn: any) { addRoute(\"DELETE\", def, fn); return finalize() as any; },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\nimport type { Timezone } from \"./timezone\";\n\n// ============ Schedule expression ============\n\n/** Singular/plural unit for rate expressions */\ntype RateUnit = \"minute\" | \"minutes\" | \"hour\" | \"hours\" | \"day\" | \"days\";\n\n/**\n * Rate expression: `rate(1 hour)`, `rate(5 minutes)`, `rate(2 days)`\n *\n * Strictly typed — autocomplete and compile-time validation for unit.\n */\ntype RateExpression = `rate(${number} ${RateUnit})`;\n\n/**\n * Cron expression: `cron(min hour dom month dow year)`\n *\n * Not deeply typed (too combinatorial for TS), but the `cron(...)` wrapper is enforced.\n *\n * @example\n * ```\n * \"cron(0 9 * * ? *)\" // daily at 9:00 UTC\n * \"cron(0 9 ? * MON-FRI *)\" // weekdays at 9:00\n * \"cron(0/15 * * * ? *)\" // every 15 minutes\n * ```\n */\ntype CronExpression = `cron(${string})`;\n\n/**\n * EventBridge Scheduler schedule expression.\n *\n * - **Rate**: `\"rate(5 minutes)\"`, `\"rate(1 hour)\"`, `\"rate(1 day)\"` — strictly typed units\n * - **Cron**: `\"cron(0 9 * * ? *)\"` — 6 fields: min hour dom month dow year\n */\nexport type ScheduleExpression = RateExpression | CronExpression;\n\n// ============ Static config ============\n\n/** Static config extracted at deploy time */\nexport type CronConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** EventBridge Scheduler schedule expression */\n schedule: ScheduleExpression;\n /** IANA timezone for the schedule (default: UTC) */\n timezone?: Timezone;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Tick handler ============\n\n/** Callback function for cron tick */\nexport type CronTickFn<C = undefined> =\n (args: SpreadCtx<C>) => Promise<void> | void;\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineCron.\n * @internal\n */\nexport type CronHandler<C = any> = {\n readonly __brand: \"effortless-cron\";\n readonly __spec: CronConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onTick?: (...args: any[]) => any;\n};\n\n// ============ Options ============\n\n/** Options passed to `defineCron()` — resource config only */\ntype CronOptions = {\n /** EventBridge Scheduler schedule expression: `\"rate(5 minutes)\"` or `\"cron(0 9 * * ? *)\"` */\n schedule: ScheduleExpression;\n /** IANA timezone for the schedule (default: UTC). Full autocomplete for all timezones. */\n timezone?: Timezone;\n};\n\n// ============ Builder ============\n\ninterface CronBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): CronBuilder<D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): CronBuilder<D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): CronBuilder<D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): CronBuilder<D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): CronBuilder<D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): CronBuilder<D, P, C2, HasFiles>;\n\n /** Handle errors thrown by onTick */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): CronBuilder<D, P, C, HasFiles>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): CronBuilder<D, P, C, HasFiles>;\n\n /** Tick handler — called on each scheduled invocation (terminal) */\n onTick(\n fn: CronTickFn<C>\n ): CronHandler<C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a cron job — scheduled Lambda invocation via EventBridge Scheduler.\n *\n * @example\n * ```typescript\n * export const sync = defineCron({ schedule: \"cron(0 9 * * ? *)\" })\n * .deps(() => ({ orders }))\n * .config(({ defineSecret }) => ({ apiKey: defineSecret() }))\n * .include(\"templates/*.html\")\n * .setup(async ({ deps, config, files }) => ({\n * db: deps.orders, key: config.apiKey, tpl: files,\n * }), { memory: 512 })\n * .onTick(async ({ db, key, tpl }) => {\n * const html = tpl.read(\"templates/report.html\")\n * })\n * ```\n */\nexport function defineCron(options: CronOptions): CronBuilder {\n const { schedule, timezone } = options;\n\n const spec: CronConfig = {\n schedule,\n ...(timezone ? { timezone } : {}),\n };\n\n const state: {\n spec: CronConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onTick?: (...args: any[]) => any;\n } = { spec };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): CronHandler => ({\n __brand: \"effortless-cron\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onTick ? { onTick: state.onTick } : {}),\n }) as CronHandler;\n\n const builder: CronBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n onTick(fn) {\n state.onTick = fn as any;\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions, Duration } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n// ============ Static config ============\n\n/** Fargate container size presets */\nexport type FargateSize =\n | \"0.25vCPU-512mb\"\n | \"0.5vCPU-1gb\"\n | \"1vCPU-2gb\"\n | \"2vCPU-4gb\"\n | \"4vCPU-8gb\";\n\n/** Static config extracted at deploy time */\nexport type WorkerConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Fargate size (default: \"0.5vCPU-1gb\") */\n size?: FargateSize;\n /** How long the worker stays alive without messages before shutting down (default: \"5m\") */\n idleTimeout?: Duration;\n /** Max messages processed in parallel (default: 1, max: 10) */\n concurrency?: number;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Handler callback ============\n\n/** Callback function for the worker's onMessage */\nexport type WorkerMessageFn<T, C = undefined> =\n (msg: T, ctx: SpreadCtx<C>) => Promise<void> | void;\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineWorker.\n * @internal\n */\nexport type WorkerHandler<_T = any, C = any> = {\n readonly __brand: \"effortless-worker\";\n readonly __spec: WorkerConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onMessage?: (...args: any[]) => any;\n};\n\n// ============ Options ============\n\n/** Options passed to `defineWorker()` — resource config only */\ntype WorkerOptions = {\n /** Fargate size (default: \"0.5vCPU-1gb\") */\n size?: FargateSize;\n /** How long the worker stays alive without messages before shutting down (default: \"5m\") */\n idleTimeout?: Duration;\n /** Max messages processed in parallel (default: 1, max: 10) */\n concurrency?: number;\n};\n\n// ============ Builder ============\n\ninterface WorkerBuilder<\n T,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers, workers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): WorkerBuilder<T, D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): WorkerBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the bundle. Chainable — call multiple times. */\n include(glob: string): WorkerBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): WorkerBuilder<T, D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): WorkerBuilder<T, D, P, C2, HasFiles>;\n\n /** Handle errors thrown by onMessage. Return \"delete\" to discard, \"retry\" to reprocess (default). */\n onError(\n fn: (args: { error: unknown; msg: T; retryCount: number } & SpreadCtx<C>) => \"retry\" | \"delete\" | void | Promise<\"retry\" | \"delete\" | void>\n ): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Cleanup callback — runs when the worker shuts down */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Process a single message from the queue (terminal) */\n onMessage(\n fn: WorkerMessageFn<T, C>\n ): WorkerHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a worker — a long-running Fargate container with an SQS queue.\n *\n * The worker stays alive while processing messages and shuts down after\n * an idle timeout with no new messages. Other handlers can send messages\n * to the worker via `deps.worker.send(msg)`.\n *\n * @typeParam T - Type of messages the worker receives via its queue\n *\n * @example\n * ```typescript\n * type Job = { type: \"export\"; userId: string }\n *\n * export const worker = defineWorker<Job>({ memory: 2048, concurrency: 5 })\n * .deps(() => ({ orders }))\n * .setup(async ({ deps }) => ({ db: deps.orders }))\n * .onMessage(async (msg, { db }) => {\n * await processJob(msg, db)\n * })\n * ```\n */\nexport function defineWorker<T = unknown>(options?: WorkerOptions): WorkerBuilder<T> {\n const spec: WorkerConfig = {\n ...(options?.size ? { size: options.size } : {}),\n ...(options?.idleTimeout ? { idleTimeout: options.idleTimeout } : {}),\n ...(options?.concurrency ? { concurrency: options.concurrency } : {}),\n };\n\n const state: {\n spec: WorkerConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onMessage?: (...args: any[]) => any;\n } = { spec };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): WorkerHandler => ({\n __brand: \"effortless-worker\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onMessage ? { onMessage: state.onMessage } : {}),\n }) as WorkerHandler;\n\n const builder: WorkerBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n onMessage(fn) {\n state.onMessage = fn as any;\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { StandardSchemaV1, StandardJSONSchemaV1 } from \"@standard-schema/spec\";\nimport type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n// ============ Static config ============\n\n/** Static config extracted at deploy time */\nexport type McpConfig = {\n /** MCP server name (used in server info) */\n name: string;\n /** MCP server version (default: \"1.0.0\") */\n version?: string;\n /** Human-readable description — sent to clients in initialize response as system prompt context */\n instructions?: string;\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n};\n\n// ============ MCP tool types ============\n\n/** JSON Schema for a tool's input parameters */\nexport type McpInputSchema = {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n};\n\n/** Content block returned by a tool handler */\nexport type McpToolContent =\n | { type: \"text\"; text: string }\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"resource\"; resource: { uri: string; text: string; mimeType?: string } };\n\n/** Result returned by a tool handler */\nexport type McpToolResult = {\n content: McpToolContent[];\n isError?: boolean;\n};\n\n// ============ MCP resource types ============\n\n/** Content returned when reading a resource */\nexport type McpResourceContent =\n | { uri: string; mimeType?: string; text: string }\n | { uri: string; mimeType?: string; blob: string };\n\n/** Legacy resource content result type used by runtime internals */\ntype McpResourceResult = McpResourceContent | McpResourceContent[] | Promise<McpResourceContent | McpResourceContent[]>;\n\n/** Infer resource params type from schema, or fall back to Record<string, string> */\ntype InferResourceParams<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : Record<string, string>;\n\n/** Resource definition — pass `params` for typed schema validation on URI template params */\nexport type McpResourceDefInput<S extends StandardSchemaV1 | undefined = undefined> = {\n /** Resource URI or URI template (e.g. \"resource://contacts/{id}\") */\n uri: string;\n /** Human-readable name */\n name: string;\n /** Schema for URI template params — provides type inference + validation */\n params?: S;\n /** Optional description */\n description?: string;\n /** Optional MIME type */\n mimeType?: string;\n};\n\n/** What resource handlers can return — plain data is auto-wrapped by the runtime */\ntype McpResourceReturn = unknown | Promise<unknown>;\n\n/** Resource handler — receives params (typed or Record<string, string>) and ctx */\ntype McpResourceHandler<C, S = undefined> = (params: InferResourceParams<S>, ctx: SpreadCtx<C>) => McpResourceReturn;\n\n// Legacy types used by wrap-mcp runtime\n/** @internal */\nexport type McpResourceDef<C = undefined> = {\n name: string;\n description?: string;\n mimeType?: string;\n handler: (ctx: SpreadCtx<C>) => McpResourceResult;\n};\n\n/** @internal */\nexport type McpResourceTemplateDef<C = undefined> = {\n name: string;\n description?: string;\n mimeType?: string;\n handler: (params: Record<string, string>, ctx: SpreadCtx<C>) => McpResourceResult;\n};\n\n/** @internal */\nexport type McpResourceMap<C = undefined> = {\n [uriOrTemplate: string]: McpResourceDef<C> | McpResourceTemplateDef<C>;\n};\n\n// ============ MCP prompt types ============\n\n/** An argument accepted by a prompt */\nexport type McpPromptArgument = {\n /** Argument name */\n name: string;\n /** Optional description */\n description?: string;\n /** Whether the argument is required (default: false) */\n required?: boolean;\n};\n\n/** Content block inside a prompt message */\nexport type McpPromptContent =\n | { type: \"text\"; text: string }\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"audio\"; data: string; mimeType: string }\n | { type: \"resource\"; resource: { uri: string; mimeType?: string; text?: string; blob?: string } };\n\n/** A single message returned by a prompt */\nexport type McpPromptMessage = {\n role: \"user\" | \"assistant\";\n content: McpPromptContent;\n};\n\n/** Result returned by a prompt handler */\nexport type McpPromptResult = {\n description?: string;\n messages: McpPromptMessage[];\n};\n\n/** @internal Legacy prompt definition used by runtime */\nexport type McpPromptDef<C = undefined> = {\n description?: string;\n arguments?: McpPromptArgument[];\n handler: (args: Record<string, string>, ctx: SpreadCtx<C>) => McpPromptResult | Promise<McpPromptResult>;\n};\n\n/** Infer prompt args type from schema, or fall back to Record<string, string> */\ntype InferPromptArgs<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : Record<string, string>;\n\n/** Prompt definition — pass a Standard Schema for `args` for typed validation, or McpPromptArgument[] for untyped */\nexport type McpPromptDefInput<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined> = {\n /** Prompt name */\n name: string;\n /** Human-readable description */\n description?: string;\n /** Args: Standard Schema for typed validation, or McpPromptArgument[] for untyped */\n args?: S;\n};\n\n/** Handler return: string auto-wraps as user message, or return full McpPromptResult */\ntype McpPromptReturn = string | McpPromptResult | Promise<string | McpPromptResult>;\n\n/** Prompt handler — receives args (typed or Record<string, string>) and ctx */\ntype McpPromptHandler<C, S = undefined> = (args: InferPromptArgs<S>, ctx: SpreadCtx<C>) => McpPromptReturn;\n\n/** Infer tool input type from schema, or fall back to any */\ntype InferToolInput<S> = S extends StandardJSONSchemaV1 ? StandardJSONSchemaV1.InferOutput<S> : any;\n\n/** Tool definition — pass a StandardJSONSchemaV1 for `input` for typed validation, or McpInputSchema for raw JSON Schema */\nexport type McpToolDefInput<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema> = {\n /** Tool name */\n name: string;\n /** Human-readable description of the tool */\n description: string;\n /** Schema for tool input: StandardJSONSchemaV1 (e.g. z.object({...})) or raw McpInputSchema */\n input: S;\n};\n\n/** Tool handler — receives input (typed or any) and ctx */\ntype McpToolHandler<C, S = McpInputSchema> = (input: InferToolInput<S>, ctx: SpreadCtx<C>) => unknown | Promise<unknown>;\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Auth factory args — receives config/deps based on what was declared */\ntype AuthArgs<D, P> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> });\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineMcp.\n * @internal\n */\nexport type McpHandler<C = any> = {\n readonly __brand: \"effortless-mcp\";\n readonly __spec: McpConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly resources?: (...args: any[]) => any;\n readonly prompts?: (...args: any[]) => any;\n readonly tools?: (...args: any[]) => any;\n};\n\n// ============ McpEntries — returned after first singular method ============\n\n/**\n * Finalized MCP handler with chainable registration methods.\n * Has `__brand` so CLI discovers it. Each `.tool()/.resource()/.prompt()` adds an entry and returns self.\n */\nexport interface McpEntries<C = undefined> extends McpHandler<C> {\n /** Register a tool */\n tool<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema>(def: McpToolDefInput<S>, handler: McpToolHandler<C, S>): McpEntries<C>;\n /** Register a resource */\n resource<S extends StandardSchemaV1 | undefined = undefined>(def: McpResourceDefInput<S>, handler: McpResourceHandler<C, S>): McpEntries<C>;\n /** Register a prompt */\n prompt<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined>(def: McpPromptDefInput<S>, handler: McpPromptHandler<C, S>): McpEntries<C>;\n}\n\n// ============ Options ============\n\n/** Options passed to `defineMcp()` */\ntype McpOptions = {\n /** MCP server name (used in server info) */\n name: string;\n /** MCP server version (default: \"1.0.0\") */\n version?: string;\n /** Human-readable description — sent to clients in initialize response as system prompt context */\n instructions?: string;\n};\n\n// ============ Builder ============\n\ninterface McpBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers, workers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): McpBuilder<D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): McpBuilder<D, P2, C, HasFiles>;\n\n /** Include static files in the bundle. Chainable — call multiple times. */\n include(glob: string): McpBuilder<D, P, C, true>;\n\n /** Configure session-based authentication. Receives resolved config/deps. All requests require a valid session. */\n auth<A2>(\n fn: (args: AuthArgs<D, P>) => import(\"./define-api\").AuthOptions<A2> | Promise<import(\"./define-api\").AuthOptions<A2>>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): McpBuilder<D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): McpBuilder<D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): McpBuilder<D, P, C2, HasFiles>;\n\n /** Handle errors thrown by tool handlers */\n onError(\n fn: (args: { error: unknown; toolName: string } & SpreadCtx<C>) => void | Promise<void>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Cleanup callback — runs on shutdown */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Register a tool */\n tool<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema>(def: McpToolDefInput<S>, handler: McpToolHandler<C, S>): McpEntries<C>;\n /** Register a resource */\n resource<S extends StandardSchemaV1 | undefined = undefined>(def: McpResourceDefInput<S>, handler: McpResourceHandler<C, S>): McpEntries<C>;\n /** Register a prompt */\n prompt<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined>(def: McpPromptDefInput<S>, handler: McpPromptHandler<C, S>): McpEntries<C>;\n\n /** Finalize the handler without adding more entries */\n build(): McpHandler<C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an MCP (Model Context Protocol) server endpoint.\n *\n * Creates a Lambda-backed MCP server that exposes tools, resources, and prompts\n * for AI models and MCP-compatible clients via Streamable HTTP (JSON-RPC over POST).\n *\n * @see https://modelcontextprotocol.io/specification/2025-03-26 — MCP specification\n * @see https://effortless-aws.com/use-cases/mcp-server/ — full documentation with examples\n */\nexport function defineMcp(options: McpOptions): McpBuilder {\n const spec: McpConfig = {\n name: options.name,\n ...(options.version ? { version: options.version } : {}),\n ...(options.instructions ? { instructions: options.instructions } : {}),\n };\n\n const state: {\n spec: McpConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n authFn?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n toolEntries: [string, any][];\n resourceEntries: [string, any][];\n promptEntries: [string, any][];\n } = { spec, toolEntries: [], resourceEntries: [], promptEntries: [] };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n // Build factory functions from accumulated entries\n const buildToolsFactory = () =>\n state.toolEntries.length > 0\n ? () => Object.fromEntries(state.toolEntries)\n : undefined;\n\n const buildResourcesFactory = () =>\n state.resourceEntries.length > 0\n ? () => Object.fromEntries(state.resourceEntries)\n : undefined;\n\n const buildPromptsFactory = () =>\n state.promptEntries.length > 0\n ? () => Object.fromEntries(state.promptEntries)\n : undefined;\n\n const finalize = (): McpHandler => {\n const tools = buildToolsFactory();\n const resources = buildResourcesFactory();\n const prompts = buildPromptsFactory();\n return {\n __brand: \"effortless-mcp\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.authFn ? { authFn: state.authFn } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(resources ? { resources } : {}),\n ...(prompts ? { prompts } : {}),\n ...(tools ? { tools } : {}),\n } as McpHandler;\n };\n\n const finalizeWithEntries = (): McpEntries => {\n const handler: any = finalize();\n\n handler.tool = (def: any, fn: any) => {\n state.toolEntries.push([def.name, { description: def.description, input: def.input, handler: fn }]);\n handler.tools = buildToolsFactory();\n return handler;\n };\n\n handler.resource = (def: any, fn: any) => {\n const entry = { name: def.name, ...(def.description ? { description: def.description } : {}), ...(def.mimeType ? { mimeType: def.mimeType } : {}), handler: fn, ...(def.params ? { params: def.params } : {}) };\n state.resourceEntries.push([def.uri, entry]);\n handler.resources = buildResourcesFactory();\n return handler;\n };\n\n handler.prompt = (def: any, fn: any) => {\n const entry = { ...(def.description ? { description: def.description } : {}), args: def.args, handler: fn };\n state.promptEntries.push([def.name, entry]);\n handler.prompts = buildPromptsFactory();\n return handler;\n };\n\n return handler as McpEntries;\n };\n\n const builder: McpBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n auth(fn: any) {\n state.authFn = fn;\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n tool(def: any, fn: any) {\n state.toolEntries.push([def.name, { description: def.description, input: def.input, handler: fn }]);\n return finalizeWithEntries() as any;\n },\n resource(def: any, fn: any) {\n const entry = { name: def.name, ...(def.description ? { description: def.description } : {}), ...(def.mimeType ? { mimeType: def.mimeType } : {}), handler: fn, ...(def.params ? { params: def.params } : {}) };\n state.resourceEntries.push([def.uri, entry]);\n return finalizeWithEntries() as any;\n },\n prompt(def: any, fn: any) {\n const entry = { ...(def.description ? { description: def.description } : {}), args: def.args, handler: fn };\n state.promptEntries.push([def.name, entry]);\n return finalizeWithEntries() as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n"],"mappings":"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","names":["options","options","ttl"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/handlers/handler-options.ts","../src/handlers/define-table.ts","../src/handlers/define-app.ts","../src/handlers/define-static-site.ts","../src/handlers/define-queue.ts","../src/handlers/define-bucket.ts","../src/handlers/define-mailer.ts","../src/handlers/define-api.ts","../src/handlers/define-cron.ts","../src/handlers/define-worker.ts","../src/handlers/define-mcp.ts"],"sourcesContent":["/**\n * Configuration for an Effortless project.\n *\n * @see {@link https://effortless-aws.website/configuration | Configuration guide}\n */\nexport type EffortlessConfig = {\n /**\n * Project name used for resource naming and tagging.\n * This becomes part of Lambda function names, IAM roles, etc.\n */\n name: string;\n\n /**\n * Default AWS region for all handlers.\n * Can be overridden per-handler or via CLI `--region` flag.\n * @default \"eu-central-1\"\n */\n region?: string;\n\n /**\n * Deployment stage (e.g., \"dev\", \"staging\", \"prod\").\n * Used for resource isolation and tagging.\n * @default \"dev\"\n */\n stage?: string;\n\n /**\n * Glob patterns or directory paths to scan for handlers.\n * Used by `eff deploy` (without file argument) to auto-discover handlers.\n *\n * @example\n * ```typescript\n * // Single directory - scans for all .ts files\n * handlers: \"src\"\n *\n * // Glob patterns\n * handlers: [\"src/**\\/*.ts\", \"lib/**\\/*.handler.ts\"]\n * ```\n */\n handlers?: string | string[];\n\n /**\n * Default Lambda settings applied to all handlers unless overridden.\n *\n * All Lambdas run on ARM64 (Graviton2) architecture — ~20% cheaper than x86_64\n * with better price-performance for most workloads.\n */\n lambda?: {\n /**\n * Lambda memory in MB. AWS allocates proportional CPU —\n * 1769 MB gives one full vCPU.\n * @default 256\n */\n memory?: number;\n\n /**\n * Lambda timeout as a human-readable string.\n * AWS maximum is 15 minutes.\n * @example \"30 seconds\", \"5 minutes\"\n */\n timeout?: string;\n\n /**\n * Node.js Lambda runtime version.\n * @default \"nodejs24.x\"\n */\n runtime?: string;\n };\n\n};\n\n/** Helper function for type-safe configuration with TypeScript autocompletion. */\nexport const defineConfig = (config: EffortlessConfig): EffortlessConfig => config;\n","// Public helpers — this file must have ZERO heavy imports (no effect, no AWS SDK, no deploy code).\n// It is the source of truth for param() and related types used by the public API.\n\n// ============ Generate spec ============\n\n/** Generator spec for auto-creating secrets at deploy time. */\nexport type GenerateSpec = `hex:${number}` | `base64:${number}` | \"uuid\";\n\n// ============ Permissions ============\n\ntype AwsService =\n | \"dynamodb\"\n | \"s3\"\n | \"sqs\"\n | \"sns\"\n | \"ses\"\n | \"ssm\"\n | \"lambda\"\n | \"events\"\n | \"secretsmanager\"\n | \"cognito-idp\"\n | \"logs\";\n\nexport type Permission = `${AwsService}:${string}` | (string & {});\n\n// ============ Duration ============\n\n/**\n * Human-readable duration. Accepts a plain number (seconds) or a string\n * with a unit suffix: `\"30s\"`, `\"5m\"`, `\"1h\"`, `\"2d\"`.\n *\n * @example\n * ```typescript\n * timeout: 30 // 30 seconds\n * timeout: \"30s\" // 30 seconds\n * timeout: \"5m\" // 300 seconds\n * timeout: \"1h\" // 3600 seconds\n * retentionPeriod: \"4d\" // 345600 seconds\n * ```\n */\nexport type Duration = number | `${number}s` | `${number}m` | `${number}h` | `${number}d`;\n\n/** Convert a Duration to seconds. */\nexport const toSeconds = (d: Duration): number => {\n if (typeof d === \"number\") return d;\n const match = d.match(/^(\\d+(?:\\.\\d+)?)(s|m|h|d)$/);\n if (!match) throw new Error(`Invalid duration: \"${d}\"`);\n const n = Number(match[1]);\n const unit = match[2];\n if (unit === \"d\") return n * 86400;\n if (unit === \"h\") return n * 3600;\n if (unit === \"m\") return n * 60;\n return n;\n};\n\n// ============ Lambda config ============\n\n/** Logging verbosity level for Lambda handlers */\nexport type LogLevel = \"error\" | \"info\" | \"debug\";\n\n/**\n * Common Lambda configuration shared by all handler types.\n */\nexport type LambdaConfig = {\n /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout (default: 30s). Accepts seconds or duration string: `\"30s\"`, `\"5m\"` */\n timeout?: Duration;\n /** Logging verbosity: \"error\" (errors only), \"info\" (+ execution summary), \"debug\" (+ input/output). Default: \"info\" */\n logLevel?: LogLevel;\n};\n\n/**\n * Lambda configuration with additional IAM permissions.\n * Used by handler types that support custom permissions (http, table, queue).\n */\nexport type LambdaWithPermissions = LambdaConfig & {\n /** Additional IAM permissions for the Lambda */\n permissions?: Permission[];\n};\n\n// ============ Lambda options (for .setup()) ============\n\n/**\n * Lambda configuration passed as argument to `.setup()`.\n * Common across all handler types that create a Lambda function.\n */\nexport type LambdaOptions = {\n /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout (default: 30s). Accepts seconds or duration string: `\"30s\"`, `\"5m\"` */\n timeout?: Duration;\n /** Additional IAM permissions for the Lambda */\n permissions?: Permission[];\n /** Logging verbosity: \"error\" (errors only), \"info\" (+ execution summary), \"debug\" (+ input/output). Default: \"info\" */\n logLevel?: LogLevel;\n};\n\n// ============ Secrets (SSM Parameters) ============\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnySecretRef = SecretRef<any>;\n\n/**\n * Reference to an SSM Parameter Store secret.\n *\n * @typeParam T - The resolved type after optional transform (default: string)\n */\nexport type SecretRef<T = string> = {\n readonly __brand: \"effortless-secret\";\n readonly key?: string;\n readonly generate?: GenerateSpec;\n readonly transform?: (raw: string) => T;\n};\n\n/**\n * Maps a config declaration to resolved value types.\n * `SecretRef<T>` resolves to `T`.\n *\n * @typeParam P - Record of config keys to SecretRef instances\n */\nexport type ResolveConfig<P> = {\n [K in keyof P]: P[K] extends SecretRef<infer T> ? T : string;\n};\n\n/** Options for `defineSecret()` without a transform. */\nexport type DefineSecretOptions = {\n /** Custom SSM key (default: derived from config property name in kebab-case) */\n key?: string;\n /** Generator spec for auto-creating the secret at deploy time: `\"hex:32\"`, `\"base64:32\"`, `\"uuid\"` */\n generate?: GenerateSpec;\n};\n\n/** Options for `defineSecret()` with a transform. */\nexport type DefineSecretOptionsWithTransform<T> = DefineSecretOptions & {\n /** Transform the raw SSM string value into a typed value */\n transform: (raw: string) => T;\n};\n\n/** The defineSecret helper function type, injected into config callbacks. */\nexport type DefineSecretFn = {\n (): SecretRef<string>;\n (options: DefineSecretOptions): SecretRef<string>;\n <T>(options: DefineSecretOptionsWithTransform<T>): SecretRef<T>;\n};\n\n/** Helpers injected into the `config` callback. */\nexport type ConfigHelpers = {\n defineSecret: DefineSecretFn;\n};\n\n/** Config factory: a function receiving helpers and returning a record of SecretRefs. */\nexport type ConfigFactory<P> = (helpers: ConfigHelpers) => P;\n\n/** The `defineSecret` implementation, passed to config callbacks. */\nexport const defineSecret: DefineSecretFn = <T = string>(\n options?: DefineSecretOptions | DefineSecretOptionsWithTransform<T>\n): SecretRef<T> => {\n return {\n __brand: \"effortless-secret\",\n ...(options?.key ? { key: options.key } : {}),\n ...(options?.generate ? { generate: options.generate } : {}),\n ...(\"transform\" in (options ?? {}) ? { transform: (options as DefineSecretOptionsWithTransform<T>).transform } : {}),\n } as SecretRef<T>;\n};\n\n/** Internal helpers object passed to config callbacks. */\nexport const configHelpers: ConfigHelpers = { defineSecret };\n\n/** Resolve a config factory to a plain record of SecretRefs. */\nexport const resolveConfigFactory = <P>(config: ConfigFactory<P>): P =>\n config(configHelpers);\n\n// ============ Backwards compatibility ============\n\n/** @deprecated Use `defineSecret()` inside a config callback instead. */\nexport const secret = defineSecret;\n/** @deprecated Use `SecretRef` instead */\nexport type ParamRef<T = string> = SecretRef<T>;\n/** @deprecated Use `AnySecretRef` instead */\nexport type AnyParamRef = AnySecretRef;\n/** @deprecated Use `defineSecret()` instead. */\nexport const param = <T = string>(key: string, transform?: (raw: string) => T): SecretRef<T> => {\n return {\n __brand: \"effortless-secret\",\n key,\n ...(transform ? { transform } : {}),\n } as SecretRef<T>;\n};\n/** @deprecated Use `defineSecret({ generate: \"hex:N\" })` instead. */\nexport const generateHex = (bytes: number) => `hex:${bytes}`;\n/** @deprecated Use `defineSecret({ generate: \"base64:N\" })` instead. */\nexport const generateBase64 = (bytes: number) => `base64:${bytes}`;\n/** @deprecated Use `defineSecret({ generate: \"uuid\" })` instead. */\nexport const generateUuid = () => \"uuid\";\n\n// ============ Single-table types ============\n\n/**\n * DynamoDB table key (always pk + sk strings in single-table design).\n */\nexport type TableKey = { pk: string; sk: string };\n\n/**\n * Full DynamoDB item in the single-table design.\n *\n * Every item has a fixed envelope: `pk`, `sk`, `tag`, `data`, and optional `ttl`.\n * `tag` is stored as a top-level DynamoDB attribute (GSI-ready).\n * If your domain type `T` includes a `tag` field, effortless auto-copies\n * `data.tag` to the top-level `tag` on `put()`, so you don't have to pass it twice.\n *\n * @typeParam T - The domain data type stored in the `data` attribute\n */\nexport type TableItem<T> = {\n pk: string;\n sk: string;\n tag: string;\n data: T;\n ttl?: number;\n};\n\n/**\n * Input type for `TableClient.put()`.\n *\n * Unlike `TableItem<T>`, this does NOT include `tag` — effortless auto-extracts\n * the top-level DynamoDB `tag` attribute from your data using the configured\n * tag field (defaults to `data.tag`, configurable via `defineTable({ tag: \"type\" })`).\n *\n * @typeParam T - The domain data type stored in the `data` attribute\n */\nexport type PutInput<T> = {\n pk: string;\n sk: string;\n data: T;\n ttl?: number;\n};\n\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LogLevel, Permission, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { TableClient } from \"../runtime/table-client\";\nimport type { TableItem } from \"./handler-options\";\nimport type { StaticFiles } from \"./shared\";\n\n/** DynamoDB attribute types for keys */\nexport type KeyType = \"string\" | \"number\" | \"binary\";\n\n/**\n * DynamoDB table key definition\n */\nexport type TableKey = {\n /** Attribute name */\n name: string;\n /** Attribute type */\n type: KeyType;\n};\n\n/** DynamoDB Streams view type - determines what data is captured in stream records */\nexport type StreamView = \"NEW_AND_OLD_IMAGES\" | \"NEW_IMAGE\" | \"OLD_IMAGE\" | \"KEYS_ONLY\";\n\n/**\n * Stream event source mapping configuration.\n * @internal\n */\nexport type TableStreamConfig = {\n /** Stream view type - what data to include in stream records (default: \"NEW_AND_OLD_IMAGES\") */\n streamView?: StreamView;\n /** Number of records to process in each Lambda invocation (1-10000, default: 100) */\n batchSize?: number;\n /** Maximum time to gather records before invoking (default: `\"2s\"`). Accepts `\"5s\"`, `\"1m\"`, etc. */\n batchWindow?: Duration;\n /** Where to start reading the stream (default: \"LATEST\") */\n startingPosition?: \"LATEST\" | \"TRIM_HORIZON\";\n /** Number of records to process concurrently within a batch (default: 1 — sequential) */\n concurrency?: number;\n /** Max retry attempts for failed records before sending to DLQ (default: 1) */\n maxRetries?: number;\n};\n\n/**\n * Configuration options for defineTable (single-table design).\n *\n * Tables always use `pk (S)` + `sk (S)` keys, `tag (S)` discriminator,\n * `data (M)` for domain fields, and `ttl (N)` for optional expiration.\n */\nexport type TableConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: Duration; logLevel?: LogLevel; permissions?: Permission[] };\n /** DynamoDB billing mode (default: \"PAY_PER_REQUEST\") */\n billingMode?: \"PAY_PER_REQUEST\" | \"PROVISIONED\";\n /**\n * Name of the field in `data` that serves as the entity type discriminant.\n * Effortless auto-copies `data[tagField]` to the top-level DynamoDB `tag` attribute on `put()`.\n * Defaults to `\"tag\"`.\n */\n tagField?: string;\n /** Stream event source mapping config — set via `.stream({...})` builder method. */\n stream?: TableStreamConfig;\n};\n\n/**\n * DynamoDB stream record passed to onRecord callback.\n *\n * `new` and `old` are full `TableItem<T>` objects with the single-table envelope.\n *\n * @typeParam T - Type of the domain data (inside `data`)\n */\nexport type TableRecord<T = Record<string, unknown>> = {\n /** Type of modification: INSERT, MODIFY, or REMOVE */\n eventName: \"INSERT\" | \"MODIFY\" | \"REMOVE\";\n /** New item value (present for INSERT and MODIFY) */\n new?: TableItem<T>;\n /** Old item value (present for MODIFY and REMOVE) */\n old?: TableItem<T>;\n /** Primary key of the affected item */\n keys: { pk: string; sk: string };\n /** Sequence number for ordering */\n sequenceNumber?: string;\n /** Approximate timestamp when the modification occurred */\n timestamp?: number;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives table/deps/config/files based on what was declared */\ntype SetupArgs<T, D, P, HasFiles extends boolean> =\n & { table: TableClient<T> }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/**\n * Callback function type for processing a single DynamoDB stream record.\n * Receives the current record and the full batch for context.\n */\nexport type TableRecordFn<T = Record<string, unknown>, C = undefined> =\n (args: { record: TableRecord<T>; batch: readonly TableRecord<T>[] }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Batch handler function for DynamoDB stream records.\n * Called once with all records in the batch.\n * Return `{ failures: string[] }` (sequence numbers) for partial batch failure reporting.\n */\nexport type TableBatchFn<T = Record<string, unknown>, C = undefined> =\n (args: { records: readonly TableRecord<T>[] }\n & SpreadCtx<C>\n ) => Promise<void | { failures: string[] }>;\n\n// ============ Static config ============\n\n/** Static config extracted by AST (no runtime callbacks) */\n// TableConfig is already defined above and used as the extracted config type.\n\n// ============ Handler type (base interface for runtime wrappers and annotations) ============\n\n/**\n * Handler object created by defineTable.\n * Used by runtime wrappers and as type annotation for circular deps.\n * @internal\n */\nexport type TableHandler<T = Record<string, unknown>, C = any> = {\n readonly __brand: \"effortless-table\";\n readonly __spec: TableConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onRecord?: (...args: any[]) => any;\n readonly onRecordBatch?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineTable()` — resource config only. Stream options go in `.stream({...})`. */\ntype TableOptions<T> = {\n /** DynamoDB billing mode (default: \"PAY_PER_REQUEST\") */\n billingMode?: \"PAY_PER_REQUEST\" | \"PROVISIONED\";\n /** Name of the field in `data` that serves as the entity type discriminant (default: \"tag\") */\n tagField?: Extract<keyof T, string>;\n /** Decode/validate function for the `data` portion of stream records */\n schema?: (input: unknown) => T;\n};\n\n// ============ Builder ============\n\ninterface TableBuilder<\n T = Record<string, unknown>,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n HasStream extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): TableBuilder<T, D2, P, C, HasFiles, HasStream>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): TableBuilder<T, D, P2, C, HasFiles, HasStream>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): TableBuilder<T, D, P, C, true, HasStream>;\n\n /** Configure the DynamoDB stream event source mapping (batch size, retries, concurrency, etc.). */\n stream: HasStream extends true\n ? never\n : (opts: TableStreamConfig) => TableBuilder<T, D, P, C, HasFiles, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): TableBuilder<T, D, P, C, HasFiles, HasStream>;\n\n /** Initialize shared state on cold start. Receives table (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<T, D, P, HasFiles>) => C2 | Promise<C2>\n ): TableBuilder<T, D, P, C2, HasFiles, HasStream>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<T, D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): TableBuilder<T, D, P, C2, HasFiles, HasStream>;\n\n /** Handle errors thrown by onRecord/onRecordBatch */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): TableBuilder<T, D, P, C, HasFiles, HasStream>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): TableBuilder<T, D, P, C, HasFiles, HasStream>;\n\n /** Per-record stream handler (terminal — returns finalized handler) */\n onRecord(\n fn: TableRecordFn<T, C>\n ): TableHandler<T, C>;\n\n /** Batch stream handler (terminal — returns finalized handler) */\n onRecordBatch(\n fn: TableBatchFn<T, C>\n ): TableHandler<T, C>;\n\n /** Finalize as resource-only table (no Lambda) */\n build(): TableHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a DynamoDB table with optional stream handler (single-table design).\n *\n * Creates a table with fixed key schema: `pk (S)` + `sk (S)`, plus `tag (S)`,\n * `data (M)`, and `ttl (N)` attributes. TTL is always enabled.\n *\n * @see {@link https://effortless-aws.website/use-cases/database | Database guide}\n */\nexport function defineTable<T = Record<string, unknown>>(): TableBuilder<T>;\nexport function defineTable<T = Record<string, unknown>>(\n options: TableOptions<T>,\n): TableBuilder<T>;\nexport function defineTable<T = Record<string, unknown>>(\n options?: TableOptions<T>,\n): TableBuilder<T> {\n const {\n schema,\n ...tableConfig\n } = options ?? {} as TableOptions<T>;\n\n const spec: TableConfig = { ...tableConfig };\n\n const state: {\n spec: TableConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n schema?: (input: unknown) => T;\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onRecord?: (...args: any[]) => any;\n onRecordBatch?: (...args: any[]) => any;\n } = {\n spec,\n ...(schema ? { schema } : {}),\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): TableHandler<T> => ({\n __brand: \"effortless-table\",\n __spec: state.spec,\n ...(state.schema ? { schema: state.schema } : {}),\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onRecord ? { onRecord: state.onRecord } : {}),\n ...(state.onRecordBatch ? { onRecordBatch: state.onRecordBatch } : {}),\n }) as TableHandler<T>;\n\n const builder: TableBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n stream(opts: TableStreamConfig) {\n state.spec = { ...state.spec, stream: { ...state.spec.stream, ...opts } };\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onRecord(fn) {\n state.onRecord = fn as any;\n return finalize() as any;\n },\n onRecordBatch(fn) {\n state.onRecordBatch = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { LambdaWithPermissions } from \"./handler-options\";\n\n/**\n * Configuration for deploying an SSR framework (Nuxt, Astro, etc.)\n * via CloudFront + S3 (static assets) + Lambda Function URL (server-side rendering).\n */\nexport type AppConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Directory containing the Lambda server handler (e.g., \".output/server\").\n * Must contain an `index.mjs` (or `index.js`) that exports a `handler` function. */\n server: string;\n /** Directory containing static assets for S3 (e.g., \".output/public\") */\n assets: string;\n /** Base URL path (default: \"/\") */\n path?: string;\n /** Shell command to build the framework output (e.g., \"nuxt build\") */\n build?: string;\n /** Custom domain name. String or stage-keyed record (e.g., { prod: \"app.example.com\" }). */\n domain?: string | Record<string, string>;\n /** CloudFront route overrides: path patterns forwarded to API Gateway instead of the SSR Lambda.\n * Keys are CloudFront path patterns (e.g., \"/api/*\"), values are handler references.\n * Example: `routes: { \"/api/*\": api }` */\n routes?: Record<string, { readonly __brand: string }>;\n};\n\n/**\n * Internal handler object created by defineApp\n * @internal\n */\nexport type AppHandler = {\n readonly __brand: \"effortless-app\";\n readonly __spec: AppConfig;\n};\n\n/**\n * Deploy an SSR framework application via CloudFront + Lambda Function URL.\n *\n * Static assets from the `assets` directory are served via S3 + CloudFront CDN.\n * Server-rendered pages are handled by a Lambda function using the framework's\n * built output from the `server` directory.\n *\n * For static-only sites (no SSR), use {@link defineStaticSite} instead.\n *\n * @see {@link https://effortless-aws.website/use-cases/web-app | Web app guide}\n *\n * @param options - App configuration: server directory, assets directory, optional build command\n * @returns Handler object used by the deployment system\n */\nexport const defineApp = () => (options: AppConfig): AppHandler => ({\n __brand: \"effortless-app\",\n __spec: options,\n});\n","/** Any branded handler that deploys to API Gateway or S3 */\ntype AnyRoutableHandler = { readonly __brand: string };\n\n/** Simplified request object passed to middleware */\nexport type MiddlewareRequest = {\n uri: string;\n method: string;\n querystring: string;\n headers: Record<string, string>;\n cookies: Record<string, string>;\n};\n\n/** Redirect the user to another URL */\nexport type MiddlewareRedirect = {\n redirect: string;\n status?: 301 | 302 | 307 | 308;\n};\n\n/** Deny access with a 403 status */\nexport type MiddlewareDeny = {\n status: 403;\n body?: string;\n};\n\n/** Middleware return type: redirect, deny, or void (continue serving) */\nexport type MiddlewareResult = MiddlewareRedirect | MiddlewareDeny | void;\n\n/** Function that runs before serving static files via Lambda@Edge */\nexport type MiddlewareHandler = (\n request: MiddlewareRequest\n) => Promise<MiddlewareResult> | MiddlewareResult;\n\n/** SEO options for auto-generating sitemap.xml, robots.txt, and submitting to Google Indexing API */\nexport type StaticSiteSeo = {\n /** Sitemap filename (e.g. \"sitemap.xml\", \"sitemap-v2.xml\") */\n sitemap: string;\n /** Path to Google service account JSON key file for Indexing API batch submission.\n * Requires adding the service account email as an owner in Google Search Console. */\n googleIndexing?: string;\n};\n\n/**\n * Configuration for a static site (S3 + CloudFront)\n */\nexport type StaticSiteConfig = {\n /** Directory containing the static site files, relative to project root */\n dir: string;\n /** Default file for directory requests (default: \"index.html\") */\n index?: string;\n /** Shell command to run before deploy to generate site content (e.g., \"npx astro build\") */\n build?: string;\n /** Path to a custom error page relative to `dir`.\n * - If set to the same value as `index` (e.g. \"index.html\"), enables SPA mode:\n * all paths that don't match a file are served with `index.html` (HTTP 200), letting the client-side router handle them.\n * - If set to a different file (e.g. \"404.html\"), that file is served with HTTP 404 for missing paths.\n * - If omitted, a default 404 page is auto-generated. */\n errorPage?: string;\n /** Custom domain name. Accepts a string (same domain for all stages) or a Record mapping stage names to domains (e.g., `{ prod: \"example.com\", dev: \"dev.example.com\" }`). Requires an ACM certificate in us-east-1. If the cert also covers www, a 301 redirect from www to non-www is set up automatically. */\n domain?: string | Record<string, string>;\n /** SEO: auto-generate sitemap.xml and robots.txt at deploy time, optionally submit URLs to Google Indexing API */\n seo?: StaticSiteSeo;\n};\n\n/** Route entry stored on the static site handler */\ntype StaticSiteRouteEntry = {\n pattern: string;\n origin: AnyRoutableHandler;\n access?: \"private\" | \"public\";\n};\n\n/**\n * Internal handler object created by defineStaticSite\n * @internal\n */\nexport type StaticSiteHandler = {\n readonly __brand: \"effortless-static-site\";\n readonly __spec: StaticSiteConfig;\n readonly routes: StaticSiteRouteEntry[];\n readonly middleware?: MiddlewareHandler;\n};\n\n/** Builder for configuring a static site before calling `.build()` */\nexport type StaticSiteBuilder = {\n route(pattern: string, origin: AnyRoutableHandler, opts?: { access?: \"private\" | \"public\" }): StaticSiteBuilder;\n middleware(fn: MiddlewareHandler): StaticSiteBuilder;\n build(): StaticSiteHandler;\n};\n\n/**\n * Deploy a static site via S3 + CloudFront CDN, with optional API and bucket route overrides.\n *\n * @see {@link https://effortless-aws.website/use-cases/web-app | Web app guide}\n *\n * @param options - Static site configuration: directory, optional SPA mode, build command, domain\n * @returns Builder with `.route()`, `.middleware()`, and `.build()` methods\n */\nexport function defineStaticSite(options: StaticSiteConfig): StaticSiteBuilder {\n const state = {\n spec: { ...options },\n routes: [] as StaticSiteRouteEntry[],\n middleware: undefined as MiddlewareHandler | undefined,\n };\n\n const builder = {\n route(pattern: string, origin: AnyRoutableHandler, opts?: { access?: \"private\" | \"public\" }) {\n state.routes.push({ pattern, origin, ...(opts?.access ? { access: opts.access } : {}) });\n return builder;\n },\n middleware(fn: MiddlewareHandler) {\n state.middleware = fn;\n return builder;\n },\n build(): StaticSiteHandler {\n return {\n __brand: \"effortless-static-site\",\n __spec: state.spec,\n routes: state.routes,\n ...(state.middleware ? { middleware: state.middleware } : {}),\n };\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LogLevel, Permission, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n/**\n * Parsed SQS message passed to the handler callbacks.\n *\n * @typeParam T - Type of the decoded message body (from schema function)\n */\nexport type QueueMessage<T = unknown> = {\n /** Unique message identifier */\n messageId: string;\n /** Receipt handle for acknowledgement */\n receiptHandle: string;\n /** Parsed message body (JSON-decoded, then optionally schema-validated) */\n body: T;\n /** Raw unparsed message body string */\n rawBody: string;\n /** Message group ID (FIFO ordering key, empty string for standard queues) */\n messageGroupId: string;\n /** Message deduplication ID (FIFO only) */\n messageDeduplicationId?: string;\n /** SQS message attributes */\n messageAttributes: Record<string, { dataType?: string; stringValue?: string }>;\n /** Approximate first receive timestamp */\n approximateFirstReceiveTimestamp?: string;\n /** Approximate receive count */\n approximateReceiveCount?: string;\n /** Sent timestamp */\n sentTimestamp?: string;\n};\n\n/**\n * Event source mapping (poller) configuration — how Lambda consumes the queue.\n */\nexport type QueuePollerConfig = {\n /** Number of messages per Lambda invocation (1-10 for FIFO, default: 10) */\n batchSize?: number;\n /** Maximum time to gather messages before invoking (default: 0). Accepts `\"5s\"`, `\"1m\"`, etc. */\n batchWindow?: Duration;\n};\n\n/**\n * Configuration options for a queue handler.\n */\nexport type QueueConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: Duration; logLevel?: LogLevel; permissions?: Permission[] };\n /**\n * Whether this is a FIFO queue (ordered, exactly-once).\n * Currently only `true` is supported — standard queue support is planned.\n */\n fifo?: boolean;\n /** Visibility timeout (default: max of timeout or 30s). Accepts `\"30s\"`, `\"5m\"`, etc. */\n visibilityTimeout?: Duration;\n /** Message retention period (default: `\"4d\"`). Accepts `\"1h\"`, `\"7d\"`, etc. */\n retentionPeriod?: Duration;\n /** Delivery delay for all messages in the queue (default: 0). Accepts `\"30s\"`, `\"5m\"`, etc. */\n delay?: Duration;\n /** Enable content-based deduplication for FIFO queues (default: true) */\n contentBasedDeduplication?: boolean;\n /** Max number of receives before a message is sent to the dead-letter queue (default: 3) */\n maxReceiveCount?: number;\n /** Event source mapping config — set via `.poller({...})` builder method. */\n poller?: QueuePollerConfig;\n};\n\n// ============ Setup args ============\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/**\n * Per-message handler function.\n * Called once per message in the batch. Failures are reported individually.\n */\nexport type QueueMessageFn<T = unknown, C = undefined> =\n (args: { message: QueueMessage<T> }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Batch handler function.\n * Called once with all messages in the batch.\n * Return `{ failures: string[] }` (messageIds) for partial batch failure reporting.\n */\nexport type QueueBatchFn<T = unknown, C = undefined> =\n (args: { messages: QueueMessage<T>[] }\n & SpreadCtx<C>\n ) => Promise<void | { failures: string[] }>;\n\n// ============ Internal handler object ============\n\n/**\n * Internal handler object created by defineQueue.\n * @internal\n */\nexport type QueueHandler<T = unknown, C = any> = {\n readonly __brand: \"effortless-queue\";\n readonly __spec: QueueConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onMessage?: (...args: any[]) => any;\n readonly onMessageBatch?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineQueue()` — queue resource config */\ntype QueueOptions<T> = {\n /**\n * Whether this is a FIFO queue (ordered, exactly-once).\n * Currently only `true` is supported — standard queue support is planned.\n */\n fifo?: boolean;\n /** Visibility timeout (default: max of timeout or 30s) */\n visibilityTimeout?: Duration;\n /** Message retention period (default: \"4d\") */\n retentionPeriod?: Duration;\n /** Delivery delay for all messages in the queue (default: 0) */\n delay?: Duration;\n /** Enable content-based deduplication for FIFO queues (default: true) */\n contentBasedDeduplication?: boolean;\n /** Max number of receives before DLQ (default: 3) */\n maxReceiveCount?: number;\n /** Decode/validate function for the message body */\n schema?: (input: unknown) => T;\n};\n\n// ============ Builder ============\n\ninterface QueueBuilder<\n T = unknown,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): QueueBuilder<T, D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): QueueBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): QueueBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): QueueBuilder<T, D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): QueueBuilder<T, D, P, C2, HasFiles>;\n\n /**\n * Configure the event source mapping (poller) that delivers messages to the Lambda.\n * Call before the terminal `.onMessage` / `.onMessageBatch`.\n */\n poller(\n options: QueuePollerConfig\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Handle errors thrown by message handlers */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Per-message handler (terminal — returns finalized handler) */\n onMessage(\n fn: QueueMessageFn<T, C>\n ): QueueHandler<T, C>;\n\n /** Batch handler (terminal — returns finalized handler) */\n onMessageBatch(\n fn: QueueBatchFn<T, C>\n ): QueueHandler<T, C>;\n\n /** Finalize as a resource-only queue (no Lambda). Use when the SQS queue is consumed by an external system. */\n build(): QueueHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an SQS queue with a Lambda message handler.\n *\n * Currently only FIFO queues are supported — pass `{ fifo: true }` explicitly\n * (standard queue support is planned). Poller/batch settings live on the\n * `.poller({...})` builder method; queue-level properties stay in the options\n * object.\n *\n * @see {@link https://effortless-aws.website/use-cases/queue | Queue guide}\n */\nexport function defineQueue<T = unknown>(): QueueBuilder<T>;\nexport function defineQueue<T = unknown>(\n options: QueueOptions<T>,\n): QueueBuilder<T>;\nexport function defineQueue<T = unknown>(\n options?: QueueOptions<T>,\n): QueueBuilder<T> {\n const {\n schema,\n ...queueConfig\n } = options ?? {} as QueueOptions<T>;\n\n const spec: QueueConfig = { ...queueConfig };\n\n const state: {\n spec: QueueConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n schema?: (input: unknown) => T;\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onMessage?: (...args: any[]) => any;\n onMessageBatch?: (...args: any[]) => any;\n } = {\n spec,\n ...(schema ? { schema } : {}),\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): QueueHandler<T> => ({\n __brand: \"effortless-queue\",\n __spec: state.spec,\n ...(state.schema ? { schema: state.schema } : {}),\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onMessage ? { onMessage: state.onMessage } : {}),\n ...(state.onMessageBatch ? { onMessageBatch: state.onMessageBatch } : {}),\n }) as QueueHandler<T>;\n\n const builder: QueueBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n poller(options) {\n if (Object.keys(options).length > 0) {\n state.spec = { ...state.spec, poller: { ...state.spec.poller, ...options } };\n }\n return builder;\n },\n onMessage(fn) {\n state.onMessage = fn as any;\n return finalize() as any;\n },\n onMessageBatch(fn) {\n state.onMessageBatch = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\nimport type { BucketClient, BucketClientWithEntities } from \"../runtime/bucket-client\";\n\n/**\n * Per-entity configuration for typed JSON key-value storage within a bucket.\n */\nexport type BucketEntityConfig = {\n /** Cache duration for CloudFront/browser caching (e.g., \"10s\", \"5m\", \"1h\"). No caching if omitted. */\n cache?: Duration;\n};\n\n/**\n * Configuration options for defineBucket.\n */\nexport type BucketConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: import(\"./handler-options\").Duration; logLevel?: import(\"./handler-options\").LogLevel; permissions?: import(\"./handler-options\").Permission[] };\n /** S3 key prefix filter for event notifications (e.g., \"uploads/\") */\n prefix?: string;\n /** S3 key suffix filter for event notifications (e.g., \".jpg\") */\n suffix?: string;\n /** Typed JSON entity definitions for key-value storage */\n entities?: Record<string, BucketEntityConfig>;\n /** Local directory to seed into bucket on deploy (only uploads files that don't already exist) */\n seed?: string;\n /** Local directory to sync into bucket on every deploy (uploads new/changed, deletes removed) */\n sync?: string;\n};\n\n/**\n * S3 event record passed to onObjectCreated/onObjectRemoved callbacks.\n */\nexport type BucketEvent = {\n /** S3 event name (e.g., \"ObjectCreated:Put\", \"ObjectRemoved:Delete\") */\n eventName: string;\n /** Object key (path within the bucket) */\n key: string;\n /** Object size in bytes (present for created events) */\n size?: number;\n /** Object ETag (present for created events) */\n eTag?: string;\n /** ISO 8601 timestamp of when the event occurred */\n eventTime?: string;\n /** S3 bucket name */\n bucketName: string;\n};\n\n// ============ Setup args ============\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/** Setup factory — receives bucket/deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean, Entities extends Record<string, any> = {}> =\n & { bucket: {} extends Entities ? BucketClient : BucketClientWithEntities<Entities> }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/**\n * Callback function type for S3 ObjectCreated events\n */\nexport type BucketObjectCreatedFn<C = undefined> =\n (args: { event: BucketEvent }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Callback function type for S3 ObjectRemoved events\n */\nexport type BucketObjectRemovedFn<C = undefined> =\n (args: { event: BucketEvent }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n// ============ Internal handler object ============\n\n/**\n * Internal handler object created by defineBucket\n * @internal\n */\nexport type BucketHandler<C = any, _Entities extends Record<string, any> = {}> = {\n readonly __brand: \"effortless-bucket\";\n readonly __spec: BucketConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onObjectCreated?: (...args: any[]) => any;\n readonly onObjectRemoved?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineBucket()` — static config */\ntype BucketOptions = {\n /** S3 key prefix filter for event notifications (e.g., \"uploads/\") */\n prefix?: string;\n /** S3 key suffix filter for event notifications (e.g., \".jpg\") */\n suffix?: string;\n /** Local directory to seed into bucket on deploy (only uploads files that don't already exist) */\n seed?: string;\n /** Local directory to sync into bucket on every deploy (uploads new/changed, deletes removed) */\n sync?: string;\n};\n\n// ============ Builder ============\n\ninterface BucketBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n Entities extends Record<string, any> = {},\n> {\n /** Declare handler dependencies */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): BucketBuilder<D2, P, C, HasFiles, Entities>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): BucketBuilder<D, P2, C, HasFiles, Entities>;\n\n /** Include static files in the Lambda ZIP */\n include(glob: string): BucketBuilder<D, P, C, true, Entities>;\n\n /** Register a typed JSON entity stored as `{name}/{id}.json` in the bucket */\n entity<N extends string, T>(\n name: N,\n options?: BucketEntityConfig,\n ): BucketBuilder<D, P, C, HasFiles, Entities & { [K in N]: T }>;\n\n /** Initialize shared state on cold start with lambda options */\n setup(lambda: LambdaOptions): BucketBuilder<D, P, C, HasFiles, Entities>;\n /** Initialize shared state on cold start. Receives bucket (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles, Entities>) => C2 | Promise<C2>\n ): BucketBuilder<D, P, C2, HasFiles, Entities>;\n /** Initialize shared state on cold start with lambda options. Receives bucket (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles, Entities>) => C2 | Promise<C2>,\n lambda: LambdaOptions,\n ): BucketBuilder<D, P, C2, HasFiles, Entities>;\n\n /** Handle errors thrown by callbacks */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): BucketBuilder<D, P, C, HasFiles, Entities>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): BucketBuilder<D, P, C, HasFiles, Entities>;\n\n /** Handle S3 ObjectCreated events (terminal — returns finalized handler) */\n onObjectCreated(\n fn: BucketObjectCreatedFn<C>\n ): BucketHandler<C, Entities>;\n\n /** Handle S3 ObjectRemoved events (terminal — returns finalized handler) */\n onObjectRemoved(\n fn: BucketObjectRemovedFn<C>\n ): BucketHandler<C, Entities>;\n\n /** Finalize as resource-only bucket (no Lambda) */\n build(): BucketHandler<C, Entities>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an S3 bucket with optional event handlers.\n *\n * @see {@link https://effortless-aws.website/use-cases/storage | Storage guide}\n */\nexport function defineBucket(): BucketBuilder;\nexport function defineBucket(\n options: BucketOptions,\n): BucketBuilder;\nexport function defineBucket(\n options?: BucketOptions,\n): BucketBuilder {\n const bucketConfig = options ?? {} as BucketOptions;\n\n const spec: BucketConfig = {\n ...bucketConfig,\n };\n\n const state: {\n spec: BucketConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onObjectCreated?: (...args: any[]) => any;\n onObjectRemoved?: (...args: any[]) => any;\n } = {\n spec,\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): BucketHandler => ({\n __brand: \"effortless-bucket\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onObjectCreated ? { onObjectCreated: state.onObjectCreated } : {}),\n ...(state.onObjectRemoved ? { onObjectRemoved: state.onObjectRemoved } : {}),\n }) as BucketHandler;\n\n const builder: BucketBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n entity(name: string, options?: BucketEntityConfig) {\n state.spec = {\n ...state.spec,\n entities: { ...state.spec.entities, [name]: options ?? {} },\n };\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onObjectCreated(fn) {\n state.onObjectCreated = fn as any;\n return finalize() as any;\n },\n onObjectRemoved(fn) {\n state.onObjectRemoved = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","/**\n * Configuration options for defining a mailer (SES email identity)\n */\nexport type MailerConfig = {\n /** Domain to verify and send emails from (e.g., \"myapp.com\") */\n domain: string;\n};\n\n/**\n * Internal handler object created by defineMailer\n * @internal\n */\nexport type MailerHandler = {\n readonly __brand: \"effortless-mailer\";\n readonly __spec: MailerConfig;\n};\n\n/**\n * Define an email sender backed by Amazon SES.\n *\n * Creates an SES Email Identity for the specified domain and provides\n * a typed `EmailClient` to other handlers via `deps`.\n *\n * On first deploy, DKIM DNS records are printed to the console.\n * Add them to your DNS provider to verify the domain.\n *\n * @see {@link https://effortless-aws.website/use-cases/email | Email guide}\n */\nexport const defineMailer = () => (options: MailerConfig): MailerHandler => ({\n __brand: \"effortless-mailer\",\n __spec: options,\n});\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { LambdaWithPermissions, AnySecretRef, ResolveConfig, Duration, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory, toSeconds } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles, ResponseStream } from \"./shared\";\nimport type { HttpRequest, HttpResponse } from \"./shared\";\nimport type { AuthHelpers } from \"./auth\";\n\n// ============ Auth types ============\n\n/** Auth config options (user-facing) */\nexport type AuthOptions<A = unknown> = {\n /** HMAC secret for signing session cookies. Use `secret()` or `param()` in config. */\n secret: string;\n /** Default session lifetime (default: \"7d\"). */\n expiresIn?: Duration;\n /** Optional API token strategy for external clients. */\n apiToken?: {\n /** HTTP header to read the token from. Default: \"authorization\" (strips \"Bearer \" prefix). */\n header?: string;\n /** Verify the token value and return session data, or null if invalid. */\n verify: (value: string) => A | null | Promise<A | null>;\n /** Cache verified token results for this duration. */\n cacheTtl?: Duration;\n };\n};\n\n/** Property names reserved by the framework — cannot be used in setup return */\ntype ReservedKeys = 'req' | 'input' | 'stream' | 'ok' | 'fail';\n\n// ============ Response helpers ============\n\n/** Success response helper: `ok({ data })` → `{ status: 200, body: { data } }` */\nexport type OkHelper = (body?: unknown, status?: number) => HttpResponse;\n/** Error response helper: `fail(\"message\")` → `{ status: 400, body: { error: \"message\" } }` */\nexport type FailHelper = (message: string, status?: number) => HttpResponse;\n\n// ============ Route types ============\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/** Cache options for a GET route. Duration shorthand (e.g. \"30s\", \"5m\") or object for fine-grained control. */\nexport type CacheOptions = Duration | {\n ttl: Duration;\n swr?: Duration;\n scope?: \"public\" | \"private\";\n};\n\n/** Resolved cache config with numeric seconds */\ntype ResolvedCache =\n | { private?: false; ttl: number; swr: number }\n | { private: true; ttl: number };\n\n/** Resolve CacheOptions into numeric seconds. Shorthand = public with swr = ttl * 2. */\nconst resolveCache = (cache: CacheOptions): ResolvedCache => {\n if (typeof cache === \"number\" || typeof cache === \"string\") {\n const ttl = toSeconds(cache);\n return { ttl, swr: ttl * 2 };\n }\n const ttl = toSeconds(cache.ttl);\n if (cache.scope === \"private\") {\n return { private: true, ttl };\n }\n return {\n ttl,\n swr: cache.swr != null ? toSeconds(cache.swr) : ttl * 2,\n };\n};\n\n/** Parsed route definition stored at runtime */\nexport type RouteEntry = {\n method: HttpMethod;\n path: string;\n onRequest: (...args: any[]) => any;\n schema?: unknown;\n public?: boolean;\n cache?: ResolvedCache;\n};\n\n/** Spread ctx into route args, add AuthHelpers if A is set */\ntype SpreadCtx<C, A = undefined> =\n & ([C] extends [undefined] ? {} : C & {})\n & ([A] extends [undefined] ? {} : { auth: AuthHelpers<A> });\n\n/** Infer validated output type from a Standard Schema, or fall back to unknown */\ntype InferInput<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : unknown;\n\n/** Callback args available inside each route — ctx is spread into args */\ntype RouteArgs<C, ST, A = undefined, S = undefined> =\n & SpreadCtx<C, A>\n & { req: HttpRequest; input: InferInput<S>; ok: OkHelper; fail: FailHelper }\n & ([ST] extends [true] ? { stream: ResponseStream } : {});\n\n/** Route handler function */\ntype RouteHandler<C, ST, A = undefined, S = undefined> = (args: RouteArgs<C, ST, A, S>) => Promise<HttpResponse | void> | HttpResponse | void;\n\n/** Route definition — pass `input` for typed schema validation */\ntype RouteDef<S extends StandardSchemaV1 | undefined = undefined> = {\n path: `/${string}`;\n input?: S;\n public?: boolean;\n cache?: CacheOptions;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & { ok: OkHelper; fail: FailHelper }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Auth factory args — receives config/deps based on what was declared */\ntype AuthArgs<D, P> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> });\n\n/** Validate that setup return type does not use reserved property names */\ntype ValidateSetupReturn<C> = C & { [K in ReservedKeys]?: never };\n\n// ============ Static config ============\n\n/** Static config extracted by AST (no runtime callbacks) */\nexport type ApiConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Base path prefix for all routes (e.g., \"/api\") */\n basePath: `/${string}`;\n /** Enable response streaming. When true, the Lambda Function URL uses RESPONSE_STREAM invoke mode. */\n stream?: boolean;\n};\n\n// ============ Internal handler object ============\n\n/** Internal handler object created by defineApi */\nexport type ApiHandler<C = undefined> = {\n readonly __brand: \"effortless-api\";\n readonly __spec: ApiConfig;\n readonly onError?: (...args: any[]) => any | Promise<any>;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly routes?: RouteEntry[];\n};\n\n// ============ Builder options (plain values, no inference) ============\n\n/** Options passed to `defineApi()` */\ntype ApiOptions = {\n /** Base path prefix for all routes (e.g., \"/api\") */\n basePath: `/${string}`;\n /** Enable response streaming. When true, routes receive a `stream` arg for SSE. */\n stream?: boolean;\n};\n\n// ============ ApiRoutes — returned after first route method ============\n\n/**\n * Finalized API handler with route-adding methods.\n * Has `__brand` so CLI discovers it. Each `.get()/.post()` adds a route and returns self.\n */\nexport interface ApiRoutes<C = undefined, ST extends boolean = false, A = undefined> extends ApiHandler<C> {\n get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n}\n\n// ============ Builder ============\n\n/**\n * Builder interface for defining API handlers.\n *\n * Each method sets exactly one generic, so inference happens one step at a time.\n * This prevents cascading type errors when one property has a mistake.\n */\ninterface ApiBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n ST extends boolean = false,\n HasFiles extends boolean = false,\n A = undefined,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): ApiBuilder<D2, P, C, ST, HasFiles, A>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): ApiBuilder<D, P2, C, ST, HasFiles, A>;\n\n /** Include static files by glob pattern */\n include(glob: string): ApiBuilder<D, P, C, ST, true, A>;\n\n /** Configure session-based authentication. Receives resolved config/deps. */\n auth<A2>(\n fn: (args: AuthArgs<D, P>) => AuthOptions<A2> | Promise<AuthOptions<A2>>\n ): ApiBuilder<D, P, C, ST, HasFiles, A2>;\n\n /** Configure Lambda settings only (no init function) */\n setup(lambda: LambdaOptions): ApiBuilder<D, P, C, ST, HasFiles, A>;\n /** Initialize shared state on cold start. Receives deps/config/files based on what was declared. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => ValidateSetupReturn<C2> | Promise<ValidateSetupReturn<C2>>\n ): ApiBuilder<D, P, C2, ST, HasFiles, A>;\n /** Initialize shared state on cold start with Lambda config. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => ValidateSetupReturn<C2> | Promise<ValidateSetupReturn<C2>>,\n lambda: LambdaOptions,\n ): ApiBuilder<D, P, C2, ST, HasFiles, A>;\n\n /** Handle errors thrown by routes */\n onError(\n fn: (args: { error: unknown; req: HttpRequest; ok: OkHelper; fail: FailHelper } & SpreadCtx<C, A>) => HttpResponse | Promise<HttpResponse>\n ): ApiBuilder<D, P, C, ST, HasFiles, A>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C, A>) => void | Promise<void>\n ): ApiBuilder<D, P, C, ST, HasFiles, A>;\n\n get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an API with typed routes using a builder pattern.\n *\n * @see {@link https://effortless-aws.website/use-cases/http-api | HTTP API guide}\n */\nexport function defineApi<const O extends ApiOptions>(\n options: O,\n): ApiBuilder<undefined, undefined, undefined, O[\"stream\"] extends true ? true : false, false>;\nexport function defineApi(\n options: ApiOptions,\n): ApiBuilder {\n const { basePath, stream } = options;\n\n const state: {\n spec: ApiConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n authFn?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n routes: RouteEntry[];\n } = {\n spec: {\n basePath,\n ...(stream ? { stream } : {}),\n },\n routes: [],\n };\n\n const addRoute = (method: HttpMethod, def: { path: string; input?: unknown; public?: boolean; cache?: CacheOptions }, handler: Function) => {\n const routeCache = def.cache != null\n ? resolveCache(def.cache)\n : undefined;\n\n state.routes.push({\n method,\n path: def.path,\n onRequest: handler as any,\n ...(def.input ? { schema: def.input } : {}),\n ...(def.public ? { public: true } : {}),\n ...(routeCache ? { cache: routeCache } : {}),\n });\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): ApiRoutes => {\n const handler: any = {\n __brand: \"effortless-api\",\n __spec: state.spec,\n routes: state.routes,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.authFn ? { authFn: state.authFn } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n };\n\n // Add route methods to the finalized handler\n for (const m of [\"get\", \"post\", \"put\", \"patch\", \"delete\"] as const) {\n handler[m] = (def: any, fn: any) => {\n addRoute(m.toUpperCase() as HttpMethod, def, fn);\n handler.routes = state.routes;\n return handler;\n };\n }\n\n return handler as ApiRoutes;\n };\n\n const builder: ApiBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n auth(fn: any) {\n state.authFn = fn;\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n get(def: any, fn: any) { addRoute(\"GET\", def, fn); return finalize() as any; },\n post(def: any, fn: any) { addRoute(\"POST\", def, fn); return finalize() as any; },\n put(def: any, fn: any) { addRoute(\"PUT\", def, fn); return finalize() as any; },\n patch(def: any, fn: any) { addRoute(\"PATCH\", def, fn); return finalize() as any; },\n delete(def: any, fn: any) { addRoute(\"DELETE\", def, fn); return finalize() as any; },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\nimport type { Timezone } from \"./timezone\";\n\n// ============ Schedule expression ============\n\n/** Singular/plural unit for rate expressions */\ntype RateUnit = \"minute\" | \"minutes\" | \"hour\" | \"hours\" | \"day\" | \"days\";\n\n/**\n * Rate expression: `rate(1 hour)`, `rate(5 minutes)`, `rate(2 days)`\n *\n * Strictly typed — autocomplete and compile-time validation for unit.\n */\ntype RateExpression = `rate(${number} ${RateUnit})`;\n\n/**\n * Cron expression: `cron(min hour dom month dow year)`\n *\n * Not deeply typed (too combinatorial for TS), but the `cron(...)` wrapper is enforced.\n *\n * @example\n * ```\n * \"cron(0 9 * * ? *)\" // daily at 9:00 UTC\n * \"cron(0 9 ? * MON-FRI *)\" // weekdays at 9:00\n * \"cron(0/15 * * * ? *)\" // every 15 minutes\n * ```\n */\ntype CronExpression = `cron(${string})`;\n\n/**\n * EventBridge Scheduler schedule expression.\n *\n * - **Rate**: `\"rate(5 minutes)\"`, `\"rate(1 hour)\"`, `\"rate(1 day)\"` — strictly typed units\n * - **Cron**: `\"cron(0 9 * * ? *)\"` — 6 fields: min hour dom month dow year\n */\nexport type ScheduleExpression = RateExpression | CronExpression;\n\n// ============ Static config ============\n\n/** Static config extracted at deploy time */\nexport type CronConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** EventBridge Scheduler schedule expression */\n schedule: ScheduleExpression;\n /** IANA timezone for the schedule (default: UTC) */\n timezone?: Timezone;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Tick handler ============\n\n/** Callback function for cron tick */\nexport type CronTickFn<C = undefined> =\n (args: SpreadCtx<C>) => Promise<void> | void;\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineCron.\n * @internal\n */\nexport type CronHandler<C = any> = {\n readonly __brand: \"effortless-cron\";\n readonly __spec: CronConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onTick?: (...args: any[]) => any;\n};\n\n// ============ Options ============\n\n/** Options passed to `defineCron()` — resource config only */\ntype CronOptions = {\n /** EventBridge Scheduler schedule expression: `\"rate(5 minutes)\"` or `\"cron(0 9 * * ? *)\"` */\n schedule: ScheduleExpression;\n /** IANA timezone for the schedule (default: UTC). Full autocomplete for all timezones. */\n timezone?: Timezone;\n};\n\n// ============ Builder ============\n\ninterface CronBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): CronBuilder<D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): CronBuilder<D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): CronBuilder<D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): CronBuilder<D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): CronBuilder<D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): CronBuilder<D, P, C2, HasFiles>;\n\n /** Handle errors thrown by onTick */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): CronBuilder<D, P, C, HasFiles>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): CronBuilder<D, P, C, HasFiles>;\n\n /** Tick handler — called on each scheduled invocation (terminal) */\n onTick(\n fn: CronTickFn<C>\n ): CronHandler<C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a cron job — scheduled Lambda invocation via EventBridge Scheduler.\n *\n * @see {@link https://effortless-aws.website/definitions#definecron | Cron reference}\n */\nexport function defineCron(options: CronOptions): CronBuilder {\n const { schedule, timezone } = options;\n\n const spec: CronConfig = {\n schedule,\n ...(timezone ? { timezone } : {}),\n };\n\n const state: {\n spec: CronConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onTick?: (...args: any[]) => any;\n } = { spec };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): CronHandler => ({\n __brand: \"effortless-cron\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onTick ? { onTick: state.onTick } : {}),\n }) as CronHandler;\n\n const builder: CronBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n onTick(fn) {\n state.onTick = fn as any;\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions, Duration } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n// ============ Static config ============\n\n/** Fargate container size presets */\nexport type FargateSize =\n | \"0.25vCPU-512mb\"\n | \"0.5vCPU-1gb\"\n | \"1vCPU-2gb\"\n | \"2vCPU-4gb\"\n | \"4vCPU-8gb\";\n\n/** Static config extracted at deploy time */\nexport type WorkerConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Fargate size (default: \"0.5vCPU-1gb\") */\n size?: FargateSize;\n /** How long the worker stays alive without messages before shutting down (default: \"5m\") */\n idleTimeout?: Duration;\n /** Max messages processed in parallel (default: 1, max: 10) */\n concurrency?: number;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Handler callback ============\n\n/** Callback function for the worker's onMessage */\nexport type WorkerMessageFn<T, C = undefined> =\n (msg: T, ctx: SpreadCtx<C>) => Promise<void> | void;\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineWorker.\n * @internal\n */\nexport type WorkerHandler<_T = any, C = any> = {\n readonly __brand: \"effortless-worker\";\n readonly __spec: WorkerConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onMessage?: (...args: any[]) => any;\n};\n\n// ============ Options ============\n\n/** Options passed to `defineWorker()` — resource config only */\ntype WorkerOptions = {\n /** Fargate size (default: \"0.5vCPU-1gb\") */\n size?: FargateSize;\n /** How long the worker stays alive without messages before shutting down (default: \"5m\") */\n idleTimeout?: Duration;\n /** Max messages processed in parallel (default: 1, max: 10) */\n concurrency?: number;\n};\n\n// ============ Builder ============\n\ninterface WorkerBuilder<\n T,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers, workers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): WorkerBuilder<T, D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): WorkerBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the bundle. Chainable — call multiple times. */\n include(glob: string): WorkerBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): WorkerBuilder<T, D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): WorkerBuilder<T, D, P, C2, HasFiles>;\n\n /** Handle errors thrown by onMessage. Return \"delete\" to discard, \"retry\" to reprocess (default). */\n onError(\n fn: (args: { error: unknown; msg: T; retryCount: number } & SpreadCtx<C>) => \"retry\" | \"delete\" | void | Promise<\"retry\" | \"delete\" | void>\n ): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Cleanup callback — runs when the worker shuts down */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Process a single message from the queue (terminal) */\n onMessage(\n fn: WorkerMessageFn<T, C>\n ): WorkerHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a worker — a long-running Fargate container with an SQS queue.\n *\n * The worker stays alive while processing messages and shuts down after\n * an idle timeout with no new messages. Other handlers can send messages\n * to the worker via `deps.worker.send(msg)`.\n *\n * @typeParam T - Type of messages the worker receives via its queue\n *\n * @see {@link https://effortless-aws.website/definitions#defineworker | Worker reference}\n */\nexport function defineWorker<T = unknown>(options?: WorkerOptions): WorkerBuilder<T> {\n const spec: WorkerConfig = {\n ...(options?.size ? { size: options.size } : {}),\n ...(options?.idleTimeout ? { idleTimeout: options.idleTimeout } : {}),\n ...(options?.concurrency ? { concurrency: options.concurrency } : {}),\n };\n\n const state: {\n spec: WorkerConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onMessage?: (...args: any[]) => any;\n } = { spec };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): WorkerHandler => ({\n __brand: \"effortless-worker\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onMessage ? { onMessage: state.onMessage } : {}),\n }) as WorkerHandler;\n\n const builder: WorkerBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n onMessage(fn) {\n state.onMessage = fn as any;\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { StandardSchemaV1, StandardJSONSchemaV1 } from \"@standard-schema/spec\";\nimport type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n// ============ Static config ============\n\n/** Static config extracted at deploy time */\nexport type McpConfig = {\n /** MCP server name (used in server info) */\n name: string;\n /** MCP server version (default: \"1.0.0\") */\n version?: string;\n /** Human-readable description — sent to clients in initialize response as system prompt context */\n instructions?: string;\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n};\n\n// ============ MCP tool types ============\n\n/** JSON Schema for a tool's input parameters */\nexport type McpInputSchema = {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n};\n\n/** Content block returned by a tool handler */\nexport type McpToolContent =\n | { type: \"text\"; text: string }\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"resource\"; resource: { uri: string; text: string; mimeType?: string } };\n\n/** Result returned by a tool handler */\nexport type McpToolResult = {\n content: McpToolContent[];\n isError?: boolean;\n};\n\n// ============ MCP resource types ============\n\n/** Content returned when reading a resource */\nexport type McpResourceContent =\n | { uri: string; mimeType?: string; text: string }\n | { uri: string; mimeType?: string; blob: string };\n\n/** Legacy resource content result type used by runtime internals */\ntype McpResourceResult = McpResourceContent | McpResourceContent[] | Promise<McpResourceContent | McpResourceContent[]>;\n\n/** Infer resource params type from schema, or fall back to Record<string, string> */\ntype InferResourceParams<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : Record<string, string>;\n\n/** Resource definition — pass `params` for typed schema validation on URI template params */\nexport type McpResourceDefInput<S extends StandardSchemaV1 | undefined = undefined> = {\n /** Resource URI or URI template (e.g. \"resource://contacts/{id}\") */\n uri: string;\n /** Human-readable name */\n name: string;\n /** Schema for URI template params — provides type inference + validation */\n params?: S;\n /** Optional description */\n description?: string;\n /** Optional MIME type */\n mimeType?: string;\n};\n\n/** What resource handlers can return — plain data is auto-wrapped by the runtime */\ntype McpResourceReturn = unknown | Promise<unknown>;\n\n/** Resource handler — receives params (typed or Record<string, string>) and ctx */\ntype McpResourceHandler<C, S = undefined> = (params: InferResourceParams<S>, ctx: SpreadCtx<C>) => McpResourceReturn;\n\n// Legacy types used by wrap-mcp runtime\n/** @internal */\nexport type McpResourceDef<C = undefined> = {\n name: string;\n description?: string;\n mimeType?: string;\n handler: (ctx: SpreadCtx<C>) => McpResourceResult;\n};\n\n/** @internal */\nexport type McpResourceTemplateDef<C = undefined> = {\n name: string;\n description?: string;\n mimeType?: string;\n handler: (params: Record<string, string>, ctx: SpreadCtx<C>) => McpResourceResult;\n};\n\n/** @internal */\nexport type McpResourceMap<C = undefined> = {\n [uriOrTemplate: string]: McpResourceDef<C> | McpResourceTemplateDef<C>;\n};\n\n// ============ MCP prompt types ============\n\n/** An argument accepted by a prompt */\nexport type McpPromptArgument = {\n /** Argument name */\n name: string;\n /** Optional description */\n description?: string;\n /** Whether the argument is required (default: false) */\n required?: boolean;\n};\n\n/** Content block inside a prompt message */\nexport type McpPromptContent =\n | { type: \"text\"; text: string }\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"audio\"; data: string; mimeType: string }\n | { type: \"resource\"; resource: { uri: string; mimeType?: string; text?: string; blob?: string } };\n\n/** A single message returned by a prompt */\nexport type McpPromptMessage = {\n role: \"user\" | \"assistant\";\n content: McpPromptContent;\n};\n\n/** Result returned by a prompt handler */\nexport type McpPromptResult = {\n description?: string;\n messages: McpPromptMessage[];\n};\n\n/** @internal Legacy prompt definition used by runtime */\nexport type McpPromptDef<C = undefined> = {\n description?: string;\n arguments?: McpPromptArgument[];\n handler: (args: Record<string, string>, ctx: SpreadCtx<C>) => McpPromptResult | Promise<McpPromptResult>;\n};\n\n/** Infer prompt args type from schema, or fall back to Record<string, string> */\ntype InferPromptArgs<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : Record<string, string>;\n\n/** Prompt definition — pass a Standard Schema for `args` for typed validation, or McpPromptArgument[] for untyped */\nexport type McpPromptDefInput<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined> = {\n /** Prompt name */\n name: string;\n /** Human-readable description */\n description?: string;\n /** Args: Standard Schema for typed validation, or McpPromptArgument[] for untyped */\n args?: S;\n};\n\n/** Handler return: string auto-wraps as user message, or return full McpPromptResult */\ntype McpPromptReturn = string | McpPromptResult | Promise<string | McpPromptResult>;\n\n/** Prompt handler — receives args (typed or Record<string, string>) and ctx */\ntype McpPromptHandler<C, S = undefined> = (args: InferPromptArgs<S>, ctx: SpreadCtx<C>) => McpPromptReturn;\n\n/** Infer tool input type from schema, or fall back to any */\ntype InferToolInput<S> = S extends StandardJSONSchemaV1 ? StandardJSONSchemaV1.InferOutput<S> : any;\n\n/** Tool definition — pass a StandardJSONSchemaV1 for `input` for typed validation, or McpInputSchema for raw JSON Schema */\nexport type McpToolDefInput<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema> = {\n /** Tool name */\n name: string;\n /** Human-readable description of the tool */\n description: string;\n /** Schema for tool input: StandardJSONSchemaV1 (e.g. z.object({...})) or raw McpInputSchema */\n input: S;\n};\n\n/** Tool handler — receives input (typed or any) and ctx */\ntype McpToolHandler<C, S = McpInputSchema> = (input: InferToolInput<S>, ctx: SpreadCtx<C>) => unknown | Promise<unknown>;\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Auth factory args — receives config/deps based on what was declared */\ntype AuthArgs<D, P> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> });\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineMcp.\n * @internal\n */\nexport type McpHandler<C = any> = {\n readonly __brand: \"effortless-mcp\";\n readonly __spec: McpConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly resources?: (...args: any[]) => any;\n readonly prompts?: (...args: any[]) => any;\n readonly tools?: (...args: any[]) => any;\n};\n\n// ============ McpEntries — returned after first singular method ============\n\n/**\n * Finalized MCP handler with chainable registration methods.\n * Has `__brand` so CLI discovers it. Each `.tool()/.resource()/.prompt()` adds an entry and returns self.\n */\nexport interface McpEntries<C = undefined> extends McpHandler<C> {\n /** Register a tool */\n tool<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema>(def: McpToolDefInput<S>, handler: McpToolHandler<C, S>): McpEntries<C>;\n /** Register a resource */\n resource<S extends StandardSchemaV1 | undefined = undefined>(def: McpResourceDefInput<S>, handler: McpResourceHandler<C, S>): McpEntries<C>;\n /** Register a prompt */\n prompt<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined>(def: McpPromptDefInput<S>, handler: McpPromptHandler<C, S>): McpEntries<C>;\n}\n\n// ============ Options ============\n\n/** Options passed to `defineMcp()` */\ntype McpOptions = {\n /** MCP server name (used in server info) */\n name: string;\n /** MCP server version (default: \"1.0.0\") */\n version?: string;\n /** Human-readable description — sent to clients in initialize response as system prompt context */\n instructions?: string;\n};\n\n// ============ Builder ============\n\ninterface McpBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers, workers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): McpBuilder<D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): McpBuilder<D, P2, C, HasFiles>;\n\n /** Include static files in the bundle. Chainable — call multiple times. */\n include(glob: string): McpBuilder<D, P, C, true>;\n\n /** Configure session-based authentication. Receives resolved config/deps. All requests require a valid session. */\n auth<A2>(\n fn: (args: AuthArgs<D, P>) => import(\"./define-api\").AuthOptions<A2> | Promise<import(\"./define-api\").AuthOptions<A2>>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): McpBuilder<D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): McpBuilder<D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): McpBuilder<D, P, C2, HasFiles>;\n\n /** Handle errors thrown by tool handlers */\n onError(\n fn: (args: { error: unknown; toolName: string } & SpreadCtx<C>) => void | Promise<void>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Cleanup callback — runs on shutdown */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Register a tool */\n tool<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema>(def: McpToolDefInput<S>, handler: McpToolHandler<C, S>): McpEntries<C>;\n /** Register a resource */\n resource<S extends StandardSchemaV1 | undefined = undefined>(def: McpResourceDefInput<S>, handler: McpResourceHandler<C, S>): McpEntries<C>;\n /** Register a prompt */\n prompt<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined>(def: McpPromptDefInput<S>, handler: McpPromptHandler<C, S>): McpEntries<C>;\n\n /** Finalize the handler without adding more entries */\n build(): McpHandler<C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an MCP (Model Context Protocol) server endpoint.\n *\n * Creates a Lambda-backed MCP server that exposes tools, resources, and prompts\n * for AI models and MCP-compatible clients via Streamable HTTP (JSON-RPC over POST).\n *\n * @see {@link https://effortless-aws.website/use-cases/mcp-server | MCP Server guide}\n * @see {@link https://modelcontextprotocol.io/specification/2025-03-26 | MCP specification}\n */\nexport function defineMcp(options: McpOptions): McpBuilder {\n const spec: McpConfig = {\n name: options.name,\n ...(options.version ? { version: options.version } : {}),\n ...(options.instructions ? { instructions: options.instructions } : {}),\n };\n\n const state: {\n spec: McpConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n authFn?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n toolEntries: [string, any][];\n resourceEntries: [string, any][];\n promptEntries: [string, any][];\n } = { spec, toolEntries: [], resourceEntries: [], promptEntries: [] };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n // Build factory functions from accumulated entries\n const buildToolsFactory = () =>\n state.toolEntries.length > 0\n ? () => Object.fromEntries(state.toolEntries)\n : undefined;\n\n const buildResourcesFactory = () =>\n state.resourceEntries.length > 0\n ? () => Object.fromEntries(state.resourceEntries)\n : undefined;\n\n const buildPromptsFactory = () =>\n state.promptEntries.length > 0\n ? () => Object.fromEntries(state.promptEntries)\n : undefined;\n\n const finalize = (): McpHandler => {\n const tools = buildToolsFactory();\n const resources = buildResourcesFactory();\n const prompts = buildPromptsFactory();\n return {\n __brand: \"effortless-mcp\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.authFn ? { authFn: state.authFn } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(resources ? { resources } : {}),\n ...(prompts ? { prompts } : {}),\n ...(tools ? { tools } : {}),\n } as McpHandler;\n };\n\n const finalizeWithEntries = (): McpEntries => {\n const handler: any = finalize();\n\n handler.tool = (def: any, fn: any) => {\n state.toolEntries.push([def.name, { description: def.description, input: def.input, handler: fn }]);\n handler.tools = buildToolsFactory();\n return handler;\n };\n\n handler.resource = (def: any, fn: any) => {\n const entry = { name: def.name, ...(def.description ? { description: def.description } : {}), ...(def.mimeType ? { mimeType: def.mimeType } : {}), handler: fn, ...(def.params ? { params: def.params } : {}) };\n state.resourceEntries.push([def.uri, entry]);\n handler.resources = buildResourcesFactory();\n return handler;\n };\n\n handler.prompt = (def: any, fn: any) => {\n const entry = { ...(def.description ? { description: def.description } : {}), args: def.args, handler: fn };\n state.promptEntries.push([def.name, entry]);\n handler.prompts = buildPromptsFactory();\n return handler;\n };\n\n return handler as McpEntries;\n };\n\n const builder: McpBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n auth(fn: any) {\n state.authFn = fn;\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n tool(def: any, fn: any) {\n state.toolEntries.push([def.name, { description: def.description, input: def.input, handler: fn }]);\n return finalizeWithEntries() as any;\n },\n resource(def: any, fn: any) {\n const entry = { name: def.name, ...(def.description ? { description: def.description } : {}), ...(def.mimeType ? { mimeType: def.mimeType } : {}), handler: fn, ...(def.params ? { params: def.params } : {}) };\n state.resourceEntries.push([def.uri, entry]);\n return finalizeWithEntries() as any;\n },\n prompt(def: any, fn: any) {\n const entry = { ...(def.description ? { description: def.description } : {}), args: def.args, handler: fn };\n state.promptEntries.push([def.name, entry]);\n return finalizeWithEntries() as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n"],"mappings":"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","names":["options","options","ttl"]}
@@ -3,7 +3,7 @@ import {
3
3
  } from "../chunk-4UKWMWM5.js";
4
4
  import {
5
5
  createHandlerRuntime
6
- } from "../chunk-3NKYT4OX.js";
6
+ } from "../chunk-GQKZZ7G7.js";
7
7
  import "../chunk-VONNUUEN.js";
8
8
 
9
9
  // src/runtime/wrap-api.ts
@@ -26,22 +26,48 @@ var parseBody = (body, isBase64) => {
26
26
  return decoded;
27
27
  }
28
28
  };
29
- var toResult = (r) => {
29
+ var formatDisposition = (filename) => {
30
+ const ascii = filename.replace(/[^\x20-\x7E]/g, "_").replace(/["\\]/g, "");
31
+ return ascii === filename ? `attachment; filename="${filename}"` : `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
32
+ };
33
+ var toResult = async (r) => {
30
34
  const resolved = r.contentType ? CONTENT_TYPE_MAP[r.contentType] : void 0;
31
- const customContentType = resolved ?? r.headers?.["content-type"] ?? r.headers?.["Content-Type"];
32
- const isJson = !r.binary && (!customContentType || customContentType === "application/json");
35
+ const explicitContentType = resolved ?? r.headers?.["content-type"] ?? r.headers?.["Content-Type"];
36
+ let bodyStr;
37
+ let isBase64Encoded = false;
38
+ let inferredContentType;
39
+ if (r.body instanceof Uint8Array) {
40
+ bodyStr = Buffer.from(r.body.buffer, r.body.byteOffset, r.body.byteLength).toString("base64");
41
+ isBase64Encoded = true;
42
+ inferredContentType = "application/octet-stream";
43
+ } else if (r.body instanceof Blob) {
44
+ bodyStr = Buffer.from(await r.body.arrayBuffer()).toString("base64");
45
+ isBase64Encoded = true;
46
+ inferredContentType = r.body.type || "application/octet-stream";
47
+ } else if (r.binary) {
48
+ bodyStr = String(r.body ?? "");
49
+ isBase64Encoded = true;
50
+ } else {
51
+ const customContentType = explicitContentType;
52
+ const isJson = !customContentType || customContentType === "application/json";
53
+ bodyStr = isJson ? JSON.stringify(r.body) : String(r.body ?? "");
54
+ }
55
+ const finalContentType = explicitContentType ?? inferredContentType ?? "application/json";
33
56
  const cookies = r.cookies?.length ? r.cookies : r.headers?.["set-cookie"] ? [r.headers["set-cookie"]] : void 0;
34
57
  const { "set-cookie": _sc, ...headersWithoutSetCookie } = r.headers ?? {};
58
+ const explicitDisposition = r.headers?.["content-disposition"] ?? r.headers?.["Content-Disposition"];
59
+ const disposition = explicitDisposition ?? (r.downloadAs ? formatDisposition(r.downloadAs) : void 0);
35
60
  return {
36
61
  statusCode: r.status,
37
62
  headers: {
38
- "Content-Type": customContentType ?? "application/json",
63
+ "Content-Type": finalContentType,
39
64
  ...headersWithoutSetCookie,
40
- ...resolved ? { "Content-Type": resolved } : {}
65
+ ...resolved ? { "Content-Type": resolved } : {},
66
+ ...disposition ? { "Content-Disposition": disposition } : {}
41
67
  },
42
68
  ...cookies ? { cookies } : {},
43
- body: r.binary ? String(r.body ?? "") : isJson ? JSON.stringify(r.body) : String(r.body ?? ""),
44
- ...r.binary ? { isBase64Encoded: true } : {}
69
+ body: bodyStr,
70
+ ...isBase64Encoded ? { isBase64Encoded: true } : {}
45
71
  };
46
72
  };
47
73
  var notFound = () => ({
@@ -93,7 +119,7 @@ var wrapApi = (handler) => {
93
119
  const routes = handler.routes ?? [];
94
120
  const ok = (body, status = 200) => ({ status, body });
95
121
  const fail = (message, status = 400) => ({ status, body: { error: message } });
96
- const defaultError = (error, status) => {
122
+ const defaultError = async (error, status) => {
97
123
  console.error(`[effortless:${rt.handlerName}]`, error);
98
124
  return toResult({
99
125
  status,
@@ -194,13 +220,13 @@ var wrapApi = (handler) => {
194
220
  response.headers = { ...response.headers, "Cache-Control": buildCacheControl(entry.cache) };
195
221
  }
196
222
  rt.logExecution(startTime, logInput, response.body);
197
- return toResult(response);
223
+ return await toResult(response);
198
224
  }
199
225
  rt.logExecution(startTime, logInput, "[stream]");
200
226
  return void 0;
201
227
  } catch (error) {
202
228
  rt.logError(startTime, logInput, error);
203
- return handler.onError ? toResult(await handler.onError({ error, req, ok, fail, ...ctxProps })) : defaultError(error, 500);
229
+ return handler.onError ? await toResult(await handler.onError({ error, req, ok, fail, ...ctxProps })) : await defaultError(error, 500);
204
230
  }
205
231
  } finally {
206
232
  if (handler.onCleanup && sharedArgs) {
@@ -5,7 +5,7 @@ import {
5
5
  import "../chunk-U56MLLWP.js";
6
6
  import {
7
7
  createHandlerRuntime
8
- } from "../chunk-3NKYT4OX.js";
8
+ } from "../chunk-GQKZZ7G7.js";
9
9
  import {
10
10
  toSeconds
11
11
  } from "../chunk-VONNUUEN.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createHandlerRuntime
3
- } from "../chunk-3NKYT4OX.js";
3
+ } from "../chunk-GQKZZ7G7.js";
4
4
  import "../chunk-VONNUUEN.js";
5
5
 
6
6
  // src/runtime/wrap-cron.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createHandlerRuntime
3
- } from "../chunk-3NKYT4OX.js";
3
+ } from "../chunk-GQKZZ7G7.js";
4
4
  import "../chunk-VONNUUEN.js";
5
5
 
6
6
  // src/runtime/wrap-mcp.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createHandlerRuntime
3
- } from "../chunk-3NKYT4OX.js";
3
+ } from "../chunk-GQKZZ7G7.js";
4
4
  import "../chunk-VONNUUEN.js";
5
5
 
6
6
  // src/runtime/wrap-queue.ts
@@ -4,7 +4,7 @@ import {
4
4
  import "../chunk-U56MLLWP.js";
5
5
  import {
6
6
  createHandlerRuntime
7
- } from "../chunk-3NKYT4OX.js";
7
+ } from "../chunk-GQKZZ7G7.js";
8
8
  import "../chunk-VONNUUEN.js";
9
9
 
10
10
  // src/runtime/wrap-table-stream.ts
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  buildDeps,
3
3
  buildParams
4
- } from "../chunk-3NKYT4OX.js";
4
+ } from "../chunk-GQKZZ7G7.js";
5
5
  import "../chunk-VONNUUEN.js";
6
6
 
7
7
  // src/runtime/wrap-worker.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effortless-aws",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Code-first AWS Lambda framework. Export handlers, deploy with one command.",