@fjall/generator 0.88.4 → 0.89.2

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/ast/astCdnParser.d.ts +15 -0
  3. package/dist/src/ast/astCdnParser.js +114 -0
  4. package/dist/src/ast/astCommonParser.d.ts +90 -0
  5. package/dist/src/ast/astCommonParser.js +351 -0
  6. package/dist/src/ast/astComputeParser.d.ts +14 -2
  7. package/dist/src/ast/astComputeParser.js +55 -9
  8. package/dist/src/ast/astDatabaseParser.d.ts +104 -0
  9. package/dist/src/ast/astDatabaseParser.js +275 -0
  10. package/dist/src/ast/astInfrastructureParser.d.ts +23 -277
  11. package/dist/src/ast/astInfrastructureParser.js +83 -1456
  12. package/dist/src/ast/astMessagingParser.d.ts +25 -0
  13. package/dist/src/ast/astMessagingParser.js +78 -0
  14. package/dist/src/ast/astNetworkParser.d.ts +70 -0
  15. package/dist/src/ast/astNetworkParser.js +219 -0
  16. package/dist/src/ast/astPatternParser.d.ts +80 -0
  17. package/dist/src/ast/astPatternParser.js +155 -0
  18. package/dist/src/ast/astStorageParser.d.ts +18 -0
  19. package/dist/src/ast/astStorageParser.js +164 -0
  20. package/dist/src/ast/index.d.ts +1 -0
  21. package/dist/src/ast/index.js +4 -0
  22. package/dist/src/dns/bindParser.d.ts +13 -0
  23. package/dist/src/dns/bindParser.js +224 -0
  24. package/dist/src/dns/bindWriter.d.ts +2 -0
  25. package/dist/src/dns/bindWriter.js +52 -0
  26. package/dist/src/dns/index.d.ts +4 -0
  27. package/dist/src/dns/index.js +4 -0
  28. package/dist/src/dns/infrastructureWriter.d.ts +2 -0
  29. package/dist/src/dns/infrastructureWriter.js +58 -0
  30. package/dist/src/dns/types.d.ts +82 -0
  31. package/dist/src/dns/types.js +52 -0
  32. package/dist/src/generation/common.d.ts +1 -16
  33. package/dist/src/generation/common.js +2 -28
  34. package/dist/src/generation/compute.js +77 -28
  35. package/dist/src/generation/index.d.ts +2 -1
  36. package/dist/src/generation/index.js +3 -1
  37. package/dist/src/generation/messagingConnections.d.ts +33 -0
  38. package/dist/src/generation/messagingConnections.js +73 -0
  39. package/dist/src/generation/storage.d.ts +5 -1
  40. package/dist/src/generation/storage.js +9 -1
  41. package/dist/src/generation/storageConnections.d.ts +3 -3
  42. package/dist/src/generation/storageConnections.js +8 -4
  43. package/dist/src/index.d.ts +1 -0
  44. package/dist/src/index.js +2 -0
  45. package/dist/src/planning/resourcePlanning.js +0 -2
  46. package/dist/src/presets/tierTypes.d.ts +4 -1
  47. package/dist/src/schemas/applicationSchemas.d.ts +854 -0
  48. package/dist/src/schemas/applicationSchemas.js +80 -0
  49. package/dist/src/schemas/baseSchemas.d.ts +206 -0
  50. package/dist/src/schemas/baseSchemas.js +248 -0
  51. package/dist/src/schemas/cdnSchemas.d.ts +61 -0
  52. package/dist/src/schemas/cdnSchemas.js +62 -0
  53. package/dist/src/schemas/computeSchemas.d.ts +723 -0
  54. package/dist/src/schemas/computeSchemas.js +727 -0
  55. package/dist/src/schemas/constants.d.ts +12 -8
  56. package/dist/src/schemas/constants.js +14 -4
  57. package/dist/src/schemas/databaseSchemas.d.ts +638 -0
  58. package/dist/src/schemas/databaseSchemas.js +366 -0
  59. package/dist/src/schemas/messagingSchemas.d.ts +20 -0
  60. package/dist/src/schemas/messagingSchemas.js +29 -0
  61. package/dist/src/schemas/networkSchemas.d.ts +246 -0
  62. package/dist/src/schemas/networkSchemas.js +125 -0
  63. package/dist/src/schemas/patternSchemas.d.ts +708 -0
  64. package/dist/src/schemas/patternSchemas.js +294 -0
  65. package/dist/src/schemas/resourceSchemas.d.ts +24 -3530
  66. package/dist/src/schemas/resourceSchemas.js +24 -2011
  67. package/dist/src/schemas/storageSchemas.d.ts +93 -0
  68. package/dist/src/schemas/storageSchemas.js +119 -0
  69. package/dist/src/util/errorUtils.d.ts +1 -2
  70. package/dist/src/util/errorUtils.js +1 -15
  71. package/dist/src/validation/patterns.d.ts +9 -0
  72. package/dist/src/validation/patterns.js +9 -0
  73. package/package.json +4 -3
