@fjall/generator 0.88.4

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.
Files changed (57) hide show
  1. package/dist/src/ast/astComputeParser.d.ts +4 -0
  2. package/dist/src/ast/astComputeParser.js +427 -0
  3. package/dist/src/ast/astInfrastructureParser.d.ts +357 -0
  4. package/dist/src/ast/astInfrastructureParser.js +1925 -0
  5. package/dist/src/ast/astSurgicalModification.d.ts +47 -0
  6. package/dist/src/ast/astSurgicalModification.js +400 -0
  7. package/dist/src/ast/index.d.ts +2 -0
  8. package/dist/src/ast/index.js +2 -0
  9. package/dist/src/aws/regions.d.ts +30 -0
  10. package/dist/src/aws/regions.js +254 -0
  11. package/dist/src/generation/common.d.ts +86 -0
  12. package/dist/src/generation/common.js +187 -0
  13. package/dist/src/generation/compute.d.ts +6 -0
  14. package/dist/src/generation/compute.js +547 -0
  15. package/dist/src/generation/database.d.ts +54 -0
  16. package/dist/src/generation/database.js +201 -0
  17. package/dist/src/generation/index.d.ts +12 -0
  18. package/dist/src/generation/index.js +18 -0
  19. package/dist/src/generation/infrastructure.d.ts +44 -0
  20. package/dist/src/generation/infrastructure.js +389 -0
  21. package/dist/src/generation/storage.d.ts +23 -0
  22. package/dist/src/generation/storage.js +174 -0
  23. package/dist/src/generation/storageConnections.d.ts +37 -0
  24. package/dist/src/generation/storageConnections.js +71 -0
  25. package/dist/src/index.d.ts +10 -0
  26. package/dist/src/index.js +19 -0
  27. package/dist/src/planning/index.d.ts +1 -0
  28. package/dist/src/planning/index.js +1 -0
  29. package/dist/src/planning/resourcePlanning.d.ts +58 -0
  30. package/dist/src/planning/resourcePlanning.js +216 -0
  31. package/dist/src/presets/index.d.ts +3 -0
  32. package/dist/src/presets/index.js +3 -0
  33. package/dist/src/presets/patternTierPresets.d.ts +93 -0
  34. package/dist/src/presets/patternTierPresets.js +131 -0
  35. package/dist/src/presets/storagePresets.d.ts +11 -0
  36. package/dist/src/presets/storagePresets.js +36 -0
  37. package/dist/src/presets/tierPresets.d.ts +59 -0
  38. package/dist/src/presets/tierPresets.js +384 -0
  39. package/dist/src/presets/tierTypes.d.ts +301 -0
  40. package/dist/src/presets/tierTypes.js +7 -0
  41. package/dist/src/schemas/constants.d.ts +74 -0
  42. package/dist/src/schemas/constants.js +208 -0
  43. package/dist/src/schemas/index.d.ts +3 -0
  44. package/dist/src/schemas/index.js +3 -0
  45. package/dist/src/schemas/instanceTypeArchitecture.d.ts +35 -0
  46. package/dist/src/schemas/instanceTypeArchitecture.js +75 -0
  47. package/dist/src/schemas/resourceSchemas.d.ts +3534 -0
  48. package/dist/src/schemas/resourceSchemas.js +2015 -0
  49. package/dist/src/types/Result.d.ts +19 -0
  50. package/dist/src/types/Result.js +31 -0
  51. package/dist/src/util/errorUtils.d.ts +2 -0
  52. package/dist/src/util/errorUtils.js +15 -0
  53. package/dist/src/validation/patterns.d.ts +300 -0
  54. package/dist/src/validation/patterns.js +360 -0
  55. package/dist/src/version.d.ts +1 -0
  56. package/dist/src/version.js +1 -0
  57. package/package.json +32 -0
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Database Code Generation
3
+ *
4
+ * Functions for generating database infrastructure code including
5
+ * RDS Instance, Aurora, and GlobalAurora configurations.
6
+ */
7
+ import { buildProperty, getVariableName, emitExtraProperties, } from "./common.js";
8
+ export const DATABASE_ENV_VARS = Object.freeze({
9
+ HOST: "DATABASE_HOST",
10
+ PORT: "DATABASE_PORT",
11
+ NAME: "DATABASE_NAME",
12
+ SSL: "DATABASE_SSL",
13
+ USERNAME: "DATABASE_USERNAME",
14
+ PASSWORD: "DATABASE_PASSWORD",
15
+ });
16
+ export const CREDENTIAL_KEYS = Object.freeze({
17
+ USERNAME: "username",
18
+ PASSWORD: "password",
19
+ });
20
+ /**
21
+ * Get databases connected to a compute resource
22
+ */
23
+ export function getConnectedDatabases(plan, compute) {
24
+ const connected = compute.connectedDatabase;
25
+ if (!connected?.length)
26
+ return [];
27
+ return plan.database.filter((db) => connected.includes(db.name));
28
+ }
29
+ /**
30
+ * Generate credential expression for database secrets.
31
+ * Both ECS and Lambda now use the unified getImport() API.
32
+ * The infrastructure layer handles the difference:
33
+ * - ECS: native Secrets Manager injection via EcsSecret.fromSecretsManager()
34
+ * - Lambda: CloudFormation dynamic reference resolved at deploy time
35
+ */
36
+ function getCredentialExpression(dbVar, credentialKey) {
37
+ return `${dbVar}.getCredentials().getImport("${credentialKey}")`;
38
+ }
39
+ /**
40
+ * Generate database environment variable entries for a list of connected databases.
41
+ * Both ECS and Lambda use containerSecretsImport with getImport() for credentials.
42
+ * Credential entries are always marked as secrets (isSecret: true).
43
+ */
44
+ export function generateDatabaseEnvVarEntries(connectedDatabases) {
45
+ const entries = [];
46
+ const addEntry = (key, expression, isSecret) => {
47
+ entries.push({ key, expression, isSecret });
48
+ };
49
+ if (connectedDatabases.length === 1) {
50
+ const dbVar = getVariableName(connectedDatabases[0]);
51
+ addEntry(DATABASE_ENV_VARS.HOST, `${dbVar}.getHostEndpoint()`, false);
52
+ addEntry(DATABASE_ENV_VARS.PORT, `${dbVar}.getHostPort()`, false);
53
+ addEntry(DATABASE_ENV_VARS.NAME, `${dbVar}.getDatabaseName()`, false);
54
+ addEntry(DATABASE_ENV_VARS.SSL, `"true"`, false);
55
+ addEntry(DATABASE_ENV_VARS.USERNAME, getCredentialExpression(dbVar, CREDENTIAL_KEYS.USERNAME), true);
56
+ addEntry(DATABASE_ENV_VARS.PASSWORD, getCredentialExpression(dbVar, CREDENTIAL_KEYS.PASSWORD), true);
57
+ }
58
+ else {
59
+ for (const [index, database] of connectedDatabases.entries()) {
60
+ const dbVar = getVariableName(database);
61
+ const suffix = index === 0 ? "" : `_${index + 1}`;
62
+ addEntry(`${DATABASE_ENV_VARS.HOST}${suffix}`, `${dbVar}.getHostEndpoint()`, false);
63
+ addEntry(`${DATABASE_ENV_VARS.PORT}${suffix}`, `${dbVar}.getHostPort()`, false);
64
+ addEntry(`${DATABASE_ENV_VARS.NAME}${suffix}`, `${dbVar}.getDatabaseName()`, false);
65
+ addEntry(`${DATABASE_ENV_VARS.SSL}${suffix}`, `"true"`, false);
66
+ addEntry(`${DATABASE_ENV_VARS.USERNAME}${suffix}`, getCredentialExpression(dbVar, CREDENTIAL_KEYS.USERNAME), true);
67
+ addEntry(`${DATABASE_ENV_VARS.PASSWORD}${suffix}`, getCredentialExpression(dbVar, CREDENTIAL_KEYS.PASSWORD), true);
68
+ }
69
+ }
70
+ return entries;
71
+ }
72
+ /**
73
+ * Format database connections array for generated infrastructure code
74
+ */
75
+ export function formatConnectionsCode(connectedDatabases) {
76
+ const dbVars = connectedDatabases.map((db) => getVariableName(db)).join(", ");
77
+ return `connections: [${dbVars}],`;
78
+ }
79
+ /**
80
+ * Build database environment variables for a compute resource
81
+ */
82
+ export function buildDatabaseEnvVars(plan, compute) {
83
+ const env = {};
84
+ const secrets = {};
85
+ if (!compute.needsConnection || !compute.connectedDatabase?.length) {
86
+ return { env, secrets };
87
+ }
88
+ const connectedDatabases = getConnectedDatabases(plan, compute);
89
+ const entries = generateDatabaseEnvVarEntries(connectedDatabases);
90
+ for (const entry of entries) {
91
+ const value = entry.expression === `"true"`
92
+ ? "true"
93
+ : { __expression: entry.expression };
94
+ if (entry.isSecret) {
95
+ secrets[entry.key] = { __expression: entry.expression };
96
+ }
97
+ else {
98
+ env[entry.key] = value;
99
+ }
100
+ }
101
+ return { env, secrets };
102
+ }
103
+ function generateDatabaseSharedProps(database) {
104
+ let code = "";
105
+ code += buildProperty(database.port !== undefined, "port", database.port);
106
+ code += buildProperty(database.deletionProtection !== undefined, "deletionProtection", database.deletionProtection);
107
+ code += buildProperty(database.snapshotIdentifier !== undefined, "snapshotIdentifier", database.snapshotIdentifier, "string");
108
+ code += buildProperty(database.snapshotUsername !== undefined, "snapshotUsername", database.snapshotUsername, "string");
109
+ return code;
110
+ }
111
+ function generateDatabaseInstanceProps(database) {
112
+ if (database.type !== "Instance")
113
+ return "";
114
+ let code = "";
115
+ code += buildProperty(database.instanceType !== undefined, "instanceType", database.instanceType, "string");
116
+ code += buildProperty(database.multiAz !== undefined, "multiAz", database.multiAz);
117
+ code += buildProperty(database.publiclyAccessible !== undefined, "publiclyAccessible", database.publiclyAccessible);
118
+ code += buildProperty(database.enableSecretRotation !== undefined, "enableSecretRotation", database.enableSecretRotation);
119
+ code += buildProperty(database.encryption !== undefined, "encryption", database.encryption, "object");
120
+ code += buildProperty(database.databaseInsights !== undefined, "databaseInsights", database.databaseInsights, "boolean-or-object");
121
+ code += buildProperty(database.proxy !== undefined, "proxy", database.proxy, "boolean-or-object");
122
+ code += buildProperty(database.readReplica !== undefined, "readReplica", database.readReplica, "boolean-or-object");
123
+ code += buildProperty(database.credentials !== undefined, "credentials", database.credentials, "object");
124
+ code += buildProperty(database.backupRetention !== undefined, "backupRetention", database.backupRetention);
125
+ return code;
126
+ }
127
+ function generateDatabaseAuroraProps(database) {
128
+ if (database.type !== "Aurora" && database.type !== "GlobalAurora")
129
+ return "";
130
+ let code = "";
131
+ code += buildProperty(database.encryption !== undefined, "encryption", database.encryption, "object");
132
+ code += buildProperty(database.databaseInsights !== undefined, "databaseInsights", database.databaseInsights, "boolean-or-object");
133
+ code += buildProperty(database.proxy !== undefined, "proxy", database.proxy, "boolean-or-object");
134
+ code += buildProperty(database.credentials !== undefined, "credentials", database.credentials, "object");
135
+ code += buildProperty(database.writer !== undefined, "writer", database.writer, "object");
136
+ code += buildProperty(database.readers !== undefined, "readers", database.readers, "boolean-or-object");
137
+ code += buildProperty(database.backupRetention !== undefined, "backupRetention", database.backupRetention);
138
+ code += buildProperty(database.preferredMaintenanceWindow !== undefined, "preferredMaintenanceWindow", database.preferredMaintenanceWindow, "string");
139
+ return code;
140
+ }
141
+ function generateDatabaseGlobalAuroraProps(database) {
142
+ if (database.type !== "GlobalAurora")
143
+ return "";
144
+ let code = "";
145
+ code += buildProperty(database.primaryRegion !== undefined, "primaryRegion", database.primaryRegion, "string");
146
+ if (database.secondaryRegions !== undefined &&
147
+ database.secondaryRegions.length > 0) {
148
+ code += `
149
+ secondaryRegions: [${database.secondaryRegions.map((r) => `"${r}"`).join(", ")}],`;
150
+ }
151
+ code += buildProperty(database.globalClusterIdentifier !== undefined, "globalClusterIdentifier", database.globalClusterIdentifier, "string");
152
+ code += buildProperty(database.enableGlobalWriteForwarding !== undefined, "enableGlobalWriteForwarding", database.enableGlobalWriteForwarding);
153
+ return code;
154
+ }
155
+ /**
156
+ * Check if a database needs a variable assignment (referenced by compute)
157
+ */
158
+ export function databaseNeedsVariable(database, plan) {
159
+ return plan.compute.some((compute) => compute.needsConnection &&
160
+ compute.connectedDatabase?.includes(database.name));
161
+ }
162
+ /**
163
+ * Generate database infrastructure code
164
+ */
165
+ export function generateDatabaseCode(plan) {
166
+ if (plan.database.length === 0)
167
+ return "";
168
+ let code = "";
169
+ for (let dbIndex = 0; dbIndex < plan.database.length; dbIndex++) {
170
+ const database = plan.database[dbIndex];
171
+ const databaseVariable = getVariableName(database);
172
+ const hasConnections = databaseNeedsVariable(database, plan);
173
+ const needsLeadingNewline = dbIndex > 0;
174
+ const prefix = hasConnections ? `const ${databaseVariable} = ` : "";
175
+ const leadingNewline = needsLeadingNewline ? "\n" : "";
176
+ code += `${leadingNewline}${prefix}app.addDatabase(
177
+ DatabaseFactory.build("${database.name}", {
178
+ vpc: app.getVpc(),
179
+ type: "${database.type}",
180
+ databaseName: "${database.databaseName}",`;
181
+ // Engine expression round-trip: emit raw CDK expression if preserved
182
+ if (database.engineExpression) {
183
+ code += `
184
+ engine: ${database.engineExpression},`;
185
+ }
186
+ else if (database.databaseEngine) {
187
+ code += `
188
+ databaseEngine: "${database.databaseEngine}",`;
189
+ }
190
+ code += generateDatabaseSharedProps(database);
191
+ code += generateDatabaseInstanceProps(database);
192
+ code += generateDatabaseAuroraProps(database);
193
+ code += generateDatabaseGlobalAuroraProps(database);
194
+ code += emitExtraProperties(database.extraProperties);
195
+ code += `
196
+ })
197
+ );
198
+ `;
199
+ }
200
+ return code;
201
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Code Generation Modules
3
+ *
4
+ * This module re-exports all code generation utilities for generating
5
+ * infrastructure code from resource plans.
6
+ */
7
+ export { type IdentifierValue, type ExpressionValue, type CallValue, type SpecialValue, isSpecialValue, toPascalCase, toKebab, toValidDatabaseName, toVariableName, formatValue, buildProperty, getVariableName, resolveResourceVariable, emitExtraProperties, } from "./common.js";
8
+ export { DATABASE_ENV_VARS, CREDENTIAL_KEYS, type DatabaseEnvVarEntry, type DatabaseEnvVars, getConnectedDatabases, generateDatabaseEnvVarEntries, formatConnectionsCode, buildDatabaseEnvVars, databaseNeedsVariable, generateDatabaseCode, } from "./database.js";
9
+ export { STORAGE_ENV_VARS, type StorageEnvVarEntry, type StorageEnvVars, getConnectedStorage, generateStorageEnvVarEntries, buildStorageEnvVars, formatAllConnectionsCode, } from "./storageConnections.js";
10
+ export { generateLambdaCode, generateLambdaConnectionsCode, generateEc2Code, generateEcsCode, generateComputeCode, } from "./compute.js";
11
+ export { generateS3Code, generateDynamoDBCode, generateSQSCode, s3NeedsVariable, } from "./storage.js";
12
+ export { generateNetworkCode, generateTags, generateAppInit, generateImports, generateCDNCode, collectCdnReferencedResources, usesPatternApproach, generatePatternCodeWithComments, } from "./infrastructure.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Code Generation Modules
3
+ *
4
+ * This module re-exports all code generation utilities for generating
5
+ * infrastructure code from resource plans.
6
+ */
7
+ // Common utilities
8
+ export { isSpecialValue, toPascalCase, toKebab, toValidDatabaseName, toVariableName, formatValue, buildProperty, getVariableName, resolveResourceVariable, emitExtraProperties, } from "./common.js";
9
+ // Database generation
10
+ export { DATABASE_ENV_VARS, CREDENTIAL_KEYS, getConnectedDatabases, generateDatabaseEnvVarEntries, formatConnectionsCode, buildDatabaseEnvVars, databaseNeedsVariable, generateDatabaseCode, } from "./database.js";
11
+ // Storage connections
12
+ export { STORAGE_ENV_VARS, getConnectedStorage, generateStorageEnvVarEntries, buildStorageEnvVars, formatAllConnectionsCode, } from "./storageConnections.js";
13
+ // Compute generation
14
+ export { generateLambdaCode, generateLambdaConnectionsCode, generateEc2Code, generateEcsCode, generateComputeCode, } from "./compute.js";
15
+ // Storage generation
16
+ export { generateS3Code, generateDynamoDBCode, generateSQSCode, s3NeedsVariable, } from "./storage.js";
17
+ // Infrastructure generation
18
+ export { generateNetworkCode, generateTags, generateAppInit, generateImports, generateCDNCode, collectCdnReferencedResources, usesPatternApproach, generatePatternCodeWithComments, } from "./infrastructure.js";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Infrastructure Code Generation
3
+ *
4
+ * Functions for generating infrastructure setup code including
5
+ * network configuration, tags, imports, CDN, and app initialisation.
6
+ */
7
+ import type { ApplicationResourcePlan } from "../schemas/resourceSchemas.js";
8
+ export declare const COST_ALLOCATION_TAG: "fjall:costAllocation:owner";
9
+ export declare const DEFAULT_COST_ALLOCATION_OWNER: "engineering";
10
+ /**
11
+ * Generate network infrastructure code for additional networks
12
+ */
13
+ export declare function generateNetworkCode(plan: ApplicationResourcePlan): string;
14
+ /**
15
+ * Generate tags infrastructure code
16
+ */
17
+ export declare function generateTags(plan: ApplicationResourcePlan): string;
18
+ /**
19
+ * Generate app initialisation code
20
+ */
21
+ export declare function generateAppInit(plan: ApplicationResourcePlan): string;
22
+ /**
23
+ * Generate import statements for the infrastructure file
24
+ */
25
+ export declare function generateImports(plan: ApplicationResourcePlan): string;
26
+ /**
27
+ * Generate CDN infrastructure code
28
+ */
29
+ export declare function generateCDNCode(plan: ApplicationResourcePlan): string;
30
+ /**
31
+ * Collect CDN-referenced resources for compute variable naming
32
+ */
33
+ export declare function collectCdnReferencedResources(plan: ApplicationResourcePlan): Set<string>;
34
+ /**
35
+ * Check if a plan uses the pattern approach (PatternFactory).
36
+ * When patternConfig is present, we generate a single PatternFactory.build() call
37
+ * instead of individual factory calls for each resource.
38
+ */
39
+ export declare function usesPatternApproach(plan: ApplicationResourcePlan): boolean;
40
+ /**
41
+ * Generate pattern infrastructure code with clean formatting.
42
+ * Handles both Payload and Next.js OpenNext patterns.
43
+ */
44
+ export declare function generatePatternCodeWithComments(plan: ApplicationResourcePlan): string;
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Infrastructure Code Generation
3
+ *
4
+ * Functions for generating infrastructure setup code including
5
+ * network configuration, tags, imports, CDN, and app initialisation.
6
+ */
7
+ import { toVariableName, toPascalCase, formatValue, resolveResourceVariable, emitExtraProperties, } from "./common.js";
8
+ import { COMPUTE_TYPE } from "../schemas/constants.js";
9
+ export const COST_ALLOCATION_TAG = "fjall:costAllocation:owner";
10
+ export const DEFAULT_COST_ALLOCATION_OWNER = "engineering";
11
+ /**
12
+ * Check if value is an empty object {}.
13
+ * Used to filter out meaningless config like `flowLogs: {}`.
14
+ */
15
+ function isEmptyObject(value) {
16
+ return (typeof value === "object" &&
17
+ value !== null &&
18
+ Object.keys(value).length === 0);
19
+ }
20
+ /**
21
+ * Clean network config by removing empty flowLogs.
22
+ * `flowLogs: {}` means "use defaults" which is the same as omitting it.
23
+ * Only keep flowLogs when it's `false` (explicitly disabled) or has config.
24
+ */
25
+ function cleanNetworkConfig(network) {
26
+ const cleaned = { ...network };
27
+ if (isEmptyObject(cleaned.flowLogs)) {
28
+ delete cleaned.flowLogs;
29
+ }
30
+ return cleaned;
31
+ }
32
+ /**
33
+ * Generate network infrastructure code for additional networks
34
+ */
35
+ export function generateNetworkCode(plan) {
36
+ if (!plan.additionalNetworks || plan.additionalNetworks.length === 0) {
37
+ return "";
38
+ }
39
+ let code = "";
40
+ for (const network of plan.additionalNetworks) {
41
+ const varName = toVariableName(network.name);
42
+ const config = {};
43
+ if (network.maxAzs !== undefined)
44
+ config.maxAzs = network.maxAzs;
45
+ if (network.natGateways !== undefined)
46
+ config.natGateways = network.natGateways;
47
+ if (network.flowLogs !== undefined)
48
+ config.flowLogs = network.flowLogs;
49
+ if (network.vpcEndpoints !== undefined)
50
+ config.vpcEndpoints = network.vpcEndpoints;
51
+ const cleanedConfig = cleanNetworkConfig(config);
52
+ code += `const ${varName} = app.addNetwork(
53
+ NetworkFactory.build("${network.name}", ${formatValue(cleanedConfig, " ")})
54
+ );
55
+
56
+ `;
57
+ }
58
+ return code;
59
+ }
60
+ /**
61
+ * Generate tags infrastructure code
62
+ */
63
+ export function generateTags(plan) {
64
+ const tags = {};
65
+ if (plan.tags && Object.keys(plan.tags).length > 0) {
66
+ Object.assign(tags, plan.tags);
67
+ }
68
+ if (!tags[COST_ALLOCATION_TAG]) {
69
+ tags[COST_ALLOCATION_TAG] = plan.owner || DEFAULT_COST_ALLOCATION_OWNER;
70
+ }
71
+ const tagEntries = Object.entries(tags)
72
+ .map(([key, value]) => ` "${key}": "${value}"`)
73
+ .join(",\n");
74
+ return `app.addTags({
75
+ ${tagEntries}
76
+ });
77
+
78
+ `;
79
+ }
80
+ /**
81
+ * Generate app initialisation code
82
+ */
83
+ export function generateAppInit(plan) {
84
+ let code = `
85
+ const appName = "${plan.appName}";
86
+ `;
87
+ const backupConfig = plan.backup && typeof plan.backup === "object" ? plan.backup : undefined;
88
+ const tunnelConfig = plan.tunnel && typeof plan.tunnel === "object" ? plan.tunnel : undefined;
89
+ const tunnelSnippet = tunnelConfig
90
+ ? tunnelConfig.instanceType
91
+ ? `tunnel: { instanceType: "${tunnelConfig.instanceType}" }`
92
+ : "tunnel: true"
93
+ : undefined;
94
+ if (plan.vpcId) {
95
+ code += `const app = App.getApp(appName, {
96
+ network: { useExisting: "${plan.vpcId}" }`;
97
+ if (backupConfig) {
98
+ code += `,
99
+ backup: { tier: "${backupConfig.tier}" }`;
100
+ }
101
+ if (tunnelSnippet) {
102
+ code += `,
103
+ ${tunnelSnippet}`;
104
+ }
105
+ code += `
106
+ });
107
+ `;
108
+ }
109
+ else if (plan.network) {
110
+ const cleanedNetwork = cleanNetworkConfig(plan.network);
111
+ code += `const app = App.getApp(appName, {
112
+ network: ${formatValue(cleanedNetwork, " ")}`;
113
+ if (backupConfig) {
114
+ code += `,
115
+ backup: { tier: "${backupConfig.tier}" }`;
116
+ }
117
+ if (tunnelSnippet) {
118
+ code += `,
119
+ ${tunnelSnippet}`;
120
+ }
121
+ code += `
122
+ });
123
+ `;
124
+ }
125
+ else {
126
+ code += `const app = App.getApp(appName, { network: false`;
127
+ if (backupConfig) {
128
+ code += `, backup: { tier: "${backupConfig.tier}" }`;
129
+ }
130
+ if (tunnelSnippet) {
131
+ code += `, ${tunnelSnippet}`;
132
+ }
133
+ code += ` });
134
+ `;
135
+ }
136
+ code += `
137
+ `;
138
+ return code;
139
+ }
140
+ /**
141
+ * Generate import statements for the infrastructure file
142
+ */
143
+ export function generateImports(plan) {
144
+ const imports = [];
145
+ if (plan.patternConfig) {
146
+ imports.push("PatternFactory");
147
+ }
148
+ else {
149
+ if (plan.database.length > 0) {
150
+ imports.push("DatabaseFactory");
151
+ }
152
+ if (plan.s3.length > 0) {
153
+ imports.push("StorageFactory");
154
+ }
155
+ if (plan.compute.length > 0) {
156
+ imports.push("ComputeFactory");
157
+ imports.push("getConfig");
158
+ }
159
+ if (plan.dynamodb &&
160
+ plan.dynamodb.length > 0 &&
161
+ plan.database.length === 0) {
162
+ imports.push("DatabaseFactory");
163
+ }
164
+ if (plan.sqs && plan.sqs.length > 0) {
165
+ imports.push("MessagingFactory");
166
+ }
167
+ if (plan.cdn) {
168
+ imports.push("CdnFactory");
169
+ }
170
+ const hasCodeBasedLambda = plan.compute.some((c) => c.type === COMPUTE_TYPE.LAMBDA && c.deployment === "code");
171
+ if (hasCodeBasedLambda) {
172
+ imports.push("Code");
173
+ imports.push("Runtime");
174
+ }
175
+ const hasFunctionUrl = plan.compute.some((c) => c.type === COMPUTE_TYPE.LAMBDA && c.functionUrl);
176
+ if (hasFunctionUrl) {
177
+ imports.push("FunctionUrlAuthType");
178
+ }
179
+ const hasArchitecture = plan.compute.some((c) => c.type === COMPUTE_TYPE.LAMBDA &&
180
+ (c.architecture || c.deployment === "container"));
181
+ if (hasArchitecture) {
182
+ imports.push("Architecture");
183
+ }
184
+ }
185
+ if (plan.additionalNetworks && plan.additionalNetworks.length > 0) {
186
+ imports.push("NetworkFactory");
187
+ }
188
+ // Merge additional named imports for @fjall/components-infrastructure
189
+ if (plan.additionalManagedImports) {
190
+ for (const extra of plan.additionalManagedImports) {
191
+ if (extra.moduleSpecifier === "@fjall/components-infrastructure" ||
192
+ extra.moduleSpecifier === "@fjall/infrastructure") {
193
+ for (const name of extra.namedImports) {
194
+ if (!imports.includes(name)) {
195
+ imports.push(name);
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ const importList = imports.length > 0 ? `, ${imports.join(", ")}` : "";
202
+ let code = `#!/usr/bin/env node
203
+
204
+ import { App${importList} } from "@fjall/components-infrastructure";
205
+ `;
206
+ // Append separate lines for other managed modules (aws-cdk-lib/*, constructs)
207
+ if (plan.additionalManagedImports) {
208
+ for (const extra of plan.additionalManagedImports) {
209
+ if (extra.moduleSpecifier === "@fjall/components-infrastructure" ||
210
+ extra.moduleSpecifier === "@fjall/infrastructure") {
211
+ continue;
212
+ }
213
+ const namedPart = extra.namedImports.length > 0
214
+ ? `{ ${extra.namedImports.join(", ")} }`
215
+ : "";
216
+ const defaultPart = extra.defaultImport ?? "";
217
+ const importParts = [defaultPart, namedPart].filter(Boolean).join(", ");
218
+ if (importParts) {
219
+ code += `import ${importParts} from "${extra.moduleSpecifier}";\n`;
220
+ }
221
+ }
222
+ }
223
+ return code;
224
+ }
225
+ /**
226
+ * Generate CDN infrastructure code
227
+ */
228
+ export function generateCDNCode(plan) {
229
+ if (!plan.cdn)
230
+ return "";
231
+ const defaultOriginVar = resolveResourceVariable(plan, plan.cdn.defaultOriginRef);
232
+ let code = `
233
+ app.addCdn(
234
+ CdnFactory.build("${plan.cdn.name}", {
235
+ originType: "auto",
236
+ origin: ${defaultOriginVar}`;
237
+ if (plan.cdn.behaviours && plan.cdn.behaviours.length > 0) {
238
+ const behavioursStr = plan.cdn.behaviours
239
+ .map((b) => {
240
+ const originVar = resolveResourceVariable(plan, b.originRef);
241
+ let bCode = `{\n pathPattern: "${b.pathPattern}",\n origin: ${originVar},`;
242
+ if (b.cachePolicy) {
243
+ bCode += `\n cachePolicy: "${b.cachePolicy}",`;
244
+ }
245
+ bCode += `\n }`;
246
+ return bCode;
247
+ })
248
+ .join(",\n ");
249
+ code += `,
250
+ behaviours: [
251
+ ${behavioursStr}
252
+ ]`;
253
+ }
254
+ if (plan.cdn.customDomain) {
255
+ code += `,
256
+ domainNames: ["${plan.cdn.customDomain}"]`;
257
+ }
258
+ if (plan.cdn.certificateArn) {
259
+ code += `,
260
+ certificateArn: "${plan.cdn.certificateArn}"`;
261
+ }
262
+ if (plan.cdn.accessGate !== undefined) {
263
+ if (plan.cdn.accessGate === false) {
264
+ code += `,
265
+ accessGate: false`;
266
+ }
267
+ else {
268
+ code += `,
269
+ accessGate: ${formatValue(plan.cdn.accessGate, " ")}`;
270
+ }
271
+ }
272
+ code += emitExtraProperties(plan.cdn.extraProperties);
273
+ code += `
274
+ })
275
+ );
276
+ `;
277
+ return code;
278
+ }
279
+ /**
280
+ * Collect CDN-referenced resources for compute variable naming
281
+ */
282
+ export function collectCdnReferencedResources(plan) {
283
+ const cdnReferencedResources = new Set();
284
+ if (plan.cdn) {
285
+ if (plan.cdn.defaultOriginRef) {
286
+ cdnReferencedResources.add(plan.cdn.defaultOriginRef);
287
+ }
288
+ if (plan.cdn.behaviours) {
289
+ for (const behaviour of plan.cdn.behaviours) {
290
+ cdnReferencedResources.add(behaviour.originRef);
291
+ }
292
+ }
293
+ }
294
+ return cdnReferencedResources;
295
+ }
296
+ /**
297
+ * Check if a plan uses the pattern approach (PatternFactory).
298
+ * When patternConfig is present, we generate a single PatternFactory.build() call
299
+ * instead of individual factory calls for each resource.
300
+ */
301
+ export function usesPatternApproach(plan) {
302
+ return plan.patternConfig !== undefined;
303
+ }
304
+ /** Default values for OpenNext pattern code generation (matches tier presets). */
305
+ const OPENNEXT_DEFAULTS = Object.freeze({
306
+ database: {
307
+ type: "Instance",
308
+ backupRetention: 7,
309
+ deletionProtection: true,
310
+ },
311
+ compute: {
312
+ server: { memorySize: 1536, timeout: 30 },
313
+ imageOptimisation: { memorySize: 1536, timeout: 30 },
314
+ revalidation: { memorySize: 768, timeout: 300 },
315
+ },
316
+ });
317
+ /**
318
+ * Generate pattern infrastructure code with clean formatting.
319
+ * Handles both Payload and Next.js OpenNext patterns.
320
+ */
321
+ export function generatePatternCodeWithComments(plan) {
322
+ if (!plan.patternConfig)
323
+ return "";
324
+ const config = plan.patternConfig;
325
+ const pascalName = toPascalCase(config.name);
326
+ const typeSuffix = config.type === "payload" ? "Payload" : "Nextjs";
327
+ const constructId = `${pascalName}${typeSuffix}`;
328
+ let code = `app.addPattern(
329
+ PatternFactory.build("${constructId}", {
330
+ type: "${config.type}",
331
+ name: "${config.name}",`;
332
+ if (config.domain) {
333
+ code += `
334
+ domain: "${config.domain}",`;
335
+ }
336
+ if (config.type === "payload" || config.type === "nextjs") {
337
+ const db = config.database || {};
338
+ const compute = config.compute || {};
339
+ const dbType = db.type || OPENNEXT_DEFAULTS.database.type;
340
+ const backupRetention = db.backupRetention ?? OPENNEXT_DEFAULTS.database.backupRetention;
341
+ const deletionProtection = db.deletionProtection ?? OPENNEXT_DEFAULTS.database.deletionProtection;
342
+ code += `
343
+ database: {
344
+ type: "${dbType}",`;
345
+ if (db.databaseName) {
346
+ code += `
347
+ databaseName: "${db.databaseName}",`;
348
+ }
349
+ if (dbType === "Instance" && db.instanceType) {
350
+ code += `
351
+ instanceType: "${db.instanceType}",`;
352
+ }
353
+ code += `
354
+ backupRetention: ${backupRetention},
355
+ deletionProtection: ${deletionProtection},
356
+ },`;
357
+ const serverMemory = compute.server?.memorySize ?? OPENNEXT_DEFAULTS.compute.server.memorySize;
358
+ const serverTimeout = compute.server?.timeout ?? OPENNEXT_DEFAULTS.compute.server.timeout;
359
+ const imageMemory = compute.imageOptimisation?.memorySize ??
360
+ OPENNEXT_DEFAULTS.compute.imageOptimisation.memorySize;
361
+ const imageTimeout = compute.imageOptimisation?.timeout ??
362
+ OPENNEXT_DEFAULTS.compute.imageOptimisation.timeout;
363
+ const revalidationMemory = compute.revalidation?.memorySize ??
364
+ OPENNEXT_DEFAULTS.compute.revalidation.memorySize;
365
+ const revalidationTimeout = compute.revalidation?.timeout ??
366
+ OPENNEXT_DEFAULTS.compute.revalidation.timeout;
367
+ code += `
368
+ compute: {
369
+ server: {
370
+ memorySize: ${serverMemory},
371
+ timeout: ${serverTimeout},
372
+ },
373
+ imageOptimisation: {
374
+ memorySize: ${imageMemory},
375
+ timeout: ${imageTimeout},
376
+ },
377
+ revalidation: {
378
+ memorySize: ${revalidationMemory},
379
+ timeout: ${revalidationTimeout},
380
+ },
381
+ },`;
382
+ }
383
+ code += `
384
+ })
385
+ );
386
+
387
+ `;
388
+ return code;
389
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Storage Code Generation
3
+ *
4
+ * Functions for generating storage infrastructure code including
5
+ * S3 buckets, DynamoDB tables, and SQS queues.
6
+ */
7
+ import type { ApplicationResourcePlan, S3ResourcePlan } from "../schemas/resourceSchemas.js";
8
+ /**
9
+ * Check if an S3 bucket needs a variable assignment (referenced by compute or CDN)
10
+ */
11
+ export declare function s3NeedsVariable(bucket: S3ResourcePlan, plan: ApplicationResourcePlan): boolean;
12
+ /**
13
+ * Generate S3 bucket infrastructure code
14
+ */
15
+ export declare function generateS3Code(plan: ApplicationResourcePlan): string;
16
+ /**
17
+ * Generate DynamoDB table infrastructure code
18
+ */
19
+ export declare function generateDynamoDBCode(plan: ApplicationResourcePlan): string;
20
+ /**
21
+ * Generate SQS queue infrastructure code
22
+ */
23
+ export declare function generateSQSCode(plan: ApplicationResourcePlan): string;