@lafken/main 0.10.1

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/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Aníbal Emilio Jorquera Cornejo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # @lafken/main
2
+
3
+ Core entry point for a Lafken serverless application. `@lafken/main` initializes the AWS provider, orchestrates resolvers and modules, and synthesizes the resulting Terraform configuration through CDKTN. It provides `createApp` to bootstrap the application and `createModule` to organize resources into logical groups.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lafken/main
9
+ ```
10
+
11
+ ## Getting Started
12
+
13
+ Create an application with resolvers and modules, then let Lafken generate all the infrastructure:
14
+
15
+ ```typescript
16
+ import { createApp, createModule } from '@lafken/main';
17
+ import { ApiResolver } from '@lafken/api/resolver';
18
+ import { QueueResolver } from '@lafken/queue/resolver';
19
+
20
+ // 1. Define modules that group related resources
21
+ const userModule = createModule({
22
+ name: 'users',
23
+ resources: [UserApi, UserQueue],
24
+ });
25
+
26
+ const billingModule = createModule({
27
+ name: 'billing',
28
+ resources: [InvoiceSchedule],
29
+ });
30
+
31
+ // 2. Create the application
32
+ await createApp({
33
+ name: 'my-app',
34
+ modules: [userModule, billingModule],
35
+ resolvers: [
36
+ new ApiResolver({ restApi: { name: 'my-api' } }),
37
+ new QueueResolver(),
38
+ ],
39
+ });
40
+ ```
41
+
42
+ ## Features
43
+
44
+ ### createApp
45
+
46
+ `createApp` is the main entry point. It initializes the AWS stack, runs all resolver lifecycle hooks (`beforeCreate` → `create` → `afterCreate`), and synthesizes the Terraform output:
47
+
48
+ ```typescript
49
+ await createApp({
50
+ name: 'my-app',
51
+ modules: [userModule, billingModule],
52
+ resolvers: [new ApiResolver(), new QueueResolver()],
53
+ globalConfig: {
54
+ lambda: {
55
+ memory: 512,
56
+ timeout: 30,
57
+ runtime: 22,
58
+ services: ['dynamodb', 's3', 'sqs'],
59
+ },
60
+ tags: {
61
+ environment: 'production',
62
+ team: 'platform',
63
+ },
64
+ },
65
+ awsProviderConfig: {
66
+ region: 'us-east-1',
67
+ profile: 'my-aws-profile',
68
+ },
69
+ s3Backend: {
70
+ bucket: 'my-terraform-state',
71
+ key: 'app/terraform.tfstate',
72
+ region: 'us-east-1',
73
+ },
74
+ extend: async (scope) => {
75
+ // Add custom CDKTN constructs after all resolvers finish
76
+ },
77
+ });
78
+ ```
79
+
80
+ #### Application Options
81
+
82
+ | Option | Type | Required | Description |
83
+ | ------------------- | ------------------- | -------- | --------------------------------------------------------- |
84
+ | `name` | `string` | Yes | Application name, used as the stack identifier |
85
+ | `modules` | `StackModule[]` | Yes | Modules created with `createModule` |
86
+ | `resolvers` | `ResolverType[]` | Yes | Resolvers that process decorated resources |
87
+ | `globalConfig` | `GlobalConfig` | No | Shared Lambda and tag settings for all resources |
88
+ | `awsProviderConfig` | `AwsProviderConfig` | No | AWS provider settings (region, profile, etc.) |
89
+ | `s3Backend` | `S3BackendConfig` | No | Remote S3 backend for Terraform state |
90
+ | `extend` | `(scope) => void` | No | Callback invoked after all resolvers finish |
91
+
92
+ ### createModule
93
+
94
+ `createModule` groups related resources into a logical unit with its own scope, IAM role, and configuration. Each resource inside the module is processed by the matching resolver based on its decorator type:
95
+
96
+ ```typescript
97
+ const orderModule = createModule({
98
+ name: 'orders',
99
+ resources: [OrderApi, OrderQueue, OrderSchedule],
100
+ globalConfig: {
101
+ lambda: {
102
+ memory: 256,
103
+ timeout: 15,
104
+ services: ['dynamodb', 'sqs'],
105
+ },
106
+ tags: {
107
+ domain: 'orders',
108
+ },
109
+ },
110
+ });
111
+ ```
112
+
113
+ #### Module Options
114
+
115
+ | Option | Type | Required | Description |
116
+ | -------------- | ----------------- | -------- | ----------------------------------------------------- |
117
+ | `name` | `string` | Yes | Module name, used as scope and tag identifier |
118
+ | `resources` | `ClassResource[]` | Yes | Decorated classes to be processed by resolvers |
119
+ | `globalConfig` | `GlobalConfig` | No | Lambda and tag settings scoped to this module |
120
+
121
+ ### Global Configuration
122
+
123
+ Global configuration applies default settings to all Lambda functions and resources. Values cascade from application to module to individual resource, with more specific settings taking precedence:
124
+
125
+ ```
126
+ App globalConfig → Module globalConfig → Resource-level config
127
+ ```
128
+
129
+ #### Lambda Configuration
130
+
131
+ | Option | Type | Description |
132
+ | ------------- | ---------------- | --------------------------------------------------------- |
133
+ | `memory` | `number` | Memory allocation in MB |
134
+ | `timeout` | `number` | Execution timeout in seconds |
135
+ | `runtime` | `20 \| 22 \| 24` | Node.js runtime version |
136
+ | `services` | `Services[]` | AWS services the Lambda can access (creates IAM role) |
137
+ | `enableTrace` | `boolean` | Enable AWS X-Ray tracing |
138
+ | `env` | `EnvironmentValue` | Environment variables for Lambda functions |
139
+
140
+ #### Available Services
141
+
142
+ Services define which AWS resources the Lambda IAM role can access:
143
+
144
+ | Service | Description |
145
+ | --------------- | -------------------------------- |
146
+ | `dynamodb` | Amazon DynamoDB |
147
+ | `s3` | Amazon S3 |
148
+ | `lambda` | AWS Lambda |
149
+ | `cloudwatch` | Amazon CloudWatch Logs |
150
+ | `sqs` | Amazon SQS |
151
+ | `state_machine` | AWS Step Functions |
152
+ | `kms` | AWS KMS |
153
+ | `ssm` | AWS Systems Manager Parameter Store |
154
+ | `event` | Amazon EventBridge |
155
+
156
+ For fine-grained control, specify individual permissions:
157
+
158
+ ```typescript
159
+ services: [
160
+ 'cloudwatch',
161
+ { type: 'dynamodb', permissions: ['Query', 'GetItem'] },
162
+ { type: 's3', permissions: ['GetObject'], resources: ['arn:aws:s3:::my-bucket/*'] },
163
+ { type: 'custom', serviceName: 'ses', permissions: ['SendEmail'] },
164
+ ]
165
+ ```
166
+
167
+ #### Tags
168
+
169
+ Tags are applied automatically to all taggable resources. Module-level tags merge with app-level tags, and resource-specific tags take highest precedence:
170
+
171
+ ```typescript
172
+ // App-level tags
173
+ globalConfig: {
174
+ tags: {
175
+ environment: 'production',
176
+ project: 'my-app',
177
+ },
178
+ }
179
+
180
+ // Module-level tags (merged with app tags)
181
+ globalConfig: {
182
+ tags: {
183
+ domain: 'orders',
184
+ },
185
+ }
186
+ ```
187
+
188
+ Lafken also adds automatic tags: `lafken:app` with the app name and `lafken:module` with the module name.
189
+
190
+ ### S3 Backend
191
+
192
+ Store Terraform state remotely in an S3 bucket for team collaboration and state locking:
193
+
194
+ ```typescript
195
+ await createApp({
196
+ name: 'my-app',
197
+ s3Backend: {
198
+ bucket: 'terraform-state-bucket',
199
+ key: 'apps/my-app/terraform.tfstate',
200
+ region: 'us-east-1',
201
+ dynamodbTable: 'terraform-locks',
202
+ },
203
+ });
204
+ ```
205
+
206
+ ### Extending the Application
207
+
208
+ The `extend` callback runs after all resolvers have finished processing. Use it to add custom infrastructure that is not covered by the standard resolvers:
209
+
210
+ ```typescript
211
+ await createApp({
212
+ name: 'my-app',
213
+ modules: [userModule],
214
+ resolvers: [new ApiResolver()],
215
+ extend: async (scope) => {
216
+ // Add any CDKTN construct directly to the stack
217
+ },
218
+ });
219
+ ```
@@ -0,0 +1,41 @@
1
+ import { App, TerraformStack } from 'cdktn';
2
+ import type { CreateAppProps } from './app.types';
3
+ export declare class AppStack extends TerraformStack {
4
+ id: string;
5
+ private props;
6
+ constructor(scope: App, id: string, props: CreateAppProps);
7
+ init(): Promise<void>;
8
+ private triggerHook;
9
+ private resolveModuleResources;
10
+ private createRole;
11
+ private addAspectProperties;
12
+ }
13
+ /**
14
+ * Creates and synthesizes a Lafken serverless application.
15
+ *
16
+ * Initializes the CDKTN application, sets up the AWS stack with the
17
+ * provided modules and resolvers, executes the full resolver lifecycle
18
+ * (beforeCreate → create → afterCreate), and synthesizes the resulting
19
+ * Terraform configuration.
20
+ *
21
+ * @param props - The application configuration including name, modules,
22
+ * resolvers, global settings, and optional extensions.
23
+ * @returns The CDKTN `App` instance and the `AppStack` created for the application.
24
+ *
25
+ * @example
26
+ * await createApp({
27
+ * name: 'my-app',
28
+ * modules: [
29
+ * //... ,
30
+ * ],
31
+ * resolvers: [new ApiResolver({ restApi: { name: 'my-api' } })],
32
+ * globalConfig: {
33
+ * lambda: { runtime: 22, memory: 512 },
34
+ * tags: { environment: 'production' },
35
+ * },
36
+ * });
37
+ */
38
+ export declare const createApp: (props: CreateAppProps) => Promise<{
39
+ app: App;
40
+ appStack: AppStack;
41
+ }>;
package/lib/app/app.js ADDED
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createApp = exports.AppStack = void 0;
4
+ const provider_1 = require("@cdktn/provider-aws/lib/provider");
5
+ const common_1 = require("@lafken/common");
6
+ const resolver_1 = require("@lafken/resolver");
7
+ const cdktn_1 = require("cdktn");
8
+ const aspect_1 = require("../aspect/aspect");
9
+ const context_1 = require("../context/context");
10
+ (0, common_1.enableBuildEnvVariable)();
11
+ class AppStack extends cdktn_1.TerraformStack {
12
+ id;
13
+ props;
14
+ constructor(scope, id, props) {
15
+ super(scope, id);
16
+ this.id = id;
17
+ this.props = props;
18
+ new context_1.AppContext(this, {
19
+ contextName: resolver_1.ContextName.app,
20
+ globalConfig: props.globalConfig?.lambda,
21
+ contextCreator: props.name,
22
+ });
23
+ new provider_1.AwsProvider(this, 'AWS', props.awsProviderConfig);
24
+ if (props.s3Backend) {
25
+ new cdktn_1.S3Backend(this, props.s3Backend);
26
+ }
27
+ this.createRole();
28
+ }
29
+ async init() {
30
+ const { resolvers, extend } = this.props;
31
+ await this.triggerHook(resolvers, 'beforeCreate');
32
+ await this.resolveModuleResources();
33
+ await this.triggerHook(resolvers, 'afterCreate');
34
+ this.addAspectProperties();
35
+ await resolver_1.lafkenResource.callDependentCallbacks();
36
+ await resolver_1.lambdaAssets.createAssets();
37
+ if (extend) {
38
+ await extend(this);
39
+ }
40
+ }
41
+ async triggerHook(resolvers, trigger) {
42
+ for (const resolver of resolvers) {
43
+ if (resolver[trigger] !== undefined) {
44
+ await resolver[trigger](this);
45
+ }
46
+ }
47
+ }
48
+ async resolveModuleResources() {
49
+ const { modules, resolvers } = this.props;
50
+ const resolversByType = resolvers.reduce((acc, resolver) => {
51
+ acc[resolver.type] = resolver;
52
+ return acc;
53
+ }, {});
54
+ await Promise.all(modules.map((module) => module(this, resolversByType)));
55
+ }
56
+ createRole() {
57
+ const roleName = `${this.props.name}-global-role`;
58
+ const lambdaRole = new resolver_1.Role(this, roleName, {
59
+ name: roleName,
60
+ services: this.props.globalConfig?.lambda?.services || [
61
+ 'dynamodb',
62
+ 's3',
63
+ 'lambda',
64
+ 'cloudwatch',
65
+ 'sqs',
66
+ 'state_machine',
67
+ 'kms',
68
+ 'ssm',
69
+ 'event',
70
+ ],
71
+ });
72
+ lambdaRole.isGlobal('app', roleName);
73
+ }
74
+ addAspectProperties() {
75
+ cdktn_1.Aspects.of(this).add(new aspect_1.AppAspect(this, 'app', {
76
+ tags: {
77
+ ...(this.props.globalConfig?.tags || {}),
78
+ 'lafken:app': this.id,
79
+ },
80
+ environment: this.props.globalConfig?.lambda?.env,
81
+ vpc: this.props.globalConfig?.lambda?.vpcConfig,
82
+ }));
83
+ }
84
+ }
85
+ exports.AppStack = AppStack;
86
+ /**
87
+ * Creates and synthesizes a Lafken serverless application.
88
+ *
89
+ * Initializes the CDKTN application, sets up the AWS stack with the
90
+ * provided modules and resolvers, executes the full resolver lifecycle
91
+ * (beforeCreate → create → afterCreate), and synthesizes the resulting
92
+ * Terraform configuration.
93
+ *
94
+ * @param props - The application configuration including name, modules,
95
+ * resolvers, global settings, and optional extensions.
96
+ * @returns The CDKTN `App` instance and the `AppStack` created for the application.
97
+ *
98
+ * @example
99
+ * await createApp({
100
+ * name: 'my-app',
101
+ * modules: [
102
+ * //... ,
103
+ * ],
104
+ * resolvers: [new ApiResolver({ restApi: { name: 'my-api' } })],
105
+ * globalConfig: {
106
+ * lambda: { runtime: 22, memory: 512 },
107
+ * tags: { environment: 'production' },
108
+ * },
109
+ * });
110
+ */
111
+ const createApp = async (props) => {
112
+ const app = new cdktn_1.App({
113
+ skipValidation: true,
114
+ });
115
+ const appStack = new AppStack(app, props.name, props);
116
+ await appStack.init();
117
+ app.synth();
118
+ return {
119
+ app,
120
+ appStack,
121
+ };
122
+ };
123
+ exports.createApp = createApp;
@@ -0,0 +1,135 @@
1
+ import type { AwsProviderConfig } from '@cdktn/provider-aws/lib/provider';
2
+ import type { LambdaGlobalConfig, ResolverType } from '@lafken/resolver';
3
+ import type { S3BackendConfig } from 'cdktn';
4
+ import type { StackModule } from '../module';
5
+ import type { ModuleResolverType } from '../module/module.types';
6
+ import type { AppStack } from './app';
7
+ /**
8
+ * Global configuration for the application.
9
+ *
10
+ * Defines shared settings that apply across all resources and Lambda
11
+ * functions in the application. Individual resources can override
12
+ * these values with their own specific configuration.
13
+ */
14
+ export interface GlobalConfig {
15
+ /**
16
+ * Global Lambda configuration.
17
+ *
18
+ * Specifies default properties for all Lambda functions in the
19
+ * application, such as memory, timeout, runtime, and services.
20
+ * These values can be overridden at the module or resource level.
21
+ *
22
+ * @example
23
+ * lambda: {
24
+ * memory: 512,
25
+ * timeout: 30,
26
+ * runtime: 22,
27
+ * services: ['s3', 'dynamodb'],
28
+ * }
29
+ */
30
+ lambda?: LambdaGlobalConfig;
31
+ /**
32
+ * Global resource tags.
33
+ *
34
+ * Specifies a set of tags that will be applied to all resources
35
+ * unless a resource explicitly defines its own tags. In that case,
36
+ * the resource-specific tags will override the global values.
37
+ */
38
+ tags?: Record<string, string>;
39
+ }
40
+ export interface CreateAppProps {
41
+ /**
42
+ * Application name.
43
+ *
44
+ * Specifies the name of the application, which is used within
45
+ * the AWS stack as an identifier for resources.
46
+ *
47
+ * @example
48
+ * name: "my-awesome-app"
49
+ */
50
+ name: string;
51
+ /**
52
+ * Application modules.
53
+ *
54
+ * Defines the set of modules to be created within the application.
55
+ * Each module groups related resources and handlers into a logical
56
+ * unit with shared configuration. Modules are created using
57
+ * `createModule()` and receive the application stack scope along
58
+ * with the registered resolvers.
59
+ *
60
+ * @example
61
+ * modules: [
62
+ * createModule({
63
+ * name: 'users',
64
+ * resources: [UserApi, UserQueue],
65
+ * }),
66
+ * ]
67
+ */
68
+ modules: ((scope: AppStack, resources: Record<string, ModuleResolverType>) => Promise<StackModule>)[];
69
+ /**
70
+ * Resource resolvers.
71
+ *
72
+ * Defines the list of resolvers responsible for creating and configuring
73
+ * resources loaded by the stacks. Each resolver can receive detailed
74
+ * configuration options depending on the type of resource it manages.
75
+ *
76
+ * For example, an `ApiResolver` can include REST API settings,
77
+ * deployment options, and authorization configurations.
78
+ */
79
+ resolvers: ResolverType[];
80
+ /**
81
+ * Global configuration for the application.
82
+ *
83
+ * Provides settings that are applied across all resources, stacks,
84
+ * and Lambda functions unless overridden at a lower level.
85
+ * This includes global Lambda properties, environment configuration,
86
+ * and resource tags.
87
+ */
88
+ globalConfig?: GlobalConfig;
89
+ /**
90
+ * AWS provider configuration.
91
+ *
92
+ * Specifies the configuration for the AWS provider used by the
93
+ * application stack. This includes settings such as the AWS region,
94
+ * profile, and other provider-level options required for
95
+ * deploying resources.
96
+ *
97
+ * @example
98
+ * awsProviderConfig: {
99
+ * region: 'us-east-1',
100
+ * profile: 'my-aws-profile',
101
+ * }
102
+ */
103
+ awsProviderConfig?: AwsProviderConfig;
104
+ /**
105
+ * S3 backend configuration for Terraform state.
106
+ *
107
+ * Configures an S3 bucket as the remote backend for storing the
108
+ * Terraform state file. This enables team collaboration, state
109
+ * locking, and centralized state management.
110
+ *
111
+ * @example
112
+ * s3Backend: {
113
+ * bucket: 'my-terraform-state',
114
+ * key: 'app/terraform.tfstate',
115
+ * region: 'us-east-1',
116
+ * }
117
+ */
118
+ s3Backend?: S3BackendConfig;
119
+ /**
120
+ * Extension callback.
121
+ *
122
+ * An optional async callback that is invoked after all modules and
123
+ * resolvers have been fully processed. Use this to add custom
124
+ * infrastructure or perform additional configuration on the
125
+ * application stack that is not covered by the standard resolvers.
126
+ *
127
+ * @param scope - The application stack instance.
128
+ *
129
+ * @example
130
+ * extend: async (scope) => {
131
+ * new S3Bucket(scope, 'custom-bucket', { bucket: 'my-bucket' });
132
+ * }
133
+ */
134
+ extend?: (scope: AppStack) => Promise<void>;
135
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export * from './app';
2
+ export * from './app.types';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./app"), exports);
18
+ __exportStar(require("./app.types"), exports);
@@ -0,0 +1,17 @@
1
+ import type { IAspect } from 'cdktn';
2
+ import type { Construct, IConstruct } from 'constructs';
3
+ import type { AppAspectProps } from './aspect.types';
4
+ export declare class AppAspect implements IAspect {
5
+ private scope;
6
+ private id;
7
+ private props;
8
+ private env;
9
+ private vpcConfig;
10
+ constructor(scope: Construct, id: string, props: AppAspectProps);
11
+ visit(node: IConstruct): void;
12
+ private initializeEnvironment;
13
+ private initializeVpcConfig;
14
+ private addEnvironmentValues;
15
+ private addVpcConfig;
16
+ private isTaggableResource;
17
+ }
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppAspect = void 0;
4
+ const lambda_function_1 = require("@cdktn/provider-aws/lib/lambda-function");
5
+ const resolver_1 = require("@lafken/resolver");
6
+ const lambdaValues = {
7
+ env: {},
8
+ vpcConfig: {},
9
+ };
10
+ class AppAspect {
11
+ scope;
12
+ id;
13
+ props;
14
+ env;
15
+ vpcConfig;
16
+ constructor(scope, id, props) {
17
+ this.scope = scope;
18
+ this.id = id;
19
+ this.props = props;
20
+ this.initializeEnvironment();
21
+ this.initializeVpcConfig();
22
+ }
23
+ visit(node) {
24
+ if (this.props.tags && this.isTaggableResource(node)) {
25
+ const currentTags = node.tagsInput || {};
26
+ node.tags = { ...this.props.tags, ...currentTags };
27
+ }
28
+ if (node instanceof lambda_function_1.LambdaFunction) {
29
+ this.addEnvironmentValues(node);
30
+ this.addVpcConfig(node);
31
+ }
32
+ }
33
+ initializeEnvironment() {
34
+ if (!this.props.environment) {
35
+ return;
36
+ }
37
+ const values = new resolver_1.Environment(this.scope, `${this.id}-env`, this.props.environment).getValues();
38
+ if (values === false) {
39
+ throw new Error(`resources in ${this.id} env not found`);
40
+ }
41
+ this.env = values;
42
+ }
43
+ initializeVpcConfig() {
44
+ if (!this.props.vpc) {
45
+ return;
46
+ }
47
+ this.vpcConfig =
48
+ typeof this.props.vpc === 'function'
49
+ ? this.props.vpc((0, resolver_1.resolverSSMValues)(this.scope))
50
+ : this.props.vpc;
51
+ }
52
+ addEnvironmentValues(node) {
53
+ if (!this.env) {
54
+ return;
55
+ }
56
+ const currentVars = node.environmentInput?.variables || {};
57
+ lambdaValues.env[node.node.addr] ??= currentVars;
58
+ node.putEnvironment({
59
+ variables: {
60
+ ...currentVars,
61
+ ...this.env,
62
+ ...lambdaValues.env[node.node.addr],
63
+ },
64
+ });
65
+ }
66
+ addVpcConfig(node) {
67
+ if (!this.vpcConfig) {
68
+ return;
69
+ }
70
+ lambdaValues.vpcConfig[node.node.addr] ??= node.vpcConfigInput || {};
71
+ const hasProperties = Object.keys(lambdaValues.vpcConfig[node.node.addr]).length > 0;
72
+ node.putVpcConfig(hasProperties ? lambdaValues.vpcConfig[node.node.addr] : this.vpcConfig);
73
+ }
74
+ isTaggableResource(resource) {
75
+ return 'tags' in resource && 'tagsInput' in resource;
76
+ }
77
+ }
78
+ exports.AppAspect = AppAspect;
@@ -0,0 +1,15 @@
1
+ import type { EnvironmentValue, VpcConfigValue } from '@lafken/common';
2
+ import type { Construct } from 'constructs';
3
+ export interface TaggableResource extends Construct {
4
+ tags?: Record<string, string>;
5
+ tagsInput?: Record<string, string>;
6
+ }
7
+ export interface AppAspectProps {
8
+ tags?: Record<string, string>;
9
+ environment?: EnvironmentValue;
10
+ vpc?: VpcConfigValue;
11
+ }
12
+ export interface OriginalLambdaValue {
13
+ env: Record<string, Record<string, string>>;
14
+ vpcConfig: Record<string, any>;
15
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ import type { Construct } from 'constructs';
2
+ import type { ContextProps } from './context.types';
3
+ export declare class AppContext {
4
+ constructor(scope: Construct, props: ContextProps);
5
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppContext = void 0;
4
+ class AppContext {
5
+ constructor(scope, props) {
6
+ const { contextName, globalConfig = {} } = props;
7
+ const { services: _services, vpcConfig: _vpcConfig, env: _env, ...contextData } = globalConfig || {};
8
+ scope.node.setContext(contextName, {
9
+ ...contextData,
10
+ contextCreator: props.contextCreator,
11
+ });
12
+ }
13
+ }
14
+ exports.AppContext = AppContext;
@@ -0,0 +1,6 @@
1
+ import type { LambdaGlobalConfig } from '@lafken/resolver';
2
+ export interface ContextProps {
3
+ globalConfig?: LambdaGlobalConfig;
4
+ contextCreator: string;
5
+ contextName: string;
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './app';
2
+ export * from './module';
package/lib/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./app"), exports);
18
+ __exportStar(require("./module"), exports);
@@ -0,0 +1,2 @@
1
+ export * from './module';
2
+ export * from './module.types';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./module"), exports);
18
+ __exportStar(require("./module.types"), exports);
@@ -0,0 +1,35 @@
1
+ import { Construct } from 'constructs';
2
+ import type { CreateModuleProps, ModuleConstruct, ModuleProps, ModuleResolverType } from './module.types';
3
+ export declare class StackModule extends Construct {
4
+ id: string;
5
+ private props;
6
+ constructor(scope: Construct, id: string, props: ModuleProps);
7
+ generateResources(): Promise<void>;
8
+ private createRole;
9
+ private addAspectProperties;
10
+ }
11
+ /**
12
+ * Creates a module factory for the Lafken application.
13
+ *
14
+ * Returns a function that, when invoked by `createApp`, instantiates a
15
+ * `StackModule` and processes all its declared resources through the
16
+ * registered resolvers. Each module groups related resources into a
17
+ * logical unit with its own scope, IAM role, tags, and optional
18
+ * Lambda configuration.
19
+ *
20
+ * @param props - The module configuration including name, resources, and
21
+ * optional global settings scoped to this module.
22
+ * @returns A factory function consumed by `createApp` to build the module
23
+ * within the application stack.
24
+ *
25
+ * @example
26
+ * createModule({
27
+ * name: 'users',
28
+ * resources: [UserApi, UserQueue],
29
+ * globalConfig: {
30
+ * lambda: { memory: 256, services: ['dynamodb'] },
31
+ * tags: { team: 'backend' },
32
+ * },
33
+ * })
34
+ */
35
+ export declare const createModule: (props: CreateModuleProps) => (scope: ModuleConstruct, resolvers: Record<string, ModuleResolverType>) => Promise<StackModule>;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createModule = exports.StackModule = void 0;
4
+ const common_1 = require("@lafken/common");
5
+ const resolver_1 = require("@lafken/resolver");
6
+ const cdktn_1 = require("cdktn");
7
+ const constructs_1 = require("constructs");
8
+ const aspect_1 = require("../aspect/aspect");
9
+ const context_1 = require("../context/context");
10
+ class StackModule extends constructs_1.Construct {
11
+ id;
12
+ props;
13
+ constructor(scope, id, props) {
14
+ super(scope, id);
15
+ this.id = id;
16
+ this.props = props;
17
+ new context_1.AppContext(this, {
18
+ contextName: resolver_1.ContextName.module,
19
+ globalConfig: props.globalConfig?.lambda,
20
+ contextCreator: props.name,
21
+ });
22
+ this.createRole();
23
+ }
24
+ async generateResources() {
25
+ const { resources } = this.props;
26
+ for (const resource of resources) {
27
+ const metadata = (0, common_1.getResourceMetadata)(resource);
28
+ const resolver = this.props.resolvers[metadata.type];
29
+ if (!resolver) {
30
+ throw new Error(`There is no resolver for the resource ${metadata.type}`);
31
+ }
32
+ await resolver.create(this, resource);
33
+ }
34
+ this.addAspectProperties();
35
+ }
36
+ createRole() {
37
+ if (!this.props.globalConfig?.lambda?.services?.length) {
38
+ return;
39
+ }
40
+ const roleName = `${this.props.name}-module-role`;
41
+ const lambdaRole = new resolver_1.Role(this, roleName, {
42
+ name: roleName,
43
+ services: this.props.globalConfig?.lambda?.services || [],
44
+ });
45
+ lambdaRole.isGlobal('module', roleName);
46
+ }
47
+ addAspectProperties() {
48
+ cdktn_1.Aspects.of(this).add(new aspect_1.AppAspect(this, this.props.name, {
49
+ tags: {
50
+ ...(this.props.globalConfig?.tags || {}),
51
+ 'lafken:module': this.props.name,
52
+ },
53
+ environment: this.props.globalConfig?.lambda?.env,
54
+ vpc: this.props.globalConfig?.lambda?.vpcConfig,
55
+ }));
56
+ }
57
+ }
58
+ exports.StackModule = StackModule;
59
+ /**
60
+ * Creates a module factory for the Lafken application.
61
+ *
62
+ * Returns a function that, when invoked by `createApp`, instantiates a
63
+ * `StackModule` and processes all its declared resources through the
64
+ * registered resolvers. Each module groups related resources into a
65
+ * logical unit with its own scope, IAM role, tags, and optional
66
+ * Lambda configuration.
67
+ *
68
+ * @param props - The module configuration including name, resources, and
69
+ * optional global settings scoped to this module.
70
+ * @returns A factory function consumed by `createApp` to build the module
71
+ * within the application stack.
72
+ *
73
+ * @example
74
+ * createModule({
75
+ * name: 'users',
76
+ * resources: [UserApi, UserQueue],
77
+ * globalConfig: {
78
+ * lambda: { memory: 256, services: ['dynamodb'] },
79
+ * tags: { team: 'backend' },
80
+ * },
81
+ * })
82
+ */
83
+ const createModule = (props) => async (scope, resolvers) => {
84
+ const module = new StackModule(scope, props.name, {
85
+ ...props,
86
+ resolvers,
87
+ });
88
+ await module.generateResources();
89
+ return module;
90
+ };
91
+ exports.createModule = createModule;
@@ -0,0 +1,42 @@
1
+ import type { ClassResource, ResourceMetadata } from '@lafken/common';
2
+ import type { ResolverType } from '@lafken/resolver';
3
+ import type { Construct } from 'constructs';
4
+ import type { GlobalConfig } from '../app/app.types';
5
+ import type { StackModule } from './module';
6
+ export interface CreateModuleProps {
7
+ /**
8
+ * Module name.
9
+ *
10
+ * Specifies the name of the module, which will be used as an identifier
11
+ * for all resources created within this stack.
12
+ */
13
+ name: string;
14
+ /**
15
+ * Module resources.
16
+ *
17
+ * Defines the list of resources to be created within this stack.
18
+ * Each item represents a resource such as Api, Queue, StateMachine, etc.
19
+ */
20
+ resources: ClassResource[];
21
+ /**
22
+ * Module-level global configuration.
23
+ *
24
+ * Provides settings that are applied to all resources and Lambda functions
25
+ * within this specific module. This configuration behaves similarly to
26
+ * the application-wide `GlobalConfig`, but excludes environment settings (`env`),
27
+ * which are managed at the application level.
28
+ */
29
+ globalConfig?: Omit<GlobalConfig, 'env'>;
30
+ }
31
+ export interface ModuleProps extends CreateModuleProps {
32
+ resolvers: Record<string, ResolverType>;
33
+ }
34
+ export interface ModuleResource {
35
+ module: StackModule;
36
+ metadata: ResourceMetadata;
37
+ Resource: ClassResource;
38
+ }
39
+ export interface ModuleResolverType extends ResolverType {
40
+ }
41
+ export interface ModuleConstruct extends Construct {
42
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@lafken/main",
3
+ "version": "0.10.1",
4
+ "private": false,
5
+ "description": "Lafken core engine - orchestrate AWS serverless infrastructure using decorators with automatic CDKTN code generation",
6
+ "keywords": [
7
+ "aws",
8
+ "serverless",
9
+ "lafken",
10
+ "infrastructure",
11
+ "infrastructure-as-code",
12
+ "cdktn",
13
+ "typescript",
14
+ "decorators",
15
+ "terraform"
16
+ ],
17
+ "homepage": "https://github.com/Hero64/lafken#readme",
18
+ "bugs": "https://github.com/Hero64/lafken/issues",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/Hero64/lafken",
22
+ "directory": "packages/main"
23
+ },
24
+ "license": "MIT",
25
+ "author": "Aníbal Jorquera",
26
+ "main": "lib/index.js",
27
+ "types": "lib/index.d.ts",
28
+ "files": [
29
+ "lib"
30
+ ],
31
+ "dependencies": {
32
+ "reflect-metadata": "^0.2.2",
33
+ "@lafken/resolver": "0.10.1"
34
+ },
35
+ "devDependencies": {
36
+ "@cdktn/provider-aws": "^23.5.0",
37
+ "cdktn": "^0.22.1",
38
+ "cdktn-vitest": "^1.0.0",
39
+ "constructs": "^10.6.0",
40
+ "typescript": "6.0.2",
41
+ "vitest": "^4.1.2",
42
+ "@lafken/common": "0.10.1"
43
+ },
44
+ "peerDependencies": {
45
+ "@cdktn/provider-aws": ">=23.0.0",
46
+ "cdktn": ">=0.22.0",
47
+ "constructs": "^10.4.5",
48
+ "@lafken/common": "0.10.1"
49
+ },
50
+ "engines": {
51
+ "node": ">=20.19"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "scripts": {
57
+ "build": "pnpm clean && tsc -p ./tsconfig.build.json",
58
+ "check-types": "tsc --noEmit -p ./tsconfig.build.json",
59
+ "clean": "rm -rf ./lib",
60
+ "dev": "tsc -w",
61
+ "test": "vitest"
62
+ }
63
+ }