effortless-aws 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -12
- package/dist/index.d.ts +194 -72
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/wrap-api.js +45 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,15 +13,8 @@ npm install effortless-aws
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { defineApi } from "effortless-aws";
|
|
15
15
|
|
|
16
|
-
export const hello = defineApi({
|
|
17
|
-
|
|
18
|
-
get: {
|
|
19
|
-
"/": async () => ({
|
|
20
|
-
status: 200,
|
|
21
|
-
body: { message: "Hello!" },
|
|
22
|
-
}),
|
|
23
|
-
},
|
|
24
|
-
});
|
|
16
|
+
export const hello = defineApi({ basePath: "/hello" })
|
|
17
|
+
.get("/", async ({ ok }) => ok({ message: "Hello!" }));
|
|
25
18
|
```
|
|
26
19
|
|
|
27
20
|
## Handlers
|
|
@@ -39,9 +32,9 @@ export const hello = defineApi({
|
|
|
39
32
|
## Features
|
|
40
33
|
|
|
41
34
|
- **Infrastructure from code** — export a handler, get the AWS resources
|
|
42
|
-
- **Typed everything** — `defineTable<Order
|
|
43
|
-
- **Cross-handler deps** —
|
|
44
|
-
- **SSM params** —
|
|
35
|
+
- **Typed everything** — `defineTable<Order>()` gives you typed `put()`, typed `deps.orders.get()`, typed `record.new`
|
|
36
|
+
- **Cross-handler deps** — `.deps(() => ({ orders }))` auto-wires IAM and injects a typed `TableClient`
|
|
37
|
+
- **SSM params** — `.config(({ defineSecret }) => ...)` fetches secrets from Parameter Store at cold start
|
|
45
38
|
- **Static files** — `static: ["templates/*.ejs"]` bundles files into the Lambda ZIP
|
|
46
39
|
- **Cold start caching** — `setup` factory runs once per cold start, cached across invocations
|
|
47
40
|
|
package/dist/index.d.ts
CHANGED
|
@@ -4,13 +4,6 @@
|
|
|
4
4
|
* @see {@link https://effortless-aws.website/configuration | Configuration guide}
|
|
5
5
|
*/
|
|
6
6
|
type EffortlessConfig = {
|
|
7
|
-
/**
|
|
8
|
-
* Project root directory. All relative paths (handlers, server, assets, etc.)
|
|
9
|
-
* are resolved from this directory.
|
|
10
|
-
* Resolved relative to where the CLI runs (process.cwd()).
|
|
11
|
-
* @default "." (current working directory)
|
|
12
|
-
*/
|
|
13
|
-
root?: string;
|
|
14
7
|
/**
|
|
15
8
|
* Project name used for resource naming and tagging.
|
|
16
9
|
* This becomes part of Lambda function names, IAM roles, etc.
|
|
@@ -177,8 +170,6 @@ declare const defineSecret: DefineSecretFn;
|
|
|
177
170
|
declare const secret: DefineSecretFn;
|
|
178
171
|
/** @deprecated Use `SecretRef` instead */
|
|
179
172
|
type ParamRef<T = string> = SecretRef<T>;
|
|
180
|
-
/** @deprecated Use `AnySecretRef` instead */
|
|
181
|
-
type AnyParamRef = AnySecretRef;
|
|
182
173
|
/** @deprecated Use `defineSecret()` instead. */
|
|
183
174
|
declare const param: <T = string>(key: string, transform?: (raw: string) => T) => SecretRef<T>;
|
|
184
175
|
/** @deprecated Use `defineSecret({ generate: "hex:N" })` instead. */
|
|
@@ -361,9 +352,9 @@ type BucketEvent = {
|
|
|
361
352
|
bucketName: string;
|
|
362
353
|
};
|
|
363
354
|
/** Spread ctx into callback args (empty when no setup) */
|
|
364
|
-
type SpreadCtx$
|
|
355
|
+
type SpreadCtx$6<C> = [C] extends [undefined] ? {} : C & {};
|
|
365
356
|
/** Setup factory — receives bucket/deps/config/files based on what was declared */
|
|
366
|
-
type SetupArgs$
|
|
357
|
+
type SetupArgs$6<D, P, HasFiles extends boolean> = {
|
|
367
358
|
bucket: BucketClient;
|
|
368
359
|
} & ([D] extends [undefined] ? {} : {
|
|
369
360
|
deps: ResolveDeps<D>;
|
|
@@ -377,18 +368,18 @@ type SetupArgs$5<D, P, HasFiles extends boolean> = {
|
|
|
377
368
|
*/
|
|
378
369
|
type BucketObjectCreatedFn<C = undefined> = (args: {
|
|
379
370
|
event: BucketEvent;
|
|
380
|
-
} & SpreadCtx$
|
|
371
|
+
} & SpreadCtx$6<C>) => Promise<void>;
|
|
381
372
|
/**
|
|
382
373
|
* Callback function type for S3 ObjectRemoved events
|
|
383
374
|
*/
|
|
384
375
|
type BucketObjectRemovedFn<C = undefined> = (args: {
|
|
385
376
|
event: BucketEvent;
|
|
386
|
-
} & SpreadCtx$
|
|
377
|
+
} & SpreadCtx$6<C>) => Promise<void>;
|
|
387
378
|
/**
|
|
388
379
|
* Internal handler object created by defineBucket
|
|
389
380
|
* @internal
|
|
390
381
|
*/
|
|
391
|
-
type BucketHandler<C = any> = {
|
|
382
|
+
type BucketHandler$1<C = any> = {
|
|
392
383
|
readonly __brand: "effortless-bucket";
|
|
393
384
|
readonly __spec: BucketConfig;
|
|
394
385
|
readonly onError?: (...args: any[]) => any;
|
|
@@ -417,21 +408,21 @@ interface BucketBuilder<D = undefined, P = undefined, C = undefined, HasFiles ex
|
|
|
417
408
|
/** Initialize shared state on cold start with lambda options */
|
|
418
409
|
setup(lambda: LambdaOptions): BucketBuilder<D, P, C, HasFiles>;
|
|
419
410
|
/** Initialize shared state on cold start. Receives bucket (self-client), deps, config, files. */
|
|
420
|
-
setup<C2>(fn: (args: SetupArgs$
|
|
411
|
+
setup<C2>(fn: (args: SetupArgs$6<D, P, HasFiles>) => C2 | Promise<C2>): BucketBuilder<D, P, C2, HasFiles>;
|
|
421
412
|
/** Initialize shared state on cold start with lambda options. Receives bucket (self-client), deps, config, files. */
|
|
422
|
-
setup<C2>(fn: (args: SetupArgs$
|
|
413
|
+
setup<C2>(fn: (args: SetupArgs$6<D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions): BucketBuilder<D, P, C2, HasFiles>;
|
|
423
414
|
/** Handle errors thrown by callbacks */
|
|
424
415
|
onError(fn: (args: {
|
|
425
416
|
error: unknown;
|
|
426
|
-
} & SpreadCtx$
|
|
417
|
+
} & SpreadCtx$6<C>) => void | Promise<void>): BucketBuilder<D, P, C, HasFiles>;
|
|
427
418
|
/** Cleanup callback — runs after each invocation, before Lambda freezes */
|
|
428
|
-
onCleanup(fn: (args: SpreadCtx$
|
|
419
|
+
onCleanup(fn: (args: SpreadCtx$6<C>) => void | Promise<void>): BucketBuilder<D, P, C, HasFiles>;
|
|
429
420
|
/** Handle S3 ObjectCreated events (terminal — returns finalized handler) */
|
|
430
|
-
onObjectCreated(fn: BucketObjectCreatedFn<C>): BucketHandler<C>;
|
|
421
|
+
onObjectCreated(fn: BucketObjectCreatedFn<C>): BucketHandler$1<C>;
|
|
431
422
|
/** Handle S3 ObjectRemoved events (terminal — returns finalized handler) */
|
|
432
|
-
onObjectRemoved(fn: BucketObjectRemovedFn<C>): BucketHandler<C>;
|
|
423
|
+
onObjectRemoved(fn: BucketObjectRemovedFn<C>): BucketHandler$1<C>;
|
|
433
424
|
/** Finalize as resource-only bucket (no Lambda) */
|
|
434
|
-
build(): BucketHandler<C>;
|
|
425
|
+
build(): BucketHandler$1<C>;
|
|
435
426
|
}
|
|
436
427
|
/**
|
|
437
428
|
* Define an S3 bucket with optional event handlers.
|
|
@@ -556,9 +547,9 @@ type FifoQueueConfig = {
|
|
|
556
547
|
maxReceiveCount?: number;
|
|
557
548
|
};
|
|
558
549
|
/** Spread ctx into callback args (empty when no setup) */
|
|
559
|
-
type SpreadCtx$
|
|
550
|
+
type SpreadCtx$5<C> = [C] extends [undefined] ? {} : C & {};
|
|
560
551
|
/** Setup factory — receives deps/config/files based on what was declared */
|
|
561
|
-
type SetupArgs$
|
|
552
|
+
type SetupArgs$5<D, P, HasFiles extends boolean> = ([D] extends [undefined] ? {} : {
|
|
562
553
|
deps: ResolveDeps<D>;
|
|
563
554
|
}) & ([P] extends [undefined] ? {} : {
|
|
564
555
|
config: ResolveConfig<P & {}>;
|
|
@@ -571,7 +562,7 @@ type SetupArgs$4<D, P, HasFiles extends boolean> = ([D] extends [undefined] ? {}
|
|
|
571
562
|
*/
|
|
572
563
|
type FifoQueueMessageFn<T = unknown, C = undefined> = (args: {
|
|
573
564
|
message: FifoQueueMessage<T>;
|
|
574
|
-
} & SpreadCtx$
|
|
565
|
+
} & SpreadCtx$5<C>) => Promise<void>;
|
|
575
566
|
/**
|
|
576
567
|
* Batch handler function.
|
|
577
568
|
* Called once with all messages in the batch.
|
|
@@ -579,14 +570,14 @@ type FifoQueueMessageFn<T = unknown, C = undefined> = (args: {
|
|
|
579
570
|
*/
|
|
580
571
|
type FifoQueueBatchFn<T = unknown, C = undefined> = (args: {
|
|
581
572
|
messages: FifoQueueMessage<T>[];
|
|
582
|
-
} & SpreadCtx$
|
|
573
|
+
} & SpreadCtx$5<C>) => Promise<void | {
|
|
583
574
|
failures: string[];
|
|
584
575
|
}>;
|
|
585
576
|
/**
|
|
586
577
|
* Internal handler object created by defineFifoQueue
|
|
587
578
|
* @internal
|
|
588
579
|
*/
|
|
589
|
-
type FifoQueueHandler<T = unknown, C = any> = {
|
|
580
|
+
type FifoQueueHandler$1<T = unknown, C = any> = {
|
|
590
581
|
readonly __brand: "effortless-fifo-queue";
|
|
591
582
|
readonly __spec: FifoQueueConfig;
|
|
592
583
|
readonly schema?: (input: unknown) => T;
|
|
@@ -628,19 +619,19 @@ interface FifoQueueBuilder<T = unknown, D = undefined, P = undefined, C = undefi
|
|
|
628
619
|
/** Configure Lambda settings only (memory, timeout, permissions, etc.) */
|
|
629
620
|
setup(lambda: LambdaOptions): FifoQueueBuilder<T, D, P, C, HasFiles>;
|
|
630
621
|
/** Initialize shared state on cold start. Receives deps, config, files. */
|
|
631
|
-
setup<C2>(fn: (args: SetupArgs$
|
|
622
|
+
setup<C2>(fn: (args: SetupArgs$5<D, P, HasFiles>) => C2 | Promise<C2>): FifoQueueBuilder<T, D, P, C2, HasFiles>;
|
|
632
623
|
/** Initialize shared state on cold start + configure Lambda settings. */
|
|
633
|
-
setup<C2>(fn: (args: SetupArgs$
|
|
624
|
+
setup<C2>(fn: (args: SetupArgs$5<D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions): FifoQueueBuilder<T, D, P, C2, HasFiles>;
|
|
634
625
|
/** Handle errors thrown by message handlers */
|
|
635
626
|
onError(fn: (args: {
|
|
636
627
|
error: unknown;
|
|
637
|
-
} & SpreadCtx$
|
|
628
|
+
} & SpreadCtx$5<C>) => void | Promise<void>): FifoQueueBuilder<T, D, P, C, HasFiles>;
|
|
638
629
|
/** Cleanup callback — runs after each invocation, before Lambda freezes */
|
|
639
|
-
onCleanup(fn: (args: SpreadCtx$
|
|
630
|
+
onCleanup(fn: (args: SpreadCtx$5<C>) => void | Promise<void>): FifoQueueBuilder<T, D, P, C, HasFiles>;
|
|
640
631
|
/** Per-message handler (terminal — returns finalized handler) */
|
|
641
|
-
onMessage(fn: FifoQueueMessageFn<T, C>): FifoQueueHandler<T, C>;
|
|
632
|
+
onMessage(fn: FifoQueueMessageFn<T, C>): FifoQueueHandler$1<T, C>;
|
|
642
633
|
/** Batch handler (terminal — returns finalized handler) */
|
|
643
|
-
onMessageBatch(fn: FifoQueueBatchFn<T, C>): FifoQueueHandler<T, C>;
|
|
634
|
+
onMessageBatch(fn: FifoQueueBatchFn<T, C>): FifoQueueHandler$1<T, C>;
|
|
644
635
|
}
|
|
645
636
|
/**
|
|
646
637
|
* Define a FIFO SQS queue with a Lambda message handler.
|
|
@@ -672,7 +663,7 @@ type WorkerConfig = {
|
|
|
672
663
|
concurrency?: number;
|
|
673
664
|
};
|
|
674
665
|
/** Setup factory — receives deps/config/files based on what was declared */
|
|
675
|
-
type SetupArgs$
|
|
666
|
+
type SetupArgs$4<D, P, HasFiles extends boolean> = ([D] extends [undefined] ? {} : {
|
|
676
667
|
deps: ResolveDeps<D>;
|
|
677
668
|
}) & ([P] extends [undefined] ? {} : {
|
|
678
669
|
config: ResolveConfig<P & {}>;
|
|
@@ -680,14 +671,14 @@ type SetupArgs$3<D, P, HasFiles extends boolean> = ([D] extends [undefined] ? {}
|
|
|
680
671
|
files: StaticFiles;
|
|
681
672
|
} : {});
|
|
682
673
|
/** Spread ctx into callback args (empty when no setup) */
|
|
683
|
-
type SpreadCtx$
|
|
674
|
+
type SpreadCtx$4<C> = [C] extends [undefined] ? {} : C & {};
|
|
684
675
|
/** Callback function for the worker's onMessage */
|
|
685
|
-
type WorkerMessageFn<T, C = undefined> = (msg: T, ctx: SpreadCtx$
|
|
676
|
+
type WorkerMessageFn<T, C = undefined> = (msg: T, ctx: SpreadCtx$4<C>) => Promise<void> | void;
|
|
686
677
|
/**
|
|
687
678
|
* Handler object created by defineWorker.
|
|
688
679
|
* @internal
|
|
689
680
|
*/
|
|
690
|
-
type WorkerHandler<T = any, C = any> = {
|
|
681
|
+
type WorkerHandler$1<T = any, C = any> = {
|
|
691
682
|
readonly __brand: "effortless-worker";
|
|
692
683
|
readonly __spec: WorkerConfig;
|
|
693
684
|
readonly onError?: (...args: any[]) => any;
|
|
@@ -717,19 +708,19 @@ interface WorkerBuilder<T, D = undefined, P = undefined, C = undefined, HasFiles
|
|
|
717
708
|
/** Configure Lambda settings only (memory, timeout, permissions, logLevel) */
|
|
718
709
|
setup(lambda: LambdaOptions): WorkerBuilder<T, D, P, C, HasFiles>;
|
|
719
710
|
/** Initialize shared state on cold start. Receives deps, config, files. */
|
|
720
|
-
setup<C2>(fn: (args: SetupArgs$
|
|
711
|
+
setup<C2>(fn: (args: SetupArgs$4<D, P, HasFiles>) => C2 | Promise<C2>): WorkerBuilder<T, D, P, C2, HasFiles>;
|
|
721
712
|
/** Initialize shared state on cold start + configure Lambda settings. */
|
|
722
|
-
setup<C2>(fn: (args: SetupArgs$
|
|
713
|
+
setup<C2>(fn: (args: SetupArgs$4<D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions): WorkerBuilder<T, D, P, C2, HasFiles>;
|
|
723
714
|
/** Handle errors thrown by onMessage. Return "delete" to discard, "retry" to reprocess (default). */
|
|
724
715
|
onError(fn: (args: {
|
|
725
716
|
error: unknown;
|
|
726
717
|
msg: T;
|
|
727
718
|
retryCount: number;
|
|
728
|
-
} & SpreadCtx$
|
|
719
|
+
} & SpreadCtx$4<C>) => "retry" | "delete" | void | Promise<"retry" | "delete" | void>): WorkerBuilder<T, D, P, C, HasFiles>;
|
|
729
720
|
/** Cleanup callback — runs when the worker shuts down */
|
|
730
|
-
onCleanup(fn: (args: SpreadCtx$
|
|
721
|
+
onCleanup(fn: (args: SpreadCtx$4<C>) => void | Promise<void>): WorkerBuilder<T, D, P, C, HasFiles>;
|
|
731
722
|
/** Process a single message from the queue (terminal) */
|
|
732
|
-
onMessage(fn: WorkerMessageFn<T, C>): WorkerHandler<T, C>;
|
|
723
|
+
onMessage(fn: WorkerMessageFn<T, C>): WorkerHandler$1<T, C>;
|
|
733
724
|
}
|
|
734
725
|
/**
|
|
735
726
|
* Define a worker — a long-running Fargate container with an SQS queue.
|
|
@@ -935,10 +926,10 @@ type WorkerClient<T = unknown> = {
|
|
|
935
926
|
};
|
|
936
927
|
|
|
937
928
|
/** Dep value types supported by the deps declaration */
|
|
938
|
-
type AnyDepHandler = TableHandler<any, any> | BucketHandler<any> | MailerHandler | FifoQueueHandler<any, any> | WorkerHandler<any, any>;
|
|
929
|
+
type AnyDepHandler = TableHandler$1<any, any> | BucketHandler$1<any> | MailerHandler | FifoQueueHandler$1<any, any> | WorkerHandler$1<any, any>;
|
|
939
930
|
/** Maps a deps declaration to resolved runtime client types */
|
|
940
931
|
type ResolveDeps<D> = {
|
|
941
|
-
[K in keyof D]: D[K] extends TableHandler<infer T> ? TableClient<T> : D[K] extends BucketHandler ? BucketClient : D[K] extends MailerHandler ? EmailClient : D[K] extends FifoQueueHandler<infer T> ? QueueClient<T> : D[K] extends WorkerHandler<infer T> ? WorkerClient<T> : never;
|
|
932
|
+
[K in keyof D]: D[K] extends TableHandler$1<infer T> ? TableClient<T> : D[K] extends BucketHandler$1 ? BucketClient : D[K] extends MailerHandler ? EmailClient : D[K] extends FifoQueueHandler$1<infer T> ? QueueClient<T> : D[K] extends WorkerHandler$1<infer T> ? WorkerClient<T> : never;
|
|
942
933
|
};
|
|
943
934
|
|
|
944
935
|
/** DynamoDB Streams view type - determines what data is captured in stream records */
|
|
@@ -1001,7 +992,7 @@ type TableRecord<T = Record<string, unknown>> = {
|
|
|
1001
992
|
timestamp?: number;
|
|
1002
993
|
};
|
|
1003
994
|
/** Setup factory — receives table/deps/config/files based on what was declared */
|
|
1004
|
-
type SetupArgs$
|
|
995
|
+
type SetupArgs$3<T, D, P, HasFiles extends boolean> = {
|
|
1005
996
|
table: TableClient<T>;
|
|
1006
997
|
} & ([D] extends [undefined] ? {} : {
|
|
1007
998
|
deps: ResolveDeps<D>;
|
|
@@ -1011,7 +1002,7 @@ type SetupArgs$2<T, D, P, HasFiles extends boolean> = {
|
|
|
1011
1002
|
files: StaticFiles;
|
|
1012
1003
|
} : {});
|
|
1013
1004
|
/** Spread ctx into callback args (empty when no setup) */
|
|
1014
|
-
type SpreadCtx$
|
|
1005
|
+
type SpreadCtx$3<C> = [C] extends [undefined] ? {} : C & {};
|
|
1015
1006
|
/**
|
|
1016
1007
|
* Callback function type for processing a single DynamoDB stream record.
|
|
1017
1008
|
* Receives the current record and the full batch for context.
|
|
@@ -1019,7 +1010,7 @@ type SpreadCtx$2<C> = [C] extends [undefined] ? {} : C & {};
|
|
|
1019
1010
|
type TableRecordFn<T = Record<string, unknown>, C = undefined> = (args: {
|
|
1020
1011
|
record: TableRecord<T>;
|
|
1021
1012
|
batch: readonly TableRecord<T>[];
|
|
1022
|
-
} & SpreadCtx$
|
|
1013
|
+
} & SpreadCtx$3<C>) => Promise<void>;
|
|
1023
1014
|
/**
|
|
1024
1015
|
* Batch handler function for DynamoDB stream records.
|
|
1025
1016
|
* Called once with all records in the batch.
|
|
@@ -1027,7 +1018,7 @@ type TableRecordFn<T = Record<string, unknown>, C = undefined> = (args: {
|
|
|
1027
1018
|
*/
|
|
1028
1019
|
type TableBatchFn<T = Record<string, unknown>, C = undefined> = (args: {
|
|
1029
1020
|
records: readonly TableRecord<T>[];
|
|
1030
|
-
} & SpreadCtx$
|
|
1021
|
+
} & SpreadCtx$3<C>) => Promise<void | {
|
|
1031
1022
|
failures: string[];
|
|
1032
1023
|
}>;
|
|
1033
1024
|
/** Static config extracted by AST (no runtime callbacks) */
|
|
@@ -1036,7 +1027,7 @@ type TableBatchFn<T = Record<string, unknown>, C = undefined> = (args: {
|
|
|
1036
1027
|
* Used by runtime wrappers and as type annotation for circular deps.
|
|
1037
1028
|
* @internal
|
|
1038
1029
|
*/
|
|
1039
|
-
type TableHandler<T = Record<string, unknown>, C = any> = {
|
|
1030
|
+
type TableHandler$1<T = Record<string, unknown>, C = any> = {
|
|
1040
1031
|
readonly __brand: "effortless-table";
|
|
1041
1032
|
readonly __spec: TableConfig;
|
|
1042
1033
|
readonly schema?: (input: unknown) => T;
|
|
@@ -1078,21 +1069,21 @@ interface TableBuilder<T = Record<string, unknown>, D = undefined, P = undefined
|
|
|
1078
1069
|
/** Configure Lambda settings only (memory, timeout, permissions, etc.) */
|
|
1079
1070
|
setup(lambda: LambdaOptions): TableBuilder<T, D, P, C, HasFiles>;
|
|
1080
1071
|
/** Initialize shared state on cold start. Receives table (self-client), deps, config, files. */
|
|
1081
|
-
setup<C2>(fn: (args: SetupArgs$
|
|
1072
|
+
setup<C2>(fn: (args: SetupArgs$3<T, D, P, HasFiles>) => C2 | Promise<C2>): TableBuilder<T, D, P, C2, HasFiles>;
|
|
1082
1073
|
/** Initialize shared state on cold start + configure Lambda settings. */
|
|
1083
|
-
setup<C2>(fn: (args: SetupArgs$
|
|
1074
|
+
setup<C2>(fn: (args: SetupArgs$3<T, D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions): TableBuilder<T, D, P, C2, HasFiles>;
|
|
1084
1075
|
/** Handle errors thrown by onRecord/onRecordBatch */
|
|
1085
1076
|
onError(fn: (args: {
|
|
1086
1077
|
error: unknown;
|
|
1087
|
-
} & SpreadCtx$
|
|
1078
|
+
} & SpreadCtx$3<C>) => void | Promise<void>): TableBuilder<T, D, P, C, HasFiles>;
|
|
1088
1079
|
/** Cleanup callback — runs after each invocation, before Lambda freezes */
|
|
1089
|
-
onCleanup(fn: (args: SpreadCtx$
|
|
1080
|
+
onCleanup(fn: (args: SpreadCtx$3<C>) => void | Promise<void>): TableBuilder<T, D, P, C, HasFiles>;
|
|
1090
1081
|
/** Per-record stream handler (terminal — returns finalized handler) */
|
|
1091
|
-
onRecord(fn: TableRecordFn<T, C>): TableHandler<T, C>;
|
|
1082
|
+
onRecord(fn: TableRecordFn<T, C>): TableHandler$1<T, C>;
|
|
1092
1083
|
/** Batch stream handler (terminal — returns finalized handler) */
|
|
1093
|
-
onRecordBatch(fn: TableBatchFn<T, C>): TableHandler<T, C>;
|
|
1084
|
+
onRecordBatch(fn: TableBatchFn<T, C>): TableHandler$1<T, C>;
|
|
1094
1085
|
/** Finalize as resource-only table (no Lambda) */
|
|
1095
|
-
build(): TableHandler<T, C>;
|
|
1086
|
+
build(): TableHandler$1<T, C>;
|
|
1096
1087
|
}
|
|
1097
1088
|
/**
|
|
1098
1089
|
* Define a DynamoDB table with optional stream handler (single-table design).
|
|
@@ -1317,11 +1308,11 @@ type RouteEntry = {
|
|
|
1317
1308
|
public?: boolean;
|
|
1318
1309
|
};
|
|
1319
1310
|
/** Spread ctx into route args: Omit auth config, add AuthHelpers if present */
|
|
1320
|
-
type SpreadCtx$
|
|
1311
|
+
type SpreadCtx$2<C> = ([C] extends [undefined] ? {} : Omit<C & {}, 'auth'>) & ([ExtractAuth<C>] extends [undefined] ? {} : {
|
|
1321
1312
|
auth: AuthHelpers<ExtractAuth<C>>;
|
|
1322
1313
|
});
|
|
1323
1314
|
/** Callback args available inside each route — ctx is spread into args */
|
|
1324
|
-
type RouteArgs<C, ST> = SpreadCtx$
|
|
1315
|
+
type RouteArgs<C, ST> = SpreadCtx$2<C> & {
|
|
1325
1316
|
req: HttpRequest;
|
|
1326
1317
|
input: unknown;
|
|
1327
1318
|
ok: OkHelper;
|
|
@@ -1336,7 +1327,7 @@ type RouteOptions = {
|
|
|
1336
1327
|
public?: boolean;
|
|
1337
1328
|
};
|
|
1338
1329
|
/** Setup factory — receives deps/config/files/enableAuth based on what was declared */
|
|
1339
|
-
type SetupArgs$
|
|
1330
|
+
type SetupArgs$2<D, P, HasFiles extends boolean> = {
|
|
1340
1331
|
enableAuth: EnableAuth;
|
|
1341
1332
|
ok: OkHelper;
|
|
1342
1333
|
fail: FailHelper;
|
|
@@ -1406,18 +1397,18 @@ interface ApiBuilder<D = undefined, P = undefined, C = undefined, ST extends boo
|
|
|
1406
1397
|
/** Configure Lambda settings only (no init function) */
|
|
1407
1398
|
setup(lambda: LambdaOptions): ApiBuilder<D, P, C, ST, HasFiles>;
|
|
1408
1399
|
/** Initialize shared state on cold start. Receives deps/config/files based on what was declared. */
|
|
1409
|
-
setup<C2>(fn: (args: SetupArgs$
|
|
1400
|
+
setup<C2>(fn: (args: SetupArgs$2<D, P, HasFiles>) => ValidateSetupReturn<C2> | Promise<ValidateSetupReturn<C2>>): ApiBuilder<D, P, C2, ST, HasFiles>;
|
|
1410
1401
|
/** Initialize shared state on cold start with Lambda config. */
|
|
1411
|
-
setup<C2>(fn: (args: SetupArgs$
|
|
1402
|
+
setup<C2>(fn: (args: SetupArgs$2<D, P, HasFiles>) => ValidateSetupReturn<C2> | Promise<ValidateSetupReturn<C2>>, lambda: LambdaOptions): ApiBuilder<D, P, C2, ST, HasFiles>;
|
|
1412
1403
|
/** Handle errors thrown by routes */
|
|
1413
1404
|
onError(fn: (args: {
|
|
1414
1405
|
error: unknown;
|
|
1415
1406
|
req: HttpRequest;
|
|
1416
1407
|
ok: OkHelper;
|
|
1417
1408
|
fail: FailHelper;
|
|
1418
|
-
} & SpreadCtx$
|
|
1409
|
+
} & SpreadCtx$2<C>) => HttpResponse | Promise<HttpResponse>): ApiBuilder<D, P, C, ST, HasFiles>;
|
|
1419
1410
|
/** Cleanup callback — runs after each invocation, before Lambda freezes */
|
|
1420
|
-
onCleanup(fn: (args: SpreadCtx$
|
|
1411
|
+
onCleanup(fn: (args: SpreadCtx$2<C>) => void | Promise<void>): ApiBuilder<D, P, C, ST, HasFiles>;
|
|
1421
1412
|
/** Add a GET route (terminal — returns finalized handler with route methods) */
|
|
1422
1413
|
get(path: `/${string}`, handler: RouteHandler<C, ST>, options?: RouteOptions): ApiRoutes<C, ST>;
|
|
1423
1414
|
/** Add a POST route (terminal) */
|
|
@@ -1494,7 +1485,7 @@ type CronConfig = {
|
|
|
1494
1485
|
timezone?: Timezone;
|
|
1495
1486
|
};
|
|
1496
1487
|
/** Setup factory — receives deps/config/files based on what was declared */
|
|
1497
|
-
type SetupArgs<D, P, HasFiles extends boolean> = ([D] extends [undefined] ? {} : {
|
|
1488
|
+
type SetupArgs$1<D, P, HasFiles extends boolean> = ([D] extends [undefined] ? {} : {
|
|
1498
1489
|
deps: ResolveDeps<D>;
|
|
1499
1490
|
}) & ([P] extends [undefined] ? {} : {
|
|
1500
1491
|
config: ResolveConfig<P & {}>;
|
|
@@ -1502,14 +1493,14 @@ type SetupArgs<D, P, HasFiles extends boolean> = ([D] extends [undefined] ? {} :
|
|
|
1502
1493
|
files: StaticFiles;
|
|
1503
1494
|
} : {});
|
|
1504
1495
|
/** Spread ctx into callback args (empty when no setup) */
|
|
1505
|
-
type SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};
|
|
1496
|
+
type SpreadCtx$1<C> = [C] extends [undefined] ? {} : C & {};
|
|
1506
1497
|
/** Callback function for cron tick */
|
|
1507
|
-
type CronTickFn<C = undefined> = (args: SpreadCtx<C>) => Promise<void> | void;
|
|
1498
|
+
type CronTickFn<C = undefined> = (args: SpreadCtx$1<C>) => Promise<void> | void;
|
|
1508
1499
|
/**
|
|
1509
1500
|
* Handler object created by defineCron.
|
|
1510
1501
|
* @internal
|
|
1511
1502
|
*/
|
|
1512
|
-
type CronHandler<C = any> = {
|
|
1503
|
+
type CronHandler$1<C = any> = {
|
|
1513
1504
|
readonly __brand: "effortless-cron";
|
|
1514
1505
|
readonly __spec: CronConfig;
|
|
1515
1506
|
readonly onError?: (...args: any[]) => any;
|
|
@@ -1537,17 +1528,17 @@ interface CronBuilder<D = undefined, P = undefined, C = undefined, HasFiles exte
|
|
|
1537
1528
|
/** Configure Lambda settings only (memory, timeout, permissions, logLevel) */
|
|
1538
1529
|
setup(lambda: LambdaOptions): CronBuilder<D, P, C, HasFiles>;
|
|
1539
1530
|
/** Initialize shared state on cold start. Receives deps, config, files. */
|
|
1540
|
-
setup<C2>(fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>): CronBuilder<D, P, C2, HasFiles>;
|
|
1531
|
+
setup<C2>(fn: (args: SetupArgs$1<D, P, HasFiles>) => C2 | Promise<C2>): CronBuilder<D, P, C2, HasFiles>;
|
|
1541
1532
|
/** Initialize shared state on cold start + configure Lambda settings. */
|
|
1542
|
-
setup<C2>(fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions): CronBuilder<D, P, C2, HasFiles>;
|
|
1533
|
+
setup<C2>(fn: (args: SetupArgs$1<D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions): CronBuilder<D, P, C2, HasFiles>;
|
|
1543
1534
|
/** Handle errors thrown by onTick */
|
|
1544
1535
|
onError(fn: (args: {
|
|
1545
1536
|
error: unknown;
|
|
1546
|
-
} & SpreadCtx<C>) => void | Promise<void>): CronBuilder<D, P, C, HasFiles>;
|
|
1537
|
+
} & SpreadCtx$1<C>) => void | Promise<void>): CronBuilder<D, P, C, HasFiles>;
|
|
1547
1538
|
/** Cleanup callback — runs after each invocation, before Lambda freezes */
|
|
1548
|
-
onCleanup(fn: (args: SpreadCtx<C>) => void | Promise<void>): CronBuilder<D, P, C, HasFiles>;
|
|
1539
|
+
onCleanup(fn: (args: SpreadCtx$1<C>) => void | Promise<void>): CronBuilder<D, P, C, HasFiles>;
|
|
1549
1540
|
/** Tick handler — called on each scheduled invocation (terminal) */
|
|
1550
|
-
onTick(fn: CronTickFn<C>): CronHandler<C>;
|
|
1541
|
+
onTick(fn: CronTickFn<C>): CronHandler$1<C>;
|
|
1551
1542
|
}
|
|
1552
1543
|
/**
|
|
1553
1544
|
* Define a cron job — scheduled Lambda invocation via EventBridge Scheduler.
|
|
@@ -1568,4 +1559,135 @@ interface CronBuilder<D = undefined, P = undefined, C = undefined, HasFiles exte
|
|
|
1568
1559
|
*/
|
|
1569
1560
|
declare function defineCron(options: CronOptions): CronBuilder;
|
|
1570
1561
|
|
|
1571
|
-
|
|
1562
|
+
/** Static config extracted at deploy time */
|
|
1563
|
+
type McpConfig = {
|
|
1564
|
+
/** MCP server name (used in server info) */
|
|
1565
|
+
name: string;
|
|
1566
|
+
/** MCP server version (default: "1.0.0") */
|
|
1567
|
+
version?: string;
|
|
1568
|
+
/** Lambda function settings (memory, timeout, permissions, etc.) */
|
|
1569
|
+
lambda?: LambdaWithPermissions;
|
|
1570
|
+
};
|
|
1571
|
+
/** JSON Schema for a tool's input parameters */
|
|
1572
|
+
type McpInputSchema = {
|
|
1573
|
+
type: "object";
|
|
1574
|
+
properties: Record<string, unknown>;
|
|
1575
|
+
required?: string[];
|
|
1576
|
+
};
|
|
1577
|
+
/** Content block returned by a tool handler */
|
|
1578
|
+
type McpToolContent = {
|
|
1579
|
+
type: "text";
|
|
1580
|
+
text: string;
|
|
1581
|
+
} | {
|
|
1582
|
+
type: "image";
|
|
1583
|
+
data: string;
|
|
1584
|
+
mimeType: string;
|
|
1585
|
+
} | {
|
|
1586
|
+
type: "resource";
|
|
1587
|
+
resource: {
|
|
1588
|
+
uri: string;
|
|
1589
|
+
text: string;
|
|
1590
|
+
mimeType?: string;
|
|
1591
|
+
};
|
|
1592
|
+
};
|
|
1593
|
+
/** Result returned by a tool handler */
|
|
1594
|
+
type McpToolResult = {
|
|
1595
|
+
content: McpToolContent[];
|
|
1596
|
+
isError?: boolean;
|
|
1597
|
+
};
|
|
1598
|
+
/** A single MCP tool definition */
|
|
1599
|
+
type McpToolDef<C = undefined> = {
|
|
1600
|
+
/** Human-readable description of the tool */
|
|
1601
|
+
description: string;
|
|
1602
|
+
/** JSON Schema describing the tool's input parameters */
|
|
1603
|
+
input: McpInputSchema;
|
|
1604
|
+
/** Handler function called when the tool is invoked */
|
|
1605
|
+
handler: (input: any, ctx: SpreadCtx<C>) => McpToolResult | Promise<McpToolResult>;
|
|
1606
|
+
};
|
|
1607
|
+
/** Setup factory — receives deps/config/files based on what was declared */
|
|
1608
|
+
type SetupArgs<D, P, HasFiles extends boolean> = ([D] extends [undefined] ? {} : {
|
|
1609
|
+
deps: ResolveDeps<D>;
|
|
1610
|
+
}) & ([P] extends [undefined] ? {} : {
|
|
1611
|
+
config: ResolveConfig<P & {}>;
|
|
1612
|
+
}) & (HasFiles extends true ? {
|
|
1613
|
+
files: StaticFiles;
|
|
1614
|
+
} : {});
|
|
1615
|
+
/** Spread ctx into callback args (empty when no setup) */
|
|
1616
|
+
type SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};
|
|
1617
|
+
/**
|
|
1618
|
+
* Handler object created by defineMcp.
|
|
1619
|
+
* @internal
|
|
1620
|
+
*/
|
|
1621
|
+
type McpHandler$1<C = any> = {
|
|
1622
|
+
readonly __brand: "effortless-mcp";
|
|
1623
|
+
readonly __spec: McpConfig;
|
|
1624
|
+
readonly onError?: (...args: any[]) => any;
|
|
1625
|
+
readonly onCleanup?: (...args: any[]) => any;
|
|
1626
|
+
readonly setup?: (...args: any[]) => C | Promise<C>;
|
|
1627
|
+
readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);
|
|
1628
|
+
readonly config?: Record<string, unknown>;
|
|
1629
|
+
readonly static?: string[];
|
|
1630
|
+
readonly tools?: (...args: any[]) => any;
|
|
1631
|
+
};
|
|
1632
|
+
/** Options passed to `defineMcp()` */
|
|
1633
|
+
type McpOptions = {
|
|
1634
|
+
/** MCP server name (used in server info) */
|
|
1635
|
+
name: string;
|
|
1636
|
+
/** MCP server version (default: "1.0.0") */
|
|
1637
|
+
version?: string;
|
|
1638
|
+
};
|
|
1639
|
+
interface McpBuilder<D = undefined, P = undefined, C = undefined, HasFiles extends boolean = false> {
|
|
1640
|
+
/** Declare handler dependencies (tables, queues, buckets, mailers, workers) */
|
|
1641
|
+
deps<D2 extends Record<string, AnyDepHandler>>(fn: () => D2): McpBuilder<D2, P, C, HasFiles>;
|
|
1642
|
+
/** Declare SSM secrets */
|
|
1643
|
+
config<P2 extends Record<string, AnySecretRef>>(fn: ConfigFactory<P2>): McpBuilder<D, P2, C, HasFiles>;
|
|
1644
|
+
/** Include static files in the bundle. Chainable — call multiple times. */
|
|
1645
|
+
include(glob: string): McpBuilder<D, P, C, true>;
|
|
1646
|
+
/** Configure Lambda settings only (memory, timeout, permissions, logLevel) */
|
|
1647
|
+
setup(lambda: LambdaOptions): McpBuilder<D, P, C, HasFiles>;
|
|
1648
|
+
/** Initialize shared state on cold start. Receives deps, config, files. */
|
|
1649
|
+
setup<C2>(fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>): McpBuilder<D, P, C2, HasFiles>;
|
|
1650
|
+
/** Initialize shared state on cold start + configure Lambda settings. */
|
|
1651
|
+
setup<C2>(fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions): McpBuilder<D, P, C2, HasFiles>;
|
|
1652
|
+
/** Handle errors thrown by tool handlers */
|
|
1653
|
+
onError(fn: (args: {
|
|
1654
|
+
error: unknown;
|
|
1655
|
+
toolName: string;
|
|
1656
|
+
} & SpreadCtx<C>) => void | Promise<void>): McpBuilder<D, P, C, HasFiles>;
|
|
1657
|
+
/** Cleanup callback — runs on shutdown */
|
|
1658
|
+
onCleanup(fn: (args: SpreadCtx<C>) => void | Promise<void>): McpBuilder<D, P, C, HasFiles>;
|
|
1659
|
+
/** Define MCP tools (terminal) */
|
|
1660
|
+
tools(fn: (ctx: SpreadCtx<C>) => Record<string, McpToolDef<C>>): McpHandler$1<C>;
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Define an MCP (Model Context Protocol) server endpoint.
|
|
1664
|
+
*
|
|
1665
|
+
* Creates a Lambda-backed MCP server that exposes tools for AI models
|
|
1666
|
+
* and MCP-compatible clients to discover and invoke.
|
|
1667
|
+
*
|
|
1668
|
+
* @example
|
|
1669
|
+
* ```typescript
|
|
1670
|
+
* export const mcp = defineMcp({ name: "my-tools" })
|
|
1671
|
+
* .deps(() => ({ users: usersTable }))
|
|
1672
|
+
* .setup(({ deps }) => ({ db: deps.users }))
|
|
1673
|
+
* .tools(({ db }) => ({
|
|
1674
|
+
* get_user: {
|
|
1675
|
+
* description: "Get a user by ID",
|
|
1676
|
+
* input: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
|
|
1677
|
+
* handler: async (input) => ({
|
|
1678
|
+
* content: [{ type: "text", text: JSON.stringify(await db.get({ pk: input.id, sk: "profile" })) }]
|
|
1679
|
+
* })
|
|
1680
|
+
* }
|
|
1681
|
+
* }))
|
|
1682
|
+
* ```
|
|
1683
|
+
*/
|
|
1684
|
+
declare function defineMcp(options: McpOptions): McpBuilder;
|
|
1685
|
+
|
|
1686
|
+
type TableHandler<T = Record<string, unknown>> = TableHandler$1<T, any>;
|
|
1687
|
+
type FifoQueueHandler<T = unknown> = FifoQueueHandler$1<T, any>;
|
|
1688
|
+
type BucketHandler = BucketHandler$1<any>;
|
|
1689
|
+
type CronHandler = CronHandler$1<any>;
|
|
1690
|
+
type WorkerHandler<T = any> = WorkerHandler$1<T, any>;
|
|
1691
|
+
type McpHandler = McpHandler$1<any>;
|
|
1692
|
+
|
|
1693
|
+
export { type ApiAuthConfig, type ApiConfig, type ApiHandler, type ApiRoutes, type AppConfig, type AppHandler, type AuthHelpers, type BucketClient, type BucketConfig, type BucketEvent, type BucketHandler, 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 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 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, param, secret, toSeconds };
|
package/dist/index.js
CHANGED
|
@@ -490,6 +490,67 @@ function defineWorker(options) {
|
|
|
490
490
|
};
|
|
491
491
|
return builder;
|
|
492
492
|
}
|
|
493
|
+
|
|
494
|
+
// src/handlers/define-mcp.ts
|
|
495
|
+
function defineMcp(options) {
|
|
496
|
+
const spec = {
|
|
497
|
+
name: options.name,
|
|
498
|
+
...options.version ? { version: options.version } : {}
|
|
499
|
+
};
|
|
500
|
+
const state = { spec };
|
|
501
|
+
const applyLambdaOptions = (lambda) => {
|
|
502
|
+
if (Object.keys(lambda).length > 0) {
|
|
503
|
+
state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
const finalize = () => ({
|
|
507
|
+
__brand: "effortless-mcp",
|
|
508
|
+
__spec: state.spec,
|
|
509
|
+
...state.onError ? { onError: state.onError } : {},
|
|
510
|
+
...state.onCleanup ? { onCleanup: state.onCleanup } : {},
|
|
511
|
+
...state.setup ? { setup: state.setup } : {},
|
|
512
|
+
...state.deps ? { deps: state.deps } : {},
|
|
513
|
+
...state.config ? { config: state.config } : {},
|
|
514
|
+
...state.static ? { static: state.static } : {},
|
|
515
|
+
...state.tools ? { tools: state.tools } : {}
|
|
516
|
+
});
|
|
517
|
+
const builder = {
|
|
518
|
+
deps(fn) {
|
|
519
|
+
state.deps = fn;
|
|
520
|
+
return builder;
|
|
521
|
+
},
|
|
522
|
+
config(fn) {
|
|
523
|
+
state.config = resolveConfigFactory(fn);
|
|
524
|
+
return builder;
|
|
525
|
+
},
|
|
526
|
+
include(glob) {
|
|
527
|
+
state.static = [...state.static ?? [], glob];
|
|
528
|
+
return builder;
|
|
529
|
+
},
|
|
530
|
+
setup(fnOrLambda, maybeLambda) {
|
|
531
|
+
if (typeof fnOrLambda === "function") {
|
|
532
|
+
state.setup = fnOrLambda;
|
|
533
|
+
if (maybeLambda) applyLambdaOptions(maybeLambda);
|
|
534
|
+
} else {
|
|
535
|
+
applyLambdaOptions(fnOrLambda);
|
|
536
|
+
}
|
|
537
|
+
return builder;
|
|
538
|
+
},
|
|
539
|
+
onError(fn) {
|
|
540
|
+
state.onError = fn;
|
|
541
|
+
return builder;
|
|
542
|
+
},
|
|
543
|
+
onCleanup(fn) {
|
|
544
|
+
state.onCleanup = fn;
|
|
545
|
+
return builder;
|
|
546
|
+
},
|
|
547
|
+
tools(fn) {
|
|
548
|
+
state.tools = fn;
|
|
549
|
+
return finalize();
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
return builder;
|
|
553
|
+
}
|
|
493
554
|
export {
|
|
494
555
|
defineApi,
|
|
495
556
|
defineApp,
|
|
@@ -498,6 +559,7 @@ export {
|
|
|
498
559
|
defineCron,
|
|
499
560
|
defineFifoQueue,
|
|
500
561
|
defineMailer,
|
|
562
|
+
defineMcp,
|
|
501
563
|
defineSecret,
|
|
502
564
|
defineStaticSite,
|
|
503
565
|
defineTable,
|
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"],"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 root directory. All relative paths (handlers, server, assets, etc.)\n * are resolved from this directory.\n * Resolved relative to where the CLI runs (process.cwd()).\n * @default \".\" (current working directory)\n */\n root?: string;\n\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/** 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 instead of S3.\n * Keys are CloudFront path patterns (e.g., \"/api/*\"), values are HTTP handlers.\n * Example: `routes: { \"/api/*\": api }` */\n routes?: Record<string, AnyRoutableHandler>;\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, 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 } from \"../runtime/bucket-client\";\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};\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> =\n & { bucket: BucketClient }\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> = {\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};\n\n// ============ Builder ============\n\ninterface BucketBuilder<\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 ): BucketBuilder<D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): BucketBuilder<D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda ZIP */\n include(glob: string): BucketBuilder<D, P, C, true>;\n\n /** Initialize shared state on cold start with lambda options */\n setup(lambda: LambdaOptions): BucketBuilder<D, P, C, HasFiles>;\n /** Initialize shared state on cold start. Receives bucket (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): BucketBuilder<D, P, C2, HasFiles>;\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>) => C2 | Promise<C2>,\n lambda: LambdaOptions,\n ): BucketBuilder<D, P, C2, HasFiles>;\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>;\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>;\n\n /** Handle S3 ObjectCreated events (terminal — returns finalized handler) */\n onObjectCreated(\n fn: BucketObjectCreatedFn<C>\n ): BucketHandler<C>;\n\n /** Handle S3 ObjectRemoved events (terminal — returns finalized handler) */\n onObjectRemoved(\n fn: BucketObjectRemovedFn<C>\n ): BucketHandler<C>;\n\n /** Finalize as resource-only bucket (no Lambda) */\n build(): BucketHandler<C>;\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 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 } 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/** Parsed route definition stored at runtime */\nexport type RouteEntry = {\n method: HttpMethod;\n path: string;\n onRequest: (...args: any[]) => any;\n public?: boolean;\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 (e.g. public) */\ntype RouteOptions = { public?: boolean };\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?: RouteOptions): 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?: RouteOptions): 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?: RouteOptions) => {\n state.routes.push({\n method,\n path,\n onRequest: handler as any,\n ...(opts?.public ? { public: true } : {}),\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"],"mappings":"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","names":[]}
|
|
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/** 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 instead of S3.\n * Keys are CloudFront path patterns (e.g., \"/api/*\"), values are HTTP handlers.\n * Example: `routes: { \"/api/*\": api }` */\n routes?: Record<string, AnyRoutableHandler>;\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, 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 } from \"../runtime/bucket-client\";\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};\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> =\n & { bucket: BucketClient }\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> = {\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};\n\n// ============ Builder ============\n\ninterface BucketBuilder<\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 ): BucketBuilder<D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): BucketBuilder<D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda ZIP */\n include(glob: string): BucketBuilder<D, P, C, true>;\n\n /** Initialize shared state on cold start with lambda options */\n setup(lambda: LambdaOptions): BucketBuilder<D, P, C, HasFiles>;\n /** Initialize shared state on cold start. Receives bucket (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): BucketBuilder<D, P, C2, HasFiles>;\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>) => C2 | Promise<C2>,\n lambda: LambdaOptions,\n ): BucketBuilder<D, P, C2, HasFiles>;\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>;\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>;\n\n /** Handle S3 ObjectCreated events (terminal — returns finalized handler) */\n onObjectCreated(\n fn: BucketObjectCreatedFn<C>\n ): BucketHandler<C>;\n\n /** Handle S3 ObjectRemoved events (terminal — returns finalized handler) */\n onObjectRemoved(\n fn: BucketObjectRemovedFn<C>\n ): BucketHandler<C>;\n\n /** Finalize as resource-only bucket (no Lambda) */\n build(): BucketHandler<C>;\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 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 } 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/** Parsed route definition stored at runtime */\nexport type RouteEntry = {\n method: HttpMethod;\n path: string;\n onRequest: (...args: any[]) => any;\n public?: boolean;\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 (e.g. public) */\ntype RouteOptions = { public?: boolean };\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?: RouteOptions): 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?: RouteOptions): 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?: RouteOptions) => {\n state.routes.push({\n method,\n path,\n onRequest: handler as any,\n ...(opts?.public ? { public: true } : {}),\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 /** 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/** 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 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 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 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};\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 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 for AI models\n * and MCP-compatible clients to discover and invoke.\n *\n * @example\n * ```typescript\n * export const mcp = defineMcp({ name: \"my-tools\" })\n * .deps(() => ({ users: usersTable }))\n * .setup(({ deps }) => ({ db: deps.users }))\n * .tools(({ db }) => ({\n * get_user: {\n * description: \"Get a user by ID\",\n * input: { type: \"object\", properties: { id: { type: \"string\" } }, required: [\"id\"] },\n * handler: async (input) => ({\n * content: [{ type: \"text\", text: JSON.stringify(await db.get({ pk: input.id, sk: \"profile\" })) }]\n * })\n * }\n * }))\n * ```\n */\nexport function defineMcp(options: McpOptions): McpBuilder {\n const spec: McpConfig = {\n name: options.name,\n ...(options.version ? { version: options.version } : {}),\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 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.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 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":[]}
|
package/dist/runtime/wrap-api.js
CHANGED
|
@@ -48,9 +48,37 @@ var unauthorized = () => ({
|
|
|
48
48
|
headers: { "Content-Type": "application/json" },
|
|
49
49
|
body: JSON.stringify({ error: "Unauthorized" })
|
|
50
50
|
});
|
|
51
|
-
var
|
|
52
|
-
|
|
53
|
-
)
|
|
51
|
+
var extractParamNames = (pattern) => {
|
|
52
|
+
const names = [];
|
|
53
|
+
pattern.replace(/\{(\w+)\}/g, (_, name) => {
|
|
54
|
+
names.push(name);
|
|
55
|
+
return "";
|
|
56
|
+
});
|
|
57
|
+
return names;
|
|
58
|
+
};
|
|
59
|
+
var patternToRegex = (pattern) => {
|
|
60
|
+
const escaped = pattern.replace(
|
|
61
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
62
|
+
(ch) => ch === "{" || ch === "}" ? ch : `\\${ch}`
|
|
63
|
+
);
|
|
64
|
+
const withParams = escaped.replace(/\{(\w+)\}/g, "([^/]+)");
|
|
65
|
+
return new RegExp(`^${withParams}$`);
|
|
66
|
+
};
|
|
67
|
+
var findRoute = (routes, method, relativePath) => {
|
|
68
|
+
for (const r of routes) {
|
|
69
|
+
if (!(r.method === method || r.method === "GET" && method === "HEAD")) continue;
|
|
70
|
+
if (r.path === relativePath) return { entry: r, params: {} };
|
|
71
|
+
const regex = patternToRegex(r.path);
|
|
72
|
+
const match = relativePath.match(regex);
|
|
73
|
+
if (match) {
|
|
74
|
+
const names = extractParamNames(r.path);
|
|
75
|
+
const params = {};
|
|
76
|
+
for (let i = 0; i < names.length; i++) params[names[i]] = decodeURIComponent(match[i + 1]);
|
|
77
|
+
return { entry: r, params };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return void 0;
|
|
81
|
+
};
|
|
54
82
|
var wrapApi = (handler) => {
|
|
55
83
|
const rt = createHandlerRuntime(handler, "api", handler.__spec.lambda?.logLevel ?? "info", () => ({ ok, fail }));
|
|
56
84
|
const basePath = handler.__spec.basePath;
|
|
@@ -85,9 +113,21 @@ var wrapApi = (handler) => {
|
|
|
85
113
|
const method = event.requestContext?.http?.method ?? event.httpMethod ?? "GET";
|
|
86
114
|
const path = event.requestContext?.http?.path ?? event.path ?? "/";
|
|
87
115
|
const headers = event.headers ?? {};
|
|
88
|
-
const query = event.queryStringParameters ?? {};
|
|
89
|
-
const params = event.pathParameters ?? {};
|
|
90
116
|
const body = parseBody(event.body, event.isBase64Encoded ?? false);
|
|
117
|
+
const logInput = { method, path, query: event.queryStringParameters ?? {}, body };
|
|
118
|
+
const relativePath = extractRelativePath(path);
|
|
119
|
+
if (!relativePath) {
|
|
120
|
+
rt.logExecution(startTime, logInput, { status: 404 });
|
|
121
|
+
return notFound();
|
|
122
|
+
}
|
|
123
|
+
const routeMatch = findRoute(routes, method, relativePath);
|
|
124
|
+
if (!routeMatch) {
|
|
125
|
+
rt.logExecution(startTime, logInput, { status: 404 });
|
|
126
|
+
return notFound();
|
|
127
|
+
}
|
|
128
|
+
const { entry, params: routeParams } = routeMatch;
|
|
129
|
+
const query = event.queryStringParameters ?? {};
|
|
130
|
+
const params = { ...event.pathParameters ?? {}, ...routeParams };
|
|
91
131
|
const merged = {
|
|
92
132
|
...query,
|
|
93
133
|
...typeof body === "object" && body !== null ? body : {},
|
|
@@ -102,17 +142,6 @@ var wrapApi = (handler) => {
|
|
|
102
142
|
body,
|
|
103
143
|
rawBody: event.body
|
|
104
144
|
};
|
|
105
|
-
const logInput = { method, path, query, body };
|
|
106
|
-
const relativePath = extractRelativePath(req.path);
|
|
107
|
-
if (!relativePath) {
|
|
108
|
-
rt.logExecution(startTime, logInput, { status: 404 });
|
|
109
|
-
return notFound();
|
|
110
|
-
}
|
|
111
|
-
const entry = findRoute(routes, req.method, relativePath);
|
|
112
|
-
if (!entry) {
|
|
113
|
-
rt.logExecution(startTime, logInput, { status: 404 });
|
|
114
|
-
return notFound();
|
|
115
|
-
}
|
|
116
145
|
const cookieHeader = req.headers["cookie"] ?? req.headers["Cookie"] ?? "";
|
|
117
146
|
let authCookie;
|
|
118
147
|
if (cookieHeader) {
|