@@ -1,2015 +1,28 @@
1
- import { z } from "zod";
2
- import { validateArchitectureMatch, getArchitectureForInstanceType, } from "./instanceTypeArchitecture.js";
3
- import { VALIDATION_PATTERNS, VALIDATION_MESSAGES, } from "../validation/patterns.js";
4
- import { DATABASE_TYPES, COMPUTE_TYPES, ECS_CAPACITY_PROVIDERS, APP_TYPES, PATTERN_TYPE_VALUES, EC2_INSTANCE_TYPES, HTTP_METHODS, STORAGE_PRESET_TYPES, S3_ENCRYPTION_TYPES, BACKUP_VAULT_TIERS, MIN_PORT, MAX_PORT, MIN_LAMBDA_MEMORY, MAX_LAMBDA_MEMORY, MIN_LAMBDA_TIMEOUT, MAX_LAMBDA_TIMEOUT, MIN_ECS_CAPACITY, MAX_ECS_CAPACITY, VALID_MONITORING_INTERVALS, constIncludes, COMPUTE_ARCHITECTURES, } from "./constants.js";
5
- import { regions } from "../aws/regions.js";
6
- // Base schemas
7
- export const ResourceNameSchema = z
8
- .string()
9
- .min(1, VALIDATION_MESSAGES.REQUIRED.RESOURCE_NAME)
10
- .max(63, VALIDATION_MESSAGES.MAX_LENGTH.RESOURCE_NAME)
11
- .regex(VALIDATION_PATTERNS.RESOURCE_NAME, VALIDATION_MESSAGES.RESOURCE_NAME);
12
- export const BucketNameSchema = z
13
- .string()
14
- .min(1, VALIDATION_MESSAGES.REQUIRED.BUCKET_NAME)
15
- .max(63, VALIDATION_MESSAGES.MAX_LENGTH.BUCKET_NAME)
16
- .regex(VALIDATION_PATTERNS.BUCKET_NAME, VALIDATION_MESSAGES.BUCKET_NAME);
17
- export const DatabaseNameSchema = z
18
- .string()
19
- .min(1, VALIDATION_MESSAGES.REQUIRED.DATABASE_NAME)
20
- .max(63, VALIDATION_MESSAGES.MAX_LENGTH.DATABASE_NAME)
21
- .regex(VALIDATION_PATTERNS.DATABASE_NAME, VALIDATION_MESSAGES.DATABASE_NAME);
22
- export const AppNameSchema = z
23
- .string()
24
- .min(2, VALIDATION_MESSAGES.IDENTIFIER_MIN_LENGTH)
25
- .max(50, VALIDATION_MESSAGES.MAX_LENGTH.APP_NAME)
26
- .regex(VALIDATION_PATTERNS.IDENTIFIER, VALIDATION_MESSAGES.IDENTIFIER)
27
- .refine((val) => !val.endsWith("-"), {
28
- message: VALIDATION_MESSAGES.IDENTIFIER_NO_TRAILING_HYPHEN,
29
- })
30
- .refine((val) => !val.includes("--"), {
31
- message: VALIDATION_MESSAGES.IDENTIFIER_NO_CONSECUTIVE_HYPHENS,
32
- });
33
- export const PortSchema = z
34
- .number()
35
- .int(VALIDATION_MESSAGES.PORT.INTEGER)
36
- .min(MIN_PORT, VALIDATION_MESSAGES.PORT.MIN)
37
- .max(MAX_PORT, VALIDATION_MESSAGES.PORT.MAX);
38
- const optionalOrDisabled = (schema) => z.union([z.literal(false), schema]);
39
1
  /**
40
- * Extra property preserved verbatim during round-trip.
41
- * Captures properties inside managed factory calls that the parser
42
- * does not specifically handle (e.g. CDK PolicyStatement[], custom configs).
43
- */
44
- export const ExtraPropertySchema = z
45
- .object({
46
- key: z.string(),
47
- sourceText: z.string(),
48
- })
49
- .strict();
50
- /** Reusable capacity validation: ensures minCapacity <= maxCapacity. */
51
- const CAPACITY_REFINEMENT = {
52
- check: (data) => {
53
- if (data.minCapacity !== undefined && data.maxCapacity !== undefined) {
54
- return data.minCapacity <= data.maxCapacity;
55
- }
56
- return true;
57
- },
58
- params: {
59
- message: VALIDATION_MESSAGES.CAPACITY_CONSTRAINT.MIN_LTE_MAX,
60
- path: ["maxCapacity"],
61
- },
62
- };
63
- /** Reusable memory limit schema. EC2 allows 128 MiB min; ECS/containers require 512 MiB min. */
64
- const memoryLimitMiBSchema = (min) => z
65
- .number()
66
- .int(VALIDATION_MESSAGES.MEMORY_LIMIT.INTEGER)
67
- .min(min, min === 128
68
- ? VALIDATION_MESSAGES.MEMORY_LIMIT.MIN_128
69
- : VALIDATION_MESSAGES.MEMORY_LIMIT.MIN_512)
70
- .max(30720, VALIDATION_MESSAGES.MEMORY_LIMIT.MAX);
71
- /** Reusable backup retention validation. Range: 1-35 days. */
72
- const BackupRetentionSchema = z
73
- .number()
74
- .int(VALIDATION_MESSAGES.BACKUP_RETENTION.INTEGER)
75
- .min(1, VALIDATION_MESSAGES.BACKUP_RETENTION.MIN)
76
- .max(35, VALIDATION_MESSAGES.BACKUP_RETENTION.MAX);
77
- /** Reusable monitoring interval validation. Must be one of: 0, 1, 5, 10, 15, 30, 60. */
78
- const MonitoringIntervalSchema = z
79
- .number()
80
- .int(VALIDATION_MESSAGES.MONITORING_INTERVAL.INTEGER)
81
- .refine((val) => constIncludes(VALID_MONITORING_INTERVALS, val), {
82
- message: VALIDATION_MESSAGES.MONITORING_INTERVAL.VALUES,
83
- });
84
- /** Reusable database port validation. Range: 1024-65535. */
85
- const DatabasePortSchema = z
86
- .number()
87
- .int(VALIDATION_MESSAGES.DATABASE.PORT.INTEGER)
88
- .min(1024, VALIDATION_MESSAGES.DATABASE.PORT.MIN)
89
- .max(65535, VALIDATION_MESSAGES.DATABASE.PORT.MAX);
90
- /** Reusable generator capacity schemas for min/max capacity. */
91
- const GeneratorMinCapacitySchema = z
92
- .number()
93
- .int(VALIDATION_MESSAGES.CAPACITY.MIN.INTEGER)
94
- .min(MIN_ECS_CAPACITY, VALIDATION_MESSAGES.GENERATOR_CAPACITY.MIN.MIN)
95
- .max(MAX_ECS_CAPACITY, VALIDATION_MESSAGES.GENERATOR_CAPACITY.MIN.MAX);
96
- const GeneratorMaxCapacitySchema = z
97
- .number()
98
- .int(VALIDATION_MESSAGES.CAPACITY.MAX.INTEGER)
99
- .min(MIN_ECS_CAPACITY, VALIDATION_MESSAGES.GENERATOR_CAPACITY.MAX.MIN)
100
- .max(MAX_ECS_CAPACITY, VALIDATION_MESSAGES.GENERATOR_CAPACITY.MAX.MAX);
101
- /** Reusable Lambda memory validation with multiple-of-64 constraint. */
102
- const LambdaMemorySchema = z
103
- .number()
104
- .int(VALIDATION_MESSAGES.LAMBDA.MEMORY.INTEGER)
105
- .min(MIN_LAMBDA_MEMORY, VALIDATION_MESSAGES.LAMBDA.MEMORY.MIN)
106
- .max(MAX_LAMBDA_MEMORY, VALIDATION_MESSAGES.LAMBDA.MEMORY.MAX)
107
- .refine((mem) => mem === 128 || mem % 64 === 0, VALIDATION_MESSAGES.LAMBDA.MEMORY.MULTIPLE);
108
- /**
109
- * Schema for special values used in code generation.
110
- * These represent dynamic code references rather than literal values.
111
- */
112
- export const IdentifierValueSchema = z
113
- .object({ __identifier: z.string() })
114
- .strict();
115
- export const ExpressionValueSchema = z
116
- .object({ __expression: z.string() })
117
- .strict();
118
- export const CallValueSchema = z.object({ __call: z.string() }).strict();
119
- export const SpecialValueSchema = z.union([
120
- IdentifierValueSchema,
121
- ExpressionValueSchema,
122
- CallValueSchema,
123
- ]);
124
- /**
125
- * Schema for environment variable values.
126
- * Supports literal values and special code generation values.
127
- */
128
- export const EnvironmentValueSchema = z.union([
129
- z.string(),
130
- z.number(),
131
- z.boolean(),
132
- SpecialValueSchema,
133
- ]);
134
- export const EnvironmentRecordSchema = z.record(z.string(), EnvironmentValueSchema);
135
- /**
136
- * Schema for importing secrets from AWS Secrets Manager.
137
- * Matches the SecretImport type in infrastructure.
138
- */
139
- export const SecretImportSchema = z
140
- .object({
141
- /** Secret construct ID */
142
- id: z.string(),
143
- /** Secret name in Secrets Manager */
144
- name: z.string(),
145
- /** Optional field to extract from the secret */
146
- field: z.string().optional(),
147
- })
148
- .strict();
149
- /**
150
- * Record of secret imports keyed by environment variable name.
151
- */
152
- export const SecretsImportRecordSchema = z.record(z.string(), SecretImportSchema);
153
- /**
154
- * Schema for metadata values.
155
- * Supports simple types and complex AWS SDK types (VpcConfigResponse, Date, etc.)
156
- * Uses z.unknown() for flexibility with AWS resource properties whilst avoiding z.any().
157
- */
158
- export const MetadataRecordSchema = z.record(z.string(), z.unknown());
159
- export const Ec2ConfigSchema = z
160
- .object({
161
- instanceType: z.string().optional(),
162
- amiHardwareType: z.enum(["ARM", "STANDARD"]).optional(),
163
- minCapacity: z
164
- .number()
165
- .int(VALIDATION_MESSAGES.CAPACITY.MIN.INTEGER)
166
- .min(1, VALIDATION_MESSAGES.CAPACITY.MIN.MIN_1)
167
- .max(100, VALIDATION_MESSAGES.CAPACITY.MIN.MAX)
168
- .optional(),
169
- maxCapacity: z
170
- .number()
171
- .int(VALIDATION_MESSAGES.CAPACITY.MAX.INTEGER)
172
- .min(1, VALIDATION_MESSAGES.CAPACITY.MAX.MIN_1)
173
- .max(100, VALIDATION_MESSAGES.CAPACITY.MAX.MAX)
174
- .optional(),
175
- memoryLimitMiB: memoryLimitMiBSchema(128).optional(),
176
- })
177
- .strict()
178
- .superRefine((data, ctx) => {
179
- if (!data.instanceType || !data.amiHardwareType)
180
- return;
181
- const result = validateArchitectureMatch(data.instanceType, data.amiHardwareType);
182
- if (!result.valid) {
183
- ctx.addIssue({
184
- code: "custom",
185
- message: result.message ?? VALIDATION_MESSAGES.ARCHITECTURE_MISMATCH,
186
- path: ["instanceType"],
187
- });
188
- }
189
- });
190
- // Database schemas
191
- export const DatabaseTypeSchema = z
192
- .enum(DATABASE_TYPES)
193
- .describe(`Database type must be one of: ${DATABASE_TYPES.join(", ")}`);
194
- // Nested configuration schemas for database sub-resources
195
- export const ProxyConfigSchema = z
196
- .object({
197
- maxConnections: z
198
- .number()
199
- .int(VALIDATION_MESSAGES.MAX_CONNECTIONS.INTEGER)
200
- .min(1, VALIDATION_MESSAGES.MAX_CONNECTIONS.MIN)
201
- .max(100, VALIDATION_MESSAGES.MAX_CONNECTIONS.MAX)
202
- .optional(),
203
- maxIdleConnections: z
204
- .number()
205
- .int(VALIDATION_MESSAGES.PROXY_CONFIG.MAX_IDLE_CONNECTIONS.INTEGER)
206
- .min(0, VALIDATION_MESSAGES.PROXY_CONFIG.MAX_IDLE_CONNECTIONS.MIN)
207
- .max(100, VALIDATION_MESSAGES.PROXY_CONFIG.MAX_IDLE_CONNECTIONS.MAX)
208
- .optional(),
209
- connectionBorrowTimeout: z
210
- .number()
211
- .int(VALIDATION_MESSAGES.PROXY_CONFIG.BORROW_TIMEOUT.INTEGER)
212
- .min(1, VALIDATION_MESSAGES.PROXY_CONFIG.BORROW_TIMEOUT.MIN)
213
- .max(3600, VALIDATION_MESSAGES.PROXY_CONFIG.BORROW_TIMEOUT.MAX)
214
- .optional(),
215
- requireTLS: z.boolean().optional(),
216
- })
217
- .strict()
218
- .describe("RDS Proxy configuration");
219
- export const ReadReplicaConfigSchema = z
220
- .object({
221
- instanceType: z.string().optional(),
222
- availabilityZone: z.string().optional(),
223
- })
224
- .strict()
225
- .describe("Read replica configuration");
226
- export const SecretRotationConfigSchema = z
227
- .object({
228
- automaticallyAfterDays: z
229
- .number()
230
- .int(VALIDATION_MESSAGES.ROTATION.INTEGER)
231
- .min(1, VALIDATION_MESSAGES.ROTATION.MIN)
232
- .max(365, VALIDATION_MESSAGES.ROTATION.MAX)
233
- .optional(),
234
- })
235
- .strict()
236
- .describe("Secret rotation configuration");
237
- export const CredentialsConfigSchema = z
238
- .object({
239
- username: z
240
- .string()
241
- .min(1, VALIDATION_MESSAGES.REQUIRED.USERNAME)
242
- .max(63, VALIDATION_MESSAGES.USERNAME.MAX_LENGTH)
243
- .optional(),
244
- secretRotation: SecretRotationConfigSchema.optional(),
245
- })
246
- .strict()
247
- .describe("Database credentials configuration");
248
- // Combined nested config schema (accepts object or false for explicit disable)
249
- export const ProxyConfigOrFalseSchema = optionalOrDisabled(ProxyConfigSchema);
250
- export const ReadReplicaConfigOrFalseSchema = optionalOrDisabled(ReadReplicaConfigSchema);
251
- // Aurora-specific nested configuration schemas
252
- export const AuroraReaderConfigSchema = z
253
- .object({
254
- scaleWithWriter: z.boolean().optional(),
255
- enableDatabaseInsights: z.boolean().optional(),
256
- identifierSuffix: z
257
- .string()
258
- .min(1, VALIDATION_MESSAGES.IDENTIFIER_SUFFIX.REQUIRED)
259
- .max(50, VALIDATION_MESSAGES.IDENTIFIER_SUFFIX.MAX_LENGTH)
260
- .optional(),
261
- availabilityZone: z.string().optional(),
262
- })
263
- .strict()
264
- .describe("Configuration for a single Aurora reader instance");
265
- export const AuroraWriterConfigSchema = z
266
- .object({
267
- enableDatabaseInsights: z.boolean().optional(),
268
- identifierSuffix: z
269
- .string()
270
- .min(1, VALIDATION_MESSAGES.IDENTIFIER_SUFFIX.REQUIRED)
271
- .max(50, VALIDATION_MESSAGES.IDENTIFIER_SUFFIX.MAX_LENGTH)
272
- .optional(),
273
- availabilityZone: z.string().optional(),
274
- })
275
- .strict()
276
- .describe("Configuration for Aurora writer instance");
277
- export const AuroraReadersConfigSchema = z
278
- .object({
279
- count: z
280
- .number()
281
- .int(VALIDATION_MESSAGES.READER.COUNT.INTEGER)
282
- .min(0, VALIDATION_MESSAGES.READER.COUNT.MIN)
283
- .max(15, VALIDATION_MESSAGES.READER.COUNT.MAX)
284
- .optional(),
285
- instances: z
286
- .array(AuroraReaderConfigSchema)
287
- .max(15, VALIDATION_MESSAGES.READER_INSTANCES.MAX)
288
- .optional(),
289
- defaultEnableDatabaseInsights: z.boolean().optional(),
290
- })
291
- .strict()
292
- .refine((data) => !(data.count !== undefined && data.instances !== undefined), {
293
- message: VALIDATION_MESSAGES.READER_INSTANCES.COUNT_OR_INSTANCES,
294
- path: ["count"],
295
- })
296
- .describe("Aurora readers configuration");
297
- export const AuroraReadersConfigOrFalseSchema = optionalOrDisabled(AuroraReadersConfigSchema);
298
- export const AwsManagedKeySchema = z
299
- .object({
300
- awsManaged: z.literal(true),
301
- })
302
- .strict();
303
- export const CustomerManagedKeyMarkerSchema = z
304
- .object({
305
- useCMK: z.literal(true),
306
- })
307
- .strict();
308
- // Encryption key specification for generator/tier preset contexts.
309
- export const EncryptionKeySpecSchema = z.union([
310
- AwsManagedKeySchema,
311
- CustomerManagedKeyMarkerSchema,
312
- ]);
313
- export const DatabaseInsightsConfigSchema = z
314
- .object({
315
- mode: z.enum(["standard", "advanced"]).optional(),
316
- encryptionKey: EncryptionKeySpecSchema.optional(),
317
- })
318
- .strict()
319
- .describe("Database Insights configuration");
320
- export const DatabaseInsightsConfigOrFalseSchema = optionalOrDisabled(DatabaseInsightsConfigSchema);
321
- export const EncryptionConfigSchema = z
322
- .object({
323
- storageKey: EncryptionKeySpecSchema.optional(),
324
- })
325
- .strict()
326
- .describe("Encryption configuration (DESTRUCTIVE to change storageKey)");
327
- export const DatabaseResourcePlanSchema = z
328
- .object({
329
- name: ResourceNameSchema,
330
- type: DatabaseTypeSchema,
331
- databaseName: z.string().min(1, VALIDATION_MESSAGES.REQUIRED.DATABASE_NAME),
332
- // Instance-specific props (only apply when type is "Instance")
333
- instanceType: z.string().optional(),
334
- multiAz: z.boolean().optional(),
335
- publiclyAccessible: z.boolean().optional(),
336
- enableSecretRotation: z.boolean().optional(),
337
- // Shared props (Instance and Aurora)
338
- databaseInsights: DatabaseInsightsConfigOrFalseSchema.optional(),
339
- port: DatabasePortSchema.optional(),
340
- deletionProtection: z.boolean().optional(),
341
- // Nested configurations (presence-based: object = enabled, false = disabled)
342
- proxy: ProxyConfigOrFalseSchema.optional(),
343
- credentials: CredentialsConfigSchema.optional(),
344
- // Instance-specific nested config
345
- readReplica: ReadReplicaConfigOrFalseSchema.optional(),
346
- // Shared encryption config (Instance and Aurora)
347
- encryption: EncryptionConfigSchema.optional(),
348
- // Aurora/GlobalAurora-specific props
349
- writer: AuroraWriterConfigSchema.optional(),
350
- readers: AuroraReadersConfigOrFalseSchema.optional(),
351
- // Shared operational props (Instance, Aurora, GlobalAurora)
352
- backupRetention: BackupRetentionSchema.optional(),
353
- preferredMaintenanceWindow: z.string().optional(),
354
- monitoringInterval: MonitoringIntervalSchema.optional(),
355
- // GlobalAurora-specific props
356
- primaryRegion: z.string().optional(),
357
- secondaryRegions: z.array(z.string()).optional(),
358
- globalClusterIdentifier: z.string().optional(),
359
- enableGlobalWriteForwarding: z.boolean().optional(),
360
- // Shared restore/snapshot props
361
- snapshotIdentifier: z.string().optional(),
362
- snapshotUsername: z.string().optional(),
363
- // Round-trip preservation fields (set by AST parser, used by generator)
364
- databaseEngine: z.enum(["postgresql", "mysql"]).optional(),
365
- engineExpression: z.string().optional(),
366
- variableName: z.string().optional(),
367
- extraProperties: z.array(ExtraPropertySchema).optional(),
368
- })
369
- .strict();
370
- /**
371
- * Stack placement options for S3 buckets.
372
- * - "storage" (default): Created via app.addStorage() in Storage stack
373
- * - "cdn": Created in CDN stack for CloudFront-consumed buckets (avoids circular dependencies)
374
- * - "compute": Created in Compute stack for Lambda-consumed buckets
375
- */
376
- export const S3_STACK_PLACEMENTS = ["storage", "cdn", "compute"];
377
- export const S3ResourcePlanSchema = z
378
- .object({
379
- name: ResourceNameSchema,
380
- // Explicit S3 bucket name (user-provided, kebab-case)
381
- bucketName: z.string().optional(),
382
- // Access control (replaces bucketType)
383
- publicReadAccess: z.boolean().optional(),
384
- // Website hosting (presence-based)
385
- websiteHosting: z
386
- .object({
387
- indexDocument: z.string(),
388
- errorDocument: z.string().optional(),
389
- })
390
- .strict()
391
- .optional(),
392
- // Core
393
- backupVaultTier: z.enum(BACKUP_VAULT_TIERS).optional(),
394
- versioned: z.boolean().optional(),
395
- encryption: z.enum(S3_ENCRYPTION_TYPES).optional(),
396
- kmsKeyArn: z.string().optional(),
397
- // CORS (any bucket)
398
- cors: z
399
- .array(z
400
- .object({
401
- allowedOrigins: z.array(z.string()),
402
- allowedMethods: z.array(z.string()),
403
- })
404
- .strict())
405
- .optional(),
406
- // Deployment (any bucket)
407
- deployment: z
408
- .object({
409
- source: z.string(),
410
- prune: z.boolean().optional(),
411
- cacheControl: z
412
- .object({
413
- maxAge: z.number().optional(),
414
- immutable: z.boolean().optional(),
415
- })
416
- .strict()
417
- .optional(),
418
- })
419
- .strict()
420
- .optional(),
421
- // Stack placement
422
- stackPlacement: z.enum(S3_STACK_PLACEMENTS).optional(),
423
- // Round-trip preservation
424
- variableName: z.string().optional(),
425
- extraProperties: z.array(ExtraPropertySchema).optional(),
426
- })
427
- .strict()
428
- .refine((data) => data.encryption !== "KMS" || !!data.kmsKeyArn, {
429
- message: VALIDATION_MESSAGES.KMS.KEY_REQUIRED,
430
- });
431
- // Network schemas
432
- export const NatConfigSchema = z
433
- .object({
434
- count: z
435
- .number()
436
- .int(VALIDATION_MESSAGES.NAT_GATEWAY.INTEGER)
437
- .min(0, VALIDATION_MESSAGES.NAT_GATEWAY.MIN)
438
- .max(3, VALIDATION_MESSAGES.NAT_GATEWAY.MAX)
439
- .optional(),
440
- })
441
- .strict()
442
- .describe("NAT gateway configuration");
443
- export const NatConfigOrFalseSchema = optionalOrDisabled(NatConfigSchema);
444
- export const FlowLogConfigSchema = z
445
- .object({
446
- destination: z.enum(["cloudwatch", "s3"]).optional(),
447
- retentionDays: z
448
- .number()
449
- .int(VALIDATION_MESSAGES.RETENTION_DAYS.INTEGER)
450
- .min(1, VALIDATION_MESSAGES.RETENTION_DAYS.MIN)
451
- .max(365, VALIDATION_MESSAGES.RETENTION_DAYS.MAX)
452
- .optional(),
453
- trafficType: z.enum(["ALL", "ACCEPT", "REJECT"]).optional(),
454
- })
455
- .strict()
456
- .describe("VPC flow log configuration");
457
- export const FlowLogConfigOrFalseSchema = optionalOrDisabled(FlowLogConfigSchema);
458
- export const GatewayEndpointsConfigSchema = z
459
- .object({
460
- s3: z.boolean().optional(),
461
- dynamodb: z.boolean().optional(),
462
- })
463
- .strict()
464
- .describe("Gateway VPC endpoints (FREE)");
465
- export const GatewayEndpointsConfigOrFalseSchema = optionalOrDisabled(GatewayEndpointsConfigSchema);
466
- export const InterfaceEndpointsConfigSchema = z
467
- .object({
468
- ecr: z.boolean().optional(),
469
- secretsManager: z.boolean().optional(),
470
- kms: z.boolean().optional(),
471
- cloudwatchLogs: z.boolean().optional(),
472
- ssm: z.boolean().optional(),
473
- sts: z.boolean().optional(),
474
- })
475
- .strict()
476
- .describe("Interface VPC endpoints (cost per hour)");
477
- export const InterfaceEndpointsConfigOrFalseSchema = optionalOrDisabled(InterfaceEndpointsConfigSchema);
478
- export const VpcEndpointsConfigSchema = z
479
- .object({
480
- gateway: GatewayEndpointsConfigOrFalseSchema.optional(),
481
- interface: InterfaceEndpointsConfigOrFalseSchema.optional(),
482
- })
483
- .strict()
484
- .describe("VPC endpoints configuration");
485
- export const VpcEndpointsConfigOrFalseSchema = optionalOrDisabled(VpcEndpointsConfigSchema);
486
- export const NetworkResourcePlanSchema = z
487
- .object({
488
- maxAzs: z
489
- .number()
490
- .int(VALIDATION_MESSAGES.MAX_AZS.INTEGER)
491
- .min(1, VALIDATION_MESSAGES.MAX_AZS.MIN)
492
- .max(3, VALIDATION_MESSAGES.MAX_AZS.MAX)
493
- .optional(),
494
- natGateways: NatConfigOrFalseSchema.optional(),
495
- flowLogs: FlowLogConfigOrFalseSchema.optional(),
496
- vpcEndpoints: VpcEndpointsConfigOrFalseSchema.optional(),
497
- cidrMask: z
498
- .number()
499
- .int(VALIDATION_MESSAGES.CIDR_MASK.INTEGER)
500
- .min(16, VALIDATION_MESSAGES.CIDR_MASK.MIN)
501
- .max(28, VALIDATION_MESSAGES.CIDR_MASK.MAX)
502
- .optional(),
503
- })
504
- .strict();
505
- /**
506
- * Additional network resource plan schema for app.addNetwork() VPCs.
507
- * Extends NetworkResourcePlanSchema with a required name field.
508
- */
509
- export const AdditionalNetworkResourcePlanSchema = NetworkResourcePlanSchema.extend({
510
- name: z
511
- .string()
512
- .min(1, VALIDATION_MESSAGES.REQUIRED.NETWORK_NAME)
513
- .max(50, VALIDATION_MESSAGES.MAX_LENGTH.NETWORK_NAME),
514
- }).strict();
515
- /**
516
- * Network configuration for App.getApp() options.
517
- * - false: No network (S3-only apps)
518
- * - object: Create VPC with specified configuration
519
- */
520
- export const NetworkConfigSchema = optionalOrDisabled(NetworkResourcePlanSchema);
521
- /**
522
- * Network name schema for additional VPCs.
523
- * Must be PascalCase as it's used as a CDK construct ID.
524
- */
525
- export const NetworkNameSchema = z
526
- .string()
527
- .min(1, VALIDATION_MESSAGES.REQUIRED.NETWORK_NAME)
528
- .max(50, VALIDATION_MESSAGES.MAX_LENGTH.NETWORK_NAME)
529
- .regex(VALIDATION_PATTERNS.RESOURCE_NAME, VALIDATION_MESSAGES.RESOURCE_NAME);
530
- /**
531
- * Network generator schema for configuring network infrastructure.
532
- *
533
- * Behaviour depends on whether networkName is provided:
534
- * - With networkName: Adds an additional VPC via app.addNetwork()
535
- * - Without networkName: Updates the primary network config in App.getApp()
536
- */
537
- export const NetworkGeneratorSchema = z
538
- .object({
539
- appName: AppNameSchema,
540
- networkName: NetworkNameSchema.optional(),
541
- maxAzs: z
542
- .number()
543
- .int(VALIDATION_MESSAGES.MAX_AZS.INTEGER)
544
- .min(1, VALIDATION_MESSAGES.MAX_AZS.MIN)
545
- .max(3, VALIDATION_MESSAGES.MAX_AZS.MAX)
546
- .optional(),
547
- natGateways: NatConfigOrFalseSchema.optional(),
548
- flowLogs: FlowLogConfigOrFalseSchema.optional(),
549
- vpcEndpoints: VpcEndpointsConfigOrFalseSchema.optional(),
550
- })
551
- .strict();
552
- // Lambda event source schemas
553
- export const LambdaEventSourceSchema = z
554
- .object({
555
- sourceType: z.enum(["sqs", "dynamodb", "eventbridge"]),
556
- sourceRef: z.string().describe("Name of the source resource"),
557
- batchSize: z
558
- .number()
559
- .int(VALIDATION_MESSAGES.BATCH_SIZE.INTEGER)
560
- .min(1, VALIDATION_MESSAGES.BATCH_SIZE.MIN)
561
- .max(10000, VALIDATION_MESSAGES.BATCH_SIZE.MAX)
562
- .optional(),
563
- startingPosition: z.enum(["TRIM_HORIZON", "LATEST"]).optional(),
564
- maxBatchingWindow: z
565
- .number()
566
- .int(VALIDATION_MESSAGES.BATCHING_WINDOW.INTEGER)
567
- .min(0, VALIDATION_MESSAGES.BATCHING_WINDOW.MIN)
568
- .max(300, VALIDATION_MESSAGES.BATCHING_WINDOW.MAX)
569
- .optional(),
570
- reportBatchItemFailures: z.boolean().optional(),
571
- })
572
- .strict();
573
- // Compute schemas
574
- export const ComputeTypeSchema = z
575
- .enum(COMPUTE_TYPES)
576
- .describe(`Compute type must be one of: ${COMPUTE_TYPES.join(", ")}`);
577
- export const EcsCapacityProviderSchema = z
578
- .enum(ECS_CAPACITY_PROVIDERS)
579
- .describe(`ECS capacity provider must be one of: ${ECS_CAPACITY_PROVIDERS.join(", ")}`);
580
- export const InstanceTypeSchema = z
581
- .string()
582
- .refine((type) => constIncludes(EC2_INSTANCE_TYPES, type), {
583
- message: VALIDATION_MESSAGES.INSTANCE_TYPE,
584
- });
585
- /**
586
- * Container configuration for ECS tasks.
587
- * For single-container services, name is optional and auto-generated.
588
- * For multi-container tasks, first container with a port gets load balancer registration.
589
- */
590
- export const ContainerConfigSchema = z
591
- .object({
592
- /** Container name. Optional for single-container services (auto-generated). */
593
- name: z
594
- .string()
595
- .min(1, VALIDATION_MESSAGES.REQUIRED.CONTAINER_NAME)
596
- .optional(),
597
- /** Container image (ECR repo name or public image URL) */
598
- image: z.string().optional(),
599
- /** Port the container listens on */
600
- port: PortSchema.optional(),
601
- /** Environment variables */
602
- environment: EnvironmentRecordSchema.optional(),
603
- /** Secrets imported from AWS Secrets Manager */
604
- secretsImport: SecretsImportRecordSchema.optional(),
605
- /** Command to run in the container */
606
- command: z.array(z.string()).optional(),
607
- /** Entry point for the container */
608
- entryPoint: z.array(z.string()).optional(),
609
- /** Whether this container is essential (default: true) */
610
- essential: z.boolean().optional(),
611
- /** Health check configuration */
612
- healthCheck: z
613
- .object({
614
- command: z.array(z.string()),
615
- interval: z
616
- .number()
617
- .int(VALIDATION_MESSAGES.HEALTH_CHECK.INTERVAL.INTEGER)
618
- .min(5, VALIDATION_MESSAGES.HEALTH_CHECK.INTERVAL.MIN)
619
- .max(300, VALIDATION_MESSAGES.HEALTH_CHECK.INTERVAL.MAX)
620
- .optional(),
621
- timeout: z
622
- .number()
623
- .int(VALIDATION_MESSAGES.HEALTH_CHECK.TIMEOUT.INTEGER)
624
- .min(2, VALIDATION_MESSAGES.HEALTH_CHECK.TIMEOUT.MIN)
625
- .max(60, VALIDATION_MESSAGES.HEALTH_CHECK.TIMEOUT.MAX)
626
- .optional(),
627
- retries: z
628
- .number()
629
- .int(VALIDATION_MESSAGES.HEALTH_CHECK.RETRIES.INTEGER)
630
- .min(1, VALIDATION_MESSAGES.HEALTH_CHECK.RETRIES.MIN)
631
- .max(10, VALIDATION_MESSAGES.HEALTH_CHECK.RETRIES.MAX)
632
- .optional(),
633
- startPeriod: z
634
- .number()
635
- .int(VALIDATION_MESSAGES.HEALTH_CHECK.START_PERIOD.INTEGER)
636
- .min(0, VALIDATION_MESSAGES.HEALTH_CHECK.START_PERIOD.MIN)
637
- .max(300, VALIDATION_MESSAGES.HEALTH_CHECK.START_PERIOD.MAX)
638
- .optional(),
639
- })
640
- .strict()
641
- .optional(),
642
- /**
643
- * SSM secrets to inject into the container.
644
- * Secret names must start with a letter or underscore (e.g., API_KEY, db.password).
645
- * Used with ssmSecretsPath on the service to construct full SSM parameter paths.
646
- */
647
- ssmSecrets: z
648
- .array(z
649
- .string()
650
- .regex(VALIDATION_PATTERNS.SECRET_NAME, VALIDATION_MESSAGES.SECRET_NAME))
651
- .optional(),
652
- extraProperties: z.array(ExtraPropertySchema).optional(),
653
- })
654
- .strict();
655
- /**
656
- * TODO: CRITICAL - Zod schema for ImportedResourcePlan interface
2
+ * Re-export barrel for all resource schemas.
657
3
  *
658
- * This schema validates the ImportedResourcePlan interface and is preserved
659
- * for future pure AST implementation of import functionality.
4
+ * This file previously contained ~2,400 lines of Zod schemas. They have been
5
+ * split into focused domain-specific modules:
660
6
  *
661
- * When re-enabling import with pure AST:
662
- * - Remove cdkCode field (string manipulation anti-pattern)
663
- * - Remove imports field (will be handled by AST)
664
- * - Align with factory pattern for resource creation
7
+ * baseSchemas.ts — Shared utilities, name/port/env schemas, custom code, backup/tunnel
8
+ * databaseSchemas.ts — RDS, Aurora, DynamoDB, proxy, credentials, encryption
9
+ * networkSchemas.ts — VPC, NAT, flow logs, endpoints
10
+ * storageSchemas.ts — S3 resource plan and generator
11
+ * messagingSchemas.ts — SQS resource plan
12
+ * cdnSchemas.ts — CloudFront CDN resource plan and generator
13
+ * computeSchemas.ts — EC2, Lambda, ECS, container, service configs
14
+ * patternSchemas.ts — Payload/NextJS pattern configs, tier schemas
15
+ * applicationSchemas.ts — ApplicationResourcePlan, ApplicationGenerator
665
16
  *
666
- * See resourcePlanning.ts ImportedResourcePlan interface for full documentation.
667
- */
668
- export const ImportedResourcePlanSchema = z
669
- .object({
670
- id: z.string(),
671
- type: z.enum([
672
- "s3",
673
- "ec2",
674
- "lambda",
675
- "rds",
676
- "aurora",
677
- "vpc",
678
- "ecs",
679
- "elasticache",
680
- ]),
681
- name: z.string(),
682
- physicalId: z.string(),
683
- stack: z.enum(["network", "storage", "compute"]),
684
- cdkCode: z.string(),
685
- imports: z.array(z.string()),
686
- metadata: MetadataRecordSchema.optional(),
687
- })
688
- .strict();
689
- // Application schemas
690
- export const AppTypeSchema = z
691
- .enum(APP_TYPES)
692
- .describe(`Application type must be one of: ${APP_TYPES.join(", ")}`);
693
- // OpenNext pattern schemas
694
- export const PatternSchema = z
695
- .enum(PATTERN_TYPE_VALUES)
696
- .describe(`OpenNext deployment pattern: ${PATTERN_TYPE_VALUES.join(", ")}`);
697
- // Generator option schemas
698
- export const ECSGeneratorSchema = z
699
- .object({
700
- appName: AppNameSchema,
701
- clusterName: ResourceNameSchema,
702
- containerPort: PortSchema,
703
- nameProvidedByFlag: z.boolean().optional(),
704
- ecrRepository: z.string().optional(),
705
- capacityProvider: EcsCapacityProviderSchema.optional(),
706
- useAppEcr: z.boolean().optional(),
707
- desiredCount: z
708
- .number()
709
- .int(VALIDATION_MESSAGES.CAPACITY.DESIRED.INTEGER)
710
- .min(0, VALIDATION_MESSAGES.CAPACITY.DESIRED.MIN)
711
- .max(100, VALIDATION_MESSAGES.CAPACITY.DESIRED.MAX)
712
- .optional(),
713
- cpu: z
714
- .number()
715
- .int(VALIDATION_MESSAGES.CPU.INTEGER)
716
- .min(256, VALIDATION_MESSAGES.CPU.MIN)
717
- .max(4096, VALIDATION_MESSAGES.CPU.MAX)
718
- .optional(),
719
- memoryLimitMiB: memoryLimitMiBSchema(512).optional(),
720
- minCapacity: GeneratorMinCapacitySchema.optional(),
721
- maxCapacity: GeneratorMaxCapacitySchema.optional(),
722
- ec2Config: Ec2ConfigSchema.optional(),
723
- connectionConfig: z
724
- .object({
725
- connectToDatabase: z.array(z.string()).optional(),
726
- })
727
- .strict()
728
- .optional(),
729
- })
730
- .strict()
731
- .refine(CAPACITY_REFINEMENT.check, CAPACITY_REFINEMENT.params);
732
- export const LambdaGeneratorSchema = z
733
- .object({
734
- appName: AppNameSchema,
735
- functionName: ResourceNameSchema,
736
- nameProvidedByFlag: z.boolean().optional(),
737
- lambdaType: z.enum(["basic", "web", "custom"]).optional(),
738
- ecrRepository: z.string().optional(),
739
- timeout: z
740
- .number()
741
- .int(VALIDATION_MESSAGES.LAMBDA.TIMEOUT.INTEGER)
742
- .min(MIN_LAMBDA_TIMEOUT, VALIDATION_MESSAGES.LAMBDA.TIMEOUT.MIN)
743
- .max(MAX_LAMBDA_TIMEOUT, VALIDATION_MESSAGES.LAMBDA.TIMEOUT.MAX)
744
- .optional(),
745
- memory: LambdaMemorySchema.optional(),
746
- enableFunctionUrl: z.boolean().optional(),
747
- description: z.string().optional(),
748
- enableCors: z.boolean().optional(),
749
- corsOrigins: z.string().optional(),
750
- corsHeaders: z.string().optional(),
751
- corsMethods: z.array(z.enum(HTTP_METHODS)).optional(),
752
- corsCredentials: z.boolean().optional(),
753
- connectionConfig: z
754
- .object({
755
- connectToDatabase: z.array(z.string()).optional(),
756
- })
757
- .strict()
758
- .optional(),
759
- })
760
- .strict();
761
- export const EC2GeneratorSchema = z
762
- .object({
763
- appName: AppNameSchema,
764
- instanceName: ResourceNameSchema,
765
- nameProvidedByFlag: z.boolean().optional(),
766
- instanceType: InstanceTypeSchema.optional(),
767
- enableSSH: z.boolean().optional(),
768
- minCapacity: GeneratorMinCapacitySchema.optional(),
769
- maxCapacity: GeneratorMaxCapacitySchema.optional(),
770
- connectionConfig: z
771
- .object({
772
- connectToDatabase: z.array(z.string()).optional(),
773
- })
774
- .strict()
775
- .optional(),
776
- })
777
- .strict()
778
- .refine(CAPACITY_REFINEMENT.check, CAPACITY_REFINEMENT.params);
779
- // Common fields shared across all database types
780
- const DatabaseGeneratorBaseSchema = z
781
- .object({
782
- appName: AppNameSchema,
783
- nameProvidedByFlag: z.boolean().optional(),
784
- databaseName: z
785
- .string()
786
- .min(1, VALIDATION_MESSAGES.REQUIRED.DATABASE_NAME)
787
- .max(63, VALIDATION_MESSAGES.MAX_LENGTH.DATABASE_NAME)
788
- .regex(VALIDATION_PATTERNS.DATABASE_NAME, VALIDATION_MESSAGES.DATABASE_NAME),
789
- resourceName: ResourceNameSchema.optional(),
790
- connectionConfig: z
791
- .object({
792
- /** Connect to entire compute resources (legacy, cluster-level) */
793
- connectToCompute: z.array(z.string()).optional(),
794
- /** Connect to specific ECS services (format: "ClusterName/ServiceName") */
795
- connectToServices: z.array(z.string()).optional(),
796
- })
797
- .strict()
798
- .optional(),
799
- // Shared fields (all database types)
800
- databaseInsights: DatabaseInsightsConfigOrFalseSchema.optional(),
801
- port: DatabasePortSchema.optional(),
802
- proxy: ProxyConfigOrFalseSchema.optional(),
803
- credentials: CredentialsConfigSchema.optional(),
804
- encryption: EncryptionConfigSchema.optional(),
805
- deletionProtection: z.boolean().optional(),
806
- })
807
- .strict();
808
- // Type-specific field definitions shared between CLI and UI schemas
809
- const instanceSpecificFields = {
810
- databaseType: z.literal("Instance"),
811
- instanceType: z.string().optional(),
812
- multiAz: z.boolean().optional(),
813
- readReplica: ReadReplicaConfigOrFalseSchema.optional(),
814
- publiclyAccessible: z.boolean().optional(),
815
- backupRetention: BackupRetentionSchema.optional(),
816
- allocatedStorage: z.number().optional(),
817
- snapshotIdentifier: z.string().optional(),
818
- snapshotUsername: z.string().optional(),
819
- };
820
- const auroraSpecificFields = {
821
- databaseType: z.literal("Aurora"),
822
- writer: AuroraWriterConfigSchema.optional(),
823
- readers: AuroraReadersConfigOrFalseSchema.optional(),
824
- backupRetention: BackupRetentionSchema.optional(),
825
- preferredMaintenanceWindow: z.string().optional(),
826
- monitoringInterval: MonitoringIntervalSchema.optional(),
827
- snapshotIdentifier: z.string().optional(),
828
- snapshotUsername: z.string().optional(),
829
- };
830
- const globalAuroraSpecificFields = {
831
- databaseType: z.literal("GlobalAurora"),
832
- primaryRegion: z
833
- .string()
834
- .min(1, VALIDATION_MESSAGES.GLOBAL_AURORA.PRIMARY_REGION_REQUIRED),
835
- secondaryRegions: z.array(z.string()).optional(),
836
- globalClusterIdentifier: z.string().optional(),
837
- enableGlobalWriteForwarding: z.boolean().optional(),
838
- writer: AuroraWriterConfigSchema.optional(),
839
- readers: AuroraReadersConfigOrFalseSchema.optional(),
840
- backupRetention: BackupRetentionSchema.optional(),
841
- preferredMaintenanceWindow: z.string().optional(),
842
- monitoringInterval: MonitoringIntervalSchema.optional(),
843
- snapshotIdentifier: z.string().optional(),
844
- snapshotUsername: z.string().optional(),
845
- };
846
- const InstanceDatabaseGeneratorSchema = DatabaseGeneratorBaseSchema.extend(instanceSpecificFields).strict();
847
- const AuroraDatabaseGeneratorSchema = DatabaseGeneratorBaseSchema.extend(auroraSpecificFields).strict();
848
- const GlobalAuroraDatabaseGeneratorSchema = DatabaseGeneratorBaseSchema.extend(globalAuroraSpecificFields).strict();
849
- /**
850
- * Database generator schema using discriminated union.
851
- * This ensures type-specific fields are only allowed for the correct database type:
852
- * - Instance: instanceType, multiAz, readReplica
853
- * - Aurora: writer, readers, backupRetention
854
- * - GlobalAurora: primaryRegion (required!), secondaryRegions, enableGlobalWriteForwarding
855
- */
856
- export const DatabaseGeneratorSchema = z.discriminatedUnion("databaseType", [
857
- InstanceDatabaseGeneratorSchema,
858
- AuroraDatabaseGeneratorSchema,
859
- GlobalAuroraDatabaseGeneratorSchema,
860
- ]);
861
- // Base schema with required resourceName for UI
862
- const DatabaseGeneratorBaseSchemaFromUI = DatabaseGeneratorBaseSchema.extend({
863
- resourceName: ResourceNameSchema,
864
- }).strict();
865
- /**
866
- * Stricter schema for UI-originated database creation.
867
- * Requires resourceName to prevent silent failures where
868
- * the generator falls back to a default name that may conflict.
869
- */
870
- const InstanceDatabaseGeneratorSchemaFromUI = DatabaseGeneratorBaseSchemaFromUI.extend(instanceSpecificFields).strict();
871
- const AuroraDatabaseGeneratorSchemaFromUI = DatabaseGeneratorBaseSchemaFromUI.extend(auroraSpecificFields).strict();
872
- const GlobalAuroraDatabaseGeneratorSchemaFromUI = DatabaseGeneratorBaseSchemaFromUI.extend(globalAuroraSpecificFields).strict();
873
- export const DatabaseGeneratorSchemaFromUI = z.discriminatedUnion("databaseType", [
874
- InstanceDatabaseGeneratorSchemaFromUI,
875
- AuroraDatabaseGeneratorSchemaFromUI,
876
- GlobalAuroraDatabaseGeneratorSchemaFromUI,
877
- ]);
878
- /**
879
- * Service name schema.
880
- * Must be a valid identifier for ECS services.
881
- */
882
- export const EcsServiceNameSchema = z
883
- .string()
884
- .min(1, VALIDATION_MESSAGES.REQUIRED.SERVICE_NAME)
885
- .max(255, VALIDATION_MESSAGES.MAX_LENGTH.SERVICE_NAME)
886
- .regex(VALIDATION_PATTERNS.ECS_SERVICE_NAME, VALIDATION_MESSAGES.ECS_SERVICE_NAME);
887
- /**
888
- * Cluster-level configuration.
889
- * Controls the shared ALB for all services.
890
- */
891
- export const EcsClusterConfigSchema = z
892
- .object({
893
- /** Domain for HTTPS access. Creates ACM certificate + Route53 DNS. */
894
- domain: z.string().optional(),
895
- /**
896
- * Load balancer configuration.
897
- * - false: No ALB (for workers/internal services)
898
- * - "public": Internet-facing ALB (default)
899
- * - "internal": VPC-only ALB
900
- */
901
- loadBalancer: z
902
- .union([z.literal(false), z.literal("public"), z.literal("internal")])
903
- .optional(),
904
- /** Enable direct EC2 access without ALB. Opens container ports on security group. */
905
- directAccess: z.boolean().optional(),
906
- })
907
- .strict()
908
- .refine((data) => !(data.directAccess && data.domain), {
909
- message: VALIDATION_MESSAGES.DIRECT_ACCESS.NO_DOMAIN,
910
- path: ["directAccess"],
911
- })
912
- .refine((data) => !(data.directAccess && typeof data.loadBalancer === "string"), {
913
- message: VALIDATION_MESSAGES.DIRECT_ACCESS.NO_LOAD_BALANCER,
914
- })
915
- .describe("Cluster-level configuration");
916
- /**
917
- * Routing configuration for path/host-based routing on the ALB.
918
- * Required when cluster has multiple services with ports.
919
- */
920
- export const EcsRoutingConfigSchema = z
921
- .object({
922
- /** Path pattern for routing (e.g., "/api/*", "/users/*") */
923
- path: z.string().optional(),
924
- /** Host header for routing (e.g., "api.example.com") */
925
- host: z.string().optional(),
926
- /** Priority for this routing rule (1-50000). Lower = higher priority. */
927
- priority: z
928
- .number()
929
- .int(VALIDATION_MESSAGES.PRIORITY.INTEGER)
930
- .min(1, VALIDATION_MESSAGES.PRIORITY.MIN)
931
- .max(50000, VALIDATION_MESSAGES.PRIORITY.MAX)
932
- .optional(),
933
- /** Health check path for this service's target group. Default: "/" */
934
- healthCheckPath: z.string().optional(),
935
- })
936
- .strict()
937
- .describe("Path/host-based routing configuration");
938
- /**
939
- * Scaling configuration for ECS services.
940
- */
941
- export const EcsScalingConfigSchema = z
942
- .object({
943
- desiredCount: z
944
- .number()
945
- .int(VALIDATION_MESSAGES.CAPACITY.DESIRED.INTEGER)
946
- .min(0, VALIDATION_MESSAGES.CAPACITY.DESIRED.MIN)
947
- .max(100, VALIDATION_MESSAGES.CAPACITY.DESIRED.MAX)
948
- .optional(),
949
- minCapacity: z
950
- .number()
951
- .int(VALIDATION_MESSAGES.CAPACITY.MIN.INTEGER)
952
- .min(0, VALIDATION_MESSAGES.CAPACITY.MIN.MIN_0)
953
- .max(100, VALIDATION_MESSAGES.CAPACITY.MIN.MAX)
954
- .optional(),
955
- maxCapacity: z
956
- .number()
957
- .int(VALIDATION_MESSAGES.CAPACITY.MAX.INTEGER)
958
- .min(0, VALIDATION_MESSAGES.CAPACITY.MAX.MIN_0)
959
- .max(100, VALIDATION_MESSAGES.CAPACITY.MAX.MAX)
960
- .optional(),
961
- scalingType: z.enum(["CPU", "MEMORY"]).optional(),
962
- })
963
- .strict()
964
- .refine(CAPACITY_REFINEMENT.check, CAPACITY_REFINEMENT.params)
965
- .describe("ECS service scaling configuration");
966
- /**
967
- * Configuration for a service in an ECS cluster.
968
- * Each service gets its own task definition, scaling, and target group.
969
- */
970
- export const EcsServiceConfigSchema = z
971
- .object({
972
- /** Service name (unique within cluster) */
973
- name: EcsServiceNameSchema,
974
- /** Path to Dockerfile for this service (e.g., "./Dockerfile.frontend") */
975
- dockerfilePath: z.string().optional(),
976
- /** Docker build target stage for multi-stage Dockerfiles (e.g., "api", "frontend") */
977
- dockerTarget: z.string().optional(),
978
- /** Whether this service needs database connection */
979
- needsDatabaseConnection: z.boolean().optional(),
980
- /** Whether this service needs storage connection */
981
- needsStorageConnection: z.boolean().optional(),
982
- /** Container image (ECR repo name or public image URL) */
983
- image: z.string().optional(),
984
- /**
985
- * Container configuration(s) for this service.
986
- * For single-container services, container name is optional and auto-generated.
987
- * For multi-container tasks, first container with a port is the primary container.
988
- */
989
- containers: z.array(ContainerConfigSchema).optional(),
990
- /** Routing rules for this service on the cluster's ALB. Single rule or array of rules. */
991
- routing: z
992
- .union([EcsRoutingConfigSchema, z.array(EcsRoutingConfigSchema).min(1)])
993
- .optional(),
994
- /** CPU units for this service's tasks (256-4096) */
995
- cpu: z
996
- .number()
997
- .int(VALIDATION_MESSAGES.CPU.INTEGER)
998
- .min(256, VALIDATION_MESSAGES.CPU.MIN)
999
- .max(4096, VALIDATION_MESSAGES.CPU.MAX)
1000
- .optional(),
1001
- /** Memory in MiB for this service's tasks (512-30720) */
1002
- memoryLimitMiB: memoryLimitMiBSchema(512).optional(),
1003
- /** Desired number of tasks. Default: 2 */
1004
- desiredCount: z
1005
- .number()
1006
- .int(VALIDATION_MESSAGES.CAPACITY.DESIRED.INTEGER)
1007
- .min(0, VALIDATION_MESSAGES.CAPACITY.DESIRED.MIN)
1008
- .max(100, VALIDATION_MESSAGES.CAPACITY.DESIRED.MAX)
1009
- .optional(),
1010
- /**
1011
- * Scaling configuration.
1012
- * - Omit: enabled with defaults
1013
- * - false: disabled
1014
- */
1015
- scaling: optionalOrDisabled(EcsScalingConfigSchema).optional(),
1016
- /**
1017
- * Capacity provider for this service. REQUIRED.
1018
- * - "FARGATE": Serverless containers (default for most tiers)
1019
- * - "FARGATE_SPOT": Serverless with Spot pricing (cost savings)
1020
- * - "EC2": EC2-backed containers (for tinkerer tier or specific needs)
1021
- */
1022
- capacityProvider: EcsCapacityProviderSchema,
1023
- /**
1024
- * EC2 capacity configuration for this service.
1025
- * Only used when service capacityProvider is "EC2".
1026
- * Creates a dedicated ASG if config differs from existing ASGs.
1027
- */
1028
- ec2Config: Ec2ConfigSchema.optional(),
1029
- /**
1030
- * SSM Parameter Store path for secrets.
1031
- * If containers have ssmSecrets defined, this path is used as the base path.
1032
- * Format: /<app>/<cluster>/<service>
1033
- */
1034
- ssmSecretsPath: z
1035
- .string()
1036
- .regex(VALIDATION_PATTERNS.SSM_PATH, VALIDATION_MESSAGES.SSM_PATH)
1037
- .optional(),
1038
- extraProperties: z.array(ExtraPropertySchema).optional(),
1039
- })
1040
- .strict()
1041
- .describe("Configuration for a service in an ECS cluster");
1042
- /**
1043
- * Compute resource plan schema.
1044
- * Used by the code generator to produce infrastructure code.
1045
- */
1046
- export const ComputeResourcePlanSchema = z
1047
- .object({
1048
- name: ResourceNameSchema,
1049
- type: ComputeTypeSchema,
1050
- needsConnection: z.boolean(),
1051
- connectedDatabase: z.array(z.string()).optional(),
1052
- connectedStorage: z.array(z.string()).optional(),
1053
- // ECS-specific (multi-service structure)
1054
- /** Cluster configuration (domain, loadBalancer, directAccess) */
1055
- cluster: EcsClusterConfigSchema.optional(),
1056
- /** Services in this cluster. Each service specifies its own capacityProvider. */
1057
- services: z.array(EcsServiceConfigSchema).optional(),
1058
- dockerfilePath: z.string().optional(),
1059
- // Lambda-specific
1060
- /** Deployment type: "container" (ECR image) or "code" (Code.fromAsset) */
1061
- deployment: z.enum(["container", "code"]).optional(),
1062
- /** Path to Lambda code directory (for code deployment) */
1063
- codePath: z.string().optional(),
1064
- timeout: z
1065
- .number()
1066
- .int(VALIDATION_MESSAGES.LAMBDA.TIMEOUT.INTEGER)
1067
- .min(1, VALIDATION_MESSAGES.LAMBDA.TIMEOUT.MIN)
1068
- .max(900, VALIDATION_MESSAGES.LAMBDA.TIMEOUT.MAX)
1069
- .optional(),
1070
- memory: z
1071
- .number()
1072
- .int(VALIDATION_MESSAGES.LAMBDA.MEMORY.INTEGER)
1073
- .min(128, VALIDATION_MESSAGES.LAMBDA.MEMORY.MIN)
1074
- .max(10240, VALIDATION_MESSAGES.LAMBDA.MEMORY.MAX)
1075
- .optional(),
1076
- handler: z.string().optional(),
1077
- runtime: z.string().optional(),
1078
- environment: EnvironmentRecordSchema.optional(),
1079
- secrets: EnvironmentRecordSchema.optional(),
1080
- eventSources: z.array(LambdaEventSourceSchema).optional(),
1081
- /** Lambda function URL configuration for direct HTTP access */
1082
- functionUrl: z
1083
- .object({
1084
- authType: z.enum(["NONE", "AWS_IAM"]),
1085
- })
1086
- .strict()
1087
- .optional(),
1088
- /** Lambda function description */
1089
- description: z.string().optional(),
1090
- /** EventBridge schedule expression (e.g. "rate(5 minutes)") */
1091
- scheduleExpression: z.string().optional(),
1092
- /** CPU architecture (e.g. "ARM_64", "X86_64") */
1093
- architecture: z.enum(COMPUTE_ARCHITECTURES).optional(),
1094
- /** Ephemeral storage size in MiB (512-10240) */
1095
- ephemeralStorageSize: z.number().int().min(512).max(10240).optional(),
1096
- /** Override auto-generated function name */
1097
- functionName: z.string().optional(),
1098
- /** SSM Parameter Store secret names (Lambda-specific, string array) */
1099
- ssmSecrets: z.array(z.string()).optional(),
1100
- /** SSM Parameter Store path prefix for secrets */
1101
- ssmSecretsPath: z.string().optional(),
1102
- // EC2-specific (standalone EC2, not ECS EC2)
1103
- instanceType: InstanceTypeSchema.optional(),
1104
- enableSSH: z.boolean().optional(),
1105
- keyName: z.string().optional(),
1106
- userData: z.string().optional(),
1107
- securityGroups: z.array(z.string()).optional(),
1108
- // Round-trip preservation
1109
- variableName: z.string().optional(),
1110
- extraProperties: z.array(ExtraPropertySchema).optional(),
1111
- })
1112
- .strict()
1113
- .superRefine((data, ctx) => {
1114
- // ECS with EC2 services: validate instance types match ARM architecture for Docker builds.
1115
- // Skip when amiHardwareType is explicitly set — the user is deliberately choosing the architecture.
1116
- if (data.type === "ecs" && data.services) {
1117
- for (let i = 0; i < data.services.length; i++) {
1118
- const service = data.services[i];
1119
- if (service?.ec2Config?.instanceType &&
1120
- !service.ec2Config.amiHardwareType) {
1121
- const arch = getArchitectureForInstanceType(service.ec2Config.instanceType);
1122
- if (arch !== "ARM") {
1123
- ctx.addIssue({
1124
- code: "custom",
1125
- message: `Service '${service.name ?? i}' uses instance type '${service.ec2Config.instanceType}' (x86), ` +
1126
- `which requires Docker images built for linux/amd64. The default build targets linux/arm64 (Graviton). ` +
1127
- `Use a Graviton instance type (t4g, c6g, r6g, m6g, etc.) or set amiHardwareType: "STANDARD" to acknowledge x86.`,
1128
- path: ["services", i, "ec2Config", "instanceType"],
1129
- });
1130
- }
1131
- }
1132
- }
1133
- }
1134
- // Container Lambda: warn when explicit X86_64 is set without the matching Docker platform
1135
- if (data.type === "lambda" &&
1136
- data.deployment === "container" &&
1137
- data.architecture === "X86_64") {
1138
- ctx.addIssue({
1139
- code: "custom",
1140
- message: `Lambda architecture 'X86_64' requires Docker images built for linux/amd64, ` +
1141
- `but the default build targets linux/arm64. Use 'ARM_64' for Graviton Lambdas (recommended) ` +
1142
- `or ensure your build pipeline targets linux/amd64.`,
1143
- path: ["architecture"],
1144
- });
1145
- }
1146
- });
1147
- const DYNAMODB_ATTRIBUTE_TYPES = ["S", "N", "B"];
1148
- const DynamoDBKeySchema = z
1149
- .object({
1150
- name: z.string(),
1151
- type: z.enum(DYNAMODB_ATTRIBUTE_TYPES),
1152
- })
1153
- .strict();
1154
- /**
1155
- * DynamoDB table resource plan schema (for OpenNext patterns)
1156
- */
1157
- export const DynamoDBResourcePlanSchema = z
1158
- .object({
1159
- name: ResourceNameSchema,
1160
- partitionKey: DynamoDBKeySchema,
1161
- sortKey: DynamoDBKeySchema.optional(),
1162
- globalSecondaryIndexes: z
1163
- .array(z
1164
- .object({
1165
- indexName: z.string(),
1166
- partitionKey: DynamoDBKeySchema,
1167
- sortKey: DynamoDBKeySchema.optional(),
1168
- })
1169
- .strict())
1170
- .optional(),
1171
- ttlAttribute: z.string().optional(),
1172
- stream: z.boolean().optional(),
1173
- // Round-trip preservation
1174
- variableName: z.string().optional(),
1175
- extraProperties: z.array(ExtraPropertySchema).optional(),
1176
- })
1177
- .strict();
1178
- /**
1179
- * SQS queue resource plan schema (for OpenNext patterns)
1180
- */
1181
- export const SQSResourcePlanSchema = z
1182
- .object({
1183
- name: ResourceNameSchema,
1184
- queueType: z.enum(["standard", "fifo"]).default("standard"),
1185
- visibilityTimeout: z
1186
- .number()
1187
- .int(VALIDATION_MESSAGES.SQS.VISIBILITY_TIMEOUT.INTEGER)
1188
- .min(0, VALIDATION_MESSAGES.SQS.VISIBILITY_TIMEOUT.MIN)
1189
- .max(43200, VALIDATION_MESSAGES.SQS.VISIBILITY_TIMEOUT.MAX)
1190
- .optional(),
1191
- retentionPeriod: z
1192
- .number()
1193
- .int(VALIDATION_MESSAGES.SQS.RETENTION_PERIOD.INTEGER)
1194
- .min(60, VALIDATION_MESSAGES.SQS.RETENTION_PERIOD.MIN)
1195
- .max(1209600, VALIDATION_MESSAGES.SQS.RETENTION_PERIOD.MAX)
1196
- .optional(),
1197
- contentBasedDeduplication: z.boolean().optional(),
1198
- // Round-trip preservation
1199
- variableName: z.string().optional(),
1200
- extraProperties: z.array(ExtraPropertySchema).optional(),
1201
- })
1202
- .strict();
1203
- /**
1204
- * Access gate configuration for CDN distributions.
1205
- * Extensible discriminated union — currently supports basic-auth,
1206
- * future types (cognito, oidc, ip-allowlist) can be added.
1207
- */
1208
- export const AccessGateSchema = z
1209
- .object({
1210
- type: z.literal("basic-auth"),
1211
- username: z.string().min(1),
1212
- password: z.string().min(1),
1213
- })
1214
- .strict();
1215
- /**
1216
- * CDN resource plan schema (for OpenNext patterns)
1217
- */
1218
- export const CDNResourcePlanSchema = z
1219
- .object({
1220
- name: ResourceNameSchema,
1221
- defaultOriginRef: z.string(),
1222
- behaviours: z
1223
- .array(z
1224
- .object({
1225
- pathPattern: z.string(),
1226
- originRef: z.string(),
1227
- cachePolicy: z
1228
- .enum(["CACHING_OPTIMIZED", "CACHING_DISABLED"])
1229
- .optional(),
1230
- })
1231
- .strict())
1232
- .optional(),
1233
- customDomain: z.string().optional(),
1234
- certificateArn: z.string().optional(),
1235
- accessGate: z.union([z.literal(false), AccessGateSchema]).optional(),
1236
- extraProperties: z.array(ExtraPropertySchema).optional(),
1237
- })
1238
- .strict();
1239
- export const CdnGeneratorSchema = z
1240
- .object({
1241
- appName: z.string(),
1242
- name: ResourceNameSchema.optional(),
1243
- defaultOriginRef: z.string(),
1244
- behaviours: z
1245
- .array(z
1246
- .object({
1247
- pathPattern: z.string(),
1248
- originRef: z.string(),
1249
- cachePolicy: z
1250
- .enum(["CACHING_OPTIMIZED", "CACHING_DISABLED"])
1251
- .optional(),
1252
- })
1253
- .strict())
1254
- .optional(),
1255
- customDomain: z.string().optional(),
1256
- certificateArn: z.string().optional(),
1257
- accessGate: z.union([z.literal(false), AccessGateSchema]).optional(),
1258
- nameProvidedByFlag: z.boolean().optional(),
1259
- })
1260
- .strict();
1261
- /**
1262
- * Schema for adding a service to an existing ECS cluster.
1263
- * Used by the `fjall add service` command.
1264
- */
1265
- export const AddServiceGeneratorSchema = z
1266
- .object({
1267
- appName: AppNameSchema,
1268
- clusterName: ResourceNameSchema,
1269
- serviceName: EcsServiceNameSchema,
1270
- capacityProvider: EcsCapacityProviderSchema,
1271
- containerPort: PortSchema.optional(),
1272
- cpu: z
1273
- .number()
1274
- .int(VALIDATION_MESSAGES.CPU.INTEGER)
1275
- .min(256, VALIDATION_MESSAGES.CPU.MIN)
1276
- .max(4096, VALIDATION_MESSAGES.CPU.MAX)
1277
- .optional(),
1278
- memoryLimitMiB: memoryLimitMiBSchema(512).optional(),
1279
- desiredCount: z
1280
- .number()
1281
- .int(VALIDATION_MESSAGES.CAPACITY.DESIRED.INTEGER)
1282
- .min(0, VALIDATION_MESSAGES.CAPACITY.DESIRED.MIN)
1283
- .max(100, VALIDATION_MESSAGES.CAPACITY.DESIRED.MAX)
1284
- .optional(),
1285
- ec2Config: Ec2ConfigSchema.optional(),
1286
- routing: z
1287
- .union([EcsRoutingConfigSchema, z.array(EcsRoutingConfigSchema).min(1)])
1288
- .optional(),
1289
- dockerfilePath: z.string().optional(),
1290
- dockerTarget: z.string().optional(),
1291
- nameProvidedByFlag: z.boolean().optional(),
1292
- connectionConfig: z
1293
- .object({
1294
- connectToDatabase: z.array(z.string()).optional(),
1295
- })
1296
- .strict()
1297
- .optional(),
1298
- })
1299
- .strict();
1300
- /**
1301
- * Schema for adding RDS Proxy to an existing database.
1302
- * Used by the `fjall add proxy` command.
1303
- */
1304
- export const AddProxyGeneratorSchema = z
1305
- .object({
1306
- appName: AppNameSchema,
1307
- databaseName: z.string().min(1, VALIDATION_MESSAGES.REQUIRED.DATABASE_NAME),
1308
- maxConnections: z
1309
- .number()
1310
- .int(VALIDATION_MESSAGES.MAX_CONNECTIONS.INTEGER)
1311
- .min(1, VALIDATION_MESSAGES.MAX_CONNECTIONS.MIN)
1312
- .max(100, VALIDATION_MESSAGES.MAX_CONNECTIONS.MAX)
1313
- .optional(),
1314
- maxIdleConnections: z
1315
- .number()
1316
- .int(VALIDATION_MESSAGES.PROXY_CONFIG.MAX_IDLE_CONNECTIONS.INTEGER)
1317
- .min(0, VALIDATION_MESSAGES.PROXY_CONFIG.MAX_IDLE_CONNECTIONS.MIN)
1318
- .max(100, VALIDATION_MESSAGES.PROXY_CONFIG.MAX_IDLE_CONNECTIONS.MAX)
1319
- .optional(),
1320
- connectionBorrowTimeout: z
1321
- .number()
1322
- .int(VALIDATION_MESSAGES.PROXY_CONFIG.BORROW_TIMEOUT.INTEGER)
1323
- .min(1, VALIDATION_MESSAGES.PROXY_CONFIG.BORROW_TIMEOUT.MIN)
1324
- .max(3600, VALIDATION_MESSAGES.PROXY_CONFIG.BORROW_TIMEOUT.MAX)
1325
- .optional(),
1326
- requireTLS: z.boolean().optional(),
1327
- nameProvidedByFlag: z.boolean().optional(),
1328
- })
1329
- .strict();
1330
- /**
1331
- * Pattern Lambda configuration for individual functions.
1332
- */
1333
- export const PatternLambdaConfigSchema = z
1334
- .object({
1335
- /** Memory size in MB. Range: 128-10240. */
1336
- memorySize: LambdaMemorySchema.optional(),
1337
- /** Timeout in seconds. Range: 1-900. */
1338
- timeout: z
1339
- .number()
1340
- .int(VALIDATION_MESSAGES.LAMBDA.TIMEOUT.INTEGER)
1341
- .min(1, VALIDATION_MESSAGES.LAMBDA.TIMEOUT.MIN)
1342
- .max(900, VALIDATION_MESSAGES.LAMBDA.TIMEOUT.MAX)
1343
- .optional(),
1344
- /** Ephemeral storage size in MB. Range: 512-10240. Default: 512. */
1345
- ephemeralStorageSize: z
1346
- .number()
1347
- .int(VALIDATION_MESSAGES.EPHEMERAL_STORAGE.INTEGER)
1348
- .min(512, VALIDATION_MESSAGES.EPHEMERAL_STORAGE.MIN)
1349
- .max(10240, VALIDATION_MESSAGES.EPHEMERAL_STORAGE.MAX)
1350
- .optional(),
1351
- })
1352
- .strict();
1353
- /**
1354
- * Full Payload database configuration for pattern.
1355
- * Exposes ALL underlying DatabaseFactory props with pattern-specific defaults.
1356
- */
1357
- export const PayloadDatabaseConfigSchema = z
1358
- .object({
1359
- /** Database type: "Instance" (RDS Instance) or "Aurora". Default: "Instance" */
1360
- type: z.enum(["Instance", "Aurora"]).optional(),
1361
- /** Database name. Default: derived from pattern name */
1362
- databaseName: z.string().optional(),
1363
- /** Database engine. Default: "postgresql" */
1364
- databaseEngine: z.enum(["postgresql", "mysql"]).optional(),
1365
- /** Enable deletion protection. Default: true */
1366
- deletionProtection: z.boolean().optional(),
1367
- /** Backup retention in days. Default: 7 */
1368
- backupRetention: BackupRetentionSchema.optional(),
1369
- /** Database port. Default: engine-specific (5432 for PostgreSQL) */
1370
- port: DatabasePortSchema.optional(),
1371
- /** Make database publicly accessible (for local development). Default: false */
1372
- publiclyAccessible: z.boolean().optional(),
1373
- /** IP CIDR to allow when publicly accessible */
1374
- allowedIpCidr: z.string().optional(),
1375
- /** Instance type for RDS Instance databases (e.g., "t4g.small", "t4g.large") */
1376
- instanceType: z.string().optional(),
1377
- /** Allocated storage in GB for RDS Instance. Default: 20 */
1378
- allocatedStorage: z
1379
- .number()
1380
- .int(VALIDATION_MESSAGES.ALLOCATED_STORAGE.INTEGER)
1381
- .min(20, VALIDATION_MESSAGES.ALLOCATED_STORAGE.MIN)
1382
- .max(65536, VALIDATION_MESSAGES.ALLOCATED_STORAGE.MAX)
1383
- .optional(),
1384
- /** Enable Multi-AZ deployment for high availability. */
1385
- multiAz: z.boolean().optional(),
1386
- /** Read replica configuration for RDS Instance. Set to false to explicitly disable. */
1387
- readReplica: ReadReplicaConfigOrFalseSchema.optional(),
1388
- /** Aurora writer instance configuration. */
1389
- writer: AuroraWriterConfigSchema.optional(),
1390
- /** Aurora reader instances configuration. Set to false to explicitly disable. */
1391
- readers: AuroraReadersConfigOrFalseSchema.optional(),
1392
- /** Allow access from VPC CIDR (avoids cross-stack cyclic dependencies). */
1393
- allowVpcAccess: z.boolean().optional(),
1394
- /** Enhanced monitoring interval in seconds. */
1395
- monitoringInterval: MonitoringIntervalSchema.optional(),
1396
- /** Preferred maintenance window (e.g., "sun:05:00-sun:06:00"). */
1397
- preferredMaintenanceWindow: z.string().optional(),
1398
- /** Database Insights configuration. Set to false to explicitly disable. */
1399
- databaseInsights: DatabaseInsightsConfigOrFalseSchema.optional(),
1400
- /** RDS Proxy configuration for connection pooling. Set to false to explicitly disable. */
1401
- proxy: ProxyConfigOrFalseSchema.optional(),
1402
- /** Credentials configuration (username, rotation). */
1403
- credentials: CredentialsConfigSchema.optional(),
1404
- /** Encryption configuration. */
1405
- encryption: EncryptionConfigSchema.optional(),
1406
- /** ARN or identifier of snapshot to restore from */
1407
- snapshotIdentifier: z.string().optional(),
1408
- /** Username from the snapshot (required when restoring) */
1409
- snapshotUsername: z.string().optional(),
1410
- })
1411
- .strict();
1412
- /**
1413
- * Full Payload compute configuration for pattern.
1414
- * Allows per-function configuration for each Lambda in the pattern.
1415
- */
1416
- export const PayloadComputeConfigSchema = z
1417
- .object({
1418
- /** Server Lambda configuration (handles main application requests). */
1419
- server: PatternLambdaConfigSchema.optional(),
1420
- /** Image optimisation Lambda configuration (handles Next.js image optimisation). */
1421
- imageOptimisation: PatternLambdaConfigSchema.optional(),
1422
- /** Revalidation Lambda configuration (handles ISR revalidation from SQS queue). */
1423
- revalidation: PatternLambdaConfigSchema.optional(),
1424
- })
1425
- .strict();
1426
- /**
1427
- * Pattern storage bucket configuration.
1428
- */
1429
- export const PatternStorageBucketConfigSchema = z
1430
- .object({
1431
- /** Enable versioning. Default: false */
1432
- versioned: z.boolean().optional(),
1433
- })
1434
- .strict();
1435
- /**
1436
- * Full Payload storage configuration for pattern.
1437
- * Allows per-bucket configuration for each S3 bucket in the pattern.
1438
- */
1439
- export const PayloadStorageConfigSchema = z
1440
- .object({
1441
- /** Assets bucket (static files) */
1442
- assets: PatternStorageBucketConfigSchema.optional(),
1443
- /** Cache bucket (ISR cache) */
1444
- cache: PatternStorageBucketConfigSchema.optional(),
1445
- /** Media bucket (uploads) */
1446
- media: PatternStorageBucketConfigSchema.optional(),
1447
- })
1448
- .strict();
1449
- /**
1450
- * Pattern queue configuration for messaging.
1451
- */
1452
- export const PatternQueueConfigSchema = z
1453
- .object({
1454
- /** Visibility timeout in seconds. Default: matches revalidation timeout */
1455
- visibilityTimeout: z
1456
- .number()
1457
- .int(VALIDATION_MESSAGES.SQS.VISIBILITY_TIMEOUT.INTEGER)
1458
- .min(0, VALIDATION_MESSAGES.SQS.VISIBILITY_TIMEOUT.MIN)
1459
- .max(43200, VALIDATION_MESSAGES.SQS.VISIBILITY_TIMEOUT.MAX)
1460
- .optional(),
1461
- /** Message retention period in seconds. Default: 345600 (4 days) */
1462
- messageRetentionPeriod: z
1463
- .number()
1464
- .int(VALIDATION_MESSAGES.SQS.RETENTION_PERIOD.INTEGER)
1465
- .min(60, VALIDATION_MESSAGES.SQS.RETENTION_PERIOD.MIN)
1466
- .max(1209600, VALIDATION_MESSAGES.SQS.RETENTION_PERIOD.MAX)
1467
- .optional(),
1468
- /** Maximum message size in bytes. Default: 262144 (256 KB) */
1469
- maxMessageSize: z
1470
- .number()
1471
- .int(VALIDATION_MESSAGES.MAX_MESSAGE_SIZE.INTEGER)
1472
- .min(1024, VALIDATION_MESSAGES.MAX_MESSAGE_SIZE.MIN)
1473
- .max(262144, VALIDATION_MESSAGES.MAX_MESSAGE_SIZE.MAX)
1474
- .optional(),
1475
- /** Dead letter queue configuration. Set to false to explicitly disable. */
1476
- deadLetterQueue: z
1477
- .union([
1478
- z.literal(false),
1479
- z
1480
- .object({
1481
- enabled: z.boolean().optional(),
1482
- maxReceiveCount: z
1483
- .number()
1484
- .int(VALIDATION_MESSAGES.DLQ.MAX_RECEIVE_COUNT.INTEGER)
1485
- .min(1, VALIDATION_MESSAGES.DLQ.MAX_RECEIVE_COUNT.MIN)
1486
- .max(1000, VALIDATION_MESSAGES.DLQ.MAX_RECEIVE_COUNT.MAX)
1487
- .optional(),
1488
- })
1489
- .strict(),
1490
- ])
1491
- .optional(),
1492
- })
1493
- .strict();
1494
- /**
1495
- * Full Payload messaging configuration for pattern.
1496
- */
1497
- export const PayloadMessagingConfigSchema = z
1498
- .object({
1499
- /** Revalidation queue configuration */
1500
- revalidationQueue: PatternQueueConfigSchema.optional(),
1501
- })
1502
- .strict();
1503
- /**
1504
- * Payload CDN configuration for pattern.
1505
- */
1506
- export const PayloadCdnConfigSchema = z
1507
- .object({
1508
- /** Custom domain names for CloudFront */
1509
- domainNames: z.array(z.string()).optional(),
1510
- /** ACM certificate ARN for custom domains (must be in us-east-1) */
1511
- certificateArn: z.string().optional(),
1512
- })
1513
- .strict();
1514
- /**
1515
- * Payload pattern configuration.
1516
- */
1517
- export const PayloadPatternConfigSchema = z
1518
- .object({
1519
- type: z.literal("payload"),
1520
- /** Pattern name (used for resource naming) */
1521
- name: z.string().min(1, VALIDATION_MESSAGES.REQUIRED.PATTERN_NAME),
1522
- /** Custom domain (auto-creates certificate + DNS) */
1523
- domain: z.string().optional(),
1524
- /** Database configuration */
1525
- database: PayloadDatabaseConfigSchema.optional(),
1526
- /** Compute (Lambda) configuration */
1527
- compute: PayloadComputeConfigSchema.optional(),
1528
- /** Storage (S3) configuration */
1529
- storage: PayloadStorageConfigSchema.optional(),
1530
- /** Messaging (SQS) configuration */
1531
- messaging: PayloadMessagingConfigSchema.optional(),
1532
- /** CDN (CloudFront) configuration - advanced use, prefer `domain` for simple setup */
1533
- cdn: PayloadCdnConfigSchema.optional(),
1534
- /** Additional environment variables for server Lambda */
1535
- environment: z.record(z.string(), z.string()).optional(),
1536
- })
1537
- .strict();
1538
- /**
1539
- * Next.js pattern configuration.
1540
- * Shares the same OpenNext infrastructure as Payload but without
1541
- * requiring a database by default (pure Next.js with optional database).
1542
- */
1543
- export const NextJSPatternConfigSchema = z
1544
- .object({
1545
- type: z.literal("nextjs"),
1546
- /** Pattern name (used for resource naming) */
1547
- name: z.string().min(1, VALIDATION_MESSAGES.REQUIRED.PATTERN_NAME),
1548
- /** Custom domain (auto-creates certificate + DNS) */
1549
- domain: z.string().optional(),
1550
- /** Database configuration */
1551
- database: PayloadDatabaseConfigSchema.optional(),
1552
- /** Compute (Lambda) configuration */
1553
- compute: PayloadComputeConfigSchema.optional(),
1554
- /** Storage (S3) configuration */
1555
- storage: PayloadStorageConfigSchema.optional(),
1556
- /** Messaging (SQS) configuration */
1557
- messaging: PayloadMessagingConfigSchema.optional(),
1558
- /** CDN (CloudFront) configuration - advanced use, prefer `domain` for simple setup */
1559
- cdn: PayloadCdnConfigSchema.optional(),
1560
- /** Additional environment variables for server Lambda */
1561
- environment: z.record(z.string(), z.string()).optional(),
1562
- })
1563
- .strict();
1564
- /**
1565
- * Pattern configuration discriminated union.
1566
- * Extensible for future patterns (Remix, etc.)
1567
- */
1568
- export const PatternConfigSchema = z.discriminatedUnion("type", [
1569
- PayloadPatternConfigSchema,
1570
- NextJSPatternConfigSchema,
1571
- ]);
1572
- /**
1573
- * Custom code block position type.
1574
- * Defines where a custom code block appears relative to managed code.
1575
- */
1576
- export const CustomCodePositionSchema = z.enum([
1577
- "before-imports",
1578
- "after-imports",
1579
- "after-app-init",
1580
- "after-tags",
1581
- "after-resource",
1582
- "end-of-file",
1583
- ]);
1584
- /**
1585
- * Statement type for managed resources.
1586
- */
1587
- export const StatementTypeSchema = z.enum([
1588
- "import",
1589
- "app-init",
1590
- "tags",
1591
- "database",
1592
- "compute",
1593
- "storage",
1594
- "network",
1595
- "messaging",
1596
- "cdn",
1597
- "pattern",
1598
- "custom",
1599
- ]);
1600
- /**
1601
- * Custom code block schema.
1602
- * Represents user-written code that should be preserved during regeneration.
1603
- * Used by the hybrid parse-preserve-generate approach.
1604
- */
1605
- export const CustomCodeBlockSchema = z
1606
- .object({
1607
- /** The exact source text to preserve (including leading comments) */
1608
- sourceText: z.string(),
1609
- /** Position relative to managed resources */
1610
- position: CustomCodePositionSchema,
1611
- /** For "after-resource" position, the managed resource type and name */
1612
- afterManagedResource: z
1613
- .object({
1614
- type: StatementTypeSchema,
1615
- name: z.string().optional(),
1616
- })
1617
- .strict()
1618
- .optional(),
1619
- /** Original line number in source (for debugging) */
1620
- originalLine: z.number().int().optional(),
1621
- /** Leading comments associated with this block */
1622
- leadingComments: z.array(z.string()).optional(),
1623
- /** Orphaned marker - set when the resource this block was after is deleted */
1624
- orphaned: z.boolean().optional(),
1625
- /** Orphaned warning comment to add */
1626
- orphanedComment: z.string().optional(),
1627
- })
1628
- .strict();
1629
- /**
1630
- * Backup configuration schema.
1631
- * Controls automatic AWS Backup enrolment via tier presets.
1632
- * - Object with tier: Tags all resources with `fjall:disasterRecovery:tier`
1633
- * - false: Explicitly disabled (no backup tag applied)
1634
- */
1635
- export const BackupConfigSchema = z.union([
1636
- z
1637
- .object({
1638
- tier: z.enum(BACKUP_VAULT_TIERS),
1639
- })
1640
- .strict(),
1641
- z.literal(false),
1642
- ]);
1643
- /**
1644
- * Tunnel configuration schema.
1645
- * Controls bastion host creation for secure SSM database access.
1646
- * - Object with optional instanceType: Create bastion with custom config
1647
- * - false: Explicitly disabled (no bastion)
1648
- */
1649
- export const TunnelConfigSchema = z.union([
1650
- z
1651
- .object({
1652
- instanceType: z.string().optional(),
1653
- })
1654
- .strict(),
1655
- z.literal(false),
1656
- ]);
1657
- /**
1658
- * Application resource plan schema.
1659
- * Represents the complete plan for an application's infrastructure.
1660
- * Must be defined after ComputeResourcePlanSchema due to declaration order.
1661
- */
1662
- export const ApplicationResourcePlanSchema = z
1663
- .object({
1664
- appName: AppNameSchema,
1665
- type: AppTypeSchema,
1666
- pattern: PatternSchema.optional(),
1667
- /** Pattern configuration for high-level infrastructure patterns (Payload, etc.) */
1668
- patternConfig: PatternConfigSchema.optional(),
1669
- owner: z.string().optional(),
1670
- tags: z.record(z.string(), z.string()).optional(),
1671
- vpcId: z.string().optional(),
1672
- network: NetworkResourcePlanSchema.optional(),
1673
- backup: BackupConfigSchema.optional(),
1674
- tunnel: TunnelConfigSchema.optional(),
1675
- additionalNetworks: z.array(AdditionalNetworkResourcePlanSchema).optional(),
1676
- database: z.array(DatabaseResourcePlanSchema),
1677
- s3: z.array(S3ResourcePlanSchema),
1678
- compute: z.array(ComputeResourcePlanSchema),
1679
- dynamodb: z.array(DynamoDBResourcePlanSchema).optional(),
1680
- sqs: z.array(SQSResourcePlanSchema).optional(),
1681
- cdn: CDNResourcePlanSchema.optional(),
1682
- importedResources: z.array(ImportedResourcePlanSchema).default([]),
1683
- /** Custom code blocks to preserve during regeneration */
1684
- customCodeBlocks: z.array(CustomCodeBlockSchema).optional(),
1685
- /** Additional managed imports to preserve during round-trip (non-standard imports from managed modules) */
1686
- additionalManagedImports: z
1687
- .array(z
1688
- .object({
1689
- moduleSpecifier: z.string(),
1690
- namedImports: z.array(z.string()),
1691
- defaultImport: z.string().optional(),
1692
- })
1693
- .strict())
1694
- .optional(),
1695
- })
1696
- .strict();
1697
- // Compute generator schemas - discriminated union by type
1698
- const ComputeGeneratorBaseSchema = z
1699
- .object({
1700
- appName: AppNameSchema,
1701
- computeName: ResourceNameSchema,
1702
- nameProvidedByFlag: z.boolean().optional(),
1703
- connectionConfig: z
1704
- .object({
1705
- connectToDatabase: z.array(z.string()).optional(),
1706
- })
1707
- .strict()
1708
- .optional(),
1709
- })
1710
- .strict();
1711
- /**
1712
- * ECS-specific schema.
1713
- * Supports multi-service clusters with shared ALB.
1714
- *
1715
- * @example
1716
- * // Single service
1717
- * { type: "ecs", services: [{ name: "web", containers: [{ port: 3000 }] }] }
1718
- *
1719
- * @example
1720
- * // Multi-service with routing
1721
- * {
1722
- * type: "ecs",
1723
- * cluster: { domain: "api.example.com" },
1724
- * services: [
1725
- * { name: "users", containers: [{ port: 3000 }], routing: { path: "/users/*" } },
1726
- * { name: "orders", containers: [{ port: 3001 }], routing: { path: "/orders/*" } }
1727
- * ]
1728
- * }
1729
- *
1730
- * @example
1731
- * // Worker cluster (no ALB)
1732
- * {
1733
- * type: "ecs",
1734
- * cluster: { loadBalancer: false },
1735
- * services: [{ name: "processor" }]
1736
- * }
1737
- */
1738
- const EcsComputeGeneratorSchema = ComputeGeneratorBaseSchema.extend({
1739
- type: z.literal("ecs"),
1740
- /**
1741
- * Cluster configuration.
1742
- * Controls the shared ALB for all services.
1743
- */
1744
- cluster: EcsClusterConfigSchema.optional(),
1745
- /**
1746
- * Services in this cluster.
1747
- * Each service gets its own task definition, scaling, and target group.
1748
- * Each service MUST specify its own capacityProvider.
1749
- */
1750
- services: z
1751
- .array(EcsServiceConfigSchema)
1752
- .min(1, VALIDATION_MESSAGES.SERVICE.MIN_REQUIRED),
1753
- /** Path to Dockerfile for building custom image */
1754
- dockerfilePath: z.string().optional(),
1755
- })
1756
- .strict()
1757
- .refine((data) => {
1758
- // Validate: multiple services with ports require routing config
1759
- const servicesWithPorts = data.services.filter((s) => s.containers?.some((c) => c.port));
1760
- if (servicesWithPorts.length > 1) {
1761
- return servicesWithPorts.every((s) => {
1762
- const rules = Array.isArray(s.routing)
1763
- ? s.routing
1764
- : s.routing
1765
- ? [s.routing]
1766
- : [];
1767
- return rules.some((r) => r.path || r.host);
1768
- });
1769
- }
1770
- return true;
1771
- }, { message: VALIDATION_MESSAGES.SERVICE.ROUTING_REQUIRED })
1772
- .refine((data) => {
1773
- // Validate: unique service names
1774
- const names = data.services.map((s) => s.name);
1775
- return new Set(names).size === names.length;
1776
- }, { message: VALIDATION_MESSAGES.SERVICE.UNIQUE_WITHIN_CLUSTER });
1777
- /**
1778
- * Lambda-specific schema.
1779
- */
1780
- const LambdaComputeGeneratorSchema = ComputeGeneratorBaseSchema.extend({
1781
- type: z.literal("lambda"),
1782
- timeout: z
1783
- .number()
1784
- .int(VALIDATION_MESSAGES.LAMBDA.TIMEOUT.INTEGER)
1785
- .min(1, VALIDATION_MESSAGES.LAMBDA.TIMEOUT.MIN)
1786
- .max(900, VALIDATION_MESSAGES.LAMBDA.TIMEOUT.MAX)
1787
- .optional(),
1788
- memory: LambdaMemorySchema.optional(),
1789
- handler: z.string().optional(),
1790
- runtime: z.string().optional(),
1791
- environment: EnvironmentRecordSchema.optional(),
1792
- secrets: EnvironmentRecordSchema.optional(),
1793
- description: z.string().optional(),
1794
- functionUrl: z
1795
- .union([
1796
- z.object({ authType: z.enum(["AWS_IAM", "NONE"]).optional() }).strict(),
1797
- z.literal(false),
1798
- ])
1799
- .optional(),
1800
- eventSources: z.array(LambdaEventSourceSchema).optional(),
1801
- }).strict();
1802
- /**
1803
- * EC2 (standalone instance) specific schema.
1804
- */
1805
- const Ec2ComputeGeneratorSchema = ComputeGeneratorBaseSchema.extend({
1806
- type: z.literal("ec2"),
1807
- instanceType: InstanceTypeSchema.optional(),
1808
- enableSSH: z.boolean().optional(),
1809
- keyName: z.string().optional(),
1810
- userData: z.string().optional(),
1811
- securityGroups: z.array(z.string()).optional(),
1812
- }).strict();
1813
- /**
1814
- * Compute generator schema using discriminated union.
1815
- * Ensures type-specific fields are only allowed for the correct compute type.
1816
- */
1817
- export const ComputeGeneratorSchema = z.discriminatedUnion("type", [
1818
- EcsComputeGeneratorSchema,
1819
- LambdaComputeGeneratorSchema,
1820
- Ec2ComputeGeneratorSchema,
1821
- ]);
1822
- export const S3GeneratorSchema = z
1823
- .object({
1824
- appName: AppNameSchema,
1825
- bucketName: BucketNameSchema,
1826
- nameProvidedByFlag: z.boolean().optional(),
1827
- // Preset (generator-only -- resolved to params before plan creation)
1828
- storagePreset: z.enum(STORAGE_PRESET_TYPES).optional(),
1829
- // Explicit overrides
1830
- publicReadAccess: z.boolean().optional(),
1831
- websiteHosting: z
1832
- .object({
1833
- indexDocument: z.string(),
1834
- errorDocument: z.string().optional(),
1835
- })
1836
- .strict()
1837
- .optional(),
1838
- // Core
1839
- backupVaultTier: z.enum(BACKUP_VAULT_TIERS).optional(),
1840
- versioned: z.boolean().optional(),
1841
- encryption: z.enum(S3_ENCRYPTION_TYPES).optional(),
1842
- kmsKeyArn: z.string().optional(),
1843
- // CORS
1844
- cors: z
1845
- .array(z
1846
- .object({
1847
- allowedOrigins: z.array(z.string()),
1848
- allowedMethods: z.array(z.string()),
1849
- })
1850
- .strict())
1851
- .optional(),
1852
- // Connection config (non-interactive mode)
1853
- connectionConfig: z
1854
- .object({
1855
- /** Connect to entire compute resources */
1856
- connectToCompute: z.array(z.string()).optional(),
1857
- /** Connect to specific ECS services (format: "ClusterName/ServiceName") */
1858
- connectToServices: z.array(z.string()).optional(),
1859
- })
1860
- .strict()
1861
- .optional(),
1862
- })
1863
- .strict()
1864
- .refine((data) => data.encryption !== "KMS" || !!data.kmsKeyArn, {
1865
- message: VALIDATION_MESSAGES.KMS.KEY_REQUIRED,
1866
- path: ["kmsKeyArn"],
1867
- });
1868
- /**
1869
- * Simple routing configuration for application generator services.
1870
- * Matches what the UI sends (path is required, priority optional).
1871
- */
1872
- export const ApplicationServiceRoutingSchema = z
1873
- .object({
1874
- /** Path pattern for routing (e.g., "/api/*", "/*") */
1875
- path: z.string().min(1, VALIDATION_MESSAGES.REQUIRED.ROUTING_PATH),
1876
- /** Priority for this routing rule (lower = higher precedence) */
1877
- priority: z
1878
- .number()
1879
- .int(VALIDATION_MESSAGES.PRIORITY.INTEGER)
1880
- .min(1, VALIDATION_MESSAGES.PRIORITY.MIN)
1881
- .max(50000, VALIDATION_MESSAGES.PRIORITY.MAX)
1882
- .optional(),
1883
- })
1884
- .strict();
1885
- /**
1886
- * Service configuration for application generator.
1887
- * Used to configure multi-service ECS applications.
1888
- */
1889
- export const ApplicationServiceConfigSchema = z
1890
- .object({
1891
- name: z
1892
- .string()
1893
- .min(1, VALIDATION_MESSAGES.REQUIRED.SERVICE_NAME)
1894
- .max(255, VALIDATION_MESSAGES.MAX_LENGTH.SERVICE_NAME)
1895
- .regex(VALIDATION_PATTERNS.RESOURCE_NAME, VALIDATION_MESSAGES.RESOURCE_NAME),
1896
- dockerfilePath: z.string().optional(),
1897
- containerPort: PortSchema.optional(),
1898
- needsDatabaseConnection: z.boolean().optional(),
1899
- routing: z
1900
- .union([
1901
- ApplicationServiceRoutingSchema,
1902
- z.array(ApplicationServiceRoutingSchema).min(1),
1903
- ])
1904
- .optional(),
1905
- })
1906
- .strict();
1907
- /**
1908
- * Pattern tier schema for OpenNext patterns (Payload, Next.js).
1909
- * Defines configuration tiers: lightweight, standard, resilient, custom.
1910
- */
1911
- export const PatternTierSchema = z.enum([
1912
- "lightweight",
1913
- "standard",
1914
- "resilient",
1915
- "custom",
1916
- ]);
1917
- /**
1918
- * Custom database configuration for patterns.
1919
- * Used when patternTier is "custom".
1920
- */
1921
- export const CustomPatternDatabaseSchema = z
1922
- .object({
1923
- type: z.enum(["Instance", "Aurora"]),
1924
- instanceType: z.string().optional(),
1925
- backupRetention: BackupRetentionSchema.optional(),
1926
- deletionProtection: z.boolean().optional(),
1927
- encryption: z
1928
- .union([z.object({ useCMK: z.literal(true) }).strict(), z.literal(false)])
1929
- .optional(),
1930
- })
1931
- .strict();
1932
- /**
1933
- * Custom compute configuration for patterns.
1934
- * Used when patternTier is "custom".
1935
- */
1936
- export const CustomPatternComputeSchema = z
1937
- .object({
1938
- memorySize: LambdaMemorySchema.optional(),
1939
- timeout: z
1940
- .number()
1941
- .int(VALIDATION_MESSAGES.LAMBDA.TIMEOUT.INTEGER)
1942
- .min(1, VALIDATION_MESSAGES.LAMBDA.TIMEOUT.MIN)
1943
- .max(900, VALIDATION_MESSAGES.LAMBDA.TIMEOUT.MAX)
1944
- .optional(),
1945
- })
1946
- .strict();
1947
- export const ApplicationGeneratorSchema = z
1948
- .object({
1949
- name: AppNameSchema,
1950
- type: AppTypeSchema,
1951
- pattern: PatternSchema.optional(),
1952
- /** Pattern tier: lightweight, standard, resilient, or custom */
1953
- patternTier: PatternTierSchema.optional(),
1954
- /** Custom domain for pattern (e.g., cms.example.com) */
1955
- patternDomain: z.string().optional(),
1956
- /** Custom database configuration (only used when patternTier is "custom") */
1957
- customDatabase: CustomPatternDatabaseSchema.optional(),
1958
- /** Custom compute configuration (only used when patternTier is "custom") */
1959
- customCompute: CustomPatternComputeSchema.optional(),
1960
- region: z.string().optional(),
1961
- owner: z.string().optional(),
1962
- includeDatabase: z.boolean().optional(),
1963
- databaseName: z
1964
- .string()
1965
- .min(1, VALIDATION_MESSAGES.DATABASE.NAME.REQUIRED)
1966
- .max(63, VALIDATION_MESSAGES.DATABASE.NAME.MAX_LENGTH)
1967
- .optional(),
1968
- vpcId: z.string().optional(),
1969
- network: NetworkConfigSchema.optional(),
1970
- services: z.array(ApplicationServiceConfigSchema).optional(),
1971
- snapshotIdentifier: z.string().optional(),
1972
- snapshotUsername: z.string().optional(),
1973
- })
1974
- .strict();
1975
- export const OrganisationNameSchema = z
1976
- .string()
1977
- .min(2, VALIDATION_MESSAGES.IDENTIFIER_MIN_LENGTH)
1978
- .max(50, VALIDATION_MESSAGES.MAX_LENGTH.ORGANISATION_NAME)
1979
- .regex(VALIDATION_PATTERNS.IDENTIFIER, VALIDATION_MESSAGES.IDENTIFIER)
1980
- .refine((val) => !val.endsWith("-"), {
1981
- message: VALIDATION_MESSAGES.IDENTIFIER_NO_TRAILING_HYPHEN,
1982
- })
1983
- .refine((val) => !val.includes("--"), {
1984
- message: VALIDATION_MESSAGES.IDENTIFIER_NO_CONSECUTIVE_HYPHENS,
1985
- });
1986
- export const EmailSchema = z.string().email(VALIDATION_MESSAGES.EMAIL);
1987
- export const RegionSchema = z
1988
- .string()
1989
- .refine((region) => regions.includes(region), {
1990
- message: VALIDATION_MESSAGES.REGION,
1991
- });
1992
- export const OrganisationGeneratorSchema = z
1993
- .object({
1994
- name: OrganisationNameSchema,
1995
- email: EmailSchema,
1996
- accountName: z.string().optional(),
1997
- primaryRegion: RegionSchema.optional().default("us-east-1"),
1998
- secondaryRegions: z.array(RegionSchema).optional(),
1999
- force: z.boolean().optional(),
2000
- })
2001
- .strict();
2002
- export function getZodErrorMessage(error) {
2003
- const messages = error.issues.map((issue) => {
2004
- const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
2005
- return `${path}${issue.message}`;
2006
- });
2007
- return messages.join("\n");
2008
- }
2009
- // Tunnel generator schema
2010
- export const TunnelGeneratorSchema = z
2011
- .object({
2012
- appName: AppNameSchema,
2013
- instanceType: z.string().optional(),
2014
- })
2015
- .strict();
17
+ * All exports are re-exported here so existing consumers continue to work
18
+ * without any import path changes.
19
+ */
20
+ export * from "./baseSchemas.js";
21
+ export * from "./databaseSchemas.js";
22
+ export * from "./networkSchemas.js";
23
+ export * from "./storageSchemas.js";
24
+ export * from "./messagingSchemas.js";
25
+ export * from "./cdnSchemas.js";
26
+ export * from "./computeSchemas.js";
27
+ export * from "./patternSchemas.js";
28
+ export * from "./applicationSchemas.js";