effortless-aws 0.37.0 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts
CHANGED
|
@@ -101,7 +101,7 @@ type LambdaConfig = {
|
|
|
101
101
|
};
|
|
102
102
|
/**
|
|
103
103
|
* Lambda configuration with additional IAM permissions.
|
|
104
|
-
* Used by handler types that support custom permissions (http, table,
|
|
104
|
+
* Used by handler types that support custom permissions (http, table, queue).
|
|
105
105
|
*/
|
|
106
106
|
type LambdaWithPermissions = LambdaConfig & {
|
|
107
107
|
/** Additional IAM permissions for the Lambda */
|
|
@@ -543,11 +543,11 @@ type MailerHandler = {
|
|
|
543
543
|
declare const defineMailer: () => (options: MailerConfig) => MailerHandler;
|
|
544
544
|
|
|
545
545
|
/**
|
|
546
|
-
* Parsed SQS
|
|
546
|
+
* Parsed SQS message passed to the handler callbacks.
|
|
547
547
|
*
|
|
548
548
|
* @typeParam T - Type of the decoded message body (from schema function)
|
|
549
549
|
*/
|
|
550
|
-
type
|
|
550
|
+
type QueueMessage<T = unknown> = {
|
|
551
551
|
/** Unique message identifier */
|
|
552
552
|
messageId: string;
|
|
553
553
|
/** Receipt handle for acknowledgement */
|
|
@@ -556,9 +556,9 @@ type FifoQueueMessage<T = unknown> = {
|
|
|
556
556
|
body: T;
|
|
557
557
|
/** Raw unparsed message body string */
|
|
558
558
|
rawBody: string;
|
|
559
|
-
/** Message group ID (FIFO ordering key) */
|
|
559
|
+
/** Message group ID (FIFO ordering key, empty string for standard queues) */
|
|
560
560
|
messageGroupId: string;
|
|
561
|
-
/** Message deduplication ID */
|
|
561
|
+
/** Message deduplication ID (FIFO only) */
|
|
562
562
|
messageDeduplicationId?: string;
|
|
563
563
|
/** SQS message attributes */
|
|
564
564
|
messageAttributes: Record<string, {
|
|
@@ -573,9 +573,18 @@ type FifoQueueMessage<T = unknown> = {
|
|
|
573
573
|
sentTimestamp?: string;
|
|
574
574
|
};
|
|
575
575
|
/**
|
|
576
|
-
*
|
|
576
|
+
* Event source mapping (poller) configuration — how Lambda consumes the queue.
|
|
577
|
+
*/
|
|
578
|
+
type QueuePollerConfig = {
|
|
579
|
+
/** Number of messages per Lambda invocation (1-10 for FIFO, default: 10) */
|
|
580
|
+
batchSize?: number;
|
|
581
|
+
/** Maximum time to gather messages before invoking (default: 0). Accepts `"5s"`, `"1m"`, etc. */
|
|
582
|
+
batchWindow?: Duration;
|
|
583
|
+
};
|
|
584
|
+
/**
|
|
585
|
+
* Configuration options for a queue handler.
|
|
577
586
|
*/
|
|
578
|
-
type
|
|
587
|
+
type QueueConfig = {
|
|
579
588
|
/** Lambda function settings (memory, timeout, permissions, etc.) */
|
|
580
589
|
lambda?: {
|
|
581
590
|
memory?: number;
|
|
@@ -583,20 +592,23 @@ type FifoQueueConfig = {
|
|
|
583
592
|
logLevel?: LogLevel;
|
|
584
593
|
permissions?: Permission[];
|
|
585
594
|
};
|
|
586
|
-
/**
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
595
|
+
/**
|
|
596
|
+
* Whether this is a FIFO queue (ordered, exactly-once).
|
|
597
|
+
* Currently only `true` is supported — standard queue support is planned.
|
|
598
|
+
*/
|
|
599
|
+
fifo?: boolean;
|
|
590
600
|
/** Visibility timeout (default: max of timeout or 30s). Accepts `"30s"`, `"5m"`, etc. */
|
|
591
601
|
visibilityTimeout?: Duration;
|
|
592
602
|
/** Message retention period (default: `"4d"`). Accepts `"1h"`, `"7d"`, etc. */
|
|
593
603
|
retentionPeriod?: Duration;
|
|
594
604
|
/** Delivery delay for all messages in the queue (default: 0). Accepts `"30s"`, `"5m"`, etc. */
|
|
595
605
|
delay?: Duration;
|
|
596
|
-
/** Enable content-based deduplication (default: true) */
|
|
606
|
+
/** Enable content-based deduplication for FIFO queues (default: true) */
|
|
597
607
|
contentBasedDeduplication?: boolean;
|
|
598
608
|
/** Max number of receives before a message is sent to the dead-letter queue (default: 3) */
|
|
599
609
|
maxReceiveCount?: number;
|
|
610
|
+
/** Event source mapping config — set via `.poller({...})` builder method. */
|
|
611
|
+
poller?: QueuePollerConfig;
|
|
600
612
|
};
|
|
601
613
|
/** Spread ctx into callback args (empty when no setup) */
|
|
602
614
|
type SpreadCtx$5<C> = [C] extends [undefined] ? {} : C & {};
|
|
@@ -612,26 +624,26 @@ type SetupArgs$5<D, P, HasFiles extends boolean> = ([D] extends [undefined] ? {}
|
|
|
612
624
|
* Per-message handler function.
|
|
613
625
|
* Called once per message in the batch. Failures are reported individually.
|
|
614
626
|
*/
|
|
615
|
-
type
|
|
616
|
-
message:
|
|
627
|
+
type QueueMessageFn<T = unknown, C = undefined> = (args: {
|
|
628
|
+
message: QueueMessage<T>;
|
|
617
629
|
} & SpreadCtx$5<C>) => Promise<void>;
|
|
618
630
|
/**
|
|
619
631
|
* Batch handler function.
|
|
620
632
|
* Called once with all messages in the batch.
|
|
621
633
|
* Return `{ failures: string[] }` (messageIds) for partial batch failure reporting.
|
|
622
634
|
*/
|
|
623
|
-
type
|
|
624
|
-
messages:
|
|
635
|
+
type QueueBatchFn<T = unknown, C = undefined> = (args: {
|
|
636
|
+
messages: QueueMessage<T>[];
|
|
625
637
|
} & SpreadCtx$5<C>) => Promise<void | {
|
|
626
638
|
failures: string[];
|
|
627
639
|
}>;
|
|
628
640
|
/**
|
|
629
|
-
* Internal handler object created by
|
|
641
|
+
* Internal handler object created by defineQueue.
|
|
630
642
|
* @internal
|
|
631
643
|
*/
|
|
632
|
-
type
|
|
633
|
-
readonly __brand: "effortless-
|
|
634
|
-
readonly __spec:
|
|
644
|
+
type QueueHandler$1<T = unknown, C = any> = {
|
|
645
|
+
readonly __brand: "effortless-queue";
|
|
646
|
+
readonly __spec: QueueConfig;
|
|
635
647
|
readonly schema?: (input: unknown) => T;
|
|
636
648
|
readonly onError?: (...args: any[]) => any;
|
|
637
649
|
readonly onCleanup?: (...args: any[]) => any;
|
|
@@ -642,64 +654,78 @@ type FifoQueueHandler$1<T = unknown, C = any> = {
|
|
|
642
654
|
readonly onMessage?: (...args: any[]) => any;
|
|
643
655
|
readonly onMessageBatch?: (...args: any[]) => any;
|
|
644
656
|
};
|
|
645
|
-
/** Options passed to `
|
|
646
|
-
type
|
|
647
|
-
/**
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
657
|
+
/** Options passed to `defineQueue()` — queue resource config */
|
|
658
|
+
type QueueOptions<T> = {
|
|
659
|
+
/**
|
|
660
|
+
* Whether this is a FIFO queue (ordered, exactly-once).
|
|
661
|
+
* Currently only `true` is supported — standard queue support is planned.
|
|
662
|
+
*/
|
|
663
|
+
fifo?: boolean;
|
|
651
664
|
/** Visibility timeout (default: max of timeout or 30s) */
|
|
652
665
|
visibilityTimeout?: Duration;
|
|
653
666
|
/** Message retention period (default: "4d") */
|
|
654
667
|
retentionPeriod?: Duration;
|
|
655
668
|
/** Delivery delay for all messages in the queue (default: 0) */
|
|
656
669
|
delay?: Duration;
|
|
657
|
-
/** Enable content-based deduplication (default: true) */
|
|
670
|
+
/** Enable content-based deduplication for FIFO queues (default: true) */
|
|
658
671
|
contentBasedDeduplication?: boolean;
|
|
659
672
|
/** Max number of receives before DLQ (default: 3) */
|
|
660
673
|
maxReceiveCount?: number;
|
|
661
674
|
/** Decode/validate function for the message body */
|
|
662
675
|
schema?: (input: unknown) => T;
|
|
663
676
|
};
|
|
664
|
-
interface
|
|
677
|
+
interface QueueBuilder<T = unknown, D = undefined, P = undefined, C = undefined, HasFiles extends boolean = false> {
|
|
665
678
|
/** Declare handler dependencies */
|
|
666
|
-
deps<D2 extends Record<string, AnyDepHandler>>(fn: () => D2):
|
|
679
|
+
deps<D2 extends Record<string, AnyDepHandler>>(fn: () => D2): QueueBuilder<T, D2, P, C, HasFiles>;
|
|
667
680
|
/** Declare SSM secrets */
|
|
668
|
-
config<P2 extends Record<string, AnySecretRef>>(fn: ConfigFactory<P2>):
|
|
681
|
+
config<P2 extends Record<string, AnySecretRef>>(fn: ConfigFactory<P2>): QueueBuilder<T, D, P2, C, HasFiles>;
|
|
669
682
|
/** Include static files in the Lambda bundle. Chainable — call multiple times. */
|
|
670
|
-
include(glob: string):
|
|
683
|
+
include(glob: string): QueueBuilder<T, D, P, C, true>;
|
|
671
684
|
/** Configure Lambda settings only (memory, timeout, permissions, etc.) */
|
|
672
|
-
setup(lambda: LambdaOptions):
|
|
685
|
+
setup(lambda: LambdaOptions): QueueBuilder<T, D, P, C, HasFiles>;
|
|
673
686
|
/** Initialize shared state on cold start. Receives deps, config, files. */
|
|
674
|
-
setup<C2>(fn: (args: SetupArgs$5<D, P, HasFiles>) => C2 | Promise<C2>):
|
|
687
|
+
setup<C2>(fn: (args: SetupArgs$5<D, P, HasFiles>) => C2 | Promise<C2>): QueueBuilder<T, D, P, C2, HasFiles>;
|
|
675
688
|
/** Initialize shared state on cold start + configure Lambda settings. */
|
|
676
|
-
setup<C2>(fn: (args: SetupArgs$5<D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions):
|
|
689
|
+
setup<C2>(fn: (args: SetupArgs$5<D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions): QueueBuilder<T, D, P, C2, HasFiles>;
|
|
690
|
+
/**
|
|
691
|
+
* Configure the event source mapping (poller) that delivers messages to the Lambda.
|
|
692
|
+
* Call before the terminal `.onMessage` / `.onMessageBatch`.
|
|
693
|
+
*/
|
|
694
|
+
poller(options: QueuePollerConfig): QueueBuilder<T, D, P, C, HasFiles>;
|
|
677
695
|
/** Handle errors thrown by message handlers */
|
|
678
696
|
onError(fn: (args: {
|
|
679
697
|
error: unknown;
|
|
680
|
-
} & SpreadCtx$5<C>) => void | Promise<void>):
|
|
698
|
+
} & SpreadCtx$5<C>) => void | Promise<void>): QueueBuilder<T, D, P, C, HasFiles>;
|
|
681
699
|
/** Cleanup callback — runs after each invocation, before Lambda freezes */
|
|
682
|
-
onCleanup(fn: (args: SpreadCtx$5<C>) => void | Promise<void>):
|
|
700
|
+
onCleanup(fn: (args: SpreadCtx$5<C>) => void | Promise<void>): QueueBuilder<T, D, P, C, HasFiles>;
|
|
683
701
|
/** Per-message handler (terminal — returns finalized handler) */
|
|
684
|
-
onMessage(fn:
|
|
702
|
+
onMessage(fn: QueueMessageFn<T, C>): QueueHandler$1<T, C>;
|
|
685
703
|
/** Batch handler (terminal — returns finalized handler) */
|
|
686
|
-
onMessageBatch(fn:
|
|
704
|
+
onMessageBatch(fn: QueueBatchFn<T, C>): QueueHandler$1<T, C>;
|
|
705
|
+
/** Finalize as a resource-only queue (no Lambda). Use when the SQS queue is consumed by an external system. */
|
|
706
|
+
build(): QueueHandler$1<T, C>;
|
|
687
707
|
}
|
|
688
708
|
/**
|
|
689
|
-
* Define
|
|
709
|
+
* Define an SQS queue with a Lambda message handler.
|
|
710
|
+
*
|
|
711
|
+
* Currently only FIFO queues are supported — pass `{ fifo: true }` explicitly
|
|
712
|
+
* (standard queue support is planned). Poller/batch settings live on the
|
|
713
|
+
* `.poller({...})` builder method; queue-level properties stay in the options
|
|
714
|
+
* object.
|
|
690
715
|
*
|
|
691
716
|
* @see {@link https://effortless-aws.website/use-cases/queue | Queue guide}
|
|
692
717
|
*
|
|
693
718
|
* @example
|
|
694
719
|
* ```typescript
|
|
695
|
-
* export const notifications =
|
|
720
|
+
* export const notifications = defineQueue<Notif>({ fifo: true })
|
|
721
|
+
* .poller({ batchSize: 5, batchWindow: "2s" })
|
|
696
722
|
* .onMessageBatch(async ({ messages }) => {
|
|
697
723
|
* await sendAll(messages.map(m => m.body));
|
|
698
724
|
* })
|
|
699
725
|
* ```
|
|
700
726
|
*/
|
|
701
|
-
declare function
|
|
702
|
-
declare function
|
|
727
|
+
declare function defineQueue<T = unknown>(): QueueBuilder<T>;
|
|
728
|
+
declare function defineQueue<T = unknown>(options: QueueOptions<T>): QueueBuilder<T>;
|
|
703
729
|
|
|
704
730
|
/** Fargate container size presets */
|
|
705
731
|
type FargateSize = "0.25vCPU-512mb" | "0.5vCPU-1gb" | "1vCPU-2gb" | "2vCPU-4gb" | "4vCPU-8gb";
|
|
@@ -978,14 +1004,32 @@ type WorkerClient<T = unknown> = {
|
|
|
978
1004
|
};
|
|
979
1005
|
|
|
980
1006
|
/** Dep value types supported by the deps declaration */
|
|
981
|
-
type AnyDepHandler = TableHandler$1<any, any> | BucketHandler$1<any, any> | MailerHandler |
|
|
1007
|
+
type AnyDepHandler = TableHandler$1<any, any> | BucketHandler$1<any, any> | MailerHandler | QueueHandler$1<any, any> | WorkerHandler$1<any, any>;
|
|
982
1008
|
/** Maps a deps declaration to resolved runtime client types */
|
|
983
1009
|
type ResolveDeps<D> = {
|
|
984
|
-
[K in keyof D]: D[K] extends TableHandler$1<infer T> ? TableClient<T> : D[K] extends BucketHandler$1<any, infer E> ? ({} extends E ? BucketClient : BucketClientWithEntities<E>) : D[K] extends MailerHandler ? EmailClient : D[K] extends
|
|
1010
|
+
[K in keyof D]: D[K] extends TableHandler$1<infer T> ? TableClient<T> : D[K] extends BucketHandler$1<any, infer E> ? ({} extends E ? BucketClient : BucketClientWithEntities<E>) : D[K] extends MailerHandler ? EmailClient : D[K] extends QueueHandler$1<infer T> ? QueueClient<T> : D[K] extends WorkerHandler$1<infer T> ? WorkerClient<T> : never;
|
|
985
1011
|
};
|
|
986
1012
|
|
|
987
1013
|
/** DynamoDB Streams view type - determines what data is captured in stream records */
|
|
988
1014
|
type StreamView = "NEW_AND_OLD_IMAGES" | "NEW_IMAGE" | "OLD_IMAGE" | "KEYS_ONLY";
|
|
1015
|
+
/**
|
|
1016
|
+
* Stream event source mapping configuration.
|
|
1017
|
+
* @internal
|
|
1018
|
+
*/
|
|
1019
|
+
type TableStreamConfig = {
|
|
1020
|
+
/** Stream view type - what data to include in stream records (default: "NEW_AND_OLD_IMAGES") */
|
|
1021
|
+
streamView?: StreamView;
|
|
1022
|
+
/** Number of records to process in each Lambda invocation (1-10000, default: 100) */
|
|
1023
|
+
batchSize?: number;
|
|
1024
|
+
/** Maximum time to gather records before invoking (default: `"2s"`). Accepts `"5s"`, `"1m"`, etc. */
|
|
1025
|
+
batchWindow?: Duration;
|
|
1026
|
+
/** Where to start reading the stream (default: "LATEST") */
|
|
1027
|
+
startingPosition?: "LATEST" | "TRIM_HORIZON";
|
|
1028
|
+
/** Number of records to process concurrently within a batch (default: 1 — sequential) */
|
|
1029
|
+
concurrency?: number;
|
|
1030
|
+
/** Max retry attempts for failed records before sending to DLQ (default: 1) */
|
|
1031
|
+
maxRetries?: number;
|
|
1032
|
+
};
|
|
989
1033
|
/**
|
|
990
1034
|
* Configuration options for defineTable (single-table design).
|
|
991
1035
|
*
|
|
@@ -1002,22 +1046,14 @@ type TableConfig = {
|
|
|
1002
1046
|
};
|
|
1003
1047
|
/** DynamoDB billing mode (default: "PAY_PER_REQUEST") */
|
|
1004
1048
|
billingMode?: "PAY_PER_REQUEST" | "PROVISIONED";
|
|
1005
|
-
/** Stream view type - what data to include in stream records (default: "NEW_AND_OLD_IMAGES") */
|
|
1006
|
-
streamView?: StreamView;
|
|
1007
|
-
/** Number of records to process in each Lambda invocation (1-10000, default: 100) */
|
|
1008
|
-
batchSize?: number;
|
|
1009
|
-
/** Maximum time to gather records before invoking (default: `"2s"`). Accepts `"5s"`, `"1m"`, etc. */
|
|
1010
|
-
batchWindow?: Duration;
|
|
1011
|
-
/** Where to start reading the stream (default: "LATEST") */
|
|
1012
|
-
startingPosition?: "LATEST" | "TRIM_HORIZON";
|
|
1013
|
-
/** Number of records to process concurrently within a batch (default: 1 — sequential) */
|
|
1014
|
-
concurrency?: number;
|
|
1015
1049
|
/**
|
|
1016
1050
|
* Name of the field in `data` that serves as the entity type discriminant.
|
|
1017
1051
|
* Effortless auto-copies `data[tagField]` to the top-level DynamoDB `tag` attribute on `put()`.
|
|
1018
1052
|
* Defaults to `"tag"`.
|
|
1019
1053
|
*/
|
|
1020
1054
|
tagField?: string;
|
|
1055
|
+
/** Stream event source mapping config — set via `.stream({...})` builder method. */
|
|
1056
|
+
stream?: TableStreamConfig;
|
|
1021
1057
|
};
|
|
1022
1058
|
/**
|
|
1023
1059
|
* DynamoDB stream record passed to onRecord callback.
|
|
@@ -1092,44 +1128,36 @@ type TableHandler$1<T = Record<string, unknown>, C = any> = {
|
|
|
1092
1128
|
readonly onRecord?: (...args: any[]) => any;
|
|
1093
1129
|
readonly onRecordBatch?: (...args: any[]) => any;
|
|
1094
1130
|
};
|
|
1095
|
-
/** Options passed to `defineTable()` — resource config only
|
|
1131
|
+
/** Options passed to `defineTable()` — resource config only. Stream options go in `.stream({...})`. */
|
|
1096
1132
|
type TableOptions<T> = {
|
|
1097
1133
|
/** DynamoDB billing mode (default: "PAY_PER_REQUEST") */
|
|
1098
1134
|
billingMode?: "PAY_PER_REQUEST" | "PROVISIONED";
|
|
1099
|
-
/** Stream view type (default: "NEW_AND_OLD_IMAGES") */
|
|
1100
|
-
streamView?: StreamView;
|
|
1101
|
-
/** Number of records to process in each Lambda invocation (1-10000, default: 100) */
|
|
1102
|
-
batchSize?: number;
|
|
1103
|
-
/** Maximum time to gather records before invoking (default: "2s") */
|
|
1104
|
-
batchWindow?: Duration;
|
|
1105
|
-
/** Where to start reading the stream (default: "LATEST") */
|
|
1106
|
-
startingPosition?: "LATEST" | "TRIM_HORIZON";
|
|
1107
|
-
/** Number of records to process concurrently within a batch (default: 1) */
|
|
1108
|
-
concurrency?: number;
|
|
1109
1135
|
/** Name of the field in `data` that serves as the entity type discriminant (default: "tag") */
|
|
1110
1136
|
tagField?: Extract<keyof T, string>;
|
|
1111
1137
|
/** Decode/validate function for the `data` portion of stream records */
|
|
1112
1138
|
schema?: (input: unknown) => T;
|
|
1113
1139
|
};
|
|
1114
|
-
interface TableBuilder<T = Record<string, unknown>, D = undefined, P = undefined, C = undefined, HasFiles extends boolean = false> {
|
|
1140
|
+
interface TableBuilder<T = Record<string, unknown>, D = undefined, P = undefined, C = undefined, HasFiles extends boolean = false, HasStream extends boolean = false> {
|
|
1115
1141
|
/** Declare handler dependencies (tables, queues, buckets, mailers) */
|
|
1116
|
-
deps<D2 extends Record<string, AnyDepHandler>>(fn: () => D2): TableBuilder<T, D2, P, C, HasFiles>;
|
|
1142
|
+
deps<D2 extends Record<string, AnyDepHandler>>(fn: () => D2): TableBuilder<T, D2, P, C, HasFiles, HasStream>;
|
|
1117
1143
|
/** Declare SSM secrets */
|
|
1118
|
-
config<P2 extends Record<string, AnySecretRef>>(fn: ConfigFactory<P2>): TableBuilder<T, D, P2, C, HasFiles>;
|
|
1144
|
+
config<P2 extends Record<string, AnySecretRef>>(fn: ConfigFactory<P2>): TableBuilder<T, D, P2, C, HasFiles, HasStream>;
|
|
1119
1145
|
/** Include static files in the Lambda bundle. Chainable — call multiple times. */
|
|
1120
|
-
include(glob: string): TableBuilder<T, D, P, C, true>;
|
|
1146
|
+
include(glob: string): TableBuilder<T, D, P, C, true, HasStream>;
|
|
1147
|
+
/** Configure the DynamoDB stream event source mapping (batch size, retries, concurrency, etc.). */
|
|
1148
|
+
stream: HasStream extends true ? never : (opts: TableStreamConfig) => TableBuilder<T, D, P, C, HasFiles, true>;
|
|
1121
1149
|
/** Configure Lambda settings only (memory, timeout, permissions, etc.) */
|
|
1122
|
-
setup(lambda: LambdaOptions): TableBuilder<T, D, P, C, HasFiles>;
|
|
1150
|
+
setup(lambda: LambdaOptions): TableBuilder<T, D, P, C, HasFiles, HasStream>;
|
|
1123
1151
|
/** Initialize shared state on cold start. Receives table (self-client), deps, config, files. */
|
|
1124
|
-
setup<C2>(fn: (args: SetupArgs$3<T, D, P, HasFiles>) => C2 | Promise<C2>): TableBuilder<T, D, P, C2, HasFiles>;
|
|
1152
|
+
setup<C2>(fn: (args: SetupArgs$3<T, D, P, HasFiles>) => C2 | Promise<C2>): TableBuilder<T, D, P, C2, HasFiles, HasStream>;
|
|
1125
1153
|
/** Initialize shared state on cold start + configure Lambda settings. */
|
|
1126
|
-
setup<C2>(fn: (args: SetupArgs$3<T, D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions): TableBuilder<T, D, P, C2, HasFiles>;
|
|
1154
|
+
setup<C2>(fn: (args: SetupArgs$3<T, D, P, HasFiles>) => C2 | Promise<C2>, lambda: LambdaOptions): TableBuilder<T, D, P, C2, HasFiles, HasStream>;
|
|
1127
1155
|
/** Handle errors thrown by onRecord/onRecordBatch */
|
|
1128
1156
|
onError(fn: (args: {
|
|
1129
1157
|
error: unknown;
|
|
1130
|
-
} & SpreadCtx$3<C>) => void | Promise<void>): TableBuilder<T, D, P, C, HasFiles>;
|
|
1158
|
+
} & SpreadCtx$3<C>) => void | Promise<void>): TableBuilder<T, D, P, C, HasFiles, HasStream>;
|
|
1131
1159
|
/** Cleanup callback — runs after each invocation, before Lambda freezes */
|
|
1132
|
-
onCleanup(fn: (args: SpreadCtx$3<C>) => void | Promise<void>): TableBuilder<T, D, P, C, HasFiles>;
|
|
1160
|
+
onCleanup(fn: (args: SpreadCtx$3<C>) => void | Promise<void>): TableBuilder<T, D, P, C, HasFiles, HasStream>;
|
|
1133
1161
|
/** Per-record stream handler (terminal — returns finalized handler) */
|
|
1134
1162
|
onRecord(fn: TableRecordFn<T, C>): TableHandler$1<T, C>;
|
|
1135
1163
|
/** Batch stream handler (terminal — returns finalized handler) */
|
|
@@ -1147,7 +1175,8 @@ interface TableBuilder<T = Record<string, unknown>, D = undefined, P = undefined
|
|
|
1147
1175
|
*
|
|
1148
1176
|
* @example
|
|
1149
1177
|
* ```typescript
|
|
1150
|
-
* export const orders = defineTable<OrderData>(
|
|
1178
|
+
* export const orders = defineTable<OrderData>()
|
|
1179
|
+
* .stream({ batchSize: 10, concurrency: 5, maxRetries: 3 })
|
|
1151
1180
|
* .setup(({ table }) => ({ table }))
|
|
1152
1181
|
* .onRecord(async ({ record, table }) => {
|
|
1153
1182
|
* if (record.eventName === "INSERT") {
|
|
@@ -1904,10 +1933,10 @@ type McpResourceMap = McpResourceMap$1;
|
|
|
1904
1933
|
type McpPromptDef = McpPromptDef$1;
|
|
1905
1934
|
|
|
1906
1935
|
type TableHandler<T = Record<string, unknown>> = TableHandler$1<T, any>;
|
|
1907
|
-
type
|
|
1936
|
+
type QueueHandler<T = unknown> = QueueHandler$1<T, any>;
|
|
1908
1937
|
type BucketHandler<Entities extends Record<string, any> = {}> = BucketHandler$1<any, Entities>;
|
|
1909
1938
|
type CronHandler = CronHandler$1<any>;
|
|
1910
1939
|
type WorkerHandler<T = any> = WorkerHandler$1<T, any>;
|
|
1911
1940
|
type McpHandler = McpHandler$1<any>;
|
|
1912
1941
|
|
|
1913
|
-
export { type ApiConfig, type ApiHandler, type ApiRoutes, type AppConfig, type AppHandler, type AuthHelpers, type AuthOptions, type BucketClient, type BucketClientWithEntities, type BucketConfig, type BucketEntityConfig, type BucketEvent, type BucketHandler, type CacheOptions, type CdnPolicyOptions, type ConfigHelpers, type ContentType, type CronConfig, type CronHandler, type DefineSecretFn, type Duration, type EffortlessConfig, type EmailClient, type
|
|
1942
|
+
export { type ApiConfig, type ApiHandler, type ApiRoutes, type AppConfig, type AppHandler, type AuthHelpers, type AuthOptions, type BucketClient, type BucketClientWithEntities, type BucketConfig, type BucketEntityConfig, type BucketEvent, type BucketHandler, type CacheOptions, type CdnPolicyOptions, type ConfigHelpers, type ContentType, type CronConfig, type CronHandler, type DefineSecretFn, type Duration, type EffortlessConfig, type EmailClient, type HttpMethod$1 as HttpMethod, type HttpRequest, type HttpResponse, type LogLevel, type MailerConfig, type MailerHandler, type McpConfig, type McpEntries, type McpHandler, type McpInputSchema, type McpPromptArgument, type McpPromptContent, type McpPromptDef, type McpPromptMessage, type McpPromptResult, type McpResourceContent, type McpResourceDef, type McpResourceMap, type McpResourceTemplateDef, type McpToolContent, type McpToolDef, type McpToolResult, type MiddlewareDeny, type MiddlewareHandler, type MiddlewareRedirect, type MiddlewareRequest, type MiddlewareResult, type ParamRef, type Permission, type PutInput, type PutOptions, type QueryByTagParams, type QueryParams, type QueueClient, type QueueConfig, type QueueHandler, type QueueMessage, type QueuePollerConfig, type ResponseStream, type SecretRef, type SendEmailOptions, type SendMessageInput, type SkCondition, type StaticFiles, type StaticSiteConfig, type StaticSiteHandler, type StaticSiteSeo, type StoreEntityClient, type StreamView, type TableClient, type TableConfig, type TableHandler, type TableItem, type TableKey, type TableRecord, type Timezone, type UpdateActions, type WorkerClient, type WorkerConfig, type WorkerHandler, type WorkerSendOptions, defineApi, defineApp, defineBucket, defineConfig, defineCron, defineMailer, defineMcp, defineQueue, defineSecret, defineStaticSite, defineTable, defineWorker, generateBase64, generateHex, generateUuid, param, secret, toSeconds };
|
package/dist/index.js
CHANGED
|
@@ -77,6 +77,10 @@ function defineTable(options) {
|
|
|
77
77
|
state.static = [...state.static ?? [], glob];
|
|
78
78
|
return builder;
|
|
79
79
|
},
|
|
80
|
+
stream(opts) {
|
|
81
|
+
state.spec = { ...state.spec, stream: { ...state.spec.stream, ...opts } };
|
|
82
|
+
return builder;
|
|
83
|
+
},
|
|
80
84
|
setup(fnOrLambda, maybeLambda) {
|
|
81
85
|
if (typeof fnOrLambda === "function") {
|
|
82
86
|
state.setup = fnOrLambda;
|
|
@@ -143,8 +147,8 @@ function defineStaticSite(options) {
|
|
|
143
147
|
return builder;
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
// src/handlers/define-
|
|
147
|
-
function
|
|
150
|
+
// src/handlers/define-queue.ts
|
|
151
|
+
function defineQueue(options) {
|
|
148
152
|
const {
|
|
149
153
|
schema,
|
|
150
154
|
...queueConfig
|
|
@@ -160,7 +164,7 @@ function defineFifoQueue(options) {
|
|
|
160
164
|
}
|
|
161
165
|
};
|
|
162
166
|
const finalize = () => ({
|
|
163
|
-
__brand: "effortless-
|
|
167
|
+
__brand: "effortless-queue",
|
|
164
168
|
__spec: state.spec,
|
|
165
169
|
...state.schema ? { schema: state.schema } : {},
|
|
166
170
|
...state.onError ? { onError: state.onError } : {},
|
|
@@ -194,6 +198,12 @@ function defineFifoQueue(options) {
|
|
|
194
198
|
}
|
|
195
199
|
return builder;
|
|
196
200
|
},
|
|
201
|
+
poller(options2) {
|
|
202
|
+
if (Object.keys(options2).length > 0) {
|
|
203
|
+
state.spec = { ...state.spec, poller: { ...state.spec.poller, ...options2 } };
|
|
204
|
+
}
|
|
205
|
+
return builder;
|
|
206
|
+
},
|
|
197
207
|
onMessage(fn) {
|
|
198
208
|
state.onMessage = fn;
|
|
199
209
|
return finalize();
|
|
@@ -209,6 +219,9 @@ function defineFifoQueue(options) {
|
|
|
209
219
|
onCleanup(fn) {
|
|
210
220
|
state.onCleanup = fn;
|
|
211
221
|
return builder;
|
|
222
|
+
},
|
|
223
|
+
build() {
|
|
224
|
+
return finalize();
|
|
212
225
|
}
|
|
213
226
|
};
|
|
214
227
|
return builder;
|
|
@@ -658,9 +671,9 @@ export {
|
|
|
658
671
|
defineBucket,
|
|
659
672
|
defineConfig,
|
|
660
673
|
defineCron,
|
|
661
|
-
defineFifoQueue,
|
|
662
674
|
defineMailer,
|
|
663
675
|
defineMcp,
|
|
676
|
+
defineQueue,
|
|
664
677
|
defineSecret,
|
|
665
678
|
defineStaticSite,
|
|
666
679
|
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","../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 or S3 */\ntype AnyRoutableHandler = { readonly __brand: string };\n\n/** Simplified request object passed to middleware */\nexport type MiddlewareRequest = {\n uri: string;\n method: string;\n querystring: string;\n headers: Record<string, string>;\n cookies: Record<string, string>;\n};\n\n/** Redirect the user to another URL */\nexport type MiddlewareRedirect = {\n redirect: string;\n status?: 301 | 302 | 307 | 308;\n};\n\n/** Deny access with a 403 status */\nexport type MiddlewareDeny = {\n status: 403;\n body?: string;\n};\n\n/** Middleware return type: redirect, deny, or void (continue serving) */\nexport type MiddlewareResult = MiddlewareRedirect | MiddlewareDeny | void;\n\n/** Function that runs before serving static files via Lambda@Edge */\nexport type MiddlewareHandler = (\n request: MiddlewareRequest\n) => Promise<MiddlewareResult> | MiddlewareResult;\n\n/** SEO options for auto-generating sitemap.xml, robots.txt, and submitting to Google Indexing API */\nexport type StaticSiteSeo = {\n /** Sitemap filename (e.g. \"sitemap.xml\", \"sitemap-v2.xml\") */\n sitemap: string;\n /** Path to Google service account JSON key file for Indexing API batch submission.\n * Requires adding the service account email as an owner in Google Search Console. */\n googleIndexing?: string;\n};\n\n/**\n * Configuration for a static site (S3 + CloudFront)\n */\nexport type StaticSiteConfig = {\n /** Directory containing the static site files, relative to project root */\n dir: string;\n /** Default file for directory requests (default: \"index.html\") */\n index?: string;\n /** Shell command to run before deploy to generate site content (e.g., \"npx astro build\") */\n build?: string;\n /** Path to a custom error page relative to `dir`.\n * - If set to the same value as `index` (e.g. \"index.html\"), enables SPA mode:\n * all paths that don't match a file are served with `index.html` (HTTP 200), letting the client-side router handle them.\n * - If set to a different file (e.g. \"404.html\"), that file is served with HTTP 404 for missing paths.\n * - If omitted, a default 404 page is auto-generated. */\n errorPage?: string;\n /** Custom domain name. Accepts a string (same domain for all stages) or a Record mapping stage names to domains (e.g., `{ prod: \"example.com\", dev: \"dev.example.com\" }`). Requires an ACM certificate in us-east-1. If the cert also covers www, a 301 redirect from www to non-www is set up automatically. */\n domain?: string | Record<string, string>;\n /** SEO: auto-generate sitemap.xml and robots.txt at deploy time, optionally submit URLs to Google Indexing API */\n seo?: StaticSiteSeo;\n};\n\n/** Route entry stored on the static site handler */\ntype StaticSiteRouteEntry = {\n pattern: string;\n origin: AnyRoutableHandler;\n access?: \"private\" | \"public\";\n};\n\n/**\n * Internal handler object created by defineStaticSite\n * @internal\n */\nexport type StaticSiteHandler = {\n readonly __brand: \"effortless-static-site\";\n readonly __spec: StaticSiteConfig;\n readonly routes: StaticSiteRouteEntry[];\n readonly middleware?: MiddlewareHandler;\n};\n\n/** Builder for configuring a static site before calling `.build()` */\nexport type StaticSiteBuilder = {\n route(pattern: string, origin: AnyRoutableHandler, opts?: { access?: \"private\" | \"public\" }): StaticSiteBuilder;\n middleware(fn: MiddlewareHandler): StaticSiteBuilder;\n build(): StaticSiteHandler;\n};\n\n/**\n * Deploy a static site via S3 + CloudFront CDN, with optional API and bucket route overrides.\n *\n * @see {@link https://effortless-aws.website/use-cases/web-app | Web app guide}\n *\n * @param options - Static site configuration: directory, optional SPA mode, build command, domain\n * @returns Builder with `.route()`, `.middleware()`, and `.build()` methods\n */\nexport function defineStaticSite(options: StaticSiteConfig): StaticSiteBuilder {\n const state = {\n spec: { ...options },\n routes: [] as StaticSiteRouteEntry[],\n middleware: undefined as MiddlewareHandler | undefined,\n };\n\n const builder = {\n route(pattern: string, origin: AnyRoutableHandler, opts?: { access?: \"private\" | \"public\" }) {\n state.routes.push({ pattern, origin, ...(opts?.access ? { access: opts.access } : {}) });\n return builder;\n },\n middleware(fn: MiddlewareHandler) {\n state.middleware = fn;\n return builder;\n },\n build(): StaticSiteHandler {\n return {\n __brand: \"effortless-static-site\",\n __spec: state.spec,\n routes: state.routes,\n ...(state.middleware ? { middleware: state.middleware } : {}),\n };\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LogLevel, Permission, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n/**\n * Parsed SQS FIFO message passed to the handler callbacks.\n *\n * @typeParam T - Type of the decoded message body (from schema function)\n */\nexport type FifoQueueMessage<T = unknown> = {\n /** Unique message identifier */\n messageId: string;\n /** Receipt handle for acknowledgement */\n receiptHandle: string;\n /** Parsed message body (JSON-decoded, then optionally schema-validated) */\n body: T;\n /** Raw unparsed message body string */\n rawBody: string;\n /** Message group ID (FIFO ordering key) */\n messageGroupId: string;\n /** Message deduplication ID */\n messageDeduplicationId?: string;\n /** SQS message attributes */\n messageAttributes: Record<string, { dataType?: string; stringValue?: string }>;\n /** Approximate first receive timestamp */\n approximateFirstReceiveTimestamp?: string;\n /** Approximate receive count */\n approximateReceiveCount?: string;\n /** Sent timestamp */\n sentTimestamp?: string;\n};\n\n/**\n * Configuration options for a FIFO queue handler\n */\nexport type FifoQueueConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: Duration; logLevel?: LogLevel; permissions?: Permission[] };\n /** Number of messages per Lambda invocation (1-10 for FIFO, default: 10) */\n batchSize?: number;\n /** Maximum time to gather messages before invoking (default: 0). Accepts `\"5s\"`, `\"1m\"`, etc. */\n batchWindow?: Duration;\n /** Visibility timeout (default: max of timeout or 30s). Accepts `\"30s\"`, `\"5m\"`, etc. */\n visibilityTimeout?: Duration;\n /** Message retention period (default: `\"4d\"`). Accepts `\"1h\"`, `\"7d\"`, etc. */\n retentionPeriod?: Duration;\n /** Delivery delay for all messages in the queue (default: 0). Accepts `\"30s\"`, `\"5m\"`, etc. */\n delay?: Duration;\n /** Enable content-based deduplication (default: true) */\n contentBasedDeduplication?: boolean;\n /** Max number of receives before a message is sent to the dead-letter queue (default: 3) */\n maxReceiveCount?: number;\n};\n\n// ============ Setup args ============\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/**\n * Per-message handler function.\n * Called once per message in the batch. Failures are reported individually.\n */\nexport type FifoQueueMessageFn<T = unknown, C = undefined> =\n (args: { message: FifoQueueMessage<T> }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Batch handler function.\n * Called once with all messages in the batch.\n * Return `{ failures: string[] }` (messageIds) for partial batch failure reporting.\n */\nexport type FifoQueueBatchFn<T = unknown, C = undefined> =\n (args: { messages: FifoQueueMessage<T>[] }\n & SpreadCtx<C>\n ) => Promise<void | { failures: string[] }>;\n\n// ============ Internal handler object ============\n\n/**\n * Internal handler object created by defineFifoQueue\n * @internal\n */\nexport type FifoQueueHandler<T = unknown, C = any> = {\n readonly __brand: \"effortless-fifo-queue\";\n readonly __spec: FifoQueueConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onMessage?: (...args: any[]) => any;\n readonly onMessageBatch?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineFifoQueue()` — static config */\ntype FifoQueueOptions<T> = {\n /** Number of messages per Lambda invocation (1-10 for FIFO, default: 10) */\n batchSize?: number;\n /** Maximum time to gather messages before invoking (default: 0) */\n batchWindow?: Duration;\n /** Visibility timeout (default: max of timeout or 30s) */\n visibilityTimeout?: Duration;\n /** Message retention period (default: \"4d\") */\n retentionPeriod?: Duration;\n /** Delivery delay for all messages in the queue (default: 0) */\n delay?: Duration;\n /** Enable content-based deduplication (default: true) */\n contentBasedDeduplication?: boolean;\n /** Max number of receives before DLQ (default: 3) */\n maxReceiveCount?: number;\n /** Decode/validate function for the message body */\n schema?: (input: unknown) => T;\n};\n\n// ============ Builder ============\n\ninterface FifoQueueBuilder<\n T = unknown,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): FifoQueueBuilder<T, D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): FifoQueueBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): FifoQueueBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): FifoQueueBuilder<T, D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): FifoQueueBuilder<T, D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): FifoQueueBuilder<T, D, P, C2, HasFiles>;\n\n /** Handle errors thrown by message handlers */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): FifoQueueBuilder<T, D, P, C, HasFiles>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): FifoQueueBuilder<T, D, P, C, HasFiles>;\n\n /** Per-message handler (terminal — returns finalized handler) */\n onMessage(\n fn: FifoQueueMessageFn<T, C>\n ): FifoQueueHandler<T, C>;\n\n /** Batch handler (terminal — returns finalized handler) */\n onMessageBatch(\n fn: FifoQueueBatchFn<T, C>\n ): FifoQueueHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a FIFO SQS queue with a Lambda message handler.\n *\n * @see {@link https://effortless-aws.website/use-cases/queue | Queue guide}\n *\n * @example\n * ```typescript\n * export const notifications = defineFifoQueue({ batchSize: 5, schema: (i) => NotifSchema.parse(i) })\n * .onMessageBatch(async ({ messages }) => {\n * await sendAll(messages.map(m => m.body));\n * })\n * ```\n */\nexport function defineFifoQueue<T = unknown>(): FifoQueueBuilder<T>;\nexport function defineFifoQueue<T = unknown>(\n options: FifoQueueOptions<T>,\n): FifoQueueBuilder<T>;\nexport function defineFifoQueue<T = unknown>(\n options?: FifoQueueOptions<T>,\n): FifoQueueBuilder<T> {\n const {\n schema,\n ...queueConfig\n } = options ?? {} as FifoQueueOptions<T>;\n\n const spec: FifoQueueConfig = { ...queueConfig };\n\n const state: {\n spec: FifoQueueConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n schema?: (input: unknown) => T;\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onMessage?: (...args: any[]) => any;\n onMessageBatch?: (...args: any[]) => any;\n } = {\n spec,\n ...(schema ? { schema } : {}),\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): FifoQueueHandler<T> => ({\n __brand: \"effortless-fifo-queue\",\n __spec: state.spec,\n ...(state.schema ? { schema: state.schema } : {}),\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onMessage ? { onMessage: state.onMessage } : {}),\n ...(state.onMessageBatch ? { onMessageBatch: state.onMessageBatch } : {}),\n }) as FifoQueueHandler<T>;\n\n const builder: FifoQueueBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onMessage(fn) {\n state.onMessage = fn as any;\n return finalize() as any;\n },\n onMessageBatch(fn) {\n state.onMessageBatch = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\nimport type { BucketClient, BucketClientWithEntities } from \"../runtime/bucket-client\";\n\n/**\n * Per-entity configuration for typed JSON key-value storage within a bucket.\n */\nexport type BucketEntityConfig = {\n /** Cache duration for CloudFront/browser caching (e.g., \"10s\", \"5m\", \"1h\"). No caching if omitted. */\n cache?: Duration;\n};\n\n/**\n * Configuration options for defineBucket.\n */\nexport type BucketConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: import(\"./handler-options\").Duration; logLevel?: import(\"./handler-options\").LogLevel; permissions?: import(\"./handler-options\").Permission[] };\n /** S3 key prefix filter for event notifications (e.g., \"uploads/\") */\n prefix?: string;\n /** S3 key suffix filter for event notifications (e.g., \".jpg\") */\n suffix?: string;\n /** Typed JSON entity definitions for key-value storage */\n entities?: Record<string, BucketEntityConfig>;\n /** Local directory to seed into bucket on deploy (only uploads files that don't already exist) */\n seed?: string;\n /** Local directory to sync into bucket on every deploy (uploads new/changed, deletes removed) */\n sync?: string;\n};\n\n/**\n * S3 event record passed to onObjectCreated/onObjectRemoved callbacks.\n */\nexport type BucketEvent = {\n /** S3 event name (e.g., \"ObjectCreated:Put\", \"ObjectRemoved:Delete\") */\n eventName: string;\n /** Object key (path within the bucket) */\n key: string;\n /** Object size in bytes (present for created events) */\n size?: number;\n /** Object ETag (present for created events) */\n eTag?: string;\n /** ISO 8601 timestamp of when the event occurred */\n eventTime?: string;\n /** S3 bucket name */\n bucketName: string;\n};\n\n// ============ Setup args ============\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/** Setup factory — receives bucket/deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean, Entities extends Record<string, any> = {}> =\n & { bucket: {} extends Entities ? BucketClient : BucketClientWithEntities<Entities> }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/**\n * Callback function type for S3 ObjectCreated events\n */\nexport type BucketObjectCreatedFn<C = undefined> =\n (args: { event: BucketEvent }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Callback function type for S3 ObjectRemoved events\n */\nexport type BucketObjectRemovedFn<C = undefined> =\n (args: { event: BucketEvent }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n// ============ Internal handler object ============\n\n/**\n * Internal handler object created by defineBucket\n * @internal\n */\nexport type BucketHandler<C = any, _Entities extends Record<string, any> = {}> = {\n readonly __brand: \"effortless-bucket\";\n readonly __spec: BucketConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onObjectCreated?: (...args: any[]) => any;\n readonly onObjectRemoved?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineBucket()` — static config */\ntype BucketOptions = {\n /** S3 key prefix filter for event notifications (e.g., \"uploads/\") */\n prefix?: string;\n /** S3 key suffix filter for event notifications (e.g., \".jpg\") */\n suffix?: string;\n /** Local directory to seed into bucket on deploy (only uploads files that don't already exist) */\n seed?: string;\n /** Local directory to sync into bucket on every deploy (uploads new/changed, deletes removed) */\n sync?: string;\n};\n\n// ============ Builder ============\n\ninterface BucketBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n Entities extends Record<string, any> = {},\n> {\n /** Declare handler dependencies */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): BucketBuilder<D2, P, C, HasFiles, Entities>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): BucketBuilder<D, P2, C, HasFiles, Entities>;\n\n /** Include static files in the Lambda ZIP */\n include(glob: string): BucketBuilder<D, P, C, true, Entities>;\n\n /** Register a typed JSON entity stored as `{name}/{id}.json` in the bucket */\n entity<N extends string, T>(\n name: N,\n options?: BucketEntityConfig,\n ): BucketBuilder<D, P, C, HasFiles, Entities & { [K in N]: T }>;\n\n /** Initialize shared state on cold start with lambda options */\n setup(lambda: LambdaOptions): BucketBuilder<D, P, C, HasFiles, Entities>;\n /** Initialize shared state on cold start. Receives bucket (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles, Entities>) => C2 | Promise<C2>\n ): BucketBuilder<D, P, C2, HasFiles, Entities>;\n /** Initialize shared state on cold start with lambda options. Receives bucket (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles, Entities>) => C2 | Promise<C2>,\n lambda: LambdaOptions,\n ): BucketBuilder<D, P, C2, HasFiles, Entities>;\n\n /** Handle errors thrown by callbacks */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): BucketBuilder<D, P, C, HasFiles, Entities>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): BucketBuilder<D, P, C, HasFiles, Entities>;\n\n /** Handle S3 ObjectCreated events (terminal — returns finalized handler) */\n onObjectCreated(\n fn: BucketObjectCreatedFn<C>\n ): BucketHandler<C, Entities>;\n\n /** Handle S3 ObjectRemoved events (terminal — returns finalized handler) */\n onObjectRemoved(\n fn: BucketObjectRemovedFn<C>\n ): BucketHandler<C, Entities>;\n\n /** Finalize as resource-only bucket (no Lambda) */\n build(): BucketHandler<C, Entities>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an S3 bucket with optional event handlers.\n *\n * @see {@link https://effortless-aws.website/use-cases/storage | Storage guide}\n *\n * @example\n * ```typescript\n * export const uploads = defineBucket({ prefix: \"images/\", suffix: \".jpg\" })\n * .onObjectCreated(async ({ event, bucket }) => {\n * console.log(\"New upload:\", event.key);\n * })\n * ```\n */\nexport function defineBucket(): BucketBuilder;\nexport function defineBucket(\n options: BucketOptions,\n): BucketBuilder;\nexport function defineBucket(\n options?: BucketOptions,\n): BucketBuilder {\n const bucketConfig = options ?? {} as BucketOptions;\n\n const spec: BucketConfig = {\n ...bucketConfig,\n };\n\n const state: {\n spec: BucketConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onObjectCreated?: (...args: any[]) => any;\n onObjectRemoved?: (...args: any[]) => any;\n } = {\n spec,\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): BucketHandler => ({\n __brand: \"effortless-bucket\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onObjectCreated ? { onObjectCreated: state.onObjectCreated } : {}),\n ...(state.onObjectRemoved ? { onObjectRemoved: state.onObjectRemoved } : {}),\n }) as BucketHandler;\n\n const builder: BucketBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n entity(name: string, options?: BucketEntityConfig) {\n state.spec = {\n ...state.spec,\n entities: { ...state.spec.entities, [name]: options ?? {} },\n };\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onObjectCreated(fn) {\n state.onObjectCreated = fn as any;\n return finalize() as any;\n },\n onObjectRemoved(fn) {\n state.onObjectRemoved = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","/**\n * Configuration options for defining a mailer (SES email identity)\n */\nexport type MailerConfig = {\n /** Domain to verify and send emails from (e.g., \"myapp.com\") */\n domain: string;\n};\n\n/**\n * Internal handler object created by defineMailer\n * @internal\n */\nexport type MailerHandler = {\n readonly __brand: \"effortless-mailer\";\n readonly __spec: MailerConfig;\n};\n\n/**\n * Define an email sender backed by Amazon SES.\n *\n * Creates an SES Email Identity for the specified domain and provides\n * a typed `EmailClient` to other handlers via `deps`.\n *\n * On first deploy, DKIM DNS records are printed to the console.\n * Add them to your DNS provider to verify the domain.\n *\n * @see {@link https://effortless-aws.website/use-cases/email | Email guide}\n *\n * @param options - Mailer configuration with the domain to send from\n * @returns Handler object used by the deployment system and as a `deps` value\n *\n * @example Basic mailer with HTTP handler\n * ```typescript\n * export const mailer = defineMailer({ domain: \"myapp.com\" });\n *\n * export const api = defineApi({\n * basePath: \"/signup\",\n * deps: { mailer },\n * post: async ({ req, deps }) => {\n * await deps.mailer.send({\n * from: \"hello@myapp.com\",\n * to: req.body.email,\n * subject: \"Welcome!\",\n * html: \"<h1>Hi!</h1>\",\n * });\n * return { status: 200, body: { ok: true } };\n * },\n * });\n * ```\n */\nexport const defineMailer = () => (options: MailerConfig): MailerHandler => ({\n __brand: \"effortless-mailer\",\n __spec: options,\n});\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { LambdaWithPermissions, AnySecretRef, ResolveConfig, Duration, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory, toSeconds } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles, ResponseStream } from \"./shared\";\nimport type { HttpRequest, HttpResponse } from \"./shared\";\nimport type { AuthHelpers } from \"./auth\";\n\n// ============ Auth types ============\n\n/** Auth config options (user-facing) */\nexport type AuthOptions<A = unknown> = {\n /** HMAC secret for signing session cookies. Use `secret()` or `param()` in config. */\n secret: string;\n /** Default session lifetime (default: \"7d\"). */\n expiresIn?: Duration;\n /** Optional API token strategy for external clients. */\n apiToken?: {\n /** HTTP header to read the token from. Default: \"authorization\" (strips \"Bearer \" prefix). */\n header?: string;\n /** Verify the token value and return session data, or null if invalid. */\n verify: (value: string) => A | null | Promise<A | null>;\n /** Cache verified token results for this duration. */\n cacheTtl?: Duration;\n };\n};\n\n/** Property names reserved by the framework — cannot be used in setup return */\ntype ReservedKeys = 'req' | 'input' | 'stream' | 'ok' | 'fail';\n\n// ============ Response helpers ============\n\n/** Success response helper: `ok({ data })` → `{ status: 200, body: { data } }` */\nexport type OkHelper = (body?: unknown, status?: number) => HttpResponse;\n/** Error response helper: `fail(\"message\")` → `{ status: 400, body: { error: \"message\" } }` */\nexport type FailHelper = (message: string, status?: number) => HttpResponse;\n\n// ============ Route types ============\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/** Cache options for a GET route. Duration shorthand (e.g. \"30s\", \"5m\") or object for fine-grained control. */\nexport type CacheOptions = Duration | {\n ttl: Duration;\n swr?: Duration;\n scope?: \"public\" | \"private\";\n};\n\n/** Resolved cache config with numeric seconds */\ntype ResolvedCache =\n | { private?: false; ttl: number; swr: number }\n | { private: true; ttl: number };\n\n/** Resolve CacheOptions into numeric seconds. Shorthand = public with swr = ttl * 2. */\nconst resolveCache = (cache: CacheOptions): ResolvedCache => {\n if (typeof cache === \"number\" || typeof cache === \"string\") {\n const ttl = toSeconds(cache);\n return { ttl, swr: ttl * 2 };\n }\n const ttl = toSeconds(cache.ttl);\n if (cache.scope === \"private\") {\n return { private: true, ttl };\n }\n return {\n ttl,\n swr: cache.swr != null ? toSeconds(cache.swr) : ttl * 2,\n };\n};\n\n/** Parsed route definition stored at runtime */\nexport type RouteEntry = {\n method: HttpMethod;\n path: string;\n onRequest: (...args: any[]) => any;\n schema?: unknown;\n public?: boolean;\n cache?: ResolvedCache;\n};\n\n/** Spread ctx into route args, add AuthHelpers if A is set */\ntype SpreadCtx<C, A = undefined> =\n & ([C] extends [undefined] ? {} : C & {})\n & ([A] extends [undefined] ? {} : { auth: AuthHelpers<A> });\n\n/** Infer validated output type from a Standard Schema, or fall back to unknown */\ntype InferInput<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : unknown;\n\n/** Callback args available inside each route — ctx is spread into args */\ntype RouteArgs<C, ST, A = undefined, S = undefined> =\n & SpreadCtx<C, A>\n & { req: HttpRequest; input: InferInput<S>; ok: OkHelper; fail: FailHelper }\n & ([ST] extends [true] ? { stream: ResponseStream } : {});\n\n/** Route handler function */\ntype RouteHandler<C, ST, A = undefined, S = undefined> = (args: RouteArgs<C, ST, A, S>) => Promise<HttpResponse | void> | HttpResponse | void;\n\n/** Route definition — pass `input` for typed schema validation */\ntype RouteDef<S extends StandardSchemaV1 | undefined = undefined> = {\n path: `/${string}`;\n input?: S;\n public?: boolean;\n cache?: CacheOptions;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & { ok: OkHelper; fail: FailHelper }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Auth factory args — receives config/deps based on what was declared */\ntype AuthArgs<D, P> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> });\n\n/** Validate that setup return type does not use reserved property names */\ntype ValidateSetupReturn<C> = C & { [K in ReservedKeys]?: never };\n\n// ============ Static config ============\n\n/** Static config extracted by AST (no runtime callbacks) */\nexport type ApiConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Base path prefix for all routes (e.g., \"/api\") */\n basePath: `/${string}`;\n /** Enable response streaming. When true, the Lambda Function URL uses RESPONSE_STREAM invoke mode. */\n stream?: boolean;\n};\n\n// ============ Internal handler object ============\n\n/** Internal handler object created by defineApi */\nexport type ApiHandler<C = undefined> = {\n readonly __brand: \"effortless-api\";\n readonly __spec: ApiConfig;\n readonly onError?: (...args: any[]) => any | Promise<any>;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly routes?: RouteEntry[];\n};\n\n// ============ Builder options (plain values, no inference) ============\n\n/** Options passed to `defineApi()` */\ntype ApiOptions = {\n /** Base path prefix for all routes (e.g., \"/api\") */\n basePath: `/${string}`;\n /** Enable response streaming. When true, routes receive a `stream` arg for SSE. */\n stream?: boolean;\n};\n\n// ============ ApiRoutes — returned after first route method ============\n\n/**\n * Finalized API handler with route-adding methods.\n * Has `__brand` so CLI discovers it. Each `.get()/.post()` adds a route and returns self.\n */\nexport interface ApiRoutes<C = undefined, ST extends boolean = false, A = undefined> extends ApiHandler<C> {\n get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n}\n\n// ============ Builder ============\n\n/**\n * Builder interface for defining API handlers.\n *\n * Each method sets exactly one generic, so inference happens one step at a time.\n * This prevents cascading type errors when one property has a mistake.\n */\ninterface ApiBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n ST extends boolean = false,\n HasFiles extends boolean = false,\n A = undefined,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): ApiBuilder<D2, P, C, ST, HasFiles, A>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): ApiBuilder<D, P2, C, ST, HasFiles, A>;\n\n /** Include static files by glob pattern */\n include(glob: string): ApiBuilder<D, P, C, ST, true, A>;\n\n /** Configure session-based authentication. Receives resolved config/deps. */\n auth<A2>(\n fn: (args: AuthArgs<D, P>) => AuthOptions<A2> | Promise<AuthOptions<A2>>\n ): ApiBuilder<D, P, C, ST, HasFiles, A2>;\n\n /** Configure Lambda settings only (no init function) */\n setup(lambda: LambdaOptions): ApiBuilder<D, P, C, ST, HasFiles, A>;\n /** Initialize shared state on cold start. Receives deps/config/files based on what was declared. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => ValidateSetupReturn<C2> | Promise<ValidateSetupReturn<C2>>\n ): ApiBuilder<D, P, C2, ST, HasFiles, A>;\n /** Initialize shared state on cold start with Lambda config. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => ValidateSetupReturn<C2> | Promise<ValidateSetupReturn<C2>>,\n lambda: LambdaOptions,\n ): ApiBuilder<D, P, C2, ST, HasFiles, A>;\n\n /** Handle errors thrown by routes */\n onError(\n fn: (args: { error: unknown; req: HttpRequest; ok: OkHelper; fail: FailHelper } & SpreadCtx<C, A>) => HttpResponse | Promise<HttpResponse>\n ): ApiBuilder<D, P, C, ST, HasFiles, A>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C, A>) => void | Promise<void>\n ): ApiBuilder<D, P, C, ST, HasFiles, A>;\n\n get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an API with typed routes using a builder pattern.\n *\n * @see {@link https://effortless-aws.website/use-cases/http-api | HTTP API guide}\n *\n * @example\n * ```typescript\n * export const api = defineApi({ basePath: \"/api\" })\n * .deps(() => ({ users }))\n * .config(({ defineSecret }) => ({ authSecret: defineSecret() }))\n * .auth<Session>(({ config }) => ({ secret: config.authSecret, expiresIn: \"1h\" }))\n * .get({ path: \"/me\" }, async ({ users, auth, ok }) => ok(auth.session))\n * .post({ path: \"/login\", public: true }, async ({ auth, ok }) => ok(await auth.createSession()))\n * ```\n */\nexport function defineApi<const O extends ApiOptions>(\n options: O,\n): ApiBuilder<undefined, undefined, undefined, O[\"stream\"] extends true ? true : false, false>;\nexport function defineApi(\n options: ApiOptions,\n): ApiBuilder {\n const { basePath, stream } = options;\n\n const state: {\n spec: ApiConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n authFn?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n routes: RouteEntry[];\n } = {\n spec: {\n basePath,\n ...(stream ? { stream } : {}),\n },\n routes: [],\n };\n\n const addRoute = (method: HttpMethod, def: { path: string; input?: unknown; public?: boolean; cache?: CacheOptions }, handler: Function) => {\n const routeCache = def.cache != null\n ? resolveCache(def.cache)\n : undefined;\n\n state.routes.push({\n method,\n path: def.path,\n onRequest: handler as any,\n ...(def.input ? { schema: def.input } : {}),\n ...(def.public ? { public: true } : {}),\n ...(routeCache ? { cache: routeCache } : {}),\n });\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): ApiRoutes => {\n const handler: any = {\n __brand: \"effortless-api\",\n __spec: state.spec,\n routes: state.routes,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.authFn ? { authFn: state.authFn } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n };\n\n // Add route methods to the finalized handler\n for (const m of [\"get\", \"post\", \"put\", \"patch\", \"delete\"] as const) {\n handler[m] = (def: any, fn: any) => {\n addRoute(m.toUpperCase() as HttpMethod, def, fn);\n handler.routes = state.routes;\n return handler;\n };\n }\n\n return handler as ApiRoutes;\n };\n\n const builder: ApiBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n auth(fn: any) {\n state.authFn = fn;\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n get(def: any, fn: any) { addRoute(\"GET\", def, fn); return finalize() as any; },\n post(def: any, fn: any) { addRoute(\"POST\", def, fn); return finalize() as any; },\n put(def: any, fn: any) { addRoute(\"PUT\", def, fn); return finalize() as any; },\n patch(def: any, fn: any) { addRoute(\"PATCH\", def, fn); return finalize() as any; },\n delete(def: any, fn: any) { addRoute(\"DELETE\", def, fn); return finalize() as any; },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\nimport type { Timezone } from \"./timezone\";\n\n// ============ Schedule expression ============\n\n/** Singular/plural unit for rate expressions */\ntype RateUnit = \"minute\" | \"minutes\" | \"hour\" | \"hours\" | \"day\" | \"days\";\n\n/**\n * Rate expression: `rate(1 hour)`, `rate(5 minutes)`, `rate(2 days)`\n *\n * Strictly typed — autocomplete and compile-time validation for unit.\n */\ntype RateExpression = `rate(${number} ${RateUnit})`;\n\n/**\n * Cron expression: `cron(min hour dom month dow year)`\n *\n * Not deeply typed (too combinatorial for TS), but the `cron(...)` wrapper is enforced.\n *\n * @example\n * ```\n * \"cron(0 9 * * ? *)\" // daily at 9:00 UTC\n * \"cron(0 9 ? * MON-FRI *)\" // weekdays at 9:00\n * \"cron(0/15 * * * ? *)\" // every 15 minutes\n * ```\n */\ntype CronExpression = `cron(${string})`;\n\n/**\n * EventBridge Scheduler schedule expression.\n *\n * - **Rate**: `\"rate(5 minutes)\"`, `\"rate(1 hour)\"`, `\"rate(1 day)\"` — strictly typed units\n * - **Cron**: `\"cron(0 9 * * ? *)\"` — 6 fields: min hour dom month dow year\n */\nexport type ScheduleExpression = RateExpression | CronExpression;\n\n// ============ Static config ============\n\n/** Static config extracted at deploy time */\nexport type CronConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** EventBridge Scheduler schedule expression */\n schedule: ScheduleExpression;\n /** IANA timezone for the schedule (default: UTC) */\n timezone?: Timezone;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Tick handler ============\n\n/** Callback function for cron tick */\nexport type CronTickFn<C = undefined> =\n (args: SpreadCtx<C>) => Promise<void> | void;\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineCron.\n * @internal\n */\nexport type CronHandler<C = any> = {\n readonly __brand: \"effortless-cron\";\n readonly __spec: CronConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onTick?: (...args: any[]) => any;\n};\n\n// ============ Options ============\n\n/** Options passed to `defineCron()` — resource config only */\ntype CronOptions = {\n /** EventBridge Scheduler schedule expression: `\"rate(5 minutes)\"` or `\"cron(0 9 * * ? *)\"` */\n schedule: ScheduleExpression;\n /** IANA timezone for the schedule (default: UTC). Full autocomplete for all timezones. */\n timezone?: Timezone;\n};\n\n// ============ Builder ============\n\ninterface CronBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): CronBuilder<D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): CronBuilder<D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): CronBuilder<D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): CronBuilder<D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): CronBuilder<D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): CronBuilder<D, P, C2, HasFiles>;\n\n /** Handle errors thrown by onTick */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): CronBuilder<D, P, C, HasFiles>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): CronBuilder<D, P, C, HasFiles>;\n\n /** Tick handler — called on each scheduled invocation (terminal) */\n onTick(\n fn: CronTickFn<C>\n ): CronHandler<C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a cron job — scheduled Lambda invocation via EventBridge Scheduler.\n *\n * @example\n * ```typescript\n * export const sync = defineCron({ schedule: \"cron(0 9 * * ? *)\" })\n * .deps(() => ({ orders }))\n * .config(({ defineSecret }) => ({ apiKey: defineSecret() }))\n * .include(\"templates/*.html\")\n * .setup(async ({ deps, config, files }) => ({\n * db: deps.orders, key: config.apiKey, tpl: files,\n * }), { memory: 512 })\n * .onTick(async ({ db, key, tpl }) => {\n * const html = tpl.read(\"templates/report.html\")\n * })\n * ```\n */\nexport function defineCron(options: CronOptions): CronBuilder {\n const { schedule, timezone } = options;\n\n const spec: CronConfig = {\n schedule,\n ...(timezone ? { timezone } : {}),\n };\n\n const state: {\n spec: CronConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onTick?: (...args: any[]) => any;\n } = { spec };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): CronHandler => ({\n __brand: \"effortless-cron\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onTick ? { onTick: state.onTick } : {}),\n }) as CronHandler;\n\n const builder: CronBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n onTick(fn) {\n state.onTick = fn as any;\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions, Duration } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n// ============ Static config ============\n\n/** Fargate container size presets */\nexport type FargateSize =\n | \"0.25vCPU-512mb\"\n | \"0.5vCPU-1gb\"\n | \"1vCPU-2gb\"\n | \"2vCPU-4gb\"\n | \"4vCPU-8gb\";\n\n/** Static config extracted at deploy time */\nexport type WorkerConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Fargate size (default: \"0.5vCPU-1gb\") */\n size?: FargateSize;\n /** How long the worker stays alive without messages before shutting down (default: \"5m\") */\n idleTimeout?: Duration;\n /** Max messages processed in parallel (default: 1, max: 10) */\n concurrency?: number;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Handler callback ============\n\n/** Callback function for the worker's onMessage */\nexport type WorkerMessageFn<T, C = undefined> =\n (msg: T, ctx: SpreadCtx<C>) => Promise<void> | void;\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineWorker.\n * @internal\n */\nexport type WorkerHandler<_T = any, C = any> = {\n readonly __brand: \"effortless-worker\";\n readonly __spec: WorkerConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onMessage?: (...args: any[]) => any;\n};\n\n// ============ Options ============\n\n/** Options passed to `defineWorker()` — resource config only */\ntype WorkerOptions = {\n /** Fargate size (default: \"0.5vCPU-1gb\") */\n size?: FargateSize;\n /** How long the worker stays alive without messages before shutting down (default: \"5m\") */\n idleTimeout?: Duration;\n /** Max messages processed in parallel (default: 1, max: 10) */\n concurrency?: number;\n};\n\n// ============ Builder ============\n\ninterface WorkerBuilder<\n T,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers, workers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): WorkerBuilder<T, D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): WorkerBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the bundle. Chainable — call multiple times. */\n include(glob: string): WorkerBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): WorkerBuilder<T, D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): WorkerBuilder<T, D, P, C2, HasFiles>;\n\n /** Handle errors thrown by onMessage. Return \"delete\" to discard, \"retry\" to reprocess (default). */\n onError(\n fn: (args: { error: unknown; msg: T; retryCount: number } & SpreadCtx<C>) => \"retry\" | \"delete\" | void | Promise<\"retry\" | \"delete\" | void>\n ): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Cleanup callback — runs when the worker shuts down */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Process a single message from the queue (terminal) */\n onMessage(\n fn: WorkerMessageFn<T, C>\n ): WorkerHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a worker — a long-running Fargate container with an SQS queue.\n *\n * The worker stays alive while processing messages and shuts down after\n * an idle timeout with no new messages. Other handlers can send messages\n * to the worker via `deps.worker.send(msg)`.\n *\n * @typeParam T - Type of messages the worker receives via its queue\n *\n * @example\n * ```typescript\n * type Job = { type: \"export\"; userId: string }\n *\n * export const worker = defineWorker<Job>({ memory: 2048, concurrency: 5 })\n * .deps(() => ({ orders }))\n * .setup(async ({ deps }) => ({ db: deps.orders }))\n * .onMessage(async (msg, { db }) => {\n * await processJob(msg, db)\n * })\n * ```\n */\nexport function defineWorker<T = unknown>(options?: WorkerOptions): WorkerBuilder<T> {\n const spec: WorkerConfig = {\n ...(options?.size ? { size: options.size } : {}),\n ...(options?.idleTimeout ? { idleTimeout: options.idleTimeout } : {}),\n ...(options?.concurrency ? { concurrency: options.concurrency } : {}),\n };\n\n const state: {\n spec: WorkerConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onMessage?: (...args: any[]) => any;\n } = { spec };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): WorkerHandler => ({\n __brand: \"effortless-worker\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onMessage ? { onMessage: state.onMessage } : {}),\n }) as WorkerHandler;\n\n const builder: WorkerBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n onMessage(fn) {\n state.onMessage = fn as any;\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { StandardSchemaV1, StandardJSONSchemaV1 } from \"@standard-schema/spec\";\nimport type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n// ============ Static config ============\n\n/** Static config extracted at deploy time */\nexport type McpConfig = {\n /** MCP server name (used in server info) */\n name: string;\n /** MCP server version (default: \"1.0.0\") */\n version?: string;\n /** Human-readable description — sent to clients in initialize response as system prompt context */\n instructions?: string;\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n};\n\n// ============ MCP tool types ============\n\n/** JSON Schema for a tool's input parameters */\nexport type McpInputSchema = {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n};\n\n/** Content block returned by a tool handler */\nexport type McpToolContent =\n | { type: \"text\"; text: string }\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"resource\"; resource: { uri: string; text: string; mimeType?: string } };\n\n/** Result returned by a tool handler */\nexport type McpToolResult = {\n content: McpToolContent[];\n isError?: boolean;\n};\n\n// ============ MCP resource types ============\n\n/** Content returned when reading a resource */\nexport type McpResourceContent =\n | { uri: string; mimeType?: string; text: string }\n | { uri: string; mimeType?: string; blob: string };\n\n/** Legacy resource content result type used by runtime internals */\ntype McpResourceResult = McpResourceContent | McpResourceContent[] | Promise<McpResourceContent | McpResourceContent[]>;\n\n/** Infer resource params type from schema, or fall back to Record<string, string> */\ntype InferResourceParams<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : Record<string, string>;\n\n/** Resource definition — pass `params` for typed schema validation on URI template params */\nexport type McpResourceDefInput<S extends StandardSchemaV1 | undefined = undefined> = {\n /** Resource URI or URI template (e.g. \"resource://contacts/{id}\") */\n uri: string;\n /** Human-readable name */\n name: string;\n /** Schema for URI template params — provides type inference + validation */\n params?: S;\n /** Optional description */\n description?: string;\n /** Optional MIME type */\n mimeType?: string;\n};\n\n/** What resource handlers can return — plain data is auto-wrapped by the runtime */\ntype McpResourceReturn = unknown | Promise<unknown>;\n\n/** Resource handler — receives params (typed or Record<string, string>) and ctx */\ntype McpResourceHandler<C, S = undefined> = (params: InferResourceParams<S>, ctx: SpreadCtx<C>) => McpResourceReturn;\n\n// Legacy types used by wrap-mcp runtime\n/** @internal */\nexport type McpResourceDef<C = undefined> = {\n name: string;\n description?: string;\n mimeType?: string;\n handler: (ctx: SpreadCtx<C>) => McpResourceResult;\n};\n\n/** @internal */\nexport type McpResourceTemplateDef<C = undefined> = {\n name: string;\n description?: string;\n mimeType?: string;\n handler: (params: Record<string, string>, ctx: SpreadCtx<C>) => McpResourceResult;\n};\n\n/** @internal */\nexport type McpResourceMap<C = undefined> = {\n [uriOrTemplate: string]: McpResourceDef<C> | McpResourceTemplateDef<C>;\n};\n\n// ============ MCP prompt types ============\n\n/** An argument accepted by a prompt */\nexport type McpPromptArgument = {\n /** Argument name */\n name: string;\n /** Optional description */\n description?: string;\n /** Whether the argument is required (default: false) */\n required?: boolean;\n};\n\n/** Content block inside a prompt message */\nexport type McpPromptContent =\n | { type: \"text\"; text: string }\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"audio\"; data: string; mimeType: string }\n | { type: \"resource\"; resource: { uri: string; mimeType?: string; text?: string; blob?: string } };\n\n/** A single message returned by a prompt */\nexport type McpPromptMessage = {\n role: \"user\" | \"assistant\";\n content: McpPromptContent;\n};\n\n/** Result returned by a prompt handler */\nexport type McpPromptResult = {\n description?: string;\n messages: McpPromptMessage[];\n};\n\n/** @internal Legacy prompt definition used by runtime */\nexport type McpPromptDef<C = undefined> = {\n description?: string;\n arguments?: McpPromptArgument[];\n handler: (args: Record<string, string>, ctx: SpreadCtx<C>) => McpPromptResult | Promise<McpPromptResult>;\n};\n\n/** Infer prompt args type from schema, or fall back to Record<string, string> */\ntype InferPromptArgs<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : Record<string, string>;\n\n/** Prompt definition — pass a Standard Schema for `args` for typed validation, or McpPromptArgument[] for untyped */\nexport type McpPromptDefInput<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined> = {\n /** Prompt name */\n name: string;\n /** Human-readable description */\n description?: string;\n /** Args: Standard Schema for typed validation, or McpPromptArgument[] for untyped */\n args?: S;\n};\n\n/** Handler return: string auto-wraps as user message, or return full McpPromptResult */\ntype McpPromptReturn = string | McpPromptResult | Promise<string | McpPromptResult>;\n\n/** Prompt handler — receives args (typed or Record<string, string>) and ctx */\ntype McpPromptHandler<C, S = undefined> = (args: InferPromptArgs<S>, ctx: SpreadCtx<C>) => McpPromptReturn;\n\n/** Infer tool input type from schema, or fall back to any */\ntype InferToolInput<S> = S extends StandardJSONSchemaV1 ? StandardJSONSchemaV1.InferOutput<S> : any;\n\n/** Tool definition — pass a StandardJSONSchemaV1 for `input` for typed validation, or McpInputSchema for raw JSON Schema */\nexport type McpToolDefInput<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema> = {\n /** Tool name */\n name: string;\n /** Human-readable description of the tool */\n description: string;\n /** Schema for tool input: StandardJSONSchemaV1 (e.g. z.object({...})) or raw McpInputSchema */\n input: S;\n};\n\n/** Tool handler — receives input (typed or any) and ctx */\ntype McpToolHandler<C, S = McpInputSchema> = (input: InferToolInput<S>, ctx: SpreadCtx<C>) => unknown | Promise<unknown>;\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Auth factory args — receives config/deps based on what was declared */\ntype AuthArgs<D, P> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> });\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineMcp.\n * @internal\n */\nexport type McpHandler<C = any> = {\n readonly __brand: \"effortless-mcp\";\n readonly __spec: McpConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly resources?: (...args: any[]) => any;\n readonly prompts?: (...args: any[]) => any;\n readonly tools?: (...args: any[]) => any;\n};\n\n// ============ McpEntries — returned after first singular method ============\n\n/**\n * Finalized MCP handler with chainable registration methods.\n * Has `__brand` so CLI discovers it. Each `.tool()/.resource()/.prompt()` adds an entry and returns self.\n */\nexport interface McpEntries<C = undefined> extends McpHandler<C> {\n /** Register a tool */\n tool<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema>(def: McpToolDefInput<S>, handler: McpToolHandler<C, S>): McpEntries<C>;\n /** Register a resource */\n resource<S extends StandardSchemaV1 | undefined = undefined>(def: McpResourceDefInput<S>, handler: McpResourceHandler<C, S>): McpEntries<C>;\n /** Register a prompt */\n prompt<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined>(def: McpPromptDefInput<S>, handler: McpPromptHandler<C, S>): McpEntries<C>;\n}\n\n// ============ Options ============\n\n/** Options passed to `defineMcp()` */\ntype McpOptions = {\n /** MCP server name (used in server info) */\n name: string;\n /** MCP server version (default: \"1.0.0\") */\n version?: string;\n /** Human-readable description — sent to clients in initialize response as system prompt context */\n instructions?: string;\n};\n\n// ============ Builder ============\n\ninterface McpBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers, workers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): McpBuilder<D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): McpBuilder<D, P2, C, HasFiles>;\n\n /** Include static files in the bundle. Chainable — call multiple times. */\n include(glob: string): McpBuilder<D, P, C, true>;\n\n /** Configure session-based authentication. Receives resolved config/deps. All requests require a valid session. */\n auth<A2>(\n fn: (args: AuthArgs<D, P>) => import(\"./define-api\").AuthOptions<A2> | Promise<import(\"./define-api\").AuthOptions<A2>>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): McpBuilder<D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): McpBuilder<D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): McpBuilder<D, P, C2, HasFiles>;\n\n /** Handle errors thrown by tool handlers */\n onError(\n fn: (args: { error: unknown; toolName: string } & SpreadCtx<C>) => void | Promise<void>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Cleanup callback — runs on shutdown */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Register a tool */\n tool<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema>(def: McpToolDefInput<S>, handler: McpToolHandler<C, S>): McpEntries<C>;\n /** Register a resource */\n resource<S extends StandardSchemaV1 | undefined = undefined>(def: McpResourceDefInput<S>, handler: McpResourceHandler<C, S>): McpEntries<C>;\n /** Register a prompt */\n prompt<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined>(def: McpPromptDefInput<S>, handler: McpPromptHandler<C, S>): McpEntries<C>;\n\n /** Finalize the handler without adding more entries */\n build(): McpHandler<C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an MCP (Model Context Protocol) server endpoint.\n *\n * Creates a Lambda-backed MCP server that exposes tools, resources, and prompts\n * for AI models and MCP-compatible clients via Streamable HTTP (JSON-RPC over POST).\n *\n * @see https://modelcontextprotocol.io/specification/2025-03-26 — MCP specification\n * @see https://effortless-aws.com/use-cases/mcp-server/ — full documentation with examples\n */\nexport function defineMcp(options: McpOptions): McpBuilder {\n const spec: McpConfig = {\n name: options.name,\n ...(options.version ? { version: options.version } : {}),\n ...(options.instructions ? { instructions: options.instructions } : {}),\n };\n\n const state: {\n spec: McpConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n authFn?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n toolEntries: [string, any][];\n resourceEntries: [string, any][];\n promptEntries: [string, any][];\n } = { spec, toolEntries: [], resourceEntries: [], promptEntries: [] };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n // Build factory functions from accumulated entries\n const buildToolsFactory = () =>\n state.toolEntries.length > 0\n ? () => Object.fromEntries(state.toolEntries)\n : undefined;\n\n const buildResourcesFactory = () =>\n state.resourceEntries.length > 0\n ? () => Object.fromEntries(state.resourceEntries)\n : undefined;\n\n const buildPromptsFactory = () =>\n state.promptEntries.length > 0\n ? () => Object.fromEntries(state.promptEntries)\n : undefined;\n\n const finalize = (): McpHandler => {\n const tools = buildToolsFactory();\n const resources = buildResourcesFactory();\n const prompts = buildPromptsFactory();\n return {\n __brand: \"effortless-mcp\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.authFn ? { authFn: state.authFn } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(resources ? { resources } : {}),\n ...(prompts ? { prompts } : {}),\n ...(tools ? { tools } : {}),\n } as McpHandler;\n };\n\n const finalizeWithEntries = (): McpEntries => {\n const handler: any = finalize();\n\n handler.tool = (def: any, fn: any) => {\n state.toolEntries.push([def.name, { description: def.description, input: def.input, handler: fn }]);\n handler.tools = buildToolsFactory();\n return handler;\n };\n\n handler.resource = (def: any, fn: any) => {\n const entry = { name: def.name, ...(def.description ? { description: def.description } : {}), ...(def.mimeType ? { mimeType: def.mimeType } : {}), handler: fn, ...(def.params ? { params: def.params } : {}) };\n state.resourceEntries.push([def.uri, entry]);\n handler.resources = buildResourcesFactory();\n return handler;\n };\n\n handler.prompt = (def: any, fn: any) => {\n const entry = { ...(def.description ? { description: def.description } : {}), args: def.args, handler: fn };\n state.promptEntries.push([def.name, entry]);\n handler.prompts = buildPromptsFactory();\n return handler;\n };\n\n return handler as McpEntries;\n };\n\n const builder: McpBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n auth(fn: any) {\n state.authFn = fn;\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n tool(def: any, fn: any) {\n state.toolEntries.push([def.name, { description: def.description, input: def.input, handler: fn }]);\n return finalizeWithEntries() as any;\n },\n resource(def: any, fn: any) {\n const entry = { name: def.name, ...(def.description ? { description: def.description } : {}), ...(def.mimeType ? { mimeType: def.mimeType } : {}), handler: fn, ...(def.params ? { params: def.params } : {}) };\n state.resourceEntries.push([def.uri, entry]);\n return finalizeWithEntries() as any;\n },\n prompt(def: any, fn: any) {\n const entry = { ...(def.description ? { description: def.description } : {}), args: def.args, handler: fn };\n state.promptEntries.push([def.name, entry]);\n return finalizeWithEntries() as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n"],"mappings":"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","names":["options","ttl"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/handlers/handler-options.ts","../src/handlers/define-table.ts","../src/handlers/define-app.ts","../src/handlers/define-static-site.ts","../src/handlers/define-queue.ts","../src/handlers/define-bucket.ts","../src/handlers/define-mailer.ts","../src/handlers/define-api.ts","../src/handlers/define-cron.ts","../src/handlers/define-worker.ts","../src/handlers/define-mcp.ts"],"sourcesContent":["/**\n * Configuration for an Effortless project.\n *\n * @see {@link https://effortless-aws.website/configuration | Configuration guide}\n */\nexport type EffortlessConfig = {\n /**\n * Project name used for resource naming and tagging.\n * This becomes part of Lambda function names, IAM roles, etc.\n */\n name: string;\n\n /**\n * Default AWS region for all handlers.\n * Can be overridden per-handler or via CLI `--region` flag.\n * @default \"eu-central-1\"\n */\n region?: string;\n\n /**\n * Deployment stage (e.g., \"dev\", \"staging\", \"prod\").\n * Used for resource isolation and tagging.\n * @default \"dev\"\n */\n stage?: string;\n\n /**\n * Glob patterns or directory paths to scan for handlers.\n * Used by `eff deploy` (without file argument) to auto-discover handlers.\n *\n * @example\n * ```typescript\n * // Single directory - scans for all .ts files\n * handlers: \"src\"\n *\n * // Glob patterns\n * handlers: [\"src/**\\/*.ts\", \"lib/**\\/*.handler.ts\"]\n * ```\n */\n handlers?: string | string[];\n\n /**\n * Default Lambda settings applied to all handlers unless overridden.\n *\n * All Lambdas run on ARM64 (Graviton2) architecture — ~20% cheaper than x86_64\n * with better price-performance for most workloads.\n */\n lambda?: {\n /**\n * Lambda memory in MB. AWS allocates proportional CPU —\n * 1769 MB gives one full vCPU.\n * @default 256\n */\n memory?: number;\n\n /**\n * Lambda timeout as a human-readable string.\n * AWS maximum is 15 minutes.\n * @example \"30 seconds\", \"5 minutes\"\n */\n timeout?: string;\n\n /**\n * Node.js Lambda runtime version.\n * @default \"nodejs24.x\"\n */\n runtime?: string;\n };\n\n};\n\n/** Helper function for type-safe configuration with TypeScript autocompletion. */\nexport const defineConfig = (config: EffortlessConfig): EffortlessConfig => config;\n","// Public helpers — this file must have ZERO heavy imports (no effect, no AWS SDK, no deploy code).\n// It is the source of truth for param() and related types used by the public API.\n\n// ============ Generate spec ============\n\n/** Generator spec for auto-creating secrets at deploy time. */\nexport type GenerateSpec = `hex:${number}` | `base64:${number}` | \"uuid\";\n\n// ============ Permissions ============\n\ntype AwsService =\n | \"dynamodb\"\n | \"s3\"\n | \"sqs\"\n | \"sns\"\n | \"ses\"\n | \"ssm\"\n | \"lambda\"\n | \"events\"\n | \"secretsmanager\"\n | \"cognito-idp\"\n | \"logs\";\n\nexport type Permission = `${AwsService}:${string}` | (string & {});\n\n// ============ Duration ============\n\n/**\n * Human-readable duration. Accepts a plain number (seconds) or a string\n * with a unit suffix: `\"30s\"`, `\"5m\"`, `\"1h\"`, `\"2d\"`.\n *\n * @example\n * ```typescript\n * timeout: 30 // 30 seconds\n * timeout: \"30s\" // 30 seconds\n * timeout: \"5m\" // 300 seconds\n * timeout: \"1h\" // 3600 seconds\n * retentionPeriod: \"4d\" // 345600 seconds\n * ```\n */\nexport type Duration = number | `${number}s` | `${number}m` | `${number}h` | `${number}d`;\n\n/** Convert a Duration to seconds. */\nexport const toSeconds = (d: Duration): number => {\n if (typeof d === \"number\") return d;\n const match = d.match(/^(\\d+(?:\\.\\d+)?)(s|m|h|d)$/);\n if (!match) throw new Error(`Invalid duration: \"${d}\"`);\n const n = Number(match[1]);\n const unit = match[2];\n if (unit === \"d\") return n * 86400;\n if (unit === \"h\") return n * 3600;\n if (unit === \"m\") return n * 60;\n return n;\n};\n\n// ============ Lambda config ============\n\n/** Logging verbosity level for Lambda handlers */\nexport type LogLevel = \"error\" | \"info\" | \"debug\";\n\n/**\n * Common Lambda configuration shared by all handler types.\n */\nexport type LambdaConfig = {\n /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout (default: 30s). Accepts seconds or duration string: `\"30s\"`, `\"5m\"` */\n timeout?: Duration;\n /** Logging verbosity: \"error\" (errors only), \"info\" (+ execution summary), \"debug\" (+ input/output). Default: \"info\" */\n logLevel?: LogLevel;\n};\n\n/**\n * Lambda configuration with additional IAM permissions.\n * Used by handler types that support custom permissions (http, table, queue).\n */\nexport type LambdaWithPermissions = LambdaConfig & {\n /** Additional IAM permissions for the Lambda */\n permissions?: Permission[];\n};\n\n// ============ Lambda options (for .setup()) ============\n\n/**\n * Lambda configuration passed as argument to `.setup()`.\n * Common across all handler types that create a Lambda function.\n */\nexport type LambdaOptions = {\n /** Lambda memory in MB (default: 256) */\n memory?: number;\n /** Lambda timeout (default: 30s). Accepts seconds or duration string: `\"30s\"`, `\"5m\"` */\n timeout?: Duration;\n /** Additional IAM permissions for the Lambda */\n permissions?: Permission[];\n /** Logging verbosity: \"error\" (errors only), \"info\" (+ execution summary), \"debug\" (+ input/output). Default: \"info\" */\n logLevel?: LogLevel;\n};\n\n// ============ Secrets (SSM Parameters) ============\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnySecretRef = SecretRef<any>;\n\n/**\n * Reference to an SSM Parameter Store secret.\n *\n * @typeParam T - The resolved type after optional transform (default: string)\n */\nexport type SecretRef<T = string> = {\n readonly __brand: \"effortless-secret\";\n readonly key?: string;\n readonly generate?: GenerateSpec;\n readonly transform?: (raw: string) => T;\n};\n\n/**\n * Maps a config declaration to resolved value types.\n * `SecretRef<T>` resolves to `T`.\n *\n * @typeParam P - Record of config keys to SecretRef instances\n */\nexport type ResolveConfig<P> = {\n [K in keyof P]: P[K] extends SecretRef<infer T> ? T : string;\n};\n\n/** Options for `defineSecret()` without a transform. */\nexport type DefineSecretOptions = {\n /** Custom SSM key (default: derived from config property name in kebab-case) */\n key?: string;\n /** Generator spec for auto-creating the secret at deploy time: `\"hex:32\"`, `\"base64:32\"`, `\"uuid\"` */\n generate?: GenerateSpec;\n};\n\n/** Options for `defineSecret()` with a transform. */\nexport type DefineSecretOptionsWithTransform<T> = DefineSecretOptions & {\n /** Transform the raw SSM string value into a typed value */\n transform: (raw: string) => T;\n};\n\n/** The defineSecret helper function type, injected into config callbacks. */\nexport type DefineSecretFn = {\n (): SecretRef<string>;\n (options: DefineSecretOptions): SecretRef<string>;\n <T>(options: DefineSecretOptionsWithTransform<T>): SecretRef<T>;\n};\n\n/** Helpers injected into the `config` callback. */\nexport type ConfigHelpers = {\n defineSecret: DefineSecretFn;\n};\n\n/** Config factory: a function receiving helpers and returning a record of SecretRefs. */\nexport type ConfigFactory<P> = (helpers: ConfigHelpers) => P;\n\n/** The `defineSecret` implementation, passed to config callbacks. */\nexport const defineSecret: DefineSecretFn = <T = string>(\n options?: DefineSecretOptions | DefineSecretOptionsWithTransform<T>\n): SecretRef<T> => {\n return {\n __brand: \"effortless-secret\",\n ...(options?.key ? { key: options.key } : {}),\n ...(options?.generate ? { generate: options.generate } : {}),\n ...(\"transform\" in (options ?? {}) ? { transform: (options as DefineSecretOptionsWithTransform<T>).transform } : {}),\n } as SecretRef<T>;\n};\n\n/** Internal helpers object passed to config callbacks. */\nexport const configHelpers: ConfigHelpers = { defineSecret };\n\n/** Resolve a config factory to a plain record of SecretRefs. */\nexport const resolveConfigFactory = <P>(config: ConfigFactory<P>): P =>\n config(configHelpers);\n\n// ============ Backwards compatibility ============\n\n/** @deprecated Use `defineSecret()` inside a config callback instead. */\nexport const secret = defineSecret;\n/** @deprecated Use `SecretRef` instead */\nexport type ParamRef<T = string> = SecretRef<T>;\n/** @deprecated Use `AnySecretRef` instead */\nexport type AnyParamRef = AnySecretRef;\n/** @deprecated Use `defineSecret()` instead. */\nexport const param = <T = string>(key: string, transform?: (raw: string) => T): SecretRef<T> => {\n return {\n __brand: \"effortless-secret\",\n key,\n ...(transform ? { transform } : {}),\n } as SecretRef<T>;\n};\n/** @deprecated Use `defineSecret({ generate: \"hex:N\" })` instead. */\nexport const generateHex = (bytes: number) => `hex:${bytes}`;\n/** @deprecated Use `defineSecret({ generate: \"base64:N\" })` instead. */\nexport const generateBase64 = (bytes: number) => `base64:${bytes}`;\n/** @deprecated Use `defineSecret({ generate: \"uuid\" })` instead. */\nexport const generateUuid = () => \"uuid\";\n\n// ============ Single-table types ============\n\n/**\n * DynamoDB table key (always pk + sk strings in single-table design).\n */\nexport type TableKey = { pk: string; sk: string };\n\n/**\n * Full DynamoDB item in the single-table design.\n *\n * Every item has a fixed envelope: `pk`, `sk`, `tag`, `data`, and optional `ttl`.\n * `tag` is stored as a top-level DynamoDB attribute (GSI-ready).\n * If your domain type `T` includes a `tag` field, effortless auto-copies\n * `data.tag` to the top-level `tag` on `put()`, so you don't have to pass it twice.\n *\n * @typeParam T - The domain data type stored in the `data` attribute\n */\nexport type TableItem<T> = {\n pk: string;\n sk: string;\n tag: string;\n data: T;\n ttl?: number;\n};\n\n/**\n * Input type for `TableClient.put()`.\n *\n * Unlike `TableItem<T>`, this does NOT include `tag` — effortless auto-extracts\n * the top-level DynamoDB `tag` attribute from your data using the configured\n * tag field (defaults to `data.tag`, configurable via `defineTable({ tag: \"type\" })`).\n *\n * @typeParam T - The domain data type stored in the `data` attribute\n */\nexport type PutInput<T> = {\n pk: string;\n sk: string;\n data: T;\n ttl?: number;\n};\n\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LogLevel, Permission, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { TableClient } from \"../runtime/table-client\";\nimport type { TableItem } from \"./handler-options\";\nimport type { StaticFiles } from \"./shared\";\n\n/** DynamoDB attribute types for keys */\nexport type KeyType = \"string\" | \"number\" | \"binary\";\n\n/**\n * DynamoDB table key definition\n */\nexport type TableKey = {\n /** Attribute name */\n name: string;\n /** Attribute type */\n type: KeyType;\n};\n\n/** DynamoDB Streams view type - determines what data is captured in stream records */\nexport type StreamView = \"NEW_AND_OLD_IMAGES\" | \"NEW_IMAGE\" | \"OLD_IMAGE\" | \"KEYS_ONLY\";\n\n/**\n * Stream event source mapping configuration.\n * @internal\n */\nexport type TableStreamConfig = {\n /** Stream view type - what data to include in stream records (default: \"NEW_AND_OLD_IMAGES\") */\n streamView?: StreamView;\n /** Number of records to process in each Lambda invocation (1-10000, default: 100) */\n batchSize?: number;\n /** Maximum time to gather records before invoking (default: `\"2s\"`). Accepts `\"5s\"`, `\"1m\"`, etc. */\n batchWindow?: Duration;\n /** Where to start reading the stream (default: \"LATEST\") */\n startingPosition?: \"LATEST\" | \"TRIM_HORIZON\";\n /** Number of records to process concurrently within a batch (default: 1 — sequential) */\n concurrency?: number;\n /** Max retry attempts for failed records before sending to DLQ (default: 1) */\n maxRetries?: number;\n};\n\n/**\n * Configuration options for defineTable (single-table design).\n *\n * Tables always use `pk (S)` + `sk (S)` keys, `tag (S)` discriminator,\n * `data (M)` for domain fields, and `ttl (N)` for optional expiration.\n */\nexport type TableConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: Duration; logLevel?: LogLevel; permissions?: Permission[] };\n /** DynamoDB billing mode (default: \"PAY_PER_REQUEST\") */\n billingMode?: \"PAY_PER_REQUEST\" | \"PROVISIONED\";\n /**\n * Name of the field in `data` that serves as the entity type discriminant.\n * Effortless auto-copies `data[tagField]` to the top-level DynamoDB `tag` attribute on `put()`.\n * Defaults to `\"tag\"`.\n */\n tagField?: string;\n /** Stream event source mapping config — set via `.stream({...})` builder method. */\n stream?: TableStreamConfig;\n};\n\n/**\n * DynamoDB stream record passed to onRecord callback.\n *\n * `new` and `old` are full `TableItem<T>` objects with the single-table envelope.\n *\n * @typeParam T - Type of the domain data (inside `data`)\n */\nexport type TableRecord<T = Record<string, unknown>> = {\n /** Type of modification: INSERT, MODIFY, or REMOVE */\n eventName: \"INSERT\" | \"MODIFY\" | \"REMOVE\";\n /** New item value (present for INSERT and MODIFY) */\n new?: TableItem<T>;\n /** Old item value (present for MODIFY and REMOVE) */\n old?: TableItem<T>;\n /** Primary key of the affected item */\n keys: { pk: string; sk: string };\n /** Sequence number for ordering */\n sequenceNumber?: string;\n /** Approximate timestamp when the modification occurred */\n timestamp?: number;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives table/deps/config/files based on what was declared */\ntype SetupArgs<T, D, P, HasFiles extends boolean> =\n & { table: TableClient<T> }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/**\n * Callback function type for processing a single DynamoDB stream record.\n * Receives the current record and the full batch for context.\n */\nexport type TableRecordFn<T = Record<string, unknown>, C = undefined> =\n (args: { record: TableRecord<T>; batch: readonly TableRecord<T>[] }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Batch handler function for DynamoDB stream records.\n * Called once with all records in the batch.\n * Return `{ failures: string[] }` (sequence numbers) for partial batch failure reporting.\n */\nexport type TableBatchFn<T = Record<string, unknown>, C = undefined> =\n (args: { records: readonly TableRecord<T>[] }\n & SpreadCtx<C>\n ) => Promise<void | { failures: string[] }>;\n\n// ============ Static config ============\n\n/** Static config extracted by AST (no runtime callbacks) */\n// TableConfig is already defined above and used as the extracted config type.\n\n// ============ Handler type (base interface for runtime wrappers and annotations) ============\n\n/**\n * Handler object created by defineTable.\n * Used by runtime wrappers and as type annotation for circular deps.\n * @internal\n */\nexport type TableHandler<T = Record<string, unknown>, C = any> = {\n readonly __brand: \"effortless-table\";\n readonly __spec: TableConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onRecord?: (...args: any[]) => any;\n readonly onRecordBatch?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineTable()` — resource config only. Stream options go in `.stream({...})`. */\ntype TableOptions<T> = {\n /** DynamoDB billing mode (default: \"PAY_PER_REQUEST\") */\n billingMode?: \"PAY_PER_REQUEST\" | \"PROVISIONED\";\n /** Name of the field in `data` that serves as the entity type discriminant (default: \"tag\") */\n tagField?: Extract<keyof T, string>;\n /** Decode/validate function for the `data` portion of stream records */\n schema?: (input: unknown) => T;\n};\n\n// ============ Builder ============\n\ninterface TableBuilder<\n T = Record<string, unknown>,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n HasStream extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): TableBuilder<T, D2, P, C, HasFiles, HasStream>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): TableBuilder<T, D, P2, C, HasFiles, HasStream>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): TableBuilder<T, D, P, C, true, HasStream>;\n\n /** Configure the DynamoDB stream event source mapping (batch size, retries, concurrency, etc.). */\n stream: HasStream extends true\n ? never\n : (opts: TableStreamConfig) => TableBuilder<T, D, P, C, HasFiles, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): TableBuilder<T, D, P, C, HasFiles, HasStream>;\n\n /** Initialize shared state on cold start. Receives table (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<T, D, P, HasFiles>) => C2 | Promise<C2>\n ): TableBuilder<T, D, P, C2, HasFiles, HasStream>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<T, D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): TableBuilder<T, D, P, C2, HasFiles, HasStream>;\n\n /** Handle errors thrown by onRecord/onRecordBatch */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): TableBuilder<T, D, P, C, HasFiles, HasStream>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): TableBuilder<T, D, P, C, HasFiles, HasStream>;\n\n /** Per-record stream handler (terminal — returns finalized handler) */\n onRecord(\n fn: TableRecordFn<T, C>\n ): TableHandler<T, C>;\n\n /** Batch stream handler (terminal — returns finalized handler) */\n onRecordBatch(\n fn: TableBatchFn<T, C>\n ): TableHandler<T, C>;\n\n /** Finalize as resource-only table (no Lambda) */\n build(): TableHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a DynamoDB table with optional stream handler (single-table design).\n *\n * Creates a table with fixed key schema: `pk (S)` + `sk (S)`, plus `tag (S)`,\n * `data (M)`, and `ttl (N)` attributes. TTL is always enabled.\n *\n * @see {@link https://effortless-aws.website/use-cases/database | Database guide}\n *\n * @example\n * ```typescript\n * export const orders = defineTable<OrderData>()\n * .stream({ batchSize: 10, concurrency: 5, maxRetries: 3 })\n * .setup(({ table }) => ({ table }))\n * .onRecord(async ({ record, table }) => {\n * if (record.eventName === \"INSERT\") {\n * console.log(\"New order:\", record.new?.data);\n * }\n * })\n * ```\n */\nexport function defineTable<T = Record<string, unknown>>(): TableBuilder<T>;\nexport function defineTable<T = Record<string, unknown>>(\n options: TableOptions<T>,\n): TableBuilder<T>;\nexport function defineTable<T = Record<string, unknown>>(\n options?: TableOptions<T>,\n): TableBuilder<T> {\n const {\n schema,\n ...tableConfig\n } = options ?? {} as TableOptions<T>;\n\n const spec: TableConfig = { ...tableConfig };\n\n const state: {\n spec: TableConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n schema?: (input: unknown) => T;\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onRecord?: (...args: any[]) => any;\n onRecordBatch?: (...args: any[]) => any;\n } = {\n spec,\n ...(schema ? { schema } : {}),\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): TableHandler<T> => ({\n __brand: \"effortless-table\",\n __spec: state.spec,\n ...(state.schema ? { schema: state.schema } : {}),\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onRecord ? { onRecord: state.onRecord } : {}),\n ...(state.onRecordBatch ? { onRecordBatch: state.onRecordBatch } : {}),\n }) as TableHandler<T>;\n\n const builder: TableBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n stream(opts: TableStreamConfig) {\n state.spec = { ...state.spec, stream: { ...state.spec.stream, ...opts } };\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onRecord(fn) {\n state.onRecord = fn as any;\n return finalize() as any;\n },\n onRecordBatch(fn) {\n state.onRecordBatch = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { LambdaWithPermissions } from \"./handler-options\";\n\n/**\n * Configuration for deploying an SSR framework (Nuxt, Astro, etc.)\n * via CloudFront + S3 (static assets) + Lambda Function URL (server-side rendering).\n */\nexport type AppConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Directory containing the Lambda server handler (e.g., \".output/server\").\n * Must contain an `index.mjs` (or `index.js`) that exports a `handler` function. */\n server: string;\n /** Directory containing static assets for S3 (e.g., \".output/public\") */\n assets: string;\n /** Base URL path (default: \"/\") */\n path?: string;\n /** Shell command to build the framework output (e.g., \"nuxt build\") */\n build?: string;\n /** Custom domain name. String or stage-keyed record (e.g., { prod: \"app.example.com\" }). */\n domain?: string | Record<string, string>;\n /** CloudFront route overrides: path patterns forwarded to API Gateway instead of the SSR Lambda.\n * Keys are CloudFront path patterns (e.g., \"/api/*\"), values are handler references.\n * Example: `routes: { \"/api/*\": api }` */\n routes?: Record<string, { readonly __brand: string }>;\n};\n\n/**\n * Internal handler object created by defineApp\n * @internal\n */\nexport type AppHandler = {\n readonly __brand: \"effortless-app\";\n readonly __spec: AppConfig;\n};\n\n/**\n * Deploy an SSR framework application via CloudFront + Lambda Function URL.\n *\n * Static assets from the `assets` directory are served via S3 + CloudFront CDN.\n * Server-rendered pages are handled by a Lambda function using the framework's\n * built output from the `server` directory.\n *\n * For static-only sites (no SSR), use {@link defineStaticSite} instead.\n *\n * @see {@link https://effortless-aws.website/use-cases/web-app | Web app guide}\n *\n * @param options - App configuration: server directory, assets directory, optional build command\n * @returns Handler object used by the deployment system\n */\nexport const defineApp = () => (options: AppConfig): AppHandler => ({\n __brand: \"effortless-app\",\n __spec: options,\n});\n","/** Any branded handler that deploys to API Gateway or S3 */\ntype AnyRoutableHandler = { readonly __brand: string };\n\n/** Simplified request object passed to middleware */\nexport type MiddlewareRequest = {\n uri: string;\n method: string;\n querystring: string;\n headers: Record<string, string>;\n cookies: Record<string, string>;\n};\n\n/** Redirect the user to another URL */\nexport type MiddlewareRedirect = {\n redirect: string;\n status?: 301 | 302 | 307 | 308;\n};\n\n/** Deny access with a 403 status */\nexport type MiddlewareDeny = {\n status: 403;\n body?: string;\n};\n\n/** Middleware return type: redirect, deny, or void (continue serving) */\nexport type MiddlewareResult = MiddlewareRedirect | MiddlewareDeny | void;\n\n/** Function that runs before serving static files via Lambda@Edge */\nexport type MiddlewareHandler = (\n request: MiddlewareRequest\n) => Promise<MiddlewareResult> | MiddlewareResult;\n\n/** SEO options for auto-generating sitemap.xml, robots.txt, and submitting to Google Indexing API */\nexport type StaticSiteSeo = {\n /** Sitemap filename (e.g. \"sitemap.xml\", \"sitemap-v2.xml\") */\n sitemap: string;\n /** Path to Google service account JSON key file for Indexing API batch submission.\n * Requires adding the service account email as an owner in Google Search Console. */\n googleIndexing?: string;\n};\n\n/**\n * Configuration for a static site (S3 + CloudFront)\n */\nexport type StaticSiteConfig = {\n /** Directory containing the static site files, relative to project root */\n dir: string;\n /** Default file for directory requests (default: \"index.html\") */\n index?: string;\n /** Shell command to run before deploy to generate site content (e.g., \"npx astro build\") */\n build?: string;\n /** Path to a custom error page relative to `dir`.\n * - If set to the same value as `index` (e.g. \"index.html\"), enables SPA mode:\n * all paths that don't match a file are served with `index.html` (HTTP 200), letting the client-side router handle them.\n * - If set to a different file (e.g. \"404.html\"), that file is served with HTTP 404 for missing paths.\n * - If omitted, a default 404 page is auto-generated. */\n errorPage?: string;\n /** Custom domain name. Accepts a string (same domain for all stages) or a Record mapping stage names to domains (e.g., `{ prod: \"example.com\", dev: \"dev.example.com\" }`). Requires an ACM certificate in us-east-1. If the cert also covers www, a 301 redirect from www to non-www is set up automatically. */\n domain?: string | Record<string, string>;\n /** SEO: auto-generate sitemap.xml and robots.txt at deploy time, optionally submit URLs to Google Indexing API */\n seo?: StaticSiteSeo;\n};\n\n/** Route entry stored on the static site handler */\ntype StaticSiteRouteEntry = {\n pattern: string;\n origin: AnyRoutableHandler;\n access?: \"private\" | \"public\";\n};\n\n/**\n * Internal handler object created by defineStaticSite\n * @internal\n */\nexport type StaticSiteHandler = {\n readonly __brand: \"effortless-static-site\";\n readonly __spec: StaticSiteConfig;\n readonly routes: StaticSiteRouteEntry[];\n readonly middleware?: MiddlewareHandler;\n};\n\n/** Builder for configuring a static site before calling `.build()` */\nexport type StaticSiteBuilder = {\n route(pattern: string, origin: AnyRoutableHandler, opts?: { access?: \"private\" | \"public\" }): StaticSiteBuilder;\n middleware(fn: MiddlewareHandler): StaticSiteBuilder;\n build(): StaticSiteHandler;\n};\n\n/**\n * Deploy a static site via S3 + CloudFront CDN, with optional API and bucket route overrides.\n *\n * @see {@link https://effortless-aws.website/use-cases/web-app | Web app guide}\n *\n * @param options - Static site configuration: directory, optional SPA mode, build command, domain\n * @returns Builder with `.route()`, `.middleware()`, and `.build()` methods\n */\nexport function defineStaticSite(options: StaticSiteConfig): StaticSiteBuilder {\n const state = {\n spec: { ...options },\n routes: [] as StaticSiteRouteEntry[],\n middleware: undefined as MiddlewareHandler | undefined,\n };\n\n const builder = {\n route(pattern: string, origin: AnyRoutableHandler, opts?: { access?: \"private\" | \"public\" }) {\n state.routes.push({ pattern, origin, ...(opts?.access ? { access: opts.access } : {}) });\n return builder;\n },\n middleware(fn: MiddlewareHandler) {\n state.middleware = fn;\n return builder;\n },\n build(): StaticSiteHandler {\n return {\n __brand: \"effortless-static-site\",\n __spec: state.spec,\n routes: state.routes,\n ...(state.middleware ? { middleware: state.middleware } : {}),\n };\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LogLevel, Permission, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n/**\n * Parsed SQS message passed to the handler callbacks.\n *\n * @typeParam T - Type of the decoded message body (from schema function)\n */\nexport type QueueMessage<T = unknown> = {\n /** Unique message identifier */\n messageId: string;\n /** Receipt handle for acknowledgement */\n receiptHandle: string;\n /** Parsed message body (JSON-decoded, then optionally schema-validated) */\n body: T;\n /** Raw unparsed message body string */\n rawBody: string;\n /** Message group ID (FIFO ordering key, empty string for standard queues) */\n messageGroupId: string;\n /** Message deduplication ID (FIFO only) */\n messageDeduplicationId?: string;\n /** SQS message attributes */\n messageAttributes: Record<string, { dataType?: string; stringValue?: string }>;\n /** Approximate first receive timestamp */\n approximateFirstReceiveTimestamp?: string;\n /** Approximate receive count */\n approximateReceiveCount?: string;\n /** Sent timestamp */\n sentTimestamp?: string;\n};\n\n/**\n * Event source mapping (poller) configuration — how Lambda consumes the queue.\n */\nexport type QueuePollerConfig = {\n /** Number of messages per Lambda invocation (1-10 for FIFO, default: 10) */\n batchSize?: number;\n /** Maximum time to gather messages before invoking (default: 0). Accepts `\"5s\"`, `\"1m\"`, etc. */\n batchWindow?: Duration;\n};\n\n/**\n * Configuration options for a queue handler.\n */\nexport type QueueConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: Duration; logLevel?: LogLevel; permissions?: Permission[] };\n /**\n * Whether this is a FIFO queue (ordered, exactly-once).\n * Currently only `true` is supported — standard queue support is planned.\n */\n fifo?: boolean;\n /** Visibility timeout (default: max of timeout or 30s). Accepts `\"30s\"`, `\"5m\"`, etc. */\n visibilityTimeout?: Duration;\n /** Message retention period (default: `\"4d\"`). Accepts `\"1h\"`, `\"7d\"`, etc. */\n retentionPeriod?: Duration;\n /** Delivery delay for all messages in the queue (default: 0). Accepts `\"30s\"`, `\"5m\"`, etc. */\n delay?: Duration;\n /** Enable content-based deduplication for FIFO queues (default: true) */\n contentBasedDeduplication?: boolean;\n /** Max number of receives before a message is sent to the dead-letter queue (default: 3) */\n maxReceiveCount?: number;\n /** Event source mapping config — set via `.poller({...})` builder method. */\n poller?: QueuePollerConfig;\n};\n\n// ============ Setup args ============\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/**\n * Per-message handler function.\n * Called once per message in the batch. Failures are reported individually.\n */\nexport type QueueMessageFn<T = unknown, C = undefined> =\n (args: { message: QueueMessage<T> }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Batch handler function.\n * Called once with all messages in the batch.\n * Return `{ failures: string[] }` (messageIds) for partial batch failure reporting.\n */\nexport type QueueBatchFn<T = unknown, C = undefined> =\n (args: { messages: QueueMessage<T>[] }\n & SpreadCtx<C>\n ) => Promise<void | { failures: string[] }>;\n\n// ============ Internal handler object ============\n\n/**\n * Internal handler object created by defineQueue.\n * @internal\n */\nexport type QueueHandler<T = unknown, C = any> = {\n readonly __brand: \"effortless-queue\";\n readonly __spec: QueueConfig;\n readonly schema?: (input: unknown) => T;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onMessage?: (...args: any[]) => any;\n readonly onMessageBatch?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineQueue()` — queue resource config */\ntype QueueOptions<T> = {\n /**\n * Whether this is a FIFO queue (ordered, exactly-once).\n * Currently only `true` is supported — standard queue support is planned.\n */\n fifo?: boolean;\n /** Visibility timeout (default: max of timeout or 30s) */\n visibilityTimeout?: Duration;\n /** Message retention period (default: \"4d\") */\n retentionPeriod?: Duration;\n /** Delivery delay for all messages in the queue (default: 0) */\n delay?: Duration;\n /** Enable content-based deduplication for FIFO queues (default: true) */\n contentBasedDeduplication?: boolean;\n /** Max number of receives before DLQ (default: 3) */\n maxReceiveCount?: number;\n /** Decode/validate function for the message body */\n schema?: (input: unknown) => T;\n};\n\n// ============ Builder ============\n\ninterface QueueBuilder<\n T = unknown,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): QueueBuilder<T, D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): QueueBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): QueueBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, etc.) */\n setup(\n lambda: LambdaOptions\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): QueueBuilder<T, D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): QueueBuilder<T, D, P, C2, HasFiles>;\n\n /**\n * Configure the event source mapping (poller) that delivers messages to the Lambda.\n * Call before the terminal `.onMessage` / `.onMessageBatch`.\n */\n poller(\n options: QueuePollerConfig\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Handle errors thrown by message handlers */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): QueueBuilder<T, D, P, C, HasFiles>;\n\n /** Per-message handler (terminal — returns finalized handler) */\n onMessage(\n fn: QueueMessageFn<T, C>\n ): QueueHandler<T, C>;\n\n /** Batch handler (terminal — returns finalized handler) */\n onMessageBatch(\n fn: QueueBatchFn<T, C>\n ): QueueHandler<T, C>;\n\n /** Finalize as a resource-only queue (no Lambda). Use when the SQS queue is consumed by an external system. */\n build(): QueueHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an SQS queue with a Lambda message handler.\n *\n * Currently only FIFO queues are supported — pass `{ fifo: true }` explicitly\n * (standard queue support is planned). Poller/batch settings live on the\n * `.poller({...})` builder method; queue-level properties stay in the options\n * object.\n *\n * @see {@link https://effortless-aws.website/use-cases/queue | Queue guide}\n *\n * @example\n * ```typescript\n * export const notifications = defineQueue<Notif>({ fifo: true })\n * .poller({ batchSize: 5, batchWindow: \"2s\" })\n * .onMessageBatch(async ({ messages }) => {\n * await sendAll(messages.map(m => m.body));\n * })\n * ```\n */\nexport function defineQueue<T = unknown>(): QueueBuilder<T>;\nexport function defineQueue<T = unknown>(\n options: QueueOptions<T>,\n): QueueBuilder<T>;\nexport function defineQueue<T = unknown>(\n options?: QueueOptions<T>,\n): QueueBuilder<T> {\n const {\n schema,\n ...queueConfig\n } = options ?? {} as QueueOptions<T>;\n\n const spec: QueueConfig = { ...queueConfig };\n\n const state: {\n spec: QueueConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n schema?: (input: unknown) => T;\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onMessage?: (...args: any[]) => any;\n onMessageBatch?: (...args: any[]) => any;\n } = {\n spec,\n ...(schema ? { schema } : {}),\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): QueueHandler<T> => ({\n __brand: \"effortless-queue\",\n __spec: state.spec,\n ...(state.schema ? { schema: state.schema } : {}),\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onMessage ? { onMessage: state.onMessage } : {}),\n ...(state.onMessageBatch ? { onMessageBatch: state.onMessageBatch } : {}),\n }) as QueueHandler<T>;\n\n const builder: QueueBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n poller(options) {\n if (Object.keys(options).length > 0) {\n state.spec = { ...state.spec, poller: { ...state.spec.poller, ...options } };\n }\n return builder;\n },\n onMessage(fn) {\n state.onMessage = fn as any;\n return finalize() as any;\n },\n onMessageBatch(fn) {\n state.onMessageBatch = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, Duration, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\nimport type { BucketClient, BucketClientWithEntities } from \"../runtime/bucket-client\";\n\n/**\n * Per-entity configuration for typed JSON key-value storage within a bucket.\n */\nexport type BucketEntityConfig = {\n /** Cache duration for CloudFront/browser caching (e.g., \"10s\", \"5m\", \"1h\"). No caching if omitted. */\n cache?: Duration;\n};\n\n/**\n * Configuration options for defineBucket.\n */\nexport type BucketConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: { memory?: number; timeout?: import(\"./handler-options\").Duration; logLevel?: import(\"./handler-options\").LogLevel; permissions?: import(\"./handler-options\").Permission[] };\n /** S3 key prefix filter for event notifications (e.g., \"uploads/\") */\n prefix?: string;\n /** S3 key suffix filter for event notifications (e.g., \".jpg\") */\n suffix?: string;\n /** Typed JSON entity definitions for key-value storage */\n entities?: Record<string, BucketEntityConfig>;\n /** Local directory to seed into bucket on deploy (only uploads files that don't already exist) */\n seed?: string;\n /** Local directory to sync into bucket on every deploy (uploads new/changed, deletes removed) */\n sync?: string;\n};\n\n/**\n * S3 event record passed to onObjectCreated/onObjectRemoved callbacks.\n */\nexport type BucketEvent = {\n /** S3 event name (e.g., \"ObjectCreated:Put\", \"ObjectRemoved:Delete\") */\n eventName: string;\n /** Object key (path within the bucket) */\n key: string;\n /** Object size in bytes (present for created events) */\n size?: number;\n /** Object ETag (present for created events) */\n eTag?: string;\n /** ISO 8601 timestamp of when the event occurred */\n eventTime?: string;\n /** S3 bucket name */\n bucketName: string;\n};\n\n// ============ Setup args ============\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n/** Setup factory — receives bucket/deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean, Entities extends Record<string, any> = {}> =\n & { bucket: {} extends Entities ? BucketClient : BucketClientWithEntities<Entities> }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/**\n * Callback function type for S3 ObjectCreated events\n */\nexport type BucketObjectCreatedFn<C = undefined> =\n (args: { event: BucketEvent }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n/**\n * Callback function type for S3 ObjectRemoved events\n */\nexport type BucketObjectRemovedFn<C = undefined> =\n (args: { event: BucketEvent }\n & SpreadCtx<C>\n ) => Promise<void>;\n\n// ============ Internal handler object ============\n\n/**\n * Internal handler object created by defineBucket\n * @internal\n */\nexport type BucketHandler<C = any, _Entities extends Record<string, any> = {}> = {\n readonly __brand: \"effortless-bucket\";\n readonly __spec: BucketConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onObjectCreated?: (...args: any[]) => any;\n readonly onObjectRemoved?: (...args: any[]) => any;\n};\n\n// ============ Builder options ============\n\n/** Options passed to `defineBucket()` — static config */\ntype BucketOptions = {\n /** S3 key prefix filter for event notifications (e.g., \"uploads/\") */\n prefix?: string;\n /** S3 key suffix filter for event notifications (e.g., \".jpg\") */\n suffix?: string;\n /** Local directory to seed into bucket on deploy (only uploads files that don't already exist) */\n seed?: string;\n /** Local directory to sync into bucket on every deploy (uploads new/changed, deletes removed) */\n sync?: string;\n};\n\n// ============ Builder ============\n\ninterface BucketBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n Entities extends Record<string, any> = {},\n> {\n /** Declare handler dependencies */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): BucketBuilder<D2, P, C, HasFiles, Entities>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): BucketBuilder<D, P2, C, HasFiles, Entities>;\n\n /** Include static files in the Lambda ZIP */\n include(glob: string): BucketBuilder<D, P, C, true, Entities>;\n\n /** Register a typed JSON entity stored as `{name}/{id}.json` in the bucket */\n entity<N extends string, T>(\n name: N,\n options?: BucketEntityConfig,\n ): BucketBuilder<D, P, C, HasFiles, Entities & { [K in N]: T }>;\n\n /** Initialize shared state on cold start with lambda options */\n setup(lambda: LambdaOptions): BucketBuilder<D, P, C, HasFiles, Entities>;\n /** Initialize shared state on cold start. Receives bucket (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles, Entities>) => C2 | Promise<C2>\n ): BucketBuilder<D, P, C2, HasFiles, Entities>;\n /** Initialize shared state on cold start with lambda options. Receives bucket (self-client), deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles, Entities>) => C2 | Promise<C2>,\n lambda: LambdaOptions,\n ): BucketBuilder<D, P, C2, HasFiles, Entities>;\n\n /** Handle errors thrown by callbacks */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): BucketBuilder<D, P, C, HasFiles, Entities>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): BucketBuilder<D, P, C, HasFiles, Entities>;\n\n /** Handle S3 ObjectCreated events (terminal — returns finalized handler) */\n onObjectCreated(\n fn: BucketObjectCreatedFn<C>\n ): BucketHandler<C, Entities>;\n\n /** Handle S3 ObjectRemoved events (terminal — returns finalized handler) */\n onObjectRemoved(\n fn: BucketObjectRemovedFn<C>\n ): BucketHandler<C, Entities>;\n\n /** Finalize as resource-only bucket (no Lambda) */\n build(): BucketHandler<C, Entities>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an S3 bucket with optional event handlers.\n *\n * @see {@link https://effortless-aws.website/use-cases/storage | Storage guide}\n *\n * @example\n * ```typescript\n * export const uploads = defineBucket({ prefix: \"images/\", suffix: \".jpg\" })\n * .onObjectCreated(async ({ event, bucket }) => {\n * console.log(\"New upload:\", event.key);\n * })\n * ```\n */\nexport function defineBucket(): BucketBuilder;\nexport function defineBucket(\n options: BucketOptions,\n): BucketBuilder;\nexport function defineBucket(\n options?: BucketOptions,\n): BucketBuilder {\n const bucketConfig = options ?? {} as BucketOptions;\n\n const spec: BucketConfig = {\n ...bucketConfig,\n };\n\n const state: {\n spec: BucketConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onObjectCreated?: (...args: any[]) => any;\n onObjectRemoved?: (...args: any[]) => any;\n } = {\n spec,\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): BucketHandler => ({\n __brand: \"effortless-bucket\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onObjectCreated ? { onObjectCreated: state.onObjectCreated } : {}),\n ...(state.onObjectRemoved ? { onObjectRemoved: state.onObjectRemoved } : {}),\n }) as BucketHandler;\n\n const builder: BucketBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n entity(name: string, options?: BucketEntityConfig) {\n state.spec = {\n ...state.spec,\n entities: { ...state.spec.entities, [name]: options ?? {} },\n };\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onObjectCreated(fn) {\n state.onObjectCreated = fn as any;\n return finalize() as any;\n },\n onObjectRemoved(fn) {\n state.onObjectRemoved = fn as any;\n return finalize() as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","/**\n * Configuration options for defining a mailer (SES email identity)\n */\nexport type MailerConfig = {\n /** Domain to verify and send emails from (e.g., \"myapp.com\") */\n domain: string;\n};\n\n/**\n * Internal handler object created by defineMailer\n * @internal\n */\nexport type MailerHandler = {\n readonly __brand: \"effortless-mailer\";\n readonly __spec: MailerConfig;\n};\n\n/**\n * Define an email sender backed by Amazon SES.\n *\n * Creates an SES Email Identity for the specified domain and provides\n * a typed `EmailClient` to other handlers via `deps`.\n *\n * On first deploy, DKIM DNS records are printed to the console.\n * Add them to your DNS provider to verify the domain.\n *\n * @see {@link https://effortless-aws.website/use-cases/email | Email guide}\n *\n * @param options - Mailer configuration with the domain to send from\n * @returns Handler object used by the deployment system and as a `deps` value\n *\n * @example Basic mailer with HTTP handler\n * ```typescript\n * export const mailer = defineMailer({ domain: \"myapp.com\" });\n *\n * export const api = defineApi({\n * basePath: \"/signup\",\n * deps: { mailer },\n * post: async ({ req, deps }) => {\n * await deps.mailer.send({\n * from: \"hello@myapp.com\",\n * to: req.body.email,\n * subject: \"Welcome!\",\n * html: \"<h1>Hi!</h1>\",\n * });\n * return { status: 200, body: { ok: true } };\n * },\n * });\n * ```\n */\nexport const defineMailer = () => (options: MailerConfig): MailerHandler => ({\n __brand: \"effortless-mailer\",\n __spec: options,\n});\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { LambdaWithPermissions, AnySecretRef, ResolveConfig, Duration, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory, toSeconds } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles, ResponseStream } from \"./shared\";\nimport type { HttpRequest, HttpResponse } from \"./shared\";\nimport type { AuthHelpers } from \"./auth\";\n\n// ============ Auth types ============\n\n/** Auth config options (user-facing) */\nexport type AuthOptions<A = unknown> = {\n /** HMAC secret for signing session cookies. Use `secret()` or `param()` in config. */\n secret: string;\n /** Default session lifetime (default: \"7d\"). */\n expiresIn?: Duration;\n /** Optional API token strategy for external clients. */\n apiToken?: {\n /** HTTP header to read the token from. Default: \"authorization\" (strips \"Bearer \" prefix). */\n header?: string;\n /** Verify the token value and return session data, or null if invalid. */\n verify: (value: string) => A | null | Promise<A | null>;\n /** Cache verified token results for this duration. */\n cacheTtl?: Duration;\n };\n};\n\n/** Property names reserved by the framework — cannot be used in setup return */\ntype ReservedKeys = 'req' | 'input' | 'stream' | 'ok' | 'fail';\n\n// ============ Response helpers ============\n\n/** Success response helper: `ok({ data })` → `{ status: 200, body: { data } }` */\nexport type OkHelper = (body?: unknown, status?: number) => HttpResponse;\n/** Error response helper: `fail(\"message\")` → `{ status: 400, body: { error: \"message\" } }` */\nexport type FailHelper = (message: string, status?: number) => HttpResponse;\n\n// ============ Route types ============\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/** Cache options for a GET route. Duration shorthand (e.g. \"30s\", \"5m\") or object for fine-grained control. */\nexport type CacheOptions = Duration | {\n ttl: Duration;\n swr?: Duration;\n scope?: \"public\" | \"private\";\n};\n\n/** Resolved cache config with numeric seconds */\ntype ResolvedCache =\n | { private?: false; ttl: number; swr: number }\n | { private: true; ttl: number };\n\n/** Resolve CacheOptions into numeric seconds. Shorthand = public with swr = ttl * 2. */\nconst resolveCache = (cache: CacheOptions): ResolvedCache => {\n if (typeof cache === \"number\" || typeof cache === \"string\") {\n const ttl = toSeconds(cache);\n return { ttl, swr: ttl * 2 };\n }\n const ttl = toSeconds(cache.ttl);\n if (cache.scope === \"private\") {\n return { private: true, ttl };\n }\n return {\n ttl,\n swr: cache.swr != null ? toSeconds(cache.swr) : ttl * 2,\n };\n};\n\n/** Parsed route definition stored at runtime */\nexport type RouteEntry = {\n method: HttpMethod;\n path: string;\n onRequest: (...args: any[]) => any;\n schema?: unknown;\n public?: boolean;\n cache?: ResolvedCache;\n};\n\n/** Spread ctx into route args, add AuthHelpers if A is set */\ntype SpreadCtx<C, A = undefined> =\n & ([C] extends [undefined] ? {} : C & {})\n & ([A] extends [undefined] ? {} : { auth: AuthHelpers<A> });\n\n/** Infer validated output type from a Standard Schema, or fall back to unknown */\ntype InferInput<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : unknown;\n\n/** Callback args available inside each route — ctx is spread into args */\ntype RouteArgs<C, ST, A = undefined, S = undefined> =\n & SpreadCtx<C, A>\n & { req: HttpRequest; input: InferInput<S>; ok: OkHelper; fail: FailHelper }\n & ([ST] extends [true] ? { stream: ResponseStream } : {});\n\n/** Route handler function */\ntype RouteHandler<C, ST, A = undefined, S = undefined> = (args: RouteArgs<C, ST, A, S>) => Promise<HttpResponse | void> | HttpResponse | void;\n\n/** Route definition — pass `input` for typed schema validation */\ntype RouteDef<S extends StandardSchemaV1 | undefined = undefined> = {\n path: `/${string}`;\n input?: S;\n public?: boolean;\n cache?: CacheOptions;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & { ok: OkHelper; fail: FailHelper }\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Auth factory args — receives config/deps based on what was declared */\ntype AuthArgs<D, P> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> });\n\n/** Validate that setup return type does not use reserved property names */\ntype ValidateSetupReturn<C> = C & { [K in ReservedKeys]?: never };\n\n// ============ Static config ============\n\n/** Static config extracted by AST (no runtime callbacks) */\nexport type ApiConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Base path prefix for all routes (e.g., \"/api\") */\n basePath: `/${string}`;\n /** Enable response streaming. When true, the Lambda Function URL uses RESPONSE_STREAM invoke mode. */\n stream?: boolean;\n};\n\n// ============ Internal handler object ============\n\n/** Internal handler object created by defineApi */\nexport type ApiHandler<C = undefined> = {\n readonly __brand: \"effortless-api\";\n readonly __spec: ApiConfig;\n readonly onError?: (...args: any[]) => any | Promise<any>;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly routes?: RouteEntry[];\n};\n\n// ============ Builder options (plain values, no inference) ============\n\n/** Options passed to `defineApi()` */\ntype ApiOptions = {\n /** Base path prefix for all routes (e.g., \"/api\") */\n basePath: `/${string}`;\n /** Enable response streaming. When true, routes receive a `stream` arg for SSE. */\n stream?: boolean;\n};\n\n// ============ ApiRoutes — returned after first route method ============\n\n/**\n * Finalized API handler with route-adding methods.\n * Has `__brand` so CLI discovers it. Each `.get()/.post()` adds a route and returns self.\n */\nexport interface ApiRoutes<C = undefined, ST extends boolean = false, A = undefined> extends ApiHandler<C> {\n get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n}\n\n// ============ Builder ============\n\n/**\n * Builder interface for defining API handlers.\n *\n * Each method sets exactly one generic, so inference happens one step at a time.\n * This prevents cascading type errors when one property has a mistake.\n */\ninterface ApiBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n ST extends boolean = false,\n HasFiles extends boolean = false,\n A = undefined,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): ApiBuilder<D2, P, C, ST, HasFiles, A>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): ApiBuilder<D, P2, C, ST, HasFiles, A>;\n\n /** Include static files by glob pattern */\n include(glob: string): ApiBuilder<D, P, C, ST, true, A>;\n\n /** Configure session-based authentication. Receives resolved config/deps. */\n auth<A2>(\n fn: (args: AuthArgs<D, P>) => AuthOptions<A2> | Promise<AuthOptions<A2>>\n ): ApiBuilder<D, P, C, ST, HasFiles, A2>;\n\n /** Configure Lambda settings only (no init function) */\n setup(lambda: LambdaOptions): ApiBuilder<D, P, C, ST, HasFiles, A>;\n /** Initialize shared state on cold start. Receives deps/config/files based on what was declared. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => ValidateSetupReturn<C2> | Promise<ValidateSetupReturn<C2>>\n ): ApiBuilder<D, P, C2, ST, HasFiles, A>;\n /** Initialize shared state on cold start with Lambda config. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => ValidateSetupReturn<C2> | Promise<ValidateSetupReturn<C2>>,\n lambda: LambdaOptions,\n ): ApiBuilder<D, P, C2, ST, HasFiles, A>;\n\n /** Handle errors thrown by routes */\n onError(\n fn: (args: { error: unknown; req: HttpRequest; ok: OkHelper; fail: FailHelper } & SpreadCtx<C, A>) => HttpResponse | Promise<HttpResponse>\n ): ApiBuilder<D, P, C, ST, HasFiles, A>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C, A>) => void | Promise<void>\n ): ApiBuilder<D, P, C, ST, HasFiles, A>;\n\n get<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n post<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n put<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n patch<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n delete<S extends StandardSchemaV1 | undefined = undefined>(def: RouteDef<S>, handler: RouteHandler<C, ST, A, S>): ApiRoutes<C, ST, A>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an API with typed routes using a builder pattern.\n *\n * @see {@link https://effortless-aws.website/use-cases/http-api | HTTP API guide}\n *\n * @example\n * ```typescript\n * export const api = defineApi({ basePath: \"/api\" })\n * .deps(() => ({ users }))\n * .config(({ defineSecret }) => ({ authSecret: defineSecret() }))\n * .auth<Session>(({ config }) => ({ secret: config.authSecret, expiresIn: \"1h\" }))\n * .get({ path: \"/me\" }, async ({ users, auth, ok }) => ok(auth.session))\n * .post({ path: \"/login\", public: true }, async ({ auth, ok }) => ok(await auth.createSession()))\n * ```\n */\nexport function defineApi<const O extends ApiOptions>(\n options: O,\n): ApiBuilder<undefined, undefined, undefined, O[\"stream\"] extends true ? true : false, false>;\nexport function defineApi(\n options: ApiOptions,\n): ApiBuilder {\n const { basePath, stream } = options;\n\n const state: {\n spec: ApiConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n authFn?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n routes: RouteEntry[];\n } = {\n spec: {\n basePath,\n ...(stream ? { stream } : {}),\n },\n routes: [],\n };\n\n const addRoute = (method: HttpMethod, def: { path: string; input?: unknown; public?: boolean; cache?: CacheOptions }, handler: Function) => {\n const routeCache = def.cache != null\n ? resolveCache(def.cache)\n : undefined;\n\n state.routes.push({\n method,\n path: def.path,\n onRequest: handler as any,\n ...(def.input ? { schema: def.input } : {}),\n ...(def.public ? { public: true } : {}),\n ...(routeCache ? { cache: routeCache } : {}),\n });\n };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): ApiRoutes => {\n const handler: any = {\n __brand: \"effortless-api\",\n __spec: state.spec,\n routes: state.routes,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.authFn ? { authFn: state.authFn } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n };\n\n // Add route methods to the finalized handler\n for (const m of [\"get\", \"post\", \"put\", \"patch\", \"delete\"] as const) {\n handler[m] = (def: any, fn: any) => {\n addRoute(m.toUpperCase() as HttpMethod, def, fn);\n handler.routes = state.routes;\n return handler;\n };\n }\n\n return handler as ApiRoutes;\n };\n\n const builder: ApiBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob: string) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n auth(fn: any) {\n state.authFn = fn;\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n get(def: any, fn: any) { addRoute(\"GET\", def, fn); return finalize() as any; },\n post(def: any, fn: any) { addRoute(\"POST\", def, fn); return finalize() as any; },\n put(def: any, fn: any) { addRoute(\"PUT\", def, fn); return finalize() as any; },\n patch(def: any, fn: any) { addRoute(\"PATCH\", def, fn); return finalize() as any; },\n delete(def: any, fn: any) { addRoute(\"DELETE\", def, fn); return finalize() as any; },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\nimport type { Timezone } from \"./timezone\";\n\n// ============ Schedule expression ============\n\n/** Singular/plural unit for rate expressions */\ntype RateUnit = \"minute\" | \"minutes\" | \"hour\" | \"hours\" | \"day\" | \"days\";\n\n/**\n * Rate expression: `rate(1 hour)`, `rate(5 minutes)`, `rate(2 days)`\n *\n * Strictly typed — autocomplete and compile-time validation for unit.\n */\ntype RateExpression = `rate(${number} ${RateUnit})`;\n\n/**\n * Cron expression: `cron(min hour dom month dow year)`\n *\n * Not deeply typed (too combinatorial for TS), but the `cron(...)` wrapper is enforced.\n *\n * @example\n * ```\n * \"cron(0 9 * * ? *)\" // daily at 9:00 UTC\n * \"cron(0 9 ? * MON-FRI *)\" // weekdays at 9:00\n * \"cron(0/15 * * * ? *)\" // every 15 minutes\n * ```\n */\ntype CronExpression = `cron(${string})`;\n\n/**\n * EventBridge Scheduler schedule expression.\n *\n * - **Rate**: `\"rate(5 minutes)\"`, `\"rate(1 hour)\"`, `\"rate(1 day)\"` — strictly typed units\n * - **Cron**: `\"cron(0 9 * * ? *)\"` — 6 fields: min hour dom month dow year\n */\nexport type ScheduleExpression = RateExpression | CronExpression;\n\n// ============ Static config ============\n\n/** Static config extracted at deploy time */\nexport type CronConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** EventBridge Scheduler schedule expression */\n schedule: ScheduleExpression;\n /** IANA timezone for the schedule (default: UTC) */\n timezone?: Timezone;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Tick handler ============\n\n/** Callback function for cron tick */\nexport type CronTickFn<C = undefined> =\n (args: SpreadCtx<C>) => Promise<void> | void;\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineCron.\n * @internal\n */\nexport type CronHandler<C = any> = {\n readonly __brand: \"effortless-cron\";\n readonly __spec: CronConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onTick?: (...args: any[]) => any;\n};\n\n// ============ Options ============\n\n/** Options passed to `defineCron()` — resource config only */\ntype CronOptions = {\n /** EventBridge Scheduler schedule expression: `\"rate(5 minutes)\"` or `\"cron(0 9 * * ? *)\"` */\n schedule: ScheduleExpression;\n /** IANA timezone for the schedule (default: UTC). Full autocomplete for all timezones. */\n timezone?: Timezone;\n};\n\n// ============ Builder ============\n\ninterface CronBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): CronBuilder<D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): CronBuilder<D, P2, C, HasFiles>;\n\n /** Include static files in the Lambda bundle. Chainable — call multiple times. */\n include(glob: string): CronBuilder<D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): CronBuilder<D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): CronBuilder<D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): CronBuilder<D, P, C2, HasFiles>;\n\n /** Handle errors thrown by onTick */\n onError(\n fn: (args: { error: unknown } & SpreadCtx<C>) => void | Promise<void>\n ): CronBuilder<D, P, C, HasFiles>;\n\n /** Cleanup callback — runs after each invocation, before Lambda freezes */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): CronBuilder<D, P, C, HasFiles>;\n\n /** Tick handler — called on each scheduled invocation (terminal) */\n onTick(\n fn: CronTickFn<C>\n ): CronHandler<C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a cron job — scheduled Lambda invocation via EventBridge Scheduler.\n *\n * @example\n * ```typescript\n * export const sync = defineCron({ schedule: \"cron(0 9 * * ? *)\" })\n * .deps(() => ({ orders }))\n * .config(({ defineSecret }) => ({ apiKey: defineSecret() }))\n * .include(\"templates/*.html\")\n * .setup(async ({ deps, config, files }) => ({\n * db: deps.orders, key: config.apiKey, tpl: files,\n * }), { memory: 512 })\n * .onTick(async ({ db, key, tpl }) => {\n * const html = tpl.read(\"templates/report.html\")\n * })\n * ```\n */\nexport function defineCron(options: CronOptions): CronBuilder {\n const { schedule, timezone } = options;\n\n const spec: CronConfig = {\n schedule,\n ...(timezone ? { timezone } : {}),\n };\n\n const state: {\n spec: CronConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onTick?: (...args: any[]) => any;\n } = { spec };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): CronHandler => ({\n __brand: \"effortless-cron\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onTick ? { onTick: state.onTick } : {}),\n }) as CronHandler;\n\n const builder: CronBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n onTick(fn) {\n state.onTick = fn as any;\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions, Duration } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n// ============ Static config ============\n\n/** Fargate container size presets */\nexport type FargateSize =\n | \"0.25vCPU-512mb\"\n | \"0.5vCPU-1gb\"\n | \"1vCPU-2gb\"\n | \"2vCPU-4gb\"\n | \"4vCPU-8gb\";\n\n/** Static config extracted at deploy time */\nexport type WorkerConfig = {\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n /** Fargate size (default: \"0.5vCPU-1gb\") */\n size?: FargateSize;\n /** How long the worker stays alive without messages before shutting down (default: \"5m\") */\n idleTimeout?: Duration;\n /** Max messages processed in parallel (default: 1, max: 10) */\n concurrency?: number;\n};\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Handler callback ============\n\n/** Callback function for the worker's onMessage */\nexport type WorkerMessageFn<T, C = undefined> =\n (msg: T, ctx: SpreadCtx<C>) => Promise<void> | void;\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineWorker.\n * @internal\n */\nexport type WorkerHandler<_T = any, C = any> = {\n readonly __brand: \"effortless-worker\";\n readonly __spec: WorkerConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly onMessage?: (...args: any[]) => any;\n};\n\n// ============ Options ============\n\n/** Options passed to `defineWorker()` — resource config only */\ntype WorkerOptions = {\n /** Fargate size (default: \"0.5vCPU-1gb\") */\n size?: FargateSize;\n /** How long the worker stays alive without messages before shutting down (default: \"5m\") */\n idleTimeout?: Duration;\n /** Max messages processed in parallel (default: 1, max: 10) */\n concurrency?: number;\n};\n\n// ============ Builder ============\n\ninterface WorkerBuilder<\n T,\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers, workers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): WorkerBuilder<T, D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): WorkerBuilder<T, D, P2, C, HasFiles>;\n\n /** Include static files in the bundle. Chainable — call multiple times. */\n include(glob: string): WorkerBuilder<T, D, P, C, true>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): WorkerBuilder<T, D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): WorkerBuilder<T, D, P, C2, HasFiles>;\n\n /** Handle errors thrown by onMessage. Return \"delete\" to discard, \"retry\" to reprocess (default). */\n onError(\n fn: (args: { error: unknown; msg: T; retryCount: number } & SpreadCtx<C>) => \"retry\" | \"delete\" | void | Promise<\"retry\" | \"delete\" | void>\n ): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Cleanup callback — runs when the worker shuts down */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): WorkerBuilder<T, D, P, C, HasFiles>;\n\n /** Process a single message from the queue (terminal) */\n onMessage(\n fn: WorkerMessageFn<T, C>\n ): WorkerHandler<T, C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define a worker — a long-running Fargate container with an SQS queue.\n *\n * The worker stays alive while processing messages and shuts down after\n * an idle timeout with no new messages. Other handlers can send messages\n * to the worker via `deps.worker.send(msg)`.\n *\n * @typeParam T - Type of messages the worker receives via its queue\n *\n * @example\n * ```typescript\n * type Job = { type: \"export\"; userId: string }\n *\n * export const worker = defineWorker<Job>({ memory: 2048, concurrency: 5 })\n * .deps(() => ({ orders }))\n * .setup(async ({ deps }) => ({ db: deps.orders }))\n * .onMessage(async (msg, { db }) => {\n * await processJob(msg, db)\n * })\n * ```\n */\nexport function defineWorker<T = unknown>(options?: WorkerOptions): WorkerBuilder<T> {\n const spec: WorkerConfig = {\n ...(options?.size ? { size: options.size } : {}),\n ...(options?.idleTimeout ? { idleTimeout: options.idleTimeout } : {}),\n ...(options?.concurrency ? { concurrency: options.concurrency } : {}),\n };\n\n const state: {\n spec: WorkerConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n onMessage?: (...args: any[]) => any;\n } = { spec };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n const finalize = (): WorkerHandler => ({\n __brand: \"effortless-worker\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(state.onMessage ? { onMessage: state.onMessage } : {}),\n }) as WorkerHandler;\n\n const builder: WorkerBuilder<T> = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n onMessage(fn) {\n state.onMessage = fn as any;\n return finalize() as any;\n },\n };\n\n return builder;\n}\n","import type { StandardSchemaV1, StandardJSONSchemaV1 } from \"@standard-schema/spec\";\nimport type { AnySecretRef, ResolveConfig, LambdaWithPermissions, ConfigFactory, LambdaOptions } from \"./handler-options\";\nimport { resolveConfigFactory } from \"./handler-options\";\nimport type { AnyDepHandler, ResolveDeps } from \"./handler-deps\";\nimport type { StaticFiles } from \"./shared\";\n\n// ============ Static config ============\n\n/** Static config extracted at deploy time */\nexport type McpConfig = {\n /** MCP server name (used in server info) */\n name: string;\n /** MCP server version (default: \"1.0.0\") */\n version?: string;\n /** Human-readable description — sent to clients in initialize response as system prompt context */\n instructions?: string;\n /** Lambda function settings (memory, timeout, permissions, etc.) */\n lambda?: LambdaWithPermissions;\n};\n\n// ============ MCP tool types ============\n\n/** JSON Schema for a tool's input parameters */\nexport type McpInputSchema = {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n};\n\n/** Content block returned by a tool handler */\nexport type McpToolContent =\n | { type: \"text\"; text: string }\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"resource\"; resource: { uri: string; text: string; mimeType?: string } };\n\n/** Result returned by a tool handler */\nexport type McpToolResult = {\n content: McpToolContent[];\n isError?: boolean;\n};\n\n// ============ MCP resource types ============\n\n/** Content returned when reading a resource */\nexport type McpResourceContent =\n | { uri: string; mimeType?: string; text: string }\n | { uri: string; mimeType?: string; blob: string };\n\n/** Legacy resource content result type used by runtime internals */\ntype McpResourceResult = McpResourceContent | McpResourceContent[] | Promise<McpResourceContent | McpResourceContent[]>;\n\n/** Infer resource params type from schema, or fall back to Record<string, string> */\ntype InferResourceParams<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : Record<string, string>;\n\n/** Resource definition — pass `params` for typed schema validation on URI template params */\nexport type McpResourceDefInput<S extends StandardSchemaV1 | undefined = undefined> = {\n /** Resource URI or URI template (e.g. \"resource://contacts/{id}\") */\n uri: string;\n /** Human-readable name */\n name: string;\n /** Schema for URI template params — provides type inference + validation */\n params?: S;\n /** Optional description */\n description?: string;\n /** Optional MIME type */\n mimeType?: string;\n};\n\n/** What resource handlers can return — plain data is auto-wrapped by the runtime */\ntype McpResourceReturn = unknown | Promise<unknown>;\n\n/** Resource handler — receives params (typed or Record<string, string>) and ctx */\ntype McpResourceHandler<C, S = undefined> = (params: InferResourceParams<S>, ctx: SpreadCtx<C>) => McpResourceReturn;\n\n// Legacy types used by wrap-mcp runtime\n/** @internal */\nexport type McpResourceDef<C = undefined> = {\n name: string;\n description?: string;\n mimeType?: string;\n handler: (ctx: SpreadCtx<C>) => McpResourceResult;\n};\n\n/** @internal */\nexport type McpResourceTemplateDef<C = undefined> = {\n name: string;\n description?: string;\n mimeType?: string;\n handler: (params: Record<string, string>, ctx: SpreadCtx<C>) => McpResourceResult;\n};\n\n/** @internal */\nexport type McpResourceMap<C = undefined> = {\n [uriOrTemplate: string]: McpResourceDef<C> | McpResourceTemplateDef<C>;\n};\n\n// ============ MCP prompt types ============\n\n/** An argument accepted by a prompt */\nexport type McpPromptArgument = {\n /** Argument name */\n name: string;\n /** Optional description */\n description?: string;\n /** Whether the argument is required (default: false) */\n required?: boolean;\n};\n\n/** Content block inside a prompt message */\nexport type McpPromptContent =\n | { type: \"text\"; text: string }\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"audio\"; data: string; mimeType: string }\n | { type: \"resource\"; resource: { uri: string; mimeType?: string; text?: string; blob?: string } };\n\n/** A single message returned by a prompt */\nexport type McpPromptMessage = {\n role: \"user\" | \"assistant\";\n content: McpPromptContent;\n};\n\n/** Result returned by a prompt handler */\nexport type McpPromptResult = {\n description?: string;\n messages: McpPromptMessage[];\n};\n\n/** @internal Legacy prompt definition used by runtime */\nexport type McpPromptDef<C = undefined> = {\n description?: string;\n arguments?: McpPromptArgument[];\n handler: (args: Record<string, string>, ctx: SpreadCtx<C>) => McpPromptResult | Promise<McpPromptResult>;\n};\n\n/** Infer prompt args type from schema, or fall back to Record<string, string> */\ntype InferPromptArgs<S> = S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : Record<string, string>;\n\n/** Prompt definition — pass a Standard Schema for `args` for typed validation, or McpPromptArgument[] for untyped */\nexport type McpPromptDefInput<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined> = {\n /** Prompt name */\n name: string;\n /** Human-readable description */\n description?: string;\n /** Args: Standard Schema for typed validation, or McpPromptArgument[] for untyped */\n args?: S;\n};\n\n/** Handler return: string auto-wraps as user message, or return full McpPromptResult */\ntype McpPromptReturn = string | McpPromptResult | Promise<string | McpPromptResult>;\n\n/** Prompt handler — receives args (typed or Record<string, string>) and ctx */\ntype McpPromptHandler<C, S = undefined> = (args: InferPromptArgs<S>, ctx: SpreadCtx<C>) => McpPromptReturn;\n\n/** Infer tool input type from schema, or fall back to any */\ntype InferToolInput<S> = S extends StandardJSONSchemaV1 ? StandardJSONSchemaV1.InferOutput<S> : any;\n\n/** Tool definition — pass a StandardJSONSchemaV1 for `input` for typed validation, or McpInputSchema for raw JSON Schema */\nexport type McpToolDefInput<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema> = {\n /** Tool name */\n name: string;\n /** Human-readable description of the tool */\n description: string;\n /** Schema for tool input: StandardJSONSchemaV1 (e.g. z.object({...})) or raw McpInputSchema */\n input: S;\n};\n\n/** Tool handler — receives input (typed or any) and ctx */\ntype McpToolHandler<C, S = McpInputSchema> = (input: InferToolInput<S>, ctx: SpreadCtx<C>) => unknown | Promise<unknown>;\n\n// ============ Setup args ============\n\n/** Setup factory — receives deps/config/files based on what was declared */\ntype SetupArgs<D, P, HasFiles extends boolean> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> })\n & (HasFiles extends true ? { files: StaticFiles } : {});\n\n/** Auth factory args — receives config/deps based on what was declared */\ntype AuthArgs<D, P> =\n & ([D] extends [undefined] ? {} : { deps: ResolveDeps<D> })\n & ([P] extends [undefined] ? {} : { config: ResolveConfig<P & {}> });\n\n/** Spread ctx into callback args (empty when no setup) */\ntype SpreadCtx<C> = [C] extends [undefined] ? {} : C & {};\n\n// ============ Handler type ============\n\n/**\n * Handler object created by defineMcp.\n * @internal\n */\nexport type McpHandler<C = any> = {\n readonly __brand: \"effortless-mcp\";\n readonly __spec: McpConfig;\n readonly onError?: (...args: any[]) => any;\n readonly onCleanup?: (...args: any[]) => any;\n readonly setup?: (...args: any[]) => C | Promise<C>;\n readonly deps?: Record<string, unknown> | (() => Record<string, unknown>);\n readonly config?: Record<string, unknown>;\n readonly static?: string[];\n readonly resources?: (...args: any[]) => any;\n readonly prompts?: (...args: any[]) => any;\n readonly tools?: (...args: any[]) => any;\n};\n\n// ============ McpEntries — returned after first singular method ============\n\n/**\n * Finalized MCP handler with chainable registration methods.\n * Has `__brand` so CLI discovers it. Each `.tool()/.resource()/.prompt()` adds an entry and returns self.\n */\nexport interface McpEntries<C = undefined> extends McpHandler<C> {\n /** Register a tool */\n tool<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema>(def: McpToolDefInput<S>, handler: McpToolHandler<C, S>): McpEntries<C>;\n /** Register a resource */\n resource<S extends StandardSchemaV1 | undefined = undefined>(def: McpResourceDefInput<S>, handler: McpResourceHandler<C, S>): McpEntries<C>;\n /** Register a prompt */\n prompt<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined>(def: McpPromptDefInput<S>, handler: McpPromptHandler<C, S>): McpEntries<C>;\n}\n\n// ============ Options ============\n\n/** Options passed to `defineMcp()` */\ntype McpOptions = {\n /** MCP server name (used in server info) */\n name: string;\n /** MCP server version (default: \"1.0.0\") */\n version?: string;\n /** Human-readable description — sent to clients in initialize response as system prompt context */\n instructions?: string;\n};\n\n// ============ Builder ============\n\ninterface McpBuilder<\n D = undefined,\n P = undefined,\n C = undefined,\n HasFiles extends boolean = false,\n> {\n /** Declare handler dependencies (tables, queues, buckets, mailers, workers) */\n deps<D2 extends Record<string, AnyDepHandler>>(\n fn: () => D2\n ): McpBuilder<D2, P, C, HasFiles>;\n\n /** Declare SSM secrets */\n config<P2 extends Record<string, AnySecretRef>>(\n fn: ConfigFactory<P2>\n ): McpBuilder<D, P2, C, HasFiles>;\n\n /** Include static files in the bundle. Chainable — call multiple times. */\n include(glob: string): McpBuilder<D, P, C, true>;\n\n /** Configure session-based authentication. Receives resolved config/deps. All requests require a valid session. */\n auth<A2>(\n fn: (args: AuthArgs<D, P>) => import(\"./define-api\").AuthOptions<A2> | Promise<import(\"./define-api\").AuthOptions<A2>>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Configure Lambda settings only (memory, timeout, permissions, logLevel) */\n setup(lambda: LambdaOptions): McpBuilder<D, P, C, HasFiles>;\n\n /** Initialize shared state on cold start. Receives deps, config, files. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>\n ): McpBuilder<D, P, C2, HasFiles>;\n\n /** Initialize shared state on cold start + configure Lambda settings. */\n setup<C2>(\n fn: (args: SetupArgs<D, P, HasFiles>) => C2 | Promise<C2>,\n lambda: LambdaOptions\n ): McpBuilder<D, P, C2, HasFiles>;\n\n /** Handle errors thrown by tool handlers */\n onError(\n fn: (args: { error: unknown; toolName: string } & SpreadCtx<C>) => void | Promise<void>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Cleanup callback — runs on shutdown */\n onCleanup(\n fn: (args: SpreadCtx<C>) => void | Promise<void>\n ): McpBuilder<D, P, C, HasFiles>;\n\n /** Register a tool */\n tool<S extends StandardJSONSchemaV1 | McpInputSchema = McpInputSchema>(def: McpToolDefInput<S>, handler: McpToolHandler<C, S>): McpEntries<C>;\n /** Register a resource */\n resource<S extends StandardSchemaV1 | undefined = undefined>(def: McpResourceDefInput<S>, handler: McpResourceHandler<C, S>): McpEntries<C>;\n /** Register a prompt */\n prompt<S extends StandardSchemaV1 | McpPromptArgument[] | undefined = undefined>(def: McpPromptDefInput<S>, handler: McpPromptHandler<C, S>): McpEntries<C>;\n\n /** Finalize the handler without adding more entries */\n build(): McpHandler<C>;\n}\n\n// ============ Implementation ============\n\n/**\n * Define an MCP (Model Context Protocol) server endpoint.\n *\n * Creates a Lambda-backed MCP server that exposes tools, resources, and prompts\n * for AI models and MCP-compatible clients via Streamable HTTP (JSON-RPC over POST).\n *\n * @see https://modelcontextprotocol.io/specification/2025-03-26 — MCP specification\n * @see https://effortless-aws.com/use-cases/mcp-server/ — full documentation with examples\n */\nexport function defineMcp(options: McpOptions): McpBuilder {\n const spec: McpConfig = {\n name: options.name,\n ...(options.version ? { version: options.version } : {}),\n ...(options.instructions ? { instructions: options.instructions } : {}),\n };\n\n const state: {\n spec: McpConfig;\n deps?: () => Record<string, unknown>;\n config?: Record<string, unknown>;\n static?: string[];\n setup?: (...args: any[]) => any;\n authFn?: (...args: any[]) => any;\n onError?: (...args: any[]) => any;\n onCleanup?: (...args: any[]) => any;\n toolEntries: [string, any][];\n resourceEntries: [string, any][];\n promptEntries: [string, any][];\n } = { spec, toolEntries: [], resourceEntries: [], promptEntries: [] };\n\n const applyLambdaOptions = (lambda: LambdaOptions) => {\n if (Object.keys(lambda).length > 0) {\n state.spec = { ...state.spec, lambda: { ...state.spec.lambda, ...lambda } };\n }\n };\n\n // Build factory functions from accumulated entries\n const buildToolsFactory = () =>\n state.toolEntries.length > 0\n ? () => Object.fromEntries(state.toolEntries)\n : undefined;\n\n const buildResourcesFactory = () =>\n state.resourceEntries.length > 0\n ? () => Object.fromEntries(state.resourceEntries)\n : undefined;\n\n const buildPromptsFactory = () =>\n state.promptEntries.length > 0\n ? () => Object.fromEntries(state.promptEntries)\n : undefined;\n\n const finalize = (): McpHandler => {\n const tools = buildToolsFactory();\n const resources = buildResourcesFactory();\n const prompts = buildPromptsFactory();\n return {\n __brand: \"effortless-mcp\",\n __spec: state.spec,\n ...(state.onError ? { onError: state.onError } : {}),\n ...(state.onCleanup ? { onCleanup: state.onCleanup } : {}),\n ...(state.setup ? { setup: state.setup } : {}),\n ...(state.authFn ? { authFn: state.authFn } : {}),\n ...(state.deps ? { deps: state.deps } : {}),\n ...(state.config ? { config: state.config } : {}),\n ...(state.static ? { static: state.static } : {}),\n ...(resources ? { resources } : {}),\n ...(prompts ? { prompts } : {}),\n ...(tools ? { tools } : {}),\n } as McpHandler;\n };\n\n const finalizeWithEntries = (): McpEntries => {\n const handler: any = finalize();\n\n handler.tool = (def: any, fn: any) => {\n state.toolEntries.push([def.name, { description: def.description, input: def.input, handler: fn }]);\n handler.tools = buildToolsFactory();\n return handler;\n };\n\n handler.resource = (def: any, fn: any) => {\n const entry = { name: def.name, ...(def.description ? { description: def.description } : {}), ...(def.mimeType ? { mimeType: def.mimeType } : {}), handler: fn, ...(def.params ? { params: def.params } : {}) };\n state.resourceEntries.push([def.uri, entry]);\n handler.resources = buildResourcesFactory();\n return handler;\n };\n\n handler.prompt = (def: any, fn: any) => {\n const entry = { ...(def.description ? { description: def.description } : {}), args: def.args, handler: fn };\n state.promptEntries.push([def.name, entry]);\n handler.prompts = buildPromptsFactory();\n return handler;\n };\n\n return handler as McpEntries;\n };\n\n const builder: McpBuilder = {\n deps(fn) {\n state.deps = fn as any;\n return builder as any;\n },\n config(fn) {\n state.config = resolveConfigFactory(fn) as any;\n return builder as any;\n },\n include(glob) {\n state.static = [...(state.static ?? []), glob];\n return builder as any;\n },\n auth(fn: any) {\n state.authFn = fn;\n return builder as any;\n },\n setup(fnOrLambda: any, maybeLambda?: LambdaOptions) {\n if (typeof fnOrLambda === \"function\") {\n state.setup = fnOrLambda;\n if (maybeLambda) applyLambdaOptions(maybeLambda);\n } else {\n applyLambdaOptions(fnOrLambda);\n }\n return builder as any;\n },\n onError(fn) {\n state.onError = fn as any;\n return builder as any;\n },\n onCleanup(fn) {\n state.onCleanup = fn as any;\n return builder as any;\n },\n tool(def: any, fn: any) {\n state.toolEntries.push([def.name, { description: def.description, input: def.input, handler: fn }]);\n return finalizeWithEntries() as any;\n },\n resource(def: any, fn: any) {\n const entry = { name: def.name, ...(def.description ? { description: def.description } : {}), ...(def.mimeType ? { mimeType: def.mimeType } : {}), handler: fn, ...(def.params ? { params: def.params } : {}) };\n state.resourceEntries.push([def.uri, entry]);\n return finalizeWithEntries() as any;\n },\n prompt(def: any, fn: any) {\n const entry = { ...(def.description ? { description: def.description } : {}), args: def.args, handler: fn };\n state.promptEntries.push([def.name, entry]);\n return finalizeWithEntries() as any;\n },\n build() {\n return finalize() as any;\n },\n };\n\n return builder;\n}\n"],"mappings":"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","names":["options","options","ttl"]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "../chunk-3NKYT4OX.js";
|
|
4
4
|
import "../chunk-VONNUUEN.js";
|
|
5
5
|
|
|
6
|
-
// src/runtime/wrap-
|
|
6
|
+
// src/runtime/wrap-queue.ts
|
|
7
7
|
var parseMessages = (rawRecords, schema) => {
|
|
8
8
|
const messages = [];
|
|
9
9
|
const decode = schema ?? ((x) => x);
|
|
@@ -29,11 +29,11 @@ var parseMessages = (rawRecords, schema) => {
|
|
|
29
29
|
}
|
|
30
30
|
return messages;
|
|
31
31
|
};
|
|
32
|
-
var
|
|
32
|
+
var wrapQueue = (handler) => {
|
|
33
33
|
if (!handler.onMessage && !handler.onMessageBatch) {
|
|
34
|
-
throw new Error("
|
|
34
|
+
throw new Error("wrapQueue requires a handler with onMessage or onMessageBatch defined");
|
|
35
35
|
}
|
|
36
|
-
const rt = createHandlerRuntime(handler, "
|
|
36
|
+
const rt = createHandlerRuntime(handler, "queue", handler.__spec.lambda?.logLevel ?? "info");
|
|
37
37
|
const handleError = handler.onError ?? (({ error }) => console.error(`[effortless:${rt.handlerName}]`, error));
|
|
38
38
|
const fn = async (event) => {
|
|
39
39
|
const startTime = Date.now();
|
|
@@ -103,5 +103,5 @@ var wrapFifoQueue = (handler) => {
|
|
|
103
103
|
return fn;
|
|
104
104
|
};
|
|
105
105
|
export {
|
|
106
|
-
|
|
106
|
+
wrapQueue
|
|
107
107
|
};
|
|
@@ -46,7 +46,7 @@ var wrapTableStream = (handler) => {
|
|
|
46
46
|
throw new Error("wrapTableStream requires a handler with onRecord or onRecordBatch defined");
|
|
47
47
|
}
|
|
48
48
|
const tagField = handler.__spec.tagField ?? "tag";
|
|
49
|
-
const concurrency = handler.__spec.concurrency ?? 1;
|
|
49
|
+
const concurrency = handler.__spec.stream?.concurrency ?? 1;
|
|
50
50
|
let selfClient = null;
|
|
51
51
|
const getSelfClient = async () => {
|
|
52
52
|
if (selfClient) return selfClient;
|