effortless-aws 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { StandardSchemaV1, StandardJSONSchemaV1 } from '@standard-schema/spec';
2
+
1
3
  /**
2
4
  * Configuration for an Effortless project.
3
5
  *
@@ -421,7 +423,7 @@ type BucketObjectRemovedFn<C = undefined> = (args: {
421
423
  * Internal handler object created by defineBucket
422
424
  * @internal
423
425
  */
424
- type BucketHandler$1<C = any, Entities extends Record<string, any> = {}> = {
426
+ type BucketHandler$1<C = any, _Entities extends Record<string, any> = {}> = {
425
427
  readonly __brand: "effortless-bucket";
426
428
  readonly __spec: BucketConfig;
427
429
  readonly onError?: (...args: any[]) => any;
@@ -728,7 +730,7 @@ type WorkerMessageFn<T, C = undefined> = (msg: T, ctx: SpreadCtx$4<C>) => Promis
728
730
  * Handler object created by defineWorker.
729
731
  * @internal
730
732
  */
731
- type WorkerHandler$1<T = any, C = any> = {
733
+ type WorkerHandler$1<_T = any, C = any> = {
732
734
  readonly __brand: "effortless-worker";
733
735
  readonly __spec: WorkerConfig;
734
736
  readonly onError?: (...args: any[]) => any;
@@ -1392,6 +1394,7 @@ type RouteEntry = {
1392
1394
  method: HttpMethod;
1393
1395
  path: string;
1394
1396
  onRequest: (...args: any[]) => any;
1397
+ schema?: unknown;
1395
1398
  public?: boolean;
1396
1399
  cache?: ResolvedCache;
1397
1400
  };
@@ -1399,23 +1402,24 @@ type RouteEntry = {
1399
1402
  type SpreadCtx$2<C> = ([C] extends [undefined] ? {} : Omit<C & {}, 'auth'>) & ([ExtractAuth<C>] extends [undefined] ? {} : {
1400
1403
  auth: AuthHelpers<ExtractAuth<C>>;
1401
1404
  });
1405
+ /** Infer validated output type from a Standard Schema, or fall back to unknown */
1406
+ type InferInput<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : unknown;
1402
1407
  /** Callback args available inside each route — ctx is spread into args */
1403
- type RouteArgs<C, ST> = SpreadCtx$2<C> & {
1408
+ type RouteArgs<C, ST, S = undefined> = SpreadCtx$2<C> & {
1404
1409
  req: HttpRequest;
1405
- input: unknown;
1410
+ input: InferInput<S>;
1406
1411
  ok: OkHelper;
1407
1412
  fail: FailHelper;
1408
1413
  } & ([ST] extends [true] ? {
1409
1414
  stream: ResponseStream;
1410
1415
  } : {});
1411
1416
  /** Route handler function */
1412
- type RouteHandler<C, ST> = (args: RouteArgs<C, ST>) => Promise<HttpResponse | void> | HttpResponse | void;
1413
- /** Route options for all methods */
1414
- type RouteOptions = {
1417
+ type RouteHandler<C, ST, S = undefined> = (args: RouteArgs<C, ST, S>) => Promise<HttpResponse | void> | HttpResponse | void;
1418
+ /** Route definition — pass `input` for typed schema validation */
1419
+ type RouteDef<S extends StandardSchemaV1 | undefined = undefined> = {
1420
+ path: `/${string}`;
1421
+ input?: S;
1415
1422
  public?: boolean;
1416
- };
1417
- /** Route options for GET — supports caching */
1418
- type GetRouteOptions = RouteOptions & {
1419
1423
  cache?: CacheOptions;
1420
1424
  };
1421
1425
  /** Setup factory — receives deps/config/files/enableAuth based on what was declared */
@@ -1467,11 +1471,11 @@ type ApiOptions = {
1467
1471
  * Has `__brand` so CLI discovers it. Each `.get()/.post()` adds a route and returns self.
1468
1472
  */
1469
1473
  interface ApiRoutes<C = undefined, ST extends boolean = false> extends ApiHandler<C> {
1470
- get(path: `/${string}`, handler: RouteHandler<C, ST>, options?: GetRouteOptions): ApiRoutes<C, ST>;
1471
- post(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;
1472
- put(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;
1473
- patch(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;
1474
- delete(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;
1474
+ get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;
1475
+ post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;
1476
+ put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;
1477
+ patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;
1478
+ delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;
1475
1479
  }
1476
1480
  /**
1477
1481
  * Builder interface for defining API handlers.
@@ -1501,16 +1505,11 @@ interface ApiBuilder<D = undefined, P = undefined, C = undefined, ST extends boo
1501
1505
  } & SpreadCtx$2<C>) => HttpResponse | Promise<HttpResponse>): ApiBuilder<D, P, C, ST, HasFiles>;
1502
1506
  /** Cleanup callback — runs after each invocation, before Lambda freezes */
1503
1507
  onCleanup(fn: (args: SpreadCtx$2<C>) => void | Promise<void>): ApiBuilder<D, P, C, ST, HasFiles>;
1504
- /** Add a GET route (terminal returns finalized handler with route methods) */
1505
- get(path: `/${string}`, handler: RouteHandler<C, ST>, options?: GetRouteOptions): ApiRoutes<C, ST>;
1506
- /** Add a POST route (terminal) */
1507
- post(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;
1508
- /** Add a PUT route (terminal) */
1509
- put(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;
1510
- /** Add a PATCH route (terminal) */
1511
- patch(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;
1512
- /** Add a DELETE route (terminal) */
1513
- delete(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;
1508
+ get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;
1509
+ post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;
1510
+ put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;
1511
+ patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;
1512
+ delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;
1514
1513
  }
1515
1514
  /**
1516
1515
  * Define an API with typed routes using a builder pattern.
@@ -1527,8 +1526,8 @@ interface ApiBuilder<D = undefined, P = undefined, C = undefined, ST extends boo
1527
1526
  * auth: enableAuth<Session>({ secret: config.dbUrl }),
1528
1527
  * }))
1529
1528
  * .onError(({ error, fail }) => fail(String(error), 500))
1530
- * .get("/me", async ({ users, auth, ok }) => ok(auth.session))
1531
- * .post("/login", async ({ auth, ok }) => ok(await auth.createSession()), { public: true })
1529
+ * .get({ path: "/me" }, async ({ users, auth, ok }) => ok(auth.session))
1530
+ * .post({ path: "/login", public: true }, async ({ auth, ok }) => ok(await auth.createSession()))
1532
1531
  * ```
1533
1532
  */
1534
1533
  declare function defineApi<const O extends ApiOptions>(options: O): ApiBuilder<undefined, undefined, undefined, O["stream"] extends true ? true : false, false>;
@@ -1699,29 +1698,42 @@ type McpResourceContent = {
1699
1698
  mimeType?: string;
1700
1699
  blob: string;
1701
1700
  };
1702
- /** A static resource definition */
1703
- type McpResourceDef$1<C = undefined> = {
1701
+ /** Legacy resource content result type used by runtime internals */
1702
+ type McpResourceResult = McpResourceContent | McpResourceContent[] | Promise<McpResourceContent | McpResourceContent[]>;
1703
+ /** Infer resource params type from schema, or fall back to Record<string, string> */
1704
+ type InferResourceParams<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : Record<string, string>;
1705
+ /** Resource definition — pass `params` for typed schema validation on URI template params */
1706
+ type McpResourceDefInput<S extends StandardSchemaV1 | undefined = undefined> = {
1707
+ /** Resource URI or URI template (e.g. "resource://contacts/{id}") */
1708
+ uri: string;
1704
1709
  /** Human-readable name */
1705
1710
  name: string;
1711
+ /** Schema for URI template params — provides type inference + validation */
1712
+ params?: S;
1706
1713
  /** Optional description */
1707
1714
  description?: string;
1708
1715
  /** Optional MIME type */
1709
1716
  mimeType?: string;
1710
- /** Handler called on resources/read */
1711
- handler: (ctx: SpreadCtx<C>) => McpResourceContent | McpResourceContent[] | Promise<McpResourceContent | McpResourceContent[]>;
1712
1717
  };
1713
- /** A parameterized resource template (URI template RFC 6570) */
1718
+ /** What resource handlers can return plain data is auto-wrapped by the runtime */
1719
+ type McpResourceReturn = unknown | Promise<unknown>;
1720
+ /** Resource handler — receives params (typed or Record<string, string>) and ctx */
1721
+ type McpResourceHandler<C, S = undefined> = (params: InferResourceParams<S>, ctx: SpreadCtx<C>) => McpResourceReturn;
1722
+ /** @internal */
1723
+ type McpResourceDef$1<C = undefined> = {
1724
+ name: string;
1725
+ description?: string;
1726
+ mimeType?: string;
1727
+ handler: (ctx: SpreadCtx<C>) => McpResourceResult;
1728
+ };
1729
+ /** @internal */
1714
1730
  type McpResourceTemplateDef$1<C = undefined> = {
1715
- /** Human-readable name */
1716
1731
  name: string;
1717
- /** Optional description */
1718
1732
  description?: string;
1719
- /** Optional MIME type */
1720
1733
  mimeType?: string;
1721
- /** Handler called on resources/read receives matched URI params */
1722
- handler: (params: Record<string, string>, ctx: SpreadCtx<C>) => McpResourceContent | McpResourceContent[] | Promise<McpResourceContent | McpResourceContent[]>;
1734
+ handler: (params: Record<string, string>, ctx: SpreadCtx<C>) => McpResourceResult;
1723
1735
  };
1724
- /** Map of uri → resource definition, or uriTemplate → template definition */
1736
+ /** @internal */
1725
1737
  type McpResourceMap$1<C = undefined> = {
1726
1738
  [uriOrTemplate: string]: McpResourceDef$1<C> | McpResourceTemplateDef$1<C>;
1727
1739
  };
@@ -1765,24 +1777,40 @@ type McpPromptResult = {
1765
1777
  description?: string;
1766
1778
  messages: McpPromptMessage[];
1767
1779
  };
1768
- /** A single prompt definition */
1780
+ /** @internal Legacy prompt definition used by runtime */
1769
1781
  type McpPromptDef$1<C = undefined> = {
1770
- /** Human-readable description */
1771
1782
  description?: string;
1772
- /** Arguments this prompt accepts */
1773
1783
  arguments?: McpPromptArgument[];
1774
- /** Handler called on prompts/get — receives argument values */
1775
1784
  handler: (args: Record<string, string>, ctx: SpreadCtx<C>) => McpPromptResult | Promise<McpPromptResult>;
1776
1785
  };
1777
- /** A single MCP tool definition */
1778
- type McpToolDef$1<C = undefined> = {
1786
+ /** Infer prompt args type from schema, or fall back to Record<string, string> */
1787
+ type InferPromptArgs<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : Record<string, string>;
1788
+ /** Prompt definition — pass a Standard Schema for `args` for typed validation, or McpPromptArgument[] for untyped */
1789
+ type McpPromptDefInput<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined> = {
1790
+ /** Prompt name */
1791
+ name: string;
1792
+ /** Human-readable description */
1793
+ description?: string;
1794
+ /** Args: Standard Schema for typed validation, or McpPromptArgument[] for untyped */
1795
+ args?: S;
1796
+ };
1797
+ /** Handler return: string auto-wraps as user message, or return full McpPromptResult */
1798
+ type McpPromptReturn = string | McpPromptResult | Promise<string | McpPromptResult>;
1799
+ /** Prompt handler — receives args (typed or Record<string, string>) and ctx */
1800
+ type McpPromptHandler<C, S = undefined> = (args: InferPromptArgs<S>, ctx: SpreadCtx<C>) => McpPromptReturn;
1801
+ /** Infer tool input type from schema, or fall back to any */
1802
+ type InferToolInput<S> = S extends StandardJSONSchemaV1 ? StandardJSONSchemaV1.InferOutput<S> : any;
1803
+ /** Tool definition — pass a StandardJSONSchemaV1 for `input` for typed validation, or McpInputSchema for raw JSON Schema */
1804
+ type McpToolDefInput<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema> = {
1805
+ /** Tool name */
1806
+ name: string;
1779
1807
  /** Human-readable description of the tool */
1780
1808
  description: string;
1781
- /** JSON Schema describing the tool's input parameters */
1782
- input: McpInputSchema;
1783
- /** Handler function called when the tool is invoked */
1784
- handler: (input: any, ctx: SpreadCtx<C>) => McpToolResult | Promise<McpToolResult>;
1809
+ /** Schema for tool input: StandardJSONSchemaV1 (e.g. z.object({...})) or raw McpInputSchema */
1810
+ input: S;
1785
1811
  };
1812
+ /** Tool handler — receives input (typed or any) and ctx */
1813
+ type McpToolHandler<C, S = McpInputSchema> = (input: InferToolInput<S>, ctx: SpreadCtx<C>) => unknown | Promise<unknown>;
1786
1814
  /** Setup factory — receives deps/config/files/enableAuth based on what was declared */
1787
1815
  type SetupArgs<D, P, HasFiles extends boolean> = {
1788
1816
  enableAuth: EnableAuth;
@@ -1812,6 +1840,18 @@ type McpHandler$1<C = any> = {
1812
1840
  readonly prompts?: (...args: any[]) => any;
1813
1841
  readonly tools?: (...args: any[]) => any;
1814
1842
  };
1843
+ /**
1844
+ * Finalized MCP handler with chainable registration methods.
1845
+ * Has `__brand` so CLI discovers it. Each `.tool()/.resource()/.prompt()` adds an entry and returns self.
1846
+ */
1847
+ interface McpEntries<C = undefined> extends McpHandler$1<C> {
1848
+ /** Register a tool */
1849
+ tool<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema>(def: McpToolDefInput<S>, handler: McpToolHandler<C, S>): McpEntries<C>;
1850
+ /** Register a resource */
1851
+ resource<S extends StandardSchemaV1 | undefined = undefined>(def: McpResourceDefInput<S>, handler: McpResourceHandler<C, S>): McpEntries<C>;
1852
+ /** Register a prompt */
1853
+ prompt<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined>(def: McpPromptDefInput<S>, handler: McpPromptHandler<C, S>): McpEntries<C>;
1854
+ }
1815
1855
  /** Options passed to `defineMcp()` */
1816
1856
  type McpOptions = {
1817
1857
  /** MCP server name (used in server info) */
@@ -1841,12 +1881,14 @@ interface McpBuilder<D = undefined, P = undefined, C = undefined, HasFiles exten
1841
1881
  } & SpreadCtx<C>) => void | Promise<void>): McpBuilder<D, P, C, HasFiles>;
1842
1882
  /** Cleanup callback — runs on shutdown */
1843
1883
  onCleanup(fn: (args: SpreadCtx<C>) => void | Promise<void>): McpBuilder<D, P, C, HasFiles>;
1844
- /** Define MCP resources (chainable) */
1845
- resources(fn: (ctx: SpreadCtx<C>) => McpResourceMap$1<C>): McpBuilder<D, P, C, HasFiles>;
1846
- /** Define MCP prompts (chainable) */
1847
- prompts(fn: (ctx: SpreadCtx<C>) => Record<string, McpPromptDef$1<C>>): McpBuilder<D, P, C, HasFiles>;
1848
- /** Define MCP tools (terminal) */
1849
- tools(fn: (ctx: SpreadCtx<C>) => Record<string, McpToolDef$1<C>>): McpHandler$1<C>;
1884
+ /** Register a tool */
1885
+ tool<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema>(def: McpToolDefInput<S>, handler: McpToolHandler<C, S>): McpEntries<C>;
1886
+ /** Register a resource */
1887
+ resource<S extends StandardSchemaV1 | undefined = undefined>(def: McpResourceDefInput<S>, handler: McpResourceHandler<C, S>): McpEntries<C>;
1888
+ /** Register a prompt */
1889
+ prompt<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined>(def: McpPromptDefInput<S>, handler: McpPromptHandler<C, S>): McpEntries<C>;
1890
+ /** Finalize the handler without adding more entries */
1891
+ build(): McpHandler$1<C>;
1850
1892
  }
1851
1893
  /**
1852
1894
  * Define an MCP (Model Context Protocol) server endpoint.
@@ -1859,7 +1901,7 @@ interface McpBuilder<D = undefined, P = undefined, C = undefined, HasFiles exten
1859
1901
  */
1860
1902
  declare function defineMcp(options: McpOptions): McpBuilder;
1861
1903
 
1862
- type McpToolDef = McpToolDef$1;
1904
+ type McpToolDef = McpToolDefInput;
1863
1905
  type McpResourceDef = McpResourceDef$1;
1864
1906
  type McpResourceTemplateDef = McpResourceTemplateDef$1;
1865
1907
  type McpResourceMap = McpResourceMap$1;
@@ -1872,4 +1914,4 @@ type CronHandler = CronHandler$1<any>;
1872
1914
  type WorkerHandler<T = any> = WorkerHandler$1<T, any>;
1873
1915
  type McpHandler = McpHandler$1<any>;
1874
1916
 
1875
- export { type ApiAuthConfig, type ApiConfig, type ApiHandler, type ApiRoutes, type AppConfig, type AppHandler, type AuthHelpers, type BucketClient, type BucketClientWithEntities, type BucketConfig, type BucketEntityConfig, type BucketEvent, type BucketHandler, type BucketRouteConfig, type CacheOptions, type CdnPolicyOptions, type ConfigHelpers, type ContentType, type CronConfig, type CronHandler, type DefineSecretFn, type Duration, type EffortlessConfig, type EmailClient, type FifoQueueConfig, type FifoQueueHandler, type FifoQueueMessage, type HttpMethod$1 as HttpMethod, type HttpRequest, type HttpResponse, type LogLevel, type MailerConfig, type MailerHandler, type McpConfig, type McpHandler, type McpInputSchema, type McpPromptArgument, type McpPromptContent, type McpPromptDef, type McpPromptMessage, type McpPromptResult, type McpResourceContent, type McpResourceDef, type McpResourceMap, type McpResourceTemplateDef, type McpToolContent, type McpToolDef, type McpToolResult, type MiddlewareDeny, type MiddlewareHandler, type MiddlewareRedirect, type MiddlewareRequest, type MiddlewareResult, type ParamRef, type Permission, type PutInput, type PutOptions, type QueryByTagParams, type QueryParams, type QueueClient, type ResponseStream, type SecretRef, type SendEmailOptions, type SendMessageInput, type SkCondition, type StaticFiles, type StaticSiteConfig, type StaticSiteHandler, type StaticSiteSeo, type StoreEntityClient, type StreamView, type TableClient, type TableConfig, type TableHandler, type TableItem, type TableKey, type TableRecord, type Timezone, type UpdateActions, type WorkerClient, type WorkerConfig, type WorkerHandler, type WorkerSendOptions, defineApi, defineApp, defineBucket, defineConfig, defineCron, defineFifoQueue, defineMailer, defineMcp, defineSecret, defineStaticSite, defineTable, defineWorker, generateBase64, generateHex, generateUuid, isBucketRoute, param, secret, toSeconds };
1917
+ export { type ApiAuthConfig, type ApiConfig, type ApiHandler, type ApiRoutes, type AppConfig, type AppHandler, type AuthHelpers, type BucketClient, type BucketClientWithEntities, type BucketConfig, type BucketEntityConfig, type BucketEvent, type BucketHandler, type BucketRouteConfig, type CacheOptions, type CdnPolicyOptions, type ConfigHelpers, type ContentType, type CronConfig, type CronHandler, type DefineSecretFn, type Duration, type EffortlessConfig, type EmailClient, type FifoQueueConfig, type FifoQueueHandler, type FifoQueueMessage, type HttpMethod$1 as HttpMethod, type HttpRequest, type HttpResponse, type LogLevel, type MailerConfig, type MailerHandler, type McpConfig, type McpEntries, type McpHandler, type McpInputSchema, type McpPromptArgument, type McpPromptContent, type McpPromptDef, type McpPromptMessage, type McpPromptResult, type McpResourceContent, type McpResourceDef, type McpResourceMap, type McpResourceTemplateDef, type McpToolContent, type McpToolDef, type McpToolResult, type MiddlewareDeny, type MiddlewareHandler, type MiddlewareRedirect, type MiddlewareRequest, type MiddlewareResult, type ParamRef, type Permission, type PutInput, type PutOptions, type QueryByTagParams, type QueryParams, type QueueClient, type ResponseStream, type SecretRef, type SendEmailOptions, type SendMessageInput, type SkCondition, type StaticFiles, type StaticSiteConfig, type StaticSiteHandler, type StaticSiteSeo, type StoreEntityClient, type StreamView, type TableClient, type TableConfig, type TableHandler, type TableItem, type TableKey, type TableRecord, type Timezone, type UpdateActions, type WorkerClient, type WorkerConfig, type WorkerHandler, type WorkerSendOptions, defineApi, defineApp, defineBucket, defineConfig, defineCron, defineFifoQueue, defineMailer, defineMcp, defineSecret, defineStaticSite, defineTable, defineWorker, generateBase64, generateHex, generateUuid, isBucketRoute, param, secret, toSeconds };
package/dist/index.js CHANGED
@@ -301,13 +301,14 @@ function defineApi(options) {
301
301
  },
302
302
  routes: []
303
303
  };
304
- const addRoute = (method, path, handler, opts) => {
305
- const routeCache = opts?.cache != null ? resolveCache(opts.cache) : void 0;
304
+ const addRoute = (method, def, handler) => {
305
+ const routeCache = def.cache != null ? resolveCache(def.cache) : void 0;
306
306
  state.routes.push({
307
307
  method,
308
- path,
308
+ path: def.path,
309
309
  onRequest: handler,
310
- ...opts?.public ? { public: true } : {},
310
+ ...def.input ? { schema: def.input } : {},
311
+ ...def.public ? { public: true } : {},
311
312
  ...routeCache ? { cache: routeCache } : {}
312
313
  });
313
314
  };
@@ -329,8 +330,8 @@ function defineApi(options) {
329
330
  ...state.static ? { static: state.static } : {}
330
331
  };
331
332
  for (const m of ["get", "post", "put", "patch", "delete"]) {
332
- handler[m] = (path, fn, opts) => {
333
- addRoute(m.toUpperCase(), path, fn, opts);
333
+ handler[m] = (def, fn) => {
334
+ addRoute(m.toUpperCase(), def, fn);
334
335
  handler.routes = state.routes;
335
336
  return handler;
336
337
  };
@@ -367,24 +368,24 @@ function defineApi(options) {
367
368
  state.onCleanup = fn;
368
369
  return builder;
369
370
  },
370
- get(path, handler, opts) {
371
- addRoute("GET", path, handler, opts);
371
+ get(def, fn) {
372
+ addRoute("GET", def, fn);
372
373
  return finalize();
373
374
  },
374
- post(path, handler, opts) {
375
- addRoute("POST", path, handler, opts);
375
+ post(def, fn) {
376
+ addRoute("POST", def, fn);
376
377
  return finalize();
377
378
  },
378
- put(path, handler, opts) {
379
- addRoute("PUT", path, handler, opts);
379
+ put(def, fn) {
380
+ addRoute("PUT", def, fn);
380
381
  return finalize();
381
382
  },
382
- patch(path, handler, opts) {
383
- addRoute("PATCH", path, handler, opts);
383
+ patch(def, fn) {
384
+ addRoute("PATCH", def, fn);
384
385
  return finalize();
385
386
  },
386
- delete(path, handler, opts) {
387
- addRoute("DELETE", path, handler, opts);
387
+ delete(def, fn) {
388
+ addRoute("DELETE", def, fn);
388
389
  return finalize();
389
390
  }
390
391
  };
@@ -522,25 +523,54 @@ function defineMcp(options) {
522
523
  ...options.version ? { version: options.version } : {},
523
524
  ...options.instructions ? { instructions: options.instructions } : {}
524
525
  };
525
- const state = { spec };
526
+ const state = { spec, toolEntries: [], resourceEntries: [], promptEntries: [] };
526
527
  const applyLambdaOptions = (lambda) => {
527
528
  if (Object.keys(lambda).length > 0) {
528
529
  state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };
529
530
  }
530
531
  };
531
- const finalize = () => ({
532
- __brand: "effortless-mcp",
533
- __spec: state.spec,
534
- ...state.onError ? { onError: state.onError } : {},
535
- ...state.onCleanup ? { onCleanup: state.onCleanup } : {},
536
- ...state.setup ? { setup: state.setup } : {},
537
- ...state.deps ? { deps: state.deps } : {},
538
- ...state.config ? { config: state.config } : {},
539
- ...state.static ? { static: state.static } : {},
540
- ...state.resources ? { resources: state.resources } : {},
541
- ...state.prompts ? { prompts: state.prompts } : {},
542
- ...state.tools ? { tools: state.tools } : {}
543
- });
532
+ const buildToolsFactory = () => state.toolEntries.length > 0 ? () => Object.fromEntries(state.toolEntries) : void 0;
533
+ const buildResourcesFactory = () => state.resourceEntries.length > 0 ? () => Object.fromEntries(state.resourceEntries) : void 0;
534
+ const buildPromptsFactory = () => state.promptEntries.length > 0 ? () => Object.fromEntries(state.promptEntries) : void 0;
535
+ const finalize = () => {
536
+ const tools = buildToolsFactory();
537
+ const resources = buildResourcesFactory();
538
+ const prompts = buildPromptsFactory();
539
+ return {
540
+ __brand: "effortless-mcp",
541
+ __spec: state.spec,
542
+ ...state.onError ? { onError: state.onError } : {},
543
+ ...state.onCleanup ? { onCleanup: state.onCleanup } : {},
544
+ ...state.setup ? { setup: state.setup } : {},
545
+ ...state.deps ? { deps: state.deps } : {},
546
+ ...state.config ? { config: state.config } : {},
547
+ ...state.static ? { static: state.static } : {},
548
+ ...resources ? { resources } : {},
549
+ ...prompts ? { prompts } : {},
550
+ ...tools ? { tools } : {}
551
+ };
552
+ };
553
+ const finalizeWithEntries = () => {
554
+ const handler = finalize();
555
+ handler.tool = (def, fn) => {
556
+ state.toolEntries.push([def.name, { description: def.description, input: def.input, handler: fn }]);
557
+ handler.tools = buildToolsFactory();
558
+ return handler;
559
+ };
560
+ handler.resource = (def, fn) => {
561
+ const entry = { name: def.name, ...def.description ? { description: def.description } : {}, ...def.mimeType ? { mimeType: def.mimeType } : {}, handler: fn, ...def.params ? { params: def.params } : {} };
562
+ state.resourceEntries.push([def.uri, entry]);
563
+ handler.resources = buildResourcesFactory();
564
+ return handler;
565
+ };
566
+ handler.prompt = (def, fn) => {
567
+ const entry = { ...def.description ? { description: def.description } : {}, args: def.args, handler: fn };
568
+ state.promptEntries.push([def.name, entry]);
569
+ handler.prompts = buildPromptsFactory();
570
+ return handler;
571
+ };
572
+ return handler;
573
+ };
544
574
  const builder = {
545
575
  deps(fn) {
546
576
  state.deps = fn;
@@ -571,16 +601,21 @@ function defineMcp(options) {
571
601
  state.onCleanup = fn;
572
602
  return builder;
573
603
  },
574
- resources(fn) {
575
- state.resources = fn;
576
- return builder;
604
+ tool(def, fn) {
605
+ state.toolEntries.push([def.name, { description: def.description, input: def.input, handler: fn }]);
606
+ return finalizeWithEntries();
577
607
  },
578
- prompts(fn) {
579
- state.prompts = fn;
580
- return builder;
608
+ resource(def, fn) {
609
+ const entry = { name: def.name, ...def.description ? { description: def.description } : {}, ...def.mimeType ? { mimeType: def.mimeType } : {}, handler: fn, ...def.params ? { params: def.params } : {} };
610
+ state.resourceEntries.push([def.uri, entry]);
611
+ return finalizeWithEntries();
612
+ },
613
+ prompt(def, fn) {
614
+ const entry = { ...def.description ? { description: def.description } : {}, args: def.args, handler: fn };
615
+ state.promptEntries.push([def.name, entry]);
616
+ return finalizeWithEntries();
581
617
  },
582
- tools(fn) {
583
- state.tools = fn;
618
+ build() {
584
619
  return finalize();
585
620
  }
586
621
  };
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-fifo-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, fifo-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 * 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 /** 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 /**\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};\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, no Lambda settings */\ntype TableOptions<T> = {\n /** DynamoDB billing mode (default: \"PAY_PER_REQUEST\") */\n billingMode?: \"PAY_PER_REQUEST\" | \"PROVISIONED\";\n /** Stream view type (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\") */\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) */\n concurrency?: number;\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> {\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>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): TableBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): TableBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): TableBuilder<T, D, P, C, HasFiles>;\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>;\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>;\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>;\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>;\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>({ batchSize: 10, concurrency: 5 })\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 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 (HttpHandler, ApiHandler, etc.) */\ntype AnyRoutableHandler = { readonly __brand: string };\n\n/** Route configuration for serving bucket content through CloudFront */\nexport type BucketRouteConfig = {\n bucket: { readonly __brand: \"effortless-bucket\" };\n /** Access control: \"private\" requires CloudFront signed cookies, \"public\" serves openly. Default: \"public\" */\n access?: \"private\" | \"public\";\n};\n\n/** A route value: either an API handler or a bucket route config */\ntype RouteValue = AnyRoutableHandler | BucketRouteConfig;\n\n/** Type guard for bucket route entries */\nexport const isBucketRoute = (v: unknown): v is BucketRouteConfig =>\n v != null && typeof v === \"object\" && \"bucket\" in v &&\n (v as any).bucket != null && typeof (v as any).bucket === \"object\" &&\n (v as any).bucket.__brand === \"effortless-bucket\";\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 handler (S3 + CloudFront)\n */\nexport type StaticSiteConfig = {\n /** Handler name. Defaults to export name if not specified */\n name?: string;\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 /** SPA mode: serve index.html for all paths that don't match a file (default: false) */\n spa?: boolean;\n /** Shell command to run before deploy to generate site content (e.g., \"npx astro build\") */\n build?: 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 /** CloudFront route overrides: path patterns forwarded to API Gateway or S3 bucket origins.\n * Keys are CloudFront path patterns (e.g., \"/api/*\"), values are HTTP handlers or bucket route configs.\n * Example: `routes: { \"/api/*\": api, \"/files/*\": { bucket: storage, access: \"private\" } }` */\n routes?: Record<string, RouteValue>;\n /** Custom 404 error page path relative to `dir` (e.g. \"404.html\").\n * For non-SPA sites only. If not set, a default page is generated automatically. */\n errorPage?: string;\n /** Lambda@Edge middleware that runs before serving pages. Use for auth checks, redirects, etc. */\n middleware?: MiddlewareHandler;\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/**\n * Internal handler object created by defineStaticSite\n * @internal\n */\nexport type StaticSiteHandler = {\n readonly __brand: \"effortless-static-site\";\n readonly __spec: StaticSiteConfig;\n};\n\n/**\n * Deploy a static site via S3 + CloudFront CDN.\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\n * @returns Handler object used by the deployment system\n */\nexport const defineStaticSite = () => (options: StaticSiteConfig): StaticSiteHandler => ({\n __brand: \"effortless-static-site\",\n __spec: options,\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 FIFO message passed to the handler callbacks.\n *\n * @typeParam T - Type of the decoded message body (from schema function)\n */\nexport type FifoQueueMessage<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) */\n messageGroupId: string;\n /** Message deduplication ID */\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 * Configuration options for a FIFO queue handler\n */\nexport type FifoQueueConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: Duration; logLevel?: LogLevel; permissions?: Permission[] };\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 /** 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 (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};\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 FifoQueueMessageFn<T = unknown, C = undefined> =\n (args: { message: FifoQueueMessage<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 FifoQueueBatchFn<T = unknown, C = undefined> =\n (args: { messages: FifoQueueMessage<T>[] }\n & SpreadCtx<C>\n ) => Promise<void | { failures: string[] }>;\n\n// ============ Internal handler object ============\n\n/**\n * Internal handler object created by defineFifoQueue\n * @internal\n */\nexport type FifoQueueHandler<T = unknown, C = any> = {\n readonly __brand: \"effortless-fifo-queue\";\n readonly __spec: FifoQueueConfig;\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 `defineFifoQueue()` — static config */\ntype FifoQueueOptions<T> = {\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) */\n batchWindow?: Duration;\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 (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 FifoQueueBuilder<\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 ): FifoQueueBuilder<T, D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): FifoQueueBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): FifoQueueBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): FifoQueueBuilder<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 ): FifoQueueBuilder<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 ): FifoQueueBuilder<T, D, P, C2, HasFiles>;\n\n /** Handle errors thrown by message handlers */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): FifoQueueBuilder<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 ): FifoQueueBuilder<T, D, P, C, HasFiles>;\n\n /** Per-message handler (terminal — returns finalized handler) */\n onMessage(\n fn: FifoQueueMessageFn<T, C>\n ): FifoQueueHandler<T, C>;\n\n /** Batch handler (terminal — returns finalized handler) */\n onMessageBatch(\n fn: FifoQueueBatchFn<T, C>\n ): FifoQueueHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a FIFO SQS queue with a Lambda message handler.\n *\n * @see {@link https://effortless-aws.website/use-cases/queue | Queue guide}\n *\n * @example\n * ```typescript\n * export const notifications = defineFifoQueue({ batchSize: 5, schema: (i) => NotifSchema.parse(i) })\n * .onMessageBatch(async ({ messages }) => {\n * await sendAll(messages.map(m => m.body));\n * })\n * ```\n */\nexport function defineFifoQueue<T = unknown>(): FifoQueueBuilder<T>;\nexport function defineFifoQueue<T = unknown>(\n options: FifoQueueOptions<T>,\n): FifoQueueBuilder<T>;\nexport function defineFifoQueue<T = unknown>(\n options?: FifoQueueOptions<T>,\n): FifoQueueBuilder<T> {\n const {\n schema,\n ...queueConfig\n } = options ?? {} as FifoQueueOptions<T>;\n\n const spec: FifoQueueConfig = { ...queueConfig };\n\n const state: {\n spec: FifoQueueConfig;\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 = (): FifoQueueHandler<T> => ({\n __brand: \"effortless-fifo-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 FifoQueueHandler<T>;\n\n const builder: FifoQueueBuilder<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 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 };\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 { 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/** Branded auth config — created by `enableAuth<A>()` helper, carries session type A */\nexport type ApiAuthConfig<A = unknown> = AuthOptions<A> & { readonly __sessionType: A };\n\n/** Type of the `enableAuth` helper injected into setup args */\nexport type EnableAuth = <A = unknown>(options: AuthOptions<A>) => ApiAuthConfig<A>;\n\n/** Runtime implementation of enableAuth — identity function with branded return type */\nexport const enableAuth: EnableAuth = <A = unknown>(options: AuthOptions<A>): ApiAuthConfig<A> =>\n options as ApiAuthConfig<A>;\n\n/** Extract session type A from ctx.auth if present */\ntype ExtractAuth<C> = C extends { auth: ApiAuthConfig<infer A> } ? A : undefined;\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 public?: boolean;\n cache?: ResolvedCache;\n};\n\n/** Spread ctx into route args: Omit auth config, add AuthHelpers if present */\ntype SpreadCtx<C> =\n & ([C] extends [undefined] ? {} : Omit<C & {}, 'auth'>)\n & ([ExtractAuth<C>] extends [undefined] ? {} : { auth: AuthHelpers<ExtractAuth<C>> });\n\n/** Callback args available inside each route — ctx is spread into args */\ntype RouteArgs<C, ST> =\n & SpreadCtx<C>\n & { req: HttpRequest; input: unknown; ok: OkHelper; fail: FailHelper }\n & ([ST] extends [true] ? { stream: ResponseStream } : {});\n\n/** Route handler function */\ntype RouteHandler<C, ST> = (args: RouteArgs<C, ST>) => Promise<HttpResponse | void> | HttpResponse | void;\n\n/** Route options for all methods */\ntype RouteOptions = { public?: boolean };\n/** Route options for GET — supports caching */\ntype GetRouteOptions = RouteOptions & { cache?: CacheOptions };\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files/enableAuth based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & { enableAuth: EnableAuth; 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/** 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> extends ApiHandler<C> {\n get(path: `/${string}`, handler: RouteHandler<C, ST>, options?: GetRouteOptions): ApiRoutes<C, ST>;\n post(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;\n put(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;\n patch(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;\n delete(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;\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> {\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>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): ApiBuilder<D, P2, C, ST, HasFiles>;\n\n /** Include static files by glob pattern */\n include(glob: string): ApiBuilder<D, P, C, ST, true>;\n\n /** Configure Lambda settings only (no init function) */\n setup(lambda: LambdaOptions): ApiBuilder<D, P, C, ST, HasFiles>;\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>;\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>;\n\n /** Handle errors thrown by routes */\n onError(\n fn: (args: { error: unknown; req: HttpRequest; ok: OkHelper; fail: FailHelper } & SpreadCtx<C>) => HttpResponse | Promise<HttpResponse>\n ): ApiBuilder<D, P, C, ST, HasFiles>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): ApiBuilder<D, P, C, ST, HasFiles>;\n\n /** Add a GET route (terminal — returns finalized handler with route methods) */\n get(path: `/${string}`, handler: RouteHandler<C, ST>, options?: GetRouteOptions): ApiRoutes<C, ST>;\n /** Add a POST route (terminal) */\n post(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;\n /** Add a PUT route (terminal) */\n put(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;\n /** Add a PATCH route (terminal) */\n patch(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;\n /** Add a DELETE route (terminal) */\n delete(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;\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\", timeout: \"30s\" })\n * .deps(() => ({ users }))\n * .config(({ defineSecret }) => ({ dbUrl: defineSecret() }))\n * .setup(async ({ deps, config, enableAuth }) => ({\n * users: deps.users,\n * auth: enableAuth<Session>({ secret: config.dbUrl }),\n * }))\n * .onError(({ error, fail }) => fail(String(error), 500))\n * .get(\"/me\", async ({ users, auth, ok }) => ok(auth.session))\n * .post(\"/login\", async ({ auth, ok }) => ok(await auth.createSession()), { public: true })\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 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, path: string, handler: Function, opts?: GetRouteOptions) => {\n const routeCache = opts?.cache != null\n ? resolveCache(opts.cache)\n : undefined;\n\n state.routes.push({\n method,\n path,\n onRequest: handler as any,\n ...(opts?.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.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] = (path: string, fn: Function, opts?: RouteOptions) => {\n addRoute(m.toUpperCase() as HttpMethod, path, fn, opts);\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 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(path, handler, opts) {\n addRoute(\"GET\", path, handler, opts);\n return finalize() as any;\n },\n post(path, handler, opts) {\n addRoute(\"POST\", path, handler, opts);\n return finalize() as any;\n },\n put(path, handler, opts) {\n addRoute(\"PUT\", path, handler, opts);\n return finalize() as any;\n },\n patch(path, handler, opts) {\n addRoute(\"PATCH\", path, handler, opts);\n return finalize() as any;\n },\n delete(path, handler, opts) {\n addRoute(\"DELETE\", path, handler, opts);\n return finalize() as any;\n },\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 { 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/** A static resource definition */\nexport type McpResourceDef<C = undefined> = {\n /** Human-readable name */\n name: string;\n /** Optional description */\n description?: string;\n /** Optional MIME type */\n mimeType?: string;\n /** Handler called on resources/read */\n handler: (ctx: SpreadCtx<C>) => McpResourceContent | McpResourceContent[] | Promise<McpResourceContent | McpResourceContent[]>;\n};\n\n/** A parameterized resource template (URI template RFC 6570) */\nexport type McpResourceTemplateDef<C = undefined> = {\n /** Human-readable name */\n name: string;\n /** Optional description */\n description?: string;\n /** Optional MIME type */\n mimeType?: string;\n /** Handler called on resources/read — receives matched URI params */\n handler: (params: Record<string, string>, ctx: SpreadCtx<C>) => McpResourceContent | McpResourceContent[] | Promise<McpResourceContent | McpResourceContent[]>;\n};\n\n/** Map of uri → resource definition, or uriTemplate → template definition */\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/** A single prompt definition */\nexport type McpPromptDef<C = undefined> = {\n /** Human-readable description */\n description?: string;\n /** Arguments this prompt accepts */\n arguments?: McpPromptArgument[];\n /** Handler called on prompts/get — receives argument values */\n handler: (args: Record<string, string>, ctx: SpreadCtx<C>) => McpPromptResult | Promise<McpPromptResult>;\n};\n\n/** A single MCP tool definition */\nexport type McpToolDef<C = undefined> = {\n /** Human-readable description of the tool */\n description: string;\n /** JSON Schema describing the tool's input parameters */\n input: McpInputSchema;\n /** Handler function called when the tool is invoked */\n handler: (input: any, ctx: SpreadCtx<C>) => McpToolResult | Promise<McpToolResult>;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files/enableAuth based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & { enableAuth: import(\"./define-api\").EnableAuth }\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 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// ============ 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 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 /** Define MCP resources (chainable) */\n resources(\n fn: (ctx: SpreadCtx<C>) => McpResourceMap<C>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Define MCP prompts (chainable) */\n prompts(\n fn: (ctx: SpreadCtx<C>) => Record<string, McpPromptDef<C>>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Define MCP tools (terminal) */\n tools(\n fn: (ctx: SpreadCtx<C>) => Record<string, McpToolDef<C>>\n ): 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 onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n resources?: (...args: any[]) => any;\n prompts?: (...args: any[]) => any;\n tools?: (...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 = (): McpHandler => ({\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.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.resources ? { resources: state.resources } : {}),\n ...(state.prompts ? { prompts: state.prompts } : {}),\n ...(state.tools ? { tools: state.tools } : {}),\n }) as McpHandler;\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 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 resources(fn) {\n state.resources = fn as any;\n return builder as any;\n },\n prompts(fn) {\n state.prompts = fn as any;\n return builder as any;\n },\n tools(fn) {\n state.tools = fn as any;\n return finalize() as any;\n },\n };\n\n return builder;\n}\n"],"mappings":"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","names":["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-fifo-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, fifo-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 * 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 /** 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 /**\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};\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, no Lambda settings */\ntype TableOptions<T> = {\n /** DynamoDB billing mode (default: \"PAY_PER_REQUEST\") */\n billingMode?: \"PAY_PER_REQUEST\" | \"PROVISIONED\";\n /** Stream view type (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\") */\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) */\n concurrency?: number;\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> {\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>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): TableBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): TableBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): TableBuilder<T, D, P, C, HasFiles>;\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>;\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>;\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>;\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>;\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>({ batchSize: 10, concurrency: 5 })\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 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 (HttpHandler, ApiHandler, etc.) */\ntype AnyRoutableHandler = { readonly __brand: string };\n\n/** Route configuration for serving bucket content through CloudFront */\nexport type BucketRouteConfig = {\n bucket: { readonly __brand: \"effortless-bucket\" };\n /** Access control: \"private\" requires CloudFront signed cookies, \"public\" serves openly. Default: \"public\" */\n access?: \"private\" | \"public\";\n};\n\n/** A route value: either an API handler or a bucket route config */\ntype RouteValue = AnyRoutableHandler | BucketRouteConfig;\n\n/** Type guard for bucket route entries */\nexport const isBucketRoute = (v: unknown): v is BucketRouteConfig =>\n v != null && typeof v === \"object\" && \"bucket\" in v &&\n (v as any).bucket != null && typeof (v as any).bucket === \"object\" &&\n (v as any).bucket.__brand === \"effortless-bucket\";\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 handler (S3 + CloudFront)\n */\nexport type StaticSiteConfig = {\n /** Handler name. Defaults to export name if not specified */\n name?: string;\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 /** SPA mode: serve index.html for all paths that don't match a file (default: false) */\n spa?: boolean;\n /** Shell command to run before deploy to generate site content (e.g., \"npx astro build\") */\n build?: 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 /** CloudFront route overrides: path patterns forwarded to API Gateway or S3 bucket origins.\n * Keys are CloudFront path patterns (e.g., \"/api/*\"), values are HTTP handlers or bucket route configs.\n * Example: `routes: { \"/api/*\": api, \"/files/*\": { bucket: storage, access: \"private\" } }` */\n routes?: Record<string, RouteValue>;\n /** Custom 404 error page path relative to `dir` (e.g. \"404.html\").\n * For non-SPA sites only. If not set, a default page is generated automatically. */\n errorPage?: string;\n /** Lambda@Edge middleware that runs before serving pages. Use for auth checks, redirects, etc. */\n middleware?: MiddlewareHandler;\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/**\n * Internal handler object created by defineStaticSite\n * @internal\n */\nexport type StaticSiteHandler = {\n readonly __brand: \"effortless-static-site\";\n readonly __spec: StaticSiteConfig;\n};\n\n/**\n * Deploy a static site via S3 + CloudFront CDN.\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\n * @returns Handler object used by the deployment system\n */\nexport const defineStaticSite = () => (options: StaticSiteConfig): StaticSiteHandler => ({\n __brand: \"effortless-static-site\",\n __spec: options,\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 FIFO message passed to the handler callbacks.\n *\n * @typeParam T - Type of the decoded message body (from schema function)\n */\nexport type FifoQueueMessage<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) */\n messageGroupId: string;\n /** Message deduplication ID */\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 * Configuration options for a FIFO queue handler\n */\nexport type FifoQueueConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: Duration; logLevel?: LogLevel; permissions?: Permission[] };\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 /** 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 (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};\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 FifoQueueMessageFn<T = unknown, C = undefined> =\n (args: { message: FifoQueueMessage<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 FifoQueueBatchFn<T = unknown, C = undefined> =\n (args: { messages: FifoQueueMessage<T>[] }\n & SpreadCtx<C>\n ) => Promise<void | { failures: string[] }>;\n\n// ============ Internal handler object ============\n\n/**\n * Internal handler object created by defineFifoQueue\n * @internal\n */\nexport type FifoQueueHandler<T = unknown, C = any> = {\n readonly __brand: \"effortless-fifo-queue\";\n readonly __spec: FifoQueueConfig;\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 `defineFifoQueue()` — static config */\ntype FifoQueueOptions<T> = {\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) */\n batchWindow?: Duration;\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 (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 FifoQueueBuilder<\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 ): FifoQueueBuilder<T, D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): FifoQueueBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): FifoQueueBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): FifoQueueBuilder<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 ): FifoQueueBuilder<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 ): FifoQueueBuilder<T, D, P, C2, HasFiles>;\n\n /** Handle errors thrown by message handlers */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): FifoQueueBuilder<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 ): FifoQueueBuilder<T, D, P, C, HasFiles>;\n\n /** Per-message handler (terminal — returns finalized handler) */\n onMessage(\n fn: FifoQueueMessageFn<T, C>\n ): FifoQueueHandler<T, C>;\n\n /** Batch handler (terminal — returns finalized handler) */\n onMessageBatch(\n fn: FifoQueueBatchFn<T, C>\n ): FifoQueueHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a FIFO SQS queue with a Lambda message handler.\n *\n * @see {@link https://effortless-aws.website/use-cases/queue | Queue guide}\n *\n * @example\n * ```typescript\n * export const notifications = defineFifoQueue({ batchSize: 5, schema: (i) => NotifSchema.parse(i) })\n * .onMessageBatch(async ({ messages }) => {\n * await sendAll(messages.map(m => m.body));\n * })\n * ```\n */\nexport function defineFifoQueue<T = unknown>(): FifoQueueBuilder<T>;\nexport function defineFifoQueue<T = unknown>(\n options: FifoQueueOptions<T>,\n): FifoQueueBuilder<T>;\nexport function defineFifoQueue<T = unknown>(\n options?: FifoQueueOptions<T>,\n): FifoQueueBuilder<T> {\n const {\n schema,\n ...queueConfig\n } = options ?? {} as FifoQueueOptions<T>;\n\n const spec: FifoQueueConfig = { ...queueConfig };\n\n const state: {\n spec: FifoQueueConfig;\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 = (): FifoQueueHandler<T> => ({\n __brand: \"effortless-fifo-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 FifoQueueHandler<T>;\n\n const builder: FifoQueueBuilder<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 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 };\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/** Branded auth config — created by `enableAuth<A>()` helper, carries session type A */\nexport type ApiAuthConfig<A = unknown> = AuthOptions<A> & { readonly __sessionType: A };\n\n/** Type of the `enableAuth` helper injected into setup args */\nexport type EnableAuth = <A = unknown>(options: AuthOptions<A>) => ApiAuthConfig<A>;\n\n/** Runtime implementation of enableAuth — identity function with branded return type */\nexport const enableAuth: EnableAuth = <A = unknown>(options: AuthOptions<A>): ApiAuthConfig<A> =>\n options as ApiAuthConfig<A>;\n\n/** Extract session type A from ctx.auth if present */\ntype ExtractAuth<C> = C extends { auth: ApiAuthConfig<infer A> } ? A : undefined;\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: Omit auth config, add AuthHelpers if present */\ntype SpreadCtx<C> =\n & ([C] extends [undefined] ? {} : Omit<C & {}, 'auth'>)\n & ([ExtractAuth<C>] extends [undefined] ? {} : { auth: AuthHelpers<ExtractAuth<C>> });\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, S = undefined> =\n & SpreadCtx<C>\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, S = undefined> = (args: RouteArgs<C, ST, 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/enableAuth based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & { enableAuth: EnableAuth; 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/** 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> extends ApiHandler<C> {\n get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;\n post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;\n put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;\n patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;\n delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;\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> {\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>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): ApiBuilder<D, P2, C, ST, HasFiles>;\n\n /** Include static files by glob pattern */\n include(glob: string): ApiBuilder<D, P, C, ST, true>;\n\n /** Configure Lambda settings only (no init function) */\n setup(lambda: LambdaOptions): ApiBuilder<D, P, C, ST, HasFiles>;\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>;\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>;\n\n /** Handle errors thrown by routes */\n onError(\n fn: (args: { error: unknown; req: HttpRequest; ok: OkHelper; fail: FailHelper } & SpreadCtx<C>) => HttpResponse | Promise<HttpResponse>\n ): ApiBuilder<D, P, C, ST, HasFiles>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): ApiBuilder<D, P, C, ST, HasFiles>;\n\n get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;\n post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;\n put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;\n patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;\n delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, S>): ApiRoutes<C, ST>;\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\", timeout: \"30s\" })\n * .deps(() => ({ users }))\n * .config(({ defineSecret }) => ({ dbUrl: defineSecret() }))\n * .setup(async ({ deps, config, enableAuth }) => ({\n * users: deps.users,\n * auth: enableAuth<Session>({ secret: config.dbUrl }),\n * }))\n * .onError(({ error, fail }) => fail(String(error), 500))\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 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.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 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/enableAuth based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & { enableAuth: import(\"./define-api\").EnableAuth }\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 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 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 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.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 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","ttl"]}
@@ -131,7 +131,7 @@ var wrapApi = (handler) => {
131
131
  }
132
132
  const { entry, params: routeParams } = routeMatch;
133
133
  const query = event.queryStringParameters ?? {};
134
- const params = { ...event.pathParameters ?? {}, ...routeParams };
134
+ const params = { ...event.pathParameters, ...routeParams };
135
135
  const merged = {
136
136
  ...query,
137
137
  ...typeof body === "object" && body !== null ? body : {},
@@ -162,10 +162,26 @@ var wrapApi = (handler) => {
162
162
  return unauthorized();
163
163
  }
164
164
  }
165
+ let validatedInput = merged;
166
+ if (entry.schema && typeof entry.schema === "object" && "~standard" in entry.schema) {
167
+ const schema = entry.schema;
168
+ if (typeof schema["~standard"].validate === "function") {
169
+ const result = await schema["~standard"].validate(merged);
170
+ if (result.issues) {
171
+ const issues = result.issues.map((i) => {
172
+ const path2 = i.path?.map((p) => typeof p === "object" ? p.key : p).join(".");
173
+ return path2 ? `${path2}: ${i.message}` : i.message;
174
+ });
175
+ rt.logExecution(startTime, logInput, { status: 400 });
176
+ return { statusCode: 400, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: "Validation failed", issues }) };
177
+ }
178
+ validatedInput = result.value;
179
+ }
180
+ }
165
181
  const { ctx, auth, ...rest } = sharedArgs;
166
182
  ctxProps = ctx && typeof ctx === "object" ? { ...ctx } : {};
167
183
  delete ctxProps.auth;
168
- const args = { ...ctxProps, req, input: merged, ok, fail, ...rest };
184
+ const args = { ...ctxProps, req, input: validatedInput, ok, fail, ...rest };
169
185
  if (auth) args.auth = auth;
170
186
  if (streamCtx) args.stream = streamCtx.stream;
171
187
  try {
@@ -39,6 +39,39 @@ var jsonResponse = (body, status = 200) => ({
39
39
  body: JSON.stringify(body)
40
40
  });
41
41
  var RESOURCE_NOT_FOUND = -32002;
42
+ var isStandardSchema = (input) => input != null && typeof input === "object" && "~standard" in input && typeof input["~standard"]?.jsonSchema?.input === "function";
43
+ var validateInput = async (schema, rawArgs, toolName) => {
44
+ const validate = schema["~standard"].validate;
45
+ if (typeof validate !== "function") return rawArgs;
46
+ const result = await validate(rawArgs);
47
+ if (result.issues) {
48
+ const messages = result.issues.map((i) => {
49
+ const path = i.path?.map((p) => typeof p === "object" ? p.key : p).join(".");
50
+ return path ? `${path}: ${i.message}` : i.message;
51
+ });
52
+ throw new Error(`Validation failed for tool "${toolName}": ${messages.join("; ")}`);
53
+ }
54
+ return result.value;
55
+ };
56
+ var wrapResourceResult = (raw, uri) => {
57
+ if (raw == null) return [{ uri, text: "null" }];
58
+ if (Array.isArray(raw)) {
59
+ if (raw.length > 0 && typeof raw[0] === "object" && raw[0] !== null && "uri" in raw[0]) return raw;
60
+ return [{ uri, text: JSON.stringify(raw) }];
61
+ }
62
+ if (typeof raw === "object" && "uri" in raw && ("text" in raw || "blob" in raw)) {
63
+ return [raw];
64
+ }
65
+ const text = typeof raw === "string" ? raw : JSON.stringify(raw);
66
+ return [{ uri, text }];
67
+ };
68
+ var wrapToolResult = (raw) => {
69
+ if (raw != null && typeof raw === "object" && "content" in raw && Array.isArray(raw.content)) {
70
+ return raw;
71
+ }
72
+ const text = typeof raw === "string" ? raw : JSON.stringify(raw ?? null);
73
+ return { content: [{ type: "text", text }] };
74
+ };
42
75
  var wrapMcp = (handler) => {
43
76
  const rt = createHandlerRuntime(handler, "mcp", handler.__spec.lambda?.logLevel ?? "info");
44
77
  const serverName = handler.__spec.name;
@@ -138,7 +171,7 @@ async function handleMethod(req, ctx, tools, resourceMap, prompts, serverName, s
138
171
  tools: Object.entries(tools).map(([name, def]) => ({
139
172
  name,
140
173
  description: def.description,
141
- inputSchema: def.input
174
+ inputSchema: isStandardSchema(def.input) ? def.input["~standard"].jsonSchema.input({ target: "draft-07" }) : def.input
142
175
  }))
143
176
  }
144
177
  };
@@ -149,8 +182,10 @@ async function handleMethod(req, ctx, tools, resourceMap, prompts, serverName, s
149
182
  }
150
183
  const tool = tools[toolName];
151
184
  try {
152
- const result = await tool.handler(req.params?.arguments ?? {}, ctx);
153
- return { jsonrpc: "2.0", id: req.id, result };
185
+ const rawArgs = req.params?.arguments ?? {};
186
+ const input = isStandardSchema(tool.input) ? await validateInput(tool.input, rawArgs, toolName) : rawArgs;
187
+ const raw = await tool.handler(input, ctx);
188
+ return { jsonrpc: "2.0", id: req.id, result: wrapToolResult(raw) };
154
189
  } catch (error) {
155
190
  return {
156
191
  jsonrpc: "2.0",
@@ -188,28 +223,47 @@ async function handleMethod(req, ctx, tools, resourceMap, prompts, serverName, s
188
223
  }
189
224
  const staticDef = resourceMap[uri];
190
225
  if (staticDef && !isTemplate(uri)) {
191
- const result = await staticDef.handler(ctx);
192
- const contents = Array.isArray(result) ? result : [result];
193
- return { jsonrpc: "2.0", id: req.id, result: { contents } };
226
+ const raw = await staticDef.handler(ctx);
227
+ return { jsonrpc: "2.0", id: req.id, result: { contents: wrapResourceResult(raw, uri) } };
194
228
  }
195
229
  for (const [template, def] of Object.entries(resourceMap)) {
196
230
  if (!isTemplate(template)) continue;
197
- const params = matchTemplate(template, uri);
198
- if (params) {
199
- const result = await def.handler(params, ctx);
200
- const contents = Array.isArray(result) ? result : [result];
201
- return { jsonrpc: "2.0", id: req.id, result: { contents } };
231
+ const rawParams = matchTemplate(template, uri);
232
+ if (rawParams) {
233
+ const defAny = def;
234
+ const params = defAny.params && typeof defAny.params === "object" && "~standard" in defAny.params ? await validateInput(defAny.params, rawParams, `resource ${template}`) : rawParams;
235
+ const raw = await def.handler(params, ctx);
236
+ return { jsonrpc: "2.0", id: req.id, result: { contents: wrapResourceResult(raw, uri) } };
202
237
  }
203
238
  }
204
239
  return { jsonrpc: "2.0", id: req.id, error: { code: RESOURCE_NOT_FOUND, message: "Resource not found", data: { uri } } };
205
240
  }
206
241
  // ── Prompts ──
207
242
  case "prompts/list": {
208
- const promptList = Object.entries(prompts).map(([name, def]) => ({
209
- name,
210
- ...def.description ? { description: def.description } : {},
211
- ...def.arguments ? { arguments: def.arguments } : {}
212
- }));
243
+ const promptList = Object.entries(prompts).map(([name, def]) => {
244
+ const defAny = def;
245
+ let args;
246
+ if (defAny.args && typeof defAny.args === "object" && "~standard" in defAny.args) {
247
+ const jsonSchema = defAny.args["~standard"].jsonSchema?.input?.({ target: "draft-07" });
248
+ if (jsonSchema?.properties) {
249
+ const required = new Set(jsonSchema.required ?? []);
250
+ args = Object.entries(jsonSchema.properties).map(([key, prop]) => ({
251
+ name: key,
252
+ ...prop.description ? { description: prop.description } : {},
253
+ ...required.has(key) ? { required: true } : {}
254
+ }));
255
+ }
256
+ } else if (Array.isArray(defAny.args)) {
257
+ args = defAny.args;
258
+ } else if (defAny.arguments) {
259
+ args = defAny.arguments;
260
+ }
261
+ return {
262
+ name,
263
+ ...def.description ? { description: def.description } : {},
264
+ ...args ? { arguments: args } : {}
265
+ };
266
+ });
213
267
  return { jsonrpc: "2.0", id: req.id, result: { prompts: promptList } };
214
268
  }
215
269
  case "prompts/get": {
@@ -218,9 +272,12 @@ async function handleMethod(req, ctx, tools, resourceMap, prompts, serverName, s
218
272
  return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.InvalidParams, message: `Unknown prompt: ${promptName}` } };
219
273
  }
220
274
  const prompt = prompts[promptName];
221
- const args = req.params?.arguments ?? {};
275
+ const rawArgs = req.params?.arguments ?? {};
222
276
  try {
223
- const result = await prompt.handler(args, ctx);
277
+ const defAny = prompt;
278
+ const args = defAny.args && typeof defAny.args === "object" && "~standard" in defAny.args ? await validateInput(defAny.args, rawArgs, `prompt ${promptName}`) : rawArgs;
279
+ const raw = await prompt.handler(args, ctx);
280
+ const result = typeof raw === "string" ? { messages: [{ role: "user", content: { type: "text", text: raw } }] } : raw;
224
281
  return { jsonrpc: "2.0", id: req.id, result };
225
282
  } catch (error) {
226
283
  return { jsonrpc: "2.0", id: req.id, error: { code: ErrorCode.InternalError, message: error instanceof Error ? error.message : String(error) } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effortless-aws",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Code-first AWS Lambda framework. Export handlers, deploy with one command.",
@@ -49,6 +49,7 @@
49
49
  "typescript": "^5.9.3",
50
50
  "vite-tsconfig-paths": "^6.1.1",
51
51
  "@modelcontextprotocol/sdk": "^1.29.0",
52
+ "@standard-schema/spec": "^1.1.0",
52
53
  "vitest": "^4.0.18"
53
54
  },
54
55
  "scripts": {