effortless-aws 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -190,7 +190,7 @@ type ResolveParams<P> = {
190
190
  */
191
191
  declare const param: <T = string>(key: string, transform?: (raw: string) => T) => ParamRef<T>;
192
192
 
193
- type AnyTableHandler$1 = TableHandler<any, any, any, any, any>;
193
+ type AnyTableHandler$1 = TableHandler<any, any, any, any, any, any>;
194
194
  /** Maps a deps declaration to resolved runtime client types */
195
195
  type ResolveDeps$1<D> = {
196
196
  [K in keyof D]: D[K] extends TableHandler<infer T, any, any, any, any> ? TableClient<T> : never;
@@ -278,7 +278,7 @@ type ContextFactory$1<C, P> = [P] extends [undefined] ? () => C | Promise<C> : (
278
278
  /**
279
279
  * Callback function type for processing a single DynamoDB stream record
280
280
  */
281
- type TableRecordFn<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined> = (args: {
281
+ type TableRecordFn<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined, S extends string[] | undefined = undefined> = (args: {
282
282
  record: TableRecord<T>;
283
283
  table: TableClient<T>;
284
284
  } & ([C] extends [undefined] ? {} : {
@@ -287,11 +287,13 @@ type TableRecordFn<T = Record<string, unknown>, C = undefined, R = void, D = und
287
287
  deps: ResolveDeps$1<D>;
288
288
  }) & ([P] extends [undefined] ? {} : {
289
289
  params: ResolveParams<P>;
290
+ }) & ([S] extends [undefined] ? {} : {
291
+ readStatic: (path: string) => string;
290
292
  })) => Promise<R>;
291
293
  /**
292
294
  * Callback function type for processing accumulated batch results
293
295
  */
294
- type TableBatchCompleteFn<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined> = (args: {
296
+ type TableBatchCompleteFn<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined, S extends string[] | undefined = undefined> = (args: {
295
297
  results: R[];
296
298
  failures: FailedRecord<T>[];
297
299
  table: TableClient<T>;
@@ -301,11 +303,13 @@ type TableBatchCompleteFn<T = Record<string, unknown>, C = undefined, R = void,
301
303
  deps: ResolveDeps$1<D>;
302
304
  }) & ([P] extends [undefined] ? {} : {
303
305
  params: ResolveParams<P>;
306
+ }) & ([S] extends [undefined] ? {} : {
307
+ readStatic: (path: string) => string;
304
308
  })) => Promise<void>;
305
309
  /**
306
310
  * Callback function type for processing all records in a batch at once
307
311
  */
308
- type TableBatchFn<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined> = (args: {
312
+ type TableBatchFn<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = (args: {
309
313
  records: TableRecord<T>[];
310
314
  table: TableClient<T>;
311
315
  } & ([C] extends [undefined] ? {} : {
@@ -314,9 +318,11 @@ type TableBatchFn<T = Record<string, unknown>, C = undefined, D = undefined, P =
314
318
  deps: ResolveDeps$1<D>;
315
319
  }) & ([P] extends [undefined] ? {} : {
316
320
  params: ResolveParams<P>;
321
+ }) & ([S] extends [undefined] ? {} : {
322
+ readStatic: (path: string) => string;
317
323
  })) => Promise<void>;
318
324
  /** Base options shared by all defineTable variants */
319
- type DefineTableBase<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined> = TableConfig & {
325
+ type DefineTableBase<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = TableConfig & {
320
326
  /**
321
327
  * Decode/validate function for stream record items (new/old images).
322
328
  * Called with the unmarshalled DynamoDB item; should return typed data or throw on validation failure.
@@ -346,31 +352,36 @@ type DefineTableBase<T = Record<string, unknown>, C = undefined, D = undefined,
346
352
  * Typed values are injected into the handler via the `params` argument.
347
353
  */
348
354
  params?: P;
355
+ /**
356
+ * Static file glob patterns to bundle into the Lambda ZIP.
357
+ * Files are accessible at runtime via the `readStatic` callback argument.
358
+ */
359
+ static?: S;
349
360
  };
350
361
  /** Per-record processing: onRecord with optional onBatchComplete */
351
- type DefineTableWithOnRecord<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined> = DefineTableBase<T, C, D, P> & {
352
- onRecord: TableRecordFn<T, C, R, D, P>;
353
- onBatchComplete?: TableBatchCompleteFn<T, C, R, D, P>;
362
+ type DefineTableWithOnRecord<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined, S extends string[] | undefined = undefined> = DefineTableBase<T, C, D, P, S> & {
363
+ onRecord: TableRecordFn<T, C, R, D, P, S>;
364
+ onBatchComplete?: TableBatchCompleteFn<T, C, R, D, P, S>;
354
365
  onBatch?: never;
355
366
  };
356
367
  /** Batch processing: onBatch processes all records at once */
357
- type DefineTableWithOnBatch<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined> = DefineTableBase<T, C, D, P> & {
358
- onBatch: TableBatchFn<T, C, D, P>;
368
+ type DefineTableWithOnBatch<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = DefineTableBase<T, C, D, P, S> & {
369
+ onBatch: TableBatchFn<T, C, D, P, S>;
359
370
  onRecord?: never;
360
371
  onBatchComplete?: never;
361
372
  };
362
373
  /** Resource-only: no handler, just creates the table */
363
- type DefineTableResourceOnly<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined> = DefineTableBase<T, C, D, P> & {
374
+ type DefineTableResourceOnly<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = DefineTableBase<T, C, D, P, S> & {
364
375
  onRecord?: never;
365
376
  onBatch?: never;
366
377
  onBatchComplete?: never;
367
378
  };
368
- type DefineTableOptions<T = Record<string, unknown>, C = undefined, R = void, D extends Record<string, AnyTableHandler$1> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined> = DefineTableWithOnRecord<T, C, R, D, P> | DefineTableWithOnBatch<T, C, D, P> | DefineTableResourceOnly<T, C, D, P>;
379
+ type DefineTableOptions<T = Record<string, unknown>, C = undefined, R = void, D extends Record<string, AnyTableHandler$1> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined, S extends string[] | undefined = undefined> = DefineTableWithOnRecord<T, C, R, D, P, S> | DefineTableWithOnBatch<T, C, D, P, S> | DefineTableResourceOnly<T, C, D, P, S>;
369
380
  /**
370
381
  * Internal handler object created by defineTable
371
382
  * @internal
372
383
  */
373
- type TableHandler<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined> = {
384
+ type TableHandler<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined, S extends string[] | undefined = undefined> = {
374
385
  readonly __brand: "effortless-table";
375
386
  readonly config: TableConfig;
376
387
  readonly schema?: (input: unknown) => T;
@@ -378,9 +389,10 @@ type TableHandler<T = Record<string, unknown>, C = undefined, R = void, D = unde
378
389
  readonly context?: (...args: any[]) => C | Promise<C>;
379
390
  readonly deps?: D;
380
391
  readonly params?: P;
381
- readonly onRecord?: TableRecordFn<T, C, R, D, P>;
382
- readonly onBatchComplete?: TableBatchCompleteFn<T, C, R, D, P>;
383
- readonly onBatch?: TableBatchFn<T, C, D, P>;
392
+ readonly static?: string[];
393
+ readonly onRecord?: TableRecordFn<T, C, R, D, P, S>;
394
+ readonly onBatchComplete?: TableBatchCompleteFn<T, C, R, D, P, S>;
395
+ readonly onBatch?: TableBatchFn<T, C, D, P, S>;
384
396
  };
385
397
  /**
386
398
  * Define a DynamoDB table with optional stream handler
@@ -413,15 +425,17 @@ type TableHandler<T = Record<string, unknown>, C = undefined, R = void, D = unde
413
425
  * });
414
426
  * ```
415
427
  */
416
- declare const defineTable: <T = Record<string, unknown>, C = undefined, R = void, D extends Record<string, AnyTableHandler$1> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined>(options: DefineTableOptions<T, C, R, D, P>) => TableHandler<T, C, R, D, P>;
428
+ declare const defineTable: <T = Record<string, unknown>, C = undefined, R = void, D extends Record<string, AnyTableHandler$1> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined, S extends string[] | undefined = undefined>(options: DefineTableOptions<T, C, R, D, P, S>) => TableHandler<T, C, R, D, P, S>;
417
429
 
418
- type AnyTableHandler = TableHandler<any, any, any, any, any>;
430
+ type AnyTableHandler = TableHandler<any, any, any, any, any, any>;
419
431
  /** Maps a deps declaration to resolved runtime client types */
420
432
  type ResolveDeps<D> = {
421
433
  [K in keyof D]: D[K] extends TableHandler<infer T, any, any, any, any> ? TableClient<T> : never;
422
434
  };
423
435
  /** HTTP methods supported by API Gateway */
424
436
  type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
437
+ /** Short content-type aliases for common response formats */
438
+ type ContentType = "json" | "html" | "text" | "css" | "js" | "xml" | "csv" | "svg";
425
439
  /**
426
440
  * Incoming HTTP request object passed to the handler
427
441
  */
@@ -447,9 +461,21 @@ type HttpRequest = {
447
461
  type HttpResponse = {
448
462
  /** HTTP status code (e.g., 200, 404, 500) */
449
463
  status: number;
450
- /** Response body (will be JSON serialized) */
464
+ /** Response body JSON-serialized by default, or sent as string when contentType is set */
451
465
  body?: unknown;
452
- /** Response headers */
466
+ /**
467
+ * Short content-type alias. Resolves to full MIME type automatically:
468
+ * - `"json"` → `application/json` (default if omitted)
469
+ * - `"html"` → `text/html; charset=utf-8`
470
+ * - `"text"` → `text/plain; charset=utf-8`
471
+ * - `"css"` → `text/css; charset=utf-8`
472
+ * - `"js"` → `application/javascript; charset=utf-8`
473
+ * - `"xml"` → `application/xml; charset=utf-8`
474
+ * - `"csv"` → `text/csv; charset=utf-8`
475
+ * - `"svg"` → `image/svg+xml; charset=utf-8`
476
+ */
477
+ contentType?: ContentType;
478
+ /** Response headers (use for custom content-types not covered by contentType) */
453
479
  headers?: Record<string, string>;
454
480
  };
455
481
  /**
@@ -477,7 +503,7 @@ type HttpConfig = {
477
503
  * @typeParam D - Type of the deps (from deps declaration)
478
504
  * @typeParam P - Type of the params (from params declaration)
479
505
  */
480
- type HttpHandlerFn<T = undefined, C = undefined, D = undefined, P = undefined> = (args: {
506
+ type HttpHandlerFn<T = undefined, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = (args: {
481
507
  req: HttpRequest;
482
508
  } & ([T] extends [undefined] ? {} : {
483
509
  data: T;
@@ -487,6 +513,8 @@ type HttpHandlerFn<T = undefined, C = undefined, D = undefined, P = undefined> =
487
513
  deps: ResolveDeps<D>;
488
514
  }) & ([P] extends [undefined] ? {} : {
489
515
  params: ResolveParams<P>;
516
+ }) & ([S] extends [undefined] ? {} : {
517
+ readStatic: (path: string) => string;
490
518
  })) => Promise<HttpResponse>;
491
519
  /**
492
520
  * Context factory type — conditional on whether params are declared.
@@ -504,7 +532,7 @@ type ContextFactory<C, P> = [P] extends [undefined] ? () => C | Promise<C> : (ar
504
532
  * @typeParam D - Type of the deps (from deps declaration)
505
533
  * @typeParam P - Type of the params (from params declaration)
506
534
  */
507
- type DefineHttpOptions<T = undefined, C = undefined, D extends Record<string, AnyTableHandler> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined> = HttpConfig & {
535
+ type DefineHttpOptions<T = undefined, C = undefined, D extends Record<string, AnyTableHandler> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined, S extends string[] | undefined = undefined> = HttpConfig & {
508
536
  /**
509
537
  * Decode/validate function for the request body.
510
538
  * Called with the parsed body; should return typed data or throw on validation failure.
@@ -539,14 +567,19 @@ type DefineHttpOptions<T = undefined, C = undefined, D extends Record<string, An
539
567
  * Typed values are injected into the handler via the `params` argument.
540
568
  */
541
569
  params?: P;
570
+ /**
571
+ * Static file glob patterns to bundle into the Lambda ZIP.
572
+ * Files are accessible at runtime via the `readStatic` callback argument.
573
+ */
574
+ static?: S;
542
575
  /** HTTP request handler function */
543
- onRequest: HttpHandlerFn<T, C, D, P>;
576
+ onRequest: HttpHandlerFn<T, C, D, P, S>;
544
577
  };
545
578
  /**
546
579
  * Internal handler object created by defineHttp
547
580
  * @internal
548
581
  */
549
- type HttpHandler<T = undefined, C = undefined, D = undefined, P = undefined> = {
582
+ type HttpHandler<T = undefined, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = {
550
583
  readonly __brand: "effortless-http";
551
584
  readonly config: HttpConfig;
552
585
  readonly schema?: (input: unknown) => T;
@@ -554,7 +587,8 @@ type HttpHandler<T = undefined, C = undefined, D = undefined, P = undefined> = {
554
587
  readonly context?: (...args: any[]) => C | Promise<C>;
555
588
  readonly deps?: D;
556
589
  readonly params?: P;
557
- readonly onRequest: HttpHandlerFn<T, C, D, P>;
590
+ readonly static?: string[];
591
+ readonly onRequest: HttpHandlerFn<T, C, D, P, S>;
558
592
  };
559
593
  /**
560
594
  * Define an HTTP endpoint that creates an API Gateway route + Lambda function
@@ -598,7 +632,62 @@ type HttpHandler<T = undefined, C = undefined, D = undefined, P = undefined> = {
598
632
  * });
599
633
  * ```
600
634
  */
601
- declare const defineHttp: <T = undefined, C = undefined, D extends Record<string, AnyTableHandler> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined>(options: DefineHttpOptions<T, C, D, P>) => HttpHandler<T, C, D, P>;
635
+ declare const defineHttp: <T = undefined, C = undefined, D extends Record<string, AnyTableHandler> | undefined = undefined, P extends Record<string, AnyParamRef> | undefined = undefined, S extends string[] | undefined = undefined>(options: DefineHttpOptions<T, C, D, P, S>) => HttpHandler<T, C, D, P, S>;
636
+
637
+ /**
638
+ * Configuration for a static site handler (serializable, extracted at build time)
639
+ */
640
+ type SiteConfig = {
641
+ /** Handler name. Defaults to export name if not specified */
642
+ name?: string;
643
+ /** Base URL path the site is served under (e.g., "/app") */
644
+ path: string;
645
+ /** Directory containing the static site files, relative to project root */
646
+ dir: string;
647
+ /** Default file for directory requests (default: "index.html") */
648
+ index?: string;
649
+ /** SPA mode: serve index.html for all paths that don't match a file (default: false) */
650
+ spa?: boolean;
651
+ /** Shell command to run before deploy to generate site content (e.g., "npx astro build") */
652
+ build?: string;
653
+ /** Lambda memory in MB (default: 256) */
654
+ memory?: number;
655
+ /** Lambda timeout in seconds (default: 5) */
656
+ timeout?: number;
657
+ };
658
+ /**
659
+ * Internal handler object created by defineSite
660
+ * @internal
661
+ */
662
+ type SiteHandler = {
663
+ readonly __brand: "effortless-site";
664
+ readonly config: SiteConfig;
665
+ };
666
+ /**
667
+ * Define a static site endpoint that serves files from a directory via Lambda
668
+ *
669
+ * @param options - Site configuration: path, directory, optional SPA mode
670
+ * @returns Handler object used by the deployment system
671
+ *
672
+ * @example Basic static site
673
+ * ```typescript
674
+ * export const app = defineSite({
675
+ * path: "/app",
676
+ * dir: "src/webapp",
677
+ * });
678
+ * ```
679
+ *
680
+ * @example Astro site with build step
681
+ * ```typescript
682
+ * export const dashboard = defineSite({
683
+ * path: "/dashboard",
684
+ * dir: "dist",
685
+ * build: "npx astro build",
686
+ * spa: true,
687
+ * });
688
+ * ```
689
+ */
690
+ declare const defineSite: (options: SiteConfig) => SiteHandler;
602
691
 
603
692
  type BasePlatformEntity = {
604
693
  pk: string;
@@ -630,8 +719,8 @@ type ExecutionLogEntity = BasePlatformEntity & {
630
719
  type PlatformEntity = ExecutionLogEntity;
631
720
 
632
721
  type PlatformClient = {
633
- appendExecution(handlerName: string, handlerType: "http" | "table", entry: ExecutionEntry): Promise<void>;
634
- appendError(handlerName: string, handlerType: "http" | "table", entry: ErrorEntry): Promise<void>;
722
+ appendExecution(handlerName: string, handlerType: "http" | "table" | "site", entry: ExecutionEntry): Promise<void>;
723
+ appendError(handlerName: string, handlerType: "http" | "table" | "site", entry: ErrorEntry): Promise<void>;
635
724
  get<T extends PlatformEntity>(pk: string, sk: string): Promise<T | undefined>;
636
725
  query<T extends PlatformEntity>(pk: string, skPrefix?: string): Promise<T[]>;
637
726
  put(entity: PlatformEntity): Promise<void>;
@@ -639,4 +728,4 @@ type PlatformClient = {
639
728
  };
640
729
  declare const createPlatformClient: () => PlatformClient | undefined;
641
730
 
642
- export { type BasePlatformEntity, type DefineHttpOptions, type DefineTableOptions, type EffortlessConfig, type ErrorEntry, type ExecutionEntry, type ExecutionLogEntity, type FailedRecord, type HttpConfig, type HttpHandler, type HttpHandlerFn, type HttpMethod, type HttpRequest, type HttpResponse, type KeyType, type ParamRef, type PlatformClient, type PlatformEntity, type QueryParams, type ResolveDeps, type ResolveParams, type StreamView, type TableBatchCompleteFn, type TableBatchFn, type TableClient, type TableConfig, type TableHandler, type TableKey, type TableRecord, type TableRecordFn, createPlatformClient, defineConfig, defineHttp, defineTable, param };
731
+ export { type BasePlatformEntity, type ContentType, type DefineHttpOptions, type DefineTableOptions, type EffortlessConfig, type ErrorEntry, type ExecutionEntry, type ExecutionLogEntity, type FailedRecord, type HttpConfig, type HttpHandler, type HttpHandlerFn, type HttpMethod, type HttpRequest, type HttpResponse, type KeyType, type ParamRef, type PlatformClient, type PlatformEntity, type QueryParams, type ResolveDeps, type ResolveParams, type SiteConfig, type SiteHandler, type StreamView, type TableBatchCompleteFn, type TableBatchFn, type TableClient, type TableConfig, type TableHandler, type TableKey, type TableRecord, type TableRecordFn, createPlatformClient, defineConfig, defineHttp, defineSite, defineTable, param };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ var defineConfig = (config) => config;
3
3
 
4
4
  // src/handlers/define-http.ts
5
5
  var defineHttp = (options) => {
6
- const { onRequest, onError, context, schema, deps, params, ...config } = options;
6
+ const { onRequest, onError, context, schema, deps, params, static: staticFiles, ...config } = options;
7
7
  return {
8
8
  __brand: "effortless-http",
9
9
  config,
@@ -12,13 +12,14 @@ var defineHttp = (options) => {
12
12
  ...context ? { context } : {},
13
13
  ...deps ? { deps } : {},
14
14
  ...params ? { params } : {},
15
+ ...staticFiles ? { static: staticFiles } : {},
15
16
  onRequest
16
17
  };
17
18
  };
18
19
 
19
20
  // src/handlers/define-table.ts
20
21
  var defineTable = (options) => {
21
- const { onRecord, onBatchComplete, onBatch, onError, schema, context, deps, params, ...config } = options;
22
+ const { onRecord, onBatchComplete, onBatch, onError, schema, context, deps, params, static: staticFiles, ...config } = options;
22
23
  return {
23
24
  __brand: "effortless-table",
24
25
  config,
@@ -27,12 +28,19 @@ var defineTable = (options) => {
27
28
  ...context ? { context } : {},
28
29
  ...deps ? { deps } : {},
29
30
  ...params ? { params } : {},
31
+ ...staticFiles ? { static: staticFiles } : {},
30
32
  ...onRecord ? { onRecord } : {},
31
33
  ...onBatchComplete ? { onBatchComplete } : {},
32
34
  ...onBatch ? { onBatch } : {}
33
35
  };
34
36
  };
35
37
 
38
+ // src/handlers/define-site.ts
39
+ var defineSite = (options) => ({
40
+ __brand: "effortless-site",
41
+ config: options
42
+ });
43
+
36
44
  // src/handlers/param.ts
37
45
  var param = (key, transform) => ({
38
46
  __brand: "effortless-param",
@@ -134,6 +142,7 @@ export {
134
142
  createPlatformClient,
135
143
  defineConfig,
136
144
  defineHttp,
145
+ defineSite,
137
146
  defineTable,
138
147
  param
139
148
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/handlers/define-http.ts","../src/handlers/define-table.ts","../src/handlers/param.ts","../src/runtime/platform-client.ts","../src/runtime/platform-types.ts"],"sourcesContent":["/**\n * Configuration for an Effortless project.\n *\n * @example\n * ```typescript\n * // effortless.config.ts\n * import { defineConfig } from \"effortless-aws\";\n *\n * export default defineConfig({\n * name: \"my-service\",\n * region: \"eu-central-1\",\n * handlers: \"src\",\n * });\n * ```\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 settings applied to all handlers unless overridden.\n */\n defaults?: {\n /**\n * Lambda memory in MB.\n * @default 256\n */\n memory?: number;\n\n /**\n * Lambda timeout as a human-readable string.\n * @example \"30 seconds\", \"5 minutes\"\n */\n timeout?: string;\n\n /**\n * Lambda runtime.\n * @default \"nodejs22.x\"\n */\n runtime?: string;\n };\n};\n\n/**\n * Helper function for type-safe configuration.\n * Returns the config object as-is, but provides TypeScript autocompletion.\n *\n * @example\n * ```typescript\n * import { defineConfig } from \"effortless-aws\";\n *\n * export default defineConfig({\n * name: \"my-service\",\n * region: \"eu-central-1\",\n * handlers: \"src\",\n * });\n * ```\n */\nexport const defineConfig = (config: EffortlessConfig): EffortlessConfig => config;\n","import type { Permission } from \"./permissions\";\nimport type { TableHandler } from \"./define-table\";\nimport type { TableClient } from \"../runtime/table-client\";\nimport type { AnyParamRef, ResolveParams } from \"./param\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyTableHandler = TableHandler<any, any, any, any, any>;\n\n/** Maps a deps declaration to resolved runtime client types */\nexport type ResolveDeps<D> = {\n [K in keyof D]: D[K] extends TableHandler<infer T, any, any, any, any> ? TableClient<T> : never;\n};\n\n/** HTTP methods supported by API Gateway */\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n\n/**\n * Incoming HTTP request object passed to the handler\n */\nexport type HttpRequest = {\n /** HTTP method (GET, POST, etc.) */\n method: string;\n /** Request path (e.g., \"/users/123\") */\n path: string;\n /** Request headers */\n headers: Record<string, string | undefined>;\n /** Query string parameters */\n query: Record<string, string | undefined>;\n /** Path parameters extracted from route (e.g., {id} -> params.id) */\n params: Record<string, string | undefined>;\n /** Parsed request body (JSON parsed if Content-Type is application/json) */\n body: unknown;\n /** Raw unparsed request body */\n rawBody?: string;\n};\n\n/**\n * HTTP response returned from the handler\n */\nexport type HttpResponse = {\n /** HTTP status code (e.g., 200, 404, 500) */\n status: number;\n /** Response body (will be JSON serialized) */\n body?: unknown;\n /** Response headers */\n headers?: Record<string, string>;\n};\n\n/**\n * Configuration options extracted from DefineHttpOptions (without onRequest callback)\n */\nexport type HttpConfig = {\n /** Handler name. Defaults to export name if not specified */\n name?: string;\n /** HTTP method for the route */\n method: HttpMethod;\n /** Route path (e.g., \"/api/users\", \"/api/users/{id}\") */\n path: string;\n /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout in seconds (default: 30) */\n timeout?: number;\n /** Additional IAM permissions for the Lambda */\n permissions?: Permission[];\n};\n\n/**\n * Handler function type for HTTP endpoints\n *\n * @typeParam T - Type of the validated request body (from schema function)\n * @typeParam C - Type of the context/dependencies (from context function)\n * @typeParam D - Type of the deps (from deps declaration)\n * @typeParam P - Type of the params (from params declaration)\n */\nexport type HttpHandlerFn<T = undefined, C = undefined, D = undefined, P = undefined> =\n (args: { req: HttpRequest }\n & ([T] extends [undefined] ? {} : { data: T })\n & ([C] extends [undefined] ? {} : { ctx: C })\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { params: ResolveParams<P> })\n ) => Promise<HttpResponse>;\n\n/**\n * Context factory type — conditional on whether params are declared.\n * Without params: `() => C | Promise<C>`\n * With params: `(args: { params: ResolveParams<P> }) => C | Promise<C>`\n */\ntype ContextFactory<C, P> = [P] extends [undefined]\n ? () => C | Promise<C>\n : (args: { params: ResolveParams<P & {}> }) => C | Promise<C>;\n\n/**\n * Options for defining an HTTP endpoint\n *\n * @typeParam T - Type of the validated request body (inferred from schema function)\n * @typeParam C - Type of the context/dependencies returned by context function\n * @typeParam D - Type of the deps (from deps declaration)\n * @typeParam P - Type of the params (from params declaration)\n */\nexport type DefineHttpOptions<\n T = undefined,\n C = undefined,\n D extends Record<string, AnyTableHandler> | undefined = undefined,\n P extends Record<string, AnyParamRef> | undefined = undefined\n> = HttpConfig & {\n /**\n * Decode/validate function for the request body.\n * Called with the parsed body; should return typed data or throw on validation failure.\n * When provided, the handler receives validated `data` and invalid requests get a 400 response.\n *\n * Works with any validation library:\n * - Effect: `S.decodeUnknownSync(MySchema)`\n * - Zod: `(body) => myZodSchema.parse(body)`\n */\n schema?: (input: unknown) => T;\n /**\n * Error handler called when schema validation or onRequest throws.\n * Receives the error and request, returns an HttpResponse.\n * If not provided, defaults to 400 for validation errors and 500 for handler errors.\n */\n onError?: (error: unknown, req: HttpRequest) => HttpResponse;\n /**\n * Factory function to create context/dependencies for the request handler.\n * Called once on cold start, result is cached and reused across invocations.\n * When params are declared, receives resolved params as argument.\n * Supports both sync and async return values.\n */\n context?: ContextFactory<C, P>;\n /**\n * Dependencies on other handlers (tables, queues, etc.).\n * Typed clients are injected into the handler via the `deps` argument.\n */\n deps?: D;\n /**\n * SSM Parameter Store parameters.\n * Declare with `param()` helper. Values are fetched and cached at cold start.\n * Typed values are injected into the handler via the `params` argument.\n */\n params?: P;\n /** HTTP request handler function */\n onRequest: HttpHandlerFn<T, C, D, P>;\n};\n\n/**\n * Internal handler object created by defineHttp\n * @internal\n */\nexport type HttpHandler<T = undefined, C = undefined, D = undefined, P = undefined> = {\n readonly __brand: \"effortless-http\";\n readonly config: HttpConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (error: unknown, req: HttpRequest) => HttpResponse;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly context?: (...args: any[]) => C | Promise<C>;\n readonly deps?: D;\n readonly params?: P;\n readonly onRequest: HttpHandlerFn<T, C, D, P>;\n};\n\n/**\n * Define an HTTP endpoint that creates an API Gateway route + Lambda function\n *\n * @typeParam T - Type of the validated request body (inferred from schema function)\n * @typeParam C - Type of the context/dependencies (inferred from context function)\n * @typeParam D - Type of the deps (from deps declaration)\n * @typeParam P - Type of the params (from params declaration)\n * @param options - Configuration, optional schema, optional context factory, and request handler\n * @returns Handler object used by the deployment system\n *\n * @example Basic GET endpoint\n * ```typescript\n * export const hello = defineHttp({\n * method: \"GET\",\n * path: \"/hello\",\n * onRequest: async ({ req }) => ({\n * status: 200,\n * body: { message: \"Hello World!\" }\n * })\n * });\n * ```\n *\n * @example With SSM parameters\n * ```typescript\n * import { param } from \"effortless-aws\";\n *\n * export const api = defineHttp({\n * method: \"GET\",\n * path: \"/orders\",\n * params: {\n * dbUrl: param(\"database-url\"),\n * },\n * context: async ({ params }) => ({\n * pool: createPool(params.dbUrl),\n * }),\n * onRequest: async ({ req, ctx, params }) => ({\n * status: 200,\n * body: { dbUrl: params.dbUrl }\n * })\n * });\n * ```\n */\nexport const defineHttp = <\n T = undefined,\n C = undefined,\n D extends Record<string, AnyTableHandler> | undefined = undefined,\n P extends Record<string, AnyParamRef> | undefined = undefined\n>(\n options: DefineHttpOptions<T, C, D, P>\n): HttpHandler<T, C, D, P> => {\n const { onRequest, onError, context, schema, deps, params, ...config } = options;\n return {\n __brand: \"effortless-http\",\n config,\n ...(schema ? { schema } : {}),\n ...(onError ? { onError } : {}),\n ...(context ? { context } : {}),\n ...(deps ? { deps } : {}),\n ...(params ? { params } : {}),\n onRequest\n } as HttpHandler<T, C, D, P>;\n};\n","import type { Permission } from \"./permissions\";\nimport type { TableClient } from \"../runtime/table-client\";\nimport type { AnyParamRef, ResolveParams } from \"./param\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyTableHandler = TableHandler<any, any, any, any, any>;\n\n/** Maps a deps declaration to resolved runtime client types */\ntype ResolveDeps<D> = {\n [K in keyof D]: D[K] extends TableHandler<infer T, any, any, any, any> ? TableClient<T> : never;\n};\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 extracted from DefineTableOptions (without onRecord/context)\n */\nexport type TableConfig = {\n /** Table/handler name. Defaults to export name if not specified */\n name?: string;\n /** Partition key definition */\n pk: TableKey;\n /** Sort key definition (optional) */\n sk?: TableKey;\n /** DynamoDB billing mode (default: \"PAY_PER_REQUEST\") */\n billingMode?: \"PAY_PER_REQUEST\" | \"PROVISIONED\";\n /** TTL attribute name for automatic item expiration */\n ttlAttribute?: string;\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 in seconds to gather records before invoking (0-300, default: 2) */\n batchWindow?: number;\n /** Where to start reading the stream (default: \"LATEST\") */\n startingPosition?: \"LATEST\" | \"TRIM_HORIZON\";\n /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout in seconds (default: 30) */\n timeout?: number;\n /** Additional IAM permissions for the Lambda */\n permissions?: Permission[];\n};\n\n/**\n * DynamoDB stream record passed to onRecord callback\n *\n * @typeParam T - Type of the table items (new/old values)\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?: T;\n /** Old item value (present for MODIFY and REMOVE) */\n old?: T;\n /** Primary key of the affected item */\n keys: Record<string, unknown>;\n /** Sequence number for ordering */\n sequenceNumber?: string;\n /** Approximate timestamp when the modification occurred */\n timestamp?: number;\n};\n\n/**\n * Information about a failed record during batch processing\n *\n * @typeParam T - Type of the table items\n */\nexport type FailedRecord<T = Record<string, unknown>> = {\n /** The record that failed to process */\n record: TableRecord<T>;\n /** The error that occurred */\n error: unknown;\n};\n\n/**\n * Context factory type — conditional on whether params are declared.\n * Without params: `() => C | Promise<C>`\n * With params: `(args: { params: ResolveParams<P> }) => C | Promise<C>`\n */\ntype ContextFactory<C, P> = [P] extends [undefined]\n ? () => C | Promise<C>\n : (args: { params: ResolveParams<P & {}> }) => C | Promise<C>;\n\n/**\n * Callback function type for processing a single DynamoDB stream record\n */\nexport type TableRecordFn<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined> =\n (args: { record: TableRecord<T>; table: TableClient<T> }\n & ([C] extends [undefined] ? {} : { ctx: C })\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { params: ResolveParams<P> })\n ) => Promise<R>;\n\n/**\n * Callback function type for processing accumulated batch results\n */\nexport type TableBatchCompleteFn<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined> =\n (args: { results: R[]; failures: FailedRecord<T>[]; table: TableClient<T> }\n & ([C] extends [undefined] ? {} : { ctx: C })\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { params: ResolveParams<P> })\n ) => Promise<void>;\n\n/**\n * Callback function type for processing all records in a batch at once\n */\nexport type TableBatchFn<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined> =\n (args: { records: TableRecord<T>[]; table: TableClient<T> }\n & ([C] extends [undefined] ? {} : { ctx: C })\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { params: ResolveParams<P> })\n ) => Promise<void>;\n\n/** Base options shared by all defineTable variants */\ntype DefineTableBase<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined> = TableConfig & {\n /**\n * Decode/validate function for stream record items (new/old images).\n * Called with the unmarshalled DynamoDB item; should return typed data or throw on validation failure.\n * When provided, T is inferred from the return type — no need to specify generic parameters.\n */\n schema?: (input: unknown) => T;\n /**\n * Error handler called when onRecord, onBatch, or onBatchComplete throws.\n * Receives the error. If not provided, defaults to `console.error`.\n */\n onError?: (error: unknown) => void;\n /**\n * Factory function to create context/dependencies for callbacks.\n * Called once on cold start, result is cached and reused across invocations.\n * When params are declared, receives resolved params as argument.\n * Supports both sync and async return values.\n */\n context?: ContextFactory<C, P>;\n /**\n * Dependencies on other handlers (tables, queues, etc.).\n * Typed clients are injected into the handler via the `deps` argument.\n */\n deps?: D;\n /**\n * SSM Parameter Store parameters.\n * Declare with `param()` helper. Values are fetched and cached at cold start.\n * Typed values are injected into the handler via the `params` argument.\n */\n params?: P;\n};\n\n/** Per-record processing: onRecord with optional onBatchComplete */\ntype DefineTableWithOnRecord<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined> = DefineTableBase<T, C, D, P> & {\n onRecord: TableRecordFn<T, C, R, D, P>;\n onBatchComplete?: TableBatchCompleteFn<T, C, R, D, P>;\n onBatch?: never;\n};\n\n/** Batch processing: onBatch processes all records at once */\ntype DefineTableWithOnBatch<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined> = DefineTableBase<T, C, D, P> & {\n onBatch: TableBatchFn<T, C, D, P>;\n onRecord?: never;\n onBatchComplete?: never;\n};\n\n/** Resource-only: no handler, just creates the table */\ntype DefineTableResourceOnly<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined> = DefineTableBase<T, C, D, P> & {\n onRecord?: never;\n onBatch?: never;\n onBatchComplete?: never;\n};\n\nexport type DefineTableOptions<\n T = Record<string, unknown>,\n C = undefined,\n R = void,\n D extends Record<string, AnyTableHandler> | undefined = undefined,\n P extends Record<string, AnyParamRef> | undefined = undefined\n> =\n | DefineTableWithOnRecord<T, C, R, D, P>\n | DefineTableWithOnBatch<T, C, D, P>\n | DefineTableResourceOnly<T, C, D, P>;\n\n/**\n * Internal handler object created by defineTable\n * @internal\n */\nexport type TableHandler<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined> = {\n readonly __brand: \"effortless-table\";\n readonly config: TableConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (error: unknown) => void;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly context?: (...args: any[]) => C | Promise<C>;\n readonly deps?: D;\n readonly params?: P;\n readonly onRecord?: TableRecordFn<T, C, R, D, P>;\n readonly onBatchComplete?: TableBatchCompleteFn<T, C, R, D, P>;\n readonly onBatch?: TableBatchFn<T, C, D, P>;\n};\n\n/**\n * Define a DynamoDB table with optional stream handler\n *\n * Creates:\n * - DynamoDB table with specified key schema\n * - (If onRecord or onBatch provided) DynamoDB Stream + Lambda + Event Source Mapping\n *\n * @example Table with stream handler (typed)\n * ```typescript\n * type Order = { id: string; amount: number; status: string };\n *\n * export const orders = defineTable<Order>({\n * pk: { name: \"id\", type: \"string\" },\n * streamView: \"NEW_AND_OLD_IMAGES\",\n * batchSize: 10,\n * onRecord: async ({ record }) => {\n * if (record.eventName === \"INSERT\") {\n * console.log(\"New order:\", record.new?.amount);\n * }\n * }\n * });\n * ```\n *\n * @example Table only (no Lambda)\n * ```typescript\n * export const users = defineTable({\n * pk: { name: \"id\", type: \"string\" },\n * sk: { name: \"email\", type: \"string\" }\n * });\n * ```\n */\nexport const defineTable = <\n T = Record<string, unknown>,\n C = undefined,\n R = void,\n D extends Record<string, AnyTableHandler> | undefined = undefined,\n P extends Record<string, AnyParamRef> | undefined = undefined\n>(\n options: DefineTableOptions<T, C, R, D, P>\n): TableHandler<T, C, R, D, P> => {\n const { onRecord, onBatchComplete, onBatch, onError, schema, context, deps, params, ...config } = options;\n return {\n __brand: \"effortless-table\",\n config,\n ...(schema ? { schema } : {}),\n ...(onError ? { onError } : {}),\n ...(context ? { context } : {}),\n ...(deps ? { deps } : {}),\n ...(params ? { params } : {}),\n ...(onRecord ? { onRecord } : {}),\n ...(onBatchComplete ? { onBatchComplete } : {}),\n ...(onBatch ? { onBatch } : {})\n } as TableHandler<T, C, R, D, P>;\n};\n","// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyParamRef = ParamRef<any>;\n\n/**\n * Reference to an SSM Parameter Store parameter.\n *\n * @typeParam T - The resolved type after optional transform (default: string)\n */\nexport type ParamRef<T = string> = {\n readonly __brand: \"effortless-param\";\n readonly key: string;\n readonly transform?: (raw: string) => T;\n};\n\n/**\n * Maps a params declaration to resolved value types.\n *\n * @typeParam P - Record of param names to ParamRef instances\n */\nexport type ResolveParams<P> = {\n [K in keyof P]: P[K] extends ParamRef<infer T> ? T : never;\n};\n\n/**\n * Declare an SSM Parameter Store parameter.\n *\n * The key is combined with project and stage at deploy time to form the full\n * SSM path: `/${project}/${stage}/${key}`.\n *\n * @param key - Parameter key (e.g., \"database-url\")\n * @param transform - Optional function to transform the raw string value\n * @returns A ParamRef used by the deployment and runtime systems\n *\n * @example Simple string parameter\n * ```typescript\n * params: {\n * dbUrl: param(\"database-url\"),\n * }\n * ```\n *\n * @example With transform (e.g., TOML parsing)\n * ```typescript\n * import TOML from \"smol-toml\";\n *\n * params: {\n * config: param(\"app-config\", TOML.parse),\n * }\n * ```\n */\nexport const param = <T = string>(\n key: string,\n transform?: (raw: string) => T\n): ParamRef<T> => ({\n __brand: \"effortless-param\",\n key,\n ...(transform ? { transform } : {}),\n});\n","import { DynamoDB } from \"@aws-sdk/client-dynamodb\";\nimport { marshall, unmarshall } from \"@aws-sdk/util-dynamodb\";\nimport type {\n PlatformEntity,\n ExecutionEntry,\n ErrorEntry,\n} from \"./platform-types\";\nimport {\n ENV_PLATFORM_TABLE,\n dateBucket,\n computeTtl,\n} from \"./platform-types\";\n\nexport type PlatformClient = {\n appendExecution(handlerName: string, handlerType: \"http\" | \"table\", entry: ExecutionEntry): Promise<void>;\n appendError(handlerName: string, handlerType: \"http\" | \"table\", entry: ErrorEntry): Promise<void>;\n get<T extends PlatformEntity>(pk: string, sk: string): Promise<T | undefined>;\n query<T extends PlatformEntity>(pk: string, skPrefix?: string): Promise<T[]>;\n put(entity: PlatformEntity): Promise<void>;\n tableName: string;\n};\n\nexport const createPlatformClient = (): PlatformClient | undefined => {\n const tableName = process.env[ENV_PLATFORM_TABLE];\n if (!tableName) return undefined;\n\n let client: DynamoDB | null = null;\n const getClient = () => (client ??= new DynamoDB({}));\n\n const appendToList = async (\n handlerName: string,\n handlerType: \"http\" | \"table\",\n listAttr: \"executions\" | \"errors\",\n entry: ExecutionEntry | ErrorEntry\n ): Promise<void> => {\n const sk = `EXEC#${dateBucket()}`;\n\n try {\n await getClient().updateItem({\n TableName: tableName,\n Key: marshall({ pk: `HANDLER#${handlerName}`, sk }),\n UpdateExpression:\n \"SET #list = list_append(if_not_exists(#list, :empty), :entry), \" +\n \"#type = :type, #hn = :hn, #ht = :ht, #ttl = :ttl\",\n ExpressionAttributeNames: {\n \"#list\": listAttr,\n \"#type\": \"type\",\n \"#hn\": \"handlerName\",\n \"#ht\": \"handlerType\",\n \"#ttl\": \"ttl\",\n },\n ExpressionAttributeValues: marshall(\n {\n \":entry\": [entry],\n \":empty\": [],\n \":type\": \"execution-log\",\n \":hn\": handlerName,\n \":ht\": handlerType,\n \":ttl\": computeTtl(),\n },\n { removeUndefinedValues: true }\n ),\n });\n } catch (err) {\n console.error(\"[effortless] Failed to write platform record:\", err);\n }\n };\n\n return {\n tableName,\n\n async appendExecution(handlerName, handlerType, entry) {\n await appendToList(handlerName, handlerType, \"executions\", entry);\n },\n\n async appendError(handlerName, handlerType, entry) {\n await appendToList(handlerName, handlerType, \"errors\", entry);\n },\n\n async get<T extends PlatformEntity>(pk: string, sk: string): Promise<T | undefined> {\n const result = await getClient().getItem({\n TableName: tableName,\n Key: marshall({ pk, sk }),\n });\n return result.Item ? (unmarshall(result.Item) as T) : undefined;\n },\n\n async query<T extends PlatformEntity>(pk: string, skPrefix?: string): Promise<T[]> {\n const names: Record<string, string> = { \"#pk\": \"pk\" };\n const values: Record<string, unknown> = { \":pk\": pk };\n let keyCondition = \"#pk = :pk\";\n\n if (skPrefix) {\n names[\"#sk\"] = \"sk\";\n values[\":sk\"] = skPrefix;\n keyCondition += \" AND begins_with(#sk, :sk)\";\n }\n\n const result = await getClient().query({\n TableName: tableName,\n KeyConditionExpression: keyCondition,\n ExpressionAttributeNames: names,\n ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }),\n });\n\n return (result.Items ?? []).map(item => unmarshall(item) as T);\n },\n\n async put(entity: PlatformEntity) {\n try {\n await getClient().putItem({\n TableName: tableName,\n Item: marshall(entity as Record<string, unknown>, { removeUndefinedValues: true }),\n });\n } catch (err) {\n console.error(\"[effortless] Failed to write platform record:\", err);\n }\n },\n };\n};\n","export const ENV_PLATFORM_TABLE = \"EFF_PLATFORM_TABLE\";\n\nexport const DEFAULT_TTL_SECONDS = 7 * 24 * 60 * 60; // 7 days\n\n// ============ Base ============\n\nexport type BasePlatformEntity = {\n pk: string;\n sk: string;\n type: string;\n ttl?: number;\n};\n\n// ============ Execution Log ============\n\nexport type ExecutionEntry = {\n id: string;\n ts: string;\n ms: number;\n in: unknown;\n out?: unknown;\n};\n\nexport type ErrorEntry = {\n id: string;\n ts: string;\n ms: number;\n in: unknown;\n err: string;\n};\n\nexport type ExecutionLogEntity = BasePlatformEntity & {\n type: \"execution-log\";\n handlerName: string;\n handlerType: \"http\" | \"table\";\n executions: ExecutionEntry[];\n errors: ErrorEntry[];\n};\n\n// ============ Discriminated Union ============\n\nexport type PlatformEntity =\n | ExecutionLogEntity;\n // future: | IdempotencyEntity | HandlerMetaEntity\n\n// ============ Helpers ============\n\nexport const truncateForStorage = (value: unknown, maxLength = 4096): unknown => {\n if (value === undefined || value === null) return value;\n const str = typeof value === \"string\" ? value : JSON.stringify(value);\n if (str.length <= maxLength) return value;\n return str.slice(0, maxLength) + \"...[truncated]\";\n};\n\nexport const dateBucket = (date = new Date()): string =>\n date.toISOString().slice(0, 10);\n\nexport const computeTtl = (ttlSeconds = DEFAULT_TTL_SECONDS): number =>\n Math.floor(Date.now() / 1000) + ttlSeconds;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/config.ts","../src/handlers/define-http.ts","../src/handlers/define-table.ts","../src/handlers/define-site.ts","../src/handlers/param.ts","../src/runtime/platform-client.ts","../src/runtime/platform-types.ts"],"sourcesContent":["/**\n * Configuration for an Effortless project.\n *\n * @example\n * ```typescript\n * // effortless.config.ts\n * import { defineConfig } from \"effortless-aws\";\n *\n * export default defineConfig({\n * name: \"my-service\",\n * region: \"eu-central-1\",\n * handlers: \"src\",\n * });\n * ```\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 settings applied to all handlers unless overridden.\n */\n defaults?: {\n /**\n * Lambda memory in MB.\n * @default 256\n */\n memory?: number;\n\n /**\n * Lambda timeout as a human-readable string.\n * @example \"30 seconds\", \"5 minutes\"\n */\n timeout?: string;\n\n /**\n * Lambda runtime.\n * @default \"nodejs22.x\"\n */\n runtime?: string;\n };\n};\n\n/**\n * Helper function for type-safe configuration.\n * Returns the config object as-is, but provides TypeScript autocompletion.\n *\n * @example\n * ```typescript\n * import { defineConfig } from \"effortless-aws\";\n *\n * export default defineConfig({\n * name: \"my-service\",\n * region: \"eu-central-1\",\n * handlers: \"src\",\n * });\n * ```\n */\nexport const defineConfig = (config: EffortlessConfig): EffortlessConfig => config;\n","import type { Permission } from \"./permissions\";\nimport type { TableHandler } from \"./define-table\";\nimport type { TableClient } from \"../runtime/table-client\";\nimport type { AnyParamRef, ResolveParams } from \"./param\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyTableHandler = TableHandler<any, any, any, any, any, any>;\n\n/** Maps a deps declaration to resolved runtime client types */\nexport type ResolveDeps<D> = {\n [K in keyof D]: D[K] extends TableHandler<infer T, any, any, any, any> ? TableClient<T> : never;\n};\n\n/** HTTP methods supported by API Gateway */\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n\n/** Short content-type aliases for common response formats */\nexport type ContentType = \"json\" | \"html\" | \"text\" | \"css\" | \"js\" | \"xml\" | \"csv\" | \"svg\";\n\n/**\n * Incoming HTTP request object passed to the handler\n */\nexport type HttpRequest = {\n /** HTTP method (GET, POST, etc.) */\n method: string;\n /** Request path (e.g., \"/users/123\") */\n path: string;\n /** Request headers */\n headers: Record<string, string | undefined>;\n /** Query string parameters */\n query: Record<string, string | undefined>;\n /** Path parameters extracted from route (e.g., {id} -> params.id) */\n params: Record<string, string | undefined>;\n /** Parsed request body (JSON parsed if Content-Type is application/json) */\n body: unknown;\n /** Raw unparsed request body */\n rawBody?: string;\n};\n\n/**\n * HTTP response returned from the handler\n */\nexport type HttpResponse = {\n /** HTTP status code (e.g., 200, 404, 500) */\n status: number;\n /** Response body — JSON-serialized by default, or sent as string when contentType is set */\n body?: unknown;\n /**\n * Short content-type alias. Resolves to full MIME type automatically:\n * - `\"json\"` → `application/json` (default if omitted)\n * - `\"html\"` → `text/html; charset=utf-8`\n * - `\"text\"` → `text/plain; charset=utf-8`\n * - `\"css\"` → `text/css; charset=utf-8`\n * - `\"js\"` → `application/javascript; charset=utf-8`\n * - `\"xml\"` → `application/xml; charset=utf-8`\n * - `\"csv\"` → `text/csv; charset=utf-8`\n * - `\"svg\"` → `image/svg+xml; charset=utf-8`\n */\n contentType?: ContentType;\n /** Response headers (use for custom content-types not covered by contentType) */\n headers?: Record<string, string>;\n};\n\n/**\n * Configuration options extracted from DefineHttpOptions (without onRequest callback)\n */\nexport type HttpConfig = {\n /** Handler name. Defaults to export name if not specified */\n name?: string;\n /** HTTP method for the route */\n method: HttpMethod;\n /** Route path (e.g., \"/api/users\", \"/api/users/{id}\") */\n path: string;\n /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout in seconds (default: 30) */\n timeout?: number;\n /** Additional IAM permissions for the Lambda */\n permissions?: Permission[];\n};\n\n/**\n * Handler function type for HTTP endpoints\n *\n * @typeParam T - Type of the validated request body (from schema function)\n * @typeParam C - Type of the context/dependencies (from context function)\n * @typeParam D - Type of the deps (from deps declaration)\n * @typeParam P - Type of the params (from params declaration)\n */\nexport type HttpHandlerFn<T = undefined, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> =\n (args: { req: HttpRequest }\n & ([T] extends [undefined] ? {} : { data: T })\n & ([C] extends [undefined] ? {} : { ctx: C })\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { params: ResolveParams<P> })\n & ([S] extends [undefined] ? {} : { readStatic: (path: string) => string })\n ) => Promise<HttpResponse>;\n\n/**\n * Context factory type — conditional on whether params are declared.\n * Without params: `() => C | Promise<C>`\n * With params: `(args: { params: ResolveParams<P> }) => C | Promise<C>`\n */\ntype ContextFactory<C, P> = [P] extends [undefined]\n ? () => C | Promise<C>\n : (args: { params: ResolveParams<P & {}> }) => C | Promise<C>;\n\n/**\n * Options for defining an HTTP endpoint\n *\n * @typeParam T - Type of the validated request body (inferred from schema function)\n * @typeParam C - Type of the context/dependencies returned by context function\n * @typeParam D - Type of the deps (from deps declaration)\n * @typeParam P - Type of the params (from params declaration)\n */\nexport type DefineHttpOptions<\n T = undefined,\n C = undefined,\n D extends Record<string, AnyTableHandler> | undefined = undefined,\n P extends Record<string, AnyParamRef> | undefined = undefined,\n S extends string[] | undefined = undefined\n> = HttpConfig & {\n /**\n * Decode/validate function for the request body.\n * Called with the parsed body; should return typed data or throw on validation failure.\n * When provided, the handler receives validated `data` and invalid requests get a 400 response.\n *\n * Works with any validation library:\n * - Effect: `S.decodeUnknownSync(MySchema)`\n * - Zod: `(body) => myZodSchema.parse(body)`\n */\n schema?: (input: unknown) => T;\n /**\n * Error handler called when schema validation or onRequest throws.\n * Receives the error and request, returns an HttpResponse.\n * If not provided, defaults to 400 for validation errors and 500 for handler errors.\n */\n onError?: (error: unknown, req: HttpRequest) => HttpResponse;\n /**\n * Factory function to create context/dependencies for the request handler.\n * Called once on cold start, result is cached and reused across invocations.\n * When params are declared, receives resolved params as argument.\n * Supports both sync and async return values.\n */\n context?: ContextFactory<C, P>;\n /**\n * Dependencies on other handlers (tables, queues, etc.).\n * Typed clients are injected into the handler via the `deps` argument.\n */\n deps?: D;\n /**\n * SSM Parameter Store parameters.\n * Declare with `param()` helper. Values are fetched and cached at cold start.\n * Typed values are injected into the handler via the `params` argument.\n */\n params?: P;\n /**\n * Static file glob patterns to bundle into the Lambda ZIP.\n * Files are accessible at runtime via the `readStatic` callback argument.\n */\n static?: S;\n /** HTTP request handler function */\n onRequest: HttpHandlerFn<T, C, D, P, S>;\n};\n\n/**\n * Internal handler object created by defineHttp\n * @internal\n */\nexport type HttpHandler<T = undefined, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = {\n readonly __brand: \"effortless-http\";\n readonly config: HttpConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (error: unknown, req: HttpRequest) => HttpResponse;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly context?: (...args: any[]) => C | Promise<C>;\n readonly deps?: D;\n readonly params?: P;\n readonly static?: string[];\n readonly onRequest: HttpHandlerFn<T, C, D, P, S>;\n};\n\n/**\n * Define an HTTP endpoint that creates an API Gateway route + Lambda function\n *\n * @typeParam T - Type of the validated request body (inferred from schema function)\n * @typeParam C - Type of the context/dependencies (inferred from context function)\n * @typeParam D - Type of the deps (from deps declaration)\n * @typeParam P - Type of the params (from params declaration)\n * @param options - Configuration, optional schema, optional context factory, and request handler\n * @returns Handler object used by the deployment system\n *\n * @example Basic GET endpoint\n * ```typescript\n * export const hello = defineHttp({\n * method: \"GET\",\n * path: \"/hello\",\n * onRequest: async ({ req }) => ({\n * status: 200,\n * body: { message: \"Hello World!\" }\n * })\n * });\n * ```\n *\n * @example With SSM parameters\n * ```typescript\n * import { param } from \"effortless-aws\";\n *\n * export const api = defineHttp({\n * method: \"GET\",\n * path: \"/orders\",\n * params: {\n * dbUrl: param(\"database-url\"),\n * },\n * context: async ({ params }) => ({\n * pool: createPool(params.dbUrl),\n * }),\n * onRequest: async ({ req, ctx, params }) => ({\n * status: 200,\n * body: { dbUrl: params.dbUrl }\n * })\n * });\n * ```\n */\nexport const defineHttp = <\n T = undefined,\n C = undefined,\n D extends Record<string, AnyTableHandler> | undefined = undefined,\n P extends Record<string, AnyParamRef> | undefined = undefined,\n S extends string[] | undefined = undefined\n>(\n options: DefineHttpOptions<T, C, D, P, S>\n): HttpHandler<T, C, D, P, S> => {\n const { onRequest, onError, context, schema, deps, params, static: staticFiles, ...config } = options;\n return {\n __brand: \"effortless-http\",\n config,\n ...(schema ? { schema } : {}),\n ...(onError ? { onError } : {}),\n ...(context ? { context } : {}),\n ...(deps ? { deps } : {}),\n ...(params ? { params } : {}),\n ...(staticFiles ? { static: staticFiles } : {}),\n onRequest\n } as HttpHandler<T, C, D, P, S>;\n};\n","import type { Permission } from \"./permissions\";\nimport type { TableClient } from \"../runtime/table-client\";\nimport type { AnyParamRef, ResolveParams } from \"./param\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyTableHandler = TableHandler<any, any, any, any, any, any>;\n\n/** Maps a deps declaration to resolved runtime client types */\ntype ResolveDeps<D> = {\n [K in keyof D]: D[K] extends TableHandler<infer T, any, any, any, any> ? TableClient<T> : never;\n};\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 extracted from DefineTableOptions (without onRecord/context)\n */\nexport type TableConfig = {\n /** Table/handler name. Defaults to export name if not specified */\n name?: string;\n /** Partition key definition */\n pk: TableKey;\n /** Sort key definition (optional) */\n sk?: TableKey;\n /** DynamoDB billing mode (default: \"PAY_PER_REQUEST\") */\n billingMode?: \"PAY_PER_REQUEST\" | \"PROVISIONED\";\n /** TTL attribute name for automatic item expiration */\n ttlAttribute?: string;\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 in seconds to gather records before invoking (0-300, default: 2) */\n batchWindow?: number;\n /** Where to start reading the stream (default: \"LATEST\") */\n startingPosition?: \"LATEST\" | \"TRIM_HORIZON\";\n /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout in seconds (default: 30) */\n timeout?: number;\n /** Additional IAM permissions for the Lambda */\n permissions?: Permission[];\n};\n\n/**\n * DynamoDB stream record passed to onRecord callback\n *\n * @typeParam T - Type of the table items (new/old values)\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?: T;\n /** Old item value (present for MODIFY and REMOVE) */\n old?: T;\n /** Primary key of the affected item */\n keys: Record<string, unknown>;\n /** Sequence number for ordering */\n sequenceNumber?: string;\n /** Approximate timestamp when the modification occurred */\n timestamp?: number;\n};\n\n/**\n * Information about a failed record during batch processing\n *\n * @typeParam T - Type of the table items\n */\nexport type FailedRecord<T = Record<string, unknown>> = {\n /** The record that failed to process */\n record: TableRecord<T>;\n /** The error that occurred */\n error: unknown;\n};\n\n/**\n * Context factory type — conditional on whether params are declared.\n * Without params: `() => C | Promise<C>`\n * With params: `(args: { params: ResolveParams<P> }) => C | Promise<C>`\n */\ntype ContextFactory<C, P> = [P] extends [undefined]\n ? () => C | Promise<C>\n : (args: { params: ResolveParams<P & {}> }) => C | Promise<C>;\n\n/**\n * Callback function type for processing a single DynamoDB stream record\n */\nexport type TableRecordFn<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined, S extends string[] | undefined = undefined> =\n (args: { record: TableRecord<T>; table: TableClient<T> }\n & ([C] extends [undefined] ? {} : { ctx: C })\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { params: ResolveParams<P> })\n & ([S] extends [undefined] ? {} : { readStatic: (path: string) => string })\n ) => Promise<R>;\n\n/**\n * Callback function type for processing accumulated batch results\n */\nexport type TableBatchCompleteFn<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined, S extends string[] | undefined = undefined> =\n (args: { results: R[]; failures: FailedRecord<T>[]; table: TableClient<T> }\n & ([C] extends [undefined] ? {} : { ctx: C })\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { params: ResolveParams<P> })\n & ([S] extends [undefined] ? {} : { readStatic: (path: string) => string })\n ) => Promise<void>;\n\n/**\n * Callback function type for processing all records in a batch at once\n */\nexport type TableBatchFn<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> =\n (args: { records: TableRecord<T>[]; table: TableClient<T> }\n & ([C] extends [undefined] ? {} : { ctx: C })\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { params: ResolveParams<P> })\n & ([S] extends [undefined] ? {} : { readStatic: (path: string) => string })\n ) => Promise<void>;\n\n/** Base options shared by all defineTable variants */\ntype DefineTableBase<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = TableConfig & {\n /**\n * Decode/validate function for stream record items (new/old images).\n * Called with the unmarshalled DynamoDB item; should return typed data or throw on validation failure.\n * When provided, T is inferred from the return type — no need to specify generic parameters.\n */\n schema?: (input: unknown) => T;\n /**\n * Error handler called when onRecord, onBatch, or onBatchComplete throws.\n * Receives the error. If not provided, defaults to `console.error`.\n */\n onError?: (error: unknown) => void;\n /**\n * Factory function to create context/dependencies for callbacks.\n * Called once on cold start, result is cached and reused across invocations.\n * When params are declared, receives resolved params as argument.\n * Supports both sync and async return values.\n */\n context?: ContextFactory<C, P>;\n /**\n * Dependencies on other handlers (tables, queues, etc.).\n * Typed clients are injected into the handler via the `deps` argument.\n */\n deps?: D;\n /**\n * SSM Parameter Store parameters.\n * Declare with `param()` helper. Values are fetched and cached at cold start.\n * Typed values are injected into the handler via the `params` argument.\n */\n params?: P;\n /**\n * Static file glob patterns to bundle into the Lambda ZIP.\n * Files are accessible at runtime via the `readStatic` callback argument.\n */\n static?: S;\n};\n\n/** Per-record processing: onRecord with optional onBatchComplete */\ntype DefineTableWithOnRecord<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined, S extends string[] | undefined = undefined> = DefineTableBase<T, C, D, P, S> & {\n onRecord: TableRecordFn<T, C, R, D, P, S>;\n onBatchComplete?: TableBatchCompleteFn<T, C, R, D, P, S>;\n onBatch?: never;\n};\n\n/** Batch processing: onBatch processes all records at once */\ntype DefineTableWithOnBatch<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = DefineTableBase<T, C, D, P, S> & {\n onBatch: TableBatchFn<T, C, D, P, S>;\n onRecord?: never;\n onBatchComplete?: never;\n};\n\n/** Resource-only: no handler, just creates the table */\ntype DefineTableResourceOnly<T = Record<string, unknown>, C = undefined, D = undefined, P = undefined, S extends string[] | undefined = undefined> = DefineTableBase<T, C, D, P, S> & {\n onRecord?: never;\n onBatch?: never;\n onBatchComplete?: never;\n};\n\nexport type DefineTableOptions<\n T = Record<string, unknown>,\n C = undefined,\n R = void,\n D extends Record<string, AnyTableHandler> | undefined = undefined,\n P extends Record<string, AnyParamRef> | undefined = undefined,\n S extends string[] | undefined = undefined\n> =\n | DefineTableWithOnRecord<T, C, R, D, P, S>\n | DefineTableWithOnBatch<T, C, D, P, S>\n | DefineTableResourceOnly<T, C, D, P, S>;\n\n/**\n * Internal handler object created by defineTable\n * @internal\n */\nexport type TableHandler<T = Record<string, unknown>, C = undefined, R = void, D = undefined, P = undefined, S extends string[] | undefined = undefined> = {\n readonly __brand: \"effortless-table\";\n readonly config: TableConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (error: unknown) => void;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly context?: (...args: any[]) => C | Promise<C>;\n readonly deps?: D;\n readonly params?: P;\n readonly static?: string[];\n readonly onRecord?: TableRecordFn<T, C, R, D, P, S>;\n readonly onBatchComplete?: TableBatchCompleteFn<T, C, R, D, P, S>;\n readonly onBatch?: TableBatchFn<T, C, D, P, S>;\n};\n\n/**\n * Define a DynamoDB table with optional stream handler\n *\n * Creates:\n * - DynamoDB table with specified key schema\n * - (If onRecord or onBatch provided) DynamoDB Stream + Lambda + Event Source Mapping\n *\n * @example Table with stream handler (typed)\n * ```typescript\n * type Order = { id: string; amount: number; status: string };\n *\n * export const orders = defineTable<Order>({\n * pk: { name: \"id\", type: \"string\" },\n * streamView: \"NEW_AND_OLD_IMAGES\",\n * batchSize: 10,\n * onRecord: async ({ record }) => {\n * if (record.eventName === \"INSERT\") {\n * console.log(\"New order:\", record.new?.amount);\n * }\n * }\n * });\n * ```\n *\n * @example Table only (no Lambda)\n * ```typescript\n * export const users = defineTable({\n * pk: { name: \"id\", type: \"string\" },\n * sk: { name: \"email\", type: \"string\" }\n * });\n * ```\n */\nexport const defineTable = <\n T = Record<string, unknown>,\n C = undefined,\n R = void,\n D extends Record<string, AnyTableHandler> | undefined = undefined,\n P extends Record<string, AnyParamRef> | undefined = undefined,\n S extends string[] | undefined = undefined\n>(\n options: DefineTableOptions<T, C, R, D, P, S>\n): TableHandler<T, C, R, D, P, S> => {\n const { onRecord, onBatchComplete, onBatch, onError, schema, context, deps, params, static: staticFiles, ...config } = options;\n return {\n __brand: \"effortless-table\",\n config,\n ...(schema ? { schema } : {}),\n ...(onError ? { onError } : {}),\n ...(context ? { context } : {}),\n ...(deps ? { deps } : {}),\n ...(params ? { params } : {}),\n ...(staticFiles ? { static: staticFiles } : {}),\n ...(onRecord ? { onRecord } : {}),\n ...(onBatchComplete ? { onBatchComplete } : {}),\n ...(onBatch ? { onBatch } : {})\n } as TableHandler<T, C, R, D, P, S>;\n};\n","/**\n * Configuration for a static site handler (serializable, extracted at build time)\n */\nexport type SiteConfig = {\n /** Handler name. Defaults to export name if not specified */\n name?: string;\n /** Base URL path the site is served under (e.g., \"/app\") */\n path: 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 /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout in seconds (default: 5) */\n timeout?: number;\n};\n\n/**\n * Internal handler object created by defineSite\n * @internal\n */\nexport type SiteHandler = {\n readonly __brand: \"effortless-site\";\n readonly config: SiteConfig;\n};\n\n/**\n * Define a static site endpoint that serves files from a directory via Lambda\n *\n * @param options - Site configuration: path, directory, optional SPA mode\n * @returns Handler object used by the deployment system\n *\n * @example Basic static site\n * ```typescript\n * export const app = defineSite({\n * path: \"/app\",\n * dir: \"src/webapp\",\n * });\n * ```\n *\n * @example Astro site with build step\n * ```typescript\n * export const dashboard = defineSite({\n * path: \"/dashboard\",\n * dir: \"dist\",\n * build: \"npx astro build\",\n * spa: true,\n * });\n * ```\n */\nexport const defineSite = (options: SiteConfig): SiteHandler => ({\n __brand: \"effortless-site\",\n config: options,\n});\n","// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyParamRef = ParamRef<any>;\n\n/**\n * Reference to an SSM Parameter Store parameter.\n *\n * @typeParam T - The resolved type after optional transform (default: string)\n */\nexport type ParamRef<T = string> = {\n readonly __brand: \"effortless-param\";\n readonly key: string;\n readonly transform?: (raw: string) => T;\n};\n\n/**\n * Maps a params declaration to resolved value types.\n *\n * @typeParam P - Record of param names to ParamRef instances\n */\nexport type ResolveParams<P> = {\n [K in keyof P]: P[K] extends ParamRef<infer T> ? T : never;\n};\n\n/**\n * Declare an SSM Parameter Store parameter.\n *\n * The key is combined with project and stage at deploy time to form the full\n * SSM path: `/${project}/${stage}/${key}`.\n *\n * @param key - Parameter key (e.g., \"database-url\")\n * @param transform - Optional function to transform the raw string value\n * @returns A ParamRef used by the deployment and runtime systems\n *\n * @example Simple string parameter\n * ```typescript\n * params: {\n * dbUrl: param(\"database-url\"),\n * }\n * ```\n *\n * @example With transform (e.g., TOML parsing)\n * ```typescript\n * import TOML from \"smol-toml\";\n *\n * params: {\n * config: param(\"app-config\", TOML.parse),\n * }\n * ```\n */\nexport const param = <T = string>(\n key: string,\n transform?: (raw: string) => T\n): ParamRef<T> => ({\n __brand: \"effortless-param\",\n key,\n ...(transform ? { transform } : {}),\n});\n","import { DynamoDB } from \"@aws-sdk/client-dynamodb\";\nimport { marshall, unmarshall } from \"@aws-sdk/util-dynamodb\";\nimport type {\n PlatformEntity,\n ExecutionEntry,\n ErrorEntry,\n} from \"./platform-types\";\nimport {\n ENV_PLATFORM_TABLE,\n dateBucket,\n computeTtl,\n} from \"./platform-types\";\n\nexport type PlatformClient = {\n appendExecution(handlerName: string, handlerType: \"http\" | \"table\" | \"site\", entry: ExecutionEntry): Promise<void>;\n appendError(handlerName: string, handlerType: \"http\" | \"table\" | \"site\", entry: ErrorEntry): Promise<void>;\n get<T extends PlatformEntity>(pk: string, sk: string): Promise<T | undefined>;\n query<T extends PlatformEntity>(pk: string, skPrefix?: string): Promise<T[]>;\n put(entity: PlatformEntity): Promise<void>;\n tableName: string;\n};\n\nexport const createPlatformClient = (): PlatformClient | undefined => {\n const tableName = process.env[ENV_PLATFORM_TABLE];\n if (!tableName) return undefined;\n\n let client: DynamoDB | null = null;\n const getClient = () => (client ??= new DynamoDB({}));\n\n const appendToList = async (\n handlerName: string,\n handlerType: \"http\" | \"table\" | \"site\",\n listAttr: \"executions\" | \"errors\",\n entry: ExecutionEntry | ErrorEntry\n ): Promise<void> => {\n const sk = `EXEC#${dateBucket()}`;\n\n try {\n await getClient().updateItem({\n TableName: tableName,\n Key: marshall({ pk: `HANDLER#${handlerName}`, sk }),\n UpdateExpression:\n \"SET #list = list_append(if_not_exists(#list, :empty), :entry), \" +\n \"#type = :type, #hn = :hn, #ht = :ht, #ttl = :ttl\",\n ExpressionAttributeNames: {\n \"#list\": listAttr,\n \"#type\": \"type\",\n \"#hn\": \"handlerName\",\n \"#ht\": \"handlerType\",\n \"#ttl\": \"ttl\",\n },\n ExpressionAttributeValues: marshall(\n {\n \":entry\": [entry],\n \":empty\": [],\n \":type\": \"execution-log\",\n \":hn\": handlerName,\n \":ht\": handlerType,\n \":ttl\": computeTtl(),\n },\n { removeUndefinedValues: true }\n ),\n });\n } catch (err) {\n console.error(\"[effortless] Failed to write platform record:\", err);\n }\n };\n\n return {\n tableName,\n\n async appendExecution(handlerName, handlerType, entry) {\n await appendToList(handlerName, handlerType, \"executions\", entry);\n },\n\n async appendError(handlerName, handlerType, entry) {\n await appendToList(handlerName, handlerType, \"errors\", entry);\n },\n\n async get<T extends PlatformEntity>(pk: string, sk: string): Promise<T | undefined> {\n const result = await getClient().getItem({\n TableName: tableName,\n Key: marshall({ pk, sk }),\n });\n return result.Item ? (unmarshall(result.Item) as T) : undefined;\n },\n\n async query<T extends PlatformEntity>(pk: string, skPrefix?: string): Promise<T[]> {\n const names: Record<string, string> = { \"#pk\": \"pk\" };\n const values: Record<string, unknown> = { \":pk\": pk };\n let keyCondition = \"#pk = :pk\";\n\n if (skPrefix) {\n names[\"#sk\"] = \"sk\";\n values[\":sk\"] = skPrefix;\n keyCondition += \" AND begins_with(#sk, :sk)\";\n }\n\n const result = await getClient().query({\n TableName: tableName,\n KeyConditionExpression: keyCondition,\n ExpressionAttributeNames: names,\n ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }),\n });\n\n return (result.Items ?? []).map(item => unmarshall(item) as T);\n },\n\n async put(entity: PlatformEntity) {\n try {\n await getClient().putItem({\n TableName: tableName,\n Item: marshall(entity as Record<string, unknown>, { removeUndefinedValues: true }),\n });\n } catch (err) {\n console.error(\"[effortless] Failed to write platform record:\", err);\n }\n },\n };\n};\n","export const ENV_PLATFORM_TABLE = \"EFF_PLATFORM_TABLE\";\n\nexport const DEFAULT_TTL_SECONDS = 7 * 24 * 60 * 60; // 7 days\n\n// ============ Base ============\n\nexport type BasePlatformEntity = {\n pk: string;\n sk: string;\n type: string;\n ttl?: number;\n};\n\n// ============ Execution Log ============\n\nexport type ExecutionEntry = {\n id: string;\n ts: string;\n ms: number;\n in: unknown;\n out?: unknown;\n};\n\nexport type ErrorEntry = {\n id: string;\n ts: string;\n ms: number;\n in: unknown;\n err: string;\n};\n\nexport type ExecutionLogEntity = BasePlatformEntity & {\n type: \"execution-log\";\n handlerName: string;\n handlerType: \"http\" | \"table\";\n executions: ExecutionEntry[];\n errors: ErrorEntry[];\n};\n\n// ============ Discriminated Union ============\n\nexport type PlatformEntity =\n | ExecutionLogEntity;\n // future: | IdempotencyEntity | HandlerMetaEntity\n\n// ============ Helpers ============\n\nexport const truncateForStorage = (value: unknown, maxLength = 4096): unknown => {\n if (value === undefined || value === null) return value;\n const str = typeof value === \"string\" ? value : JSON.stringify(value);\n if (str.length <= maxLength) return value;\n return str.slice(0, maxLength) + \"...[truncated]\";\n};\n\nexport const dateBucket = (date = new Date()): string =>\n date.toISOString().slice(0, 10);\n\nexport const computeTtl = (ttlSeconds = DEFAULT_TTL_SECONDS): number =>\n Math.floor(Date.now() / 1000) + ttlSeconds;\n"],"mappings":"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","names":[]}
@@ -1,9 +1,19 @@
1
1
  import {
2
2
  createHandlerRuntime,
3
3
  truncateForStorage
4
- } from "../chunk-7JVA4742.js";
4
+ } from "../chunk-AHRNISIY.js";
5
5
 
6
6
  // src/runtime/wrap-http.ts
7
+ var CONTENT_TYPE_MAP = {
8
+ json: "application/json",
9
+ html: "text/html; charset=utf-8",
10
+ text: "text/plain; charset=utf-8",
11
+ css: "text/css; charset=utf-8",
12
+ js: "application/javascript; charset=utf-8",
13
+ xml: "application/xml; charset=utf-8",
14
+ csv: "text/csv; charset=utf-8",
15
+ svg: "image/svg+xml; charset=utf-8"
16
+ };
7
17
  var parseBody = (body, isBase64) => {
8
18
  if (!body) return void 0;
9
19
  const decoded = isBase64 ? Buffer.from(body, "base64").toString("utf-8") : body;
@@ -16,11 +26,16 @@ var parseBody = (body, isBase64) => {
16
26
  var wrapHttp = (handler) => {
17
27
  const rt = createHandlerRuntime(handler, "http");
18
28
  const toResult = (r) => {
19
- const customContentType = r.headers?.["content-type"] ?? r.headers?.["Content-Type"];
20
- const isJson = !customContentType || customContentType.includes("application/json");
29
+ const resolved = r.contentType ? CONTENT_TYPE_MAP[r.contentType] : void 0;
30
+ const customContentType = resolved ?? r.headers?.["content-type"] ?? r.headers?.["Content-Type"];
31
+ const isJson = !customContentType || customContentType === "application/json";
21
32
  return {
22
33
  statusCode: r.status,
23
- headers: { ...isJson ? { "Content-Type": "application/json" } : {}, ...r.headers },
34
+ headers: {
35
+ "Content-Type": customContentType ?? "application/json",
36
+ ...r.headers,
37
+ ...resolved ? { "Content-Type": resolved } : {}
38
+ },
24
39
  body: isJson ? JSON.stringify(r.body) : String(r.body ?? "")
25
40
  };
26
41
  };