@intentius/chant-lexicon-aws 0.0.4 → 0.0.6

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.
@@ -125,6 +125,7 @@ export async function generateDocs(options?: { verbose?: boolean }): Promise<voi
125
125
  outputFormat,
126
126
  serviceFromType,
127
127
  suppressPages: ["pseudo-parameters", "intrinsics", "rules"],
128
+ examplesDir: join(pkgDir, "examples"),
128
129
  extraPages: [
129
130
  {
130
131
  slug: "cloudformation",
@@ -137,13 +138,7 @@ export async function generateDocs(options?: { verbose?: boolean }): Promise<voi
137
138
  - Resolves \`AttrRef\` references to \`Fn::GetAtt\`
138
139
  - Resolves resource references to \`Ref\` intrinsics
139
140
 
140
- \`\`\`typescript
141
- // This chant declaration...
142
- export const dataBucket = new Bucket({
143
- bucketName: Sub\`\${AWS.StackName}-data\`,
144
- versioningConfiguration: $.versioningEnabled,
145
- });
146
- \`\`\`
141
+ {{file:getting-started/src/data-bucket.ts}}
147
142
 
148
143
  Produces this CloudFormation resource:
149
144
 
@@ -176,43 +171,19 @@ Common resources get fixed short names for stability. When two services define t
176
171
 
177
172
  **Discovering available resources:** Your editor's autocomplete is the best tool — every resource is a named export from the lexicon. You can also run \`chant list\` to see all resource types, or browse the generated TypeScript types.
178
173
 
179
- ## The barrel file
174
+ ## Imports and cross-file references
180
175
 
181
- Every chant project has a barrel file (conventionally \`_.ts\`) that re-exports the lexicon and provides cross-file references:
182
-
183
- \`\`\`typescript
184
- // _.ts — the barrel file
185
- export * from "@intentius/chant-lexicon-aws";
186
- import * as core from "@intentius/chant";
187
- export const $ = core.barrel(import.meta.dir);
188
- \`\`\`
176
+ Chant projects use standard TypeScript imports. Lexicon types come from the lexicon package, and sibling exports are imported directly from the file that defines them:
189
177
 
190
- Other files import the barrel and use \`$\` to reference sibling exports:
191
-
192
- \`\`\`typescript
193
- // data-bucket.ts
194
- import * as _ from "./_";
195
-
196
- export const dataBucket = new _.Bucket({
197
- bucketName: _.Sub\`\${_.AWS.StackName}-data\`,
198
- serverSideEncryptionConfiguration: _.$.encryptionDefault, // from defaults.ts
199
- });
200
- \`\`\`
178
+ {{file:getting-started/src/data-bucket.ts}}
201
179
 
202
- The \`$\` proxy lazily resolves exports from other files in the same directory. When the serializer encounters \`_.$.encryptionDefault\`, it resolves to the actual exported value and serializes the reference as \`Fn::GetAtt\` or \`Ref\` as appropriate. This is how cross-file references work without circular imports.
180
+ When you reference a resource or attribute from another file (e.g. \`dataBucket.arn\`), the serializer resolves it to \`Fn::GetAtt\` or \`Ref\` as appropriate. This is how cross-file references work standard imports, no indirection.
203
181
 
204
182
  ## Parameters
205
183
 
206
184
  CloudFormation parameters let you customize a stack at deploy time. Export a \`Parameter\` to add it to the template's \`Parameters\` section:
207
185
 
208
- \`\`\`typescript
209
- import { Parameter } from "@intentius/chant-lexicon-aws";
210
-
211
- export const environment = new Parameter("String", {
212
- description: "Deployment environment",
213
- defaultValue: "dev",
214
- });
215
- \`\`\`
186
+ {{file:getting-started/src/environment.ts}}
216
187
 
217
188
  Produces:
218
189
 
@@ -228,23 +199,13 @@ Produces:
228
199
 
229
200
  Reference parameters with \`Ref\`:
230
201
 
231
- \`\`\`typescript
232
- import { Ref } from "@intentius/chant-lexicon-aws";
233
-
234
- export const bucket = new Bucket({
235
- bucketName: Sub\`\${Ref("Environment")}-data\`,
236
- });
237
- \`\`\`
202
+ {{file:docs-snippets/src/parameter-ref.ts}}
238
203
 
239
204
  ## Outputs
240
205
 
241
206
  Use \`output()\` to create explicit stack outputs. Cross-resource \`AttrRef\` usage is also auto-detected and promoted to outputs when needed.
242
207
 
243
- \`\`\`typescript
244
- import { output } from "@intentius/chant";
245
-
246
- export const bucketArn = output(dataBucket.arn, "DataBucketArn");
247
- \`\`\`
208
+ {{file:docs-snippets/src/output-explicit.ts}}
248
209
 
249
210
  Produces:
250
211
 
@@ -260,11 +221,7 @@ Produces:
260
221
 
261
222
  Runtime context values available in every template, accessed via the \`AWS\` namespace:
262
223
 
263
- \`\`\`typescript
264
- import { AWS, Sub } from "@intentius/chant-lexicon-aws";
265
-
266
- const endpoint = Sub\`https://s3.\${AWS.Region}.\${AWS.URLSuffix}\`;
267
- \`\`\`
224
+ {{file:docs-snippets/src/pseudo-params.ts}}
268
225
 
269
226
  | Pseudo-parameter | Description |
270
227
  |---|---|
@@ -287,13 +244,7 @@ CloudFormation automatically creates dependencies between resources when you use
287
244
 
288
245
  For cases where you need an explicit dependency without a property reference, set \`dependsOn\`:
289
246
 
290
- \`\`\`typescript
291
- export const appServer = new Instance({
292
- imageId: "ami-12345678",
293
- instanceType: "t3.micro",
294
- dependsOn: ["DatabaseCluster"],
295
- });
296
- \`\`\`
247
+ {{file:docs-snippets/src/depends-on.ts}}
297
248
 
298
249
  The \`WAW010\` post-synth check warns if a \`DependsOn\` target is already referenced via \`Ref\` or \`Fn::GetAtt\` in properties — in that case the explicit dependency is redundant.
299
250
 
@@ -311,58 +262,17 @@ The \`PolicyDocument\` interface and its supporting types:
311
262
 
312
263
  Policy documents use **PascalCase keys** (\`Effect\`, \`Action\`, \`Resource\`) because they follow the IAM JSON Policy Language spec — CloudFormation passes them through to IAM as-is, unlike resource properties which are automatically converted from camelCase.
313
264
 
314
- The recommended pattern is to extract policies into your \`defaults.ts\` and reference them via the barrel:
315
-
316
- \`\`\`typescript
317
- // defaults.ts — shared trust policies and permission policies
318
- import { Sub, AWS, type PolicyDocument } from "@intentius/chant-lexicon-aws";
319
-
320
- export const lambdaTrustPolicy: PolicyDocument = {
321
- Version: "2012-10-17",
322
- Statement: [{
323
- Effect: "Allow",
324
- Principal: { Service: "lambda.amazonaws.com" },
325
- Action: "sts:AssumeRole",
326
- }],
327
- };
265
+ The recommended pattern is to extract policies into your \`defaults.ts\` and import them directly:
328
266
 
329
- export const s3ReadPolicy: PolicyDocument = {
330
- Statement: [{
331
- Effect: "Allow",
332
- Action: ["s3:GetObject", "s3:ListBucket"],
333
- Resource: "*",
334
- }],
335
- };
336
- \`\`\`
267
+ {{file:docs-snippets/src/policy-trust.ts}}
337
268
 
338
269
  Then reference them from resource files:
339
270
 
340
- \`\`\`typescript
341
- // role.ts
342
- import * as _ from "./_";
343
-
344
- export const functionRole = new _.Role({
345
- assumeRolePolicyDocument: _.$.lambdaTrustPolicy,
346
- });
347
-
348
- export const readPolicy = new _.ManagedPolicy({
349
- policyDocument: _.$.s3ReadPolicy,
350
- roles: [_.$.functionRole],
351
- });
352
- \`\`\`
271
+ {{file:docs-snippets/src/policy-role.ts}}
353
272
 
354
273
  For scoped resource ARNs, use \`Sub\` in the policy constant:
355
274
 
356
- \`\`\`typescript
357
- // defaults.ts
358
- export const bucketWritePolicy: PolicyDocument = {
359
- Statement: [{
360
- Effect: "Allow",
361
- Action: ["s3:PutObject"],
362
- Resource: Sub\`arn:aws:s3:::\${AWS.StackName}-data/*\`,
363
- }],
364
- };
365
- \`\`\`
275
+ {{file:docs-snippets/src/policy-scoped.ts}}
366
276
 
367
277
  The \`IamPolicyPrincipal\` type supports all principal forms — wildcard (\`"*"\`), AWS accounts, services, and federated providers:
368
278
 
@@ -384,13 +294,7 @@ Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
384
294
 
385
295
  Use the \`If\` intrinsic for conditional values within resource properties:
386
296
 
387
- \`\`\`typescript
388
- import { If } from "@intentius/chant-lexicon-aws";
389
-
390
- export const bucket = new Bucket({
391
- bucketName: If("IsProduction", "prod-data", "dev-data"),
392
- });
393
- \`\`\`
297
+ {{file:docs-snippets/src/conditions.ts}}
394
298
 
395
299
  CloudFormation \`Conditions\` blocks are recognized by the serializer when importing existing templates. For new stacks, use TypeScript logic for build-time decisions and \`If\` for deploy-time decisions.
396
300
 
@@ -398,44 +302,17 @@ CloudFormation \`Conditions\` blocks are recognized by the serializer when impor
398
302
 
399
303
  CloudFormation Mappings are a static lookup mechanism. In chant, use TypeScript objects instead — they're evaluated at build time and produce the same result:
400
304
 
401
- \`\`\`typescript
402
- const regionAMIs: Record<string, string> = {
403
- "us-east-1": "ami-12345678",
404
- "us-west-2": "ami-87654321",
405
- "eu-west-1": "ami-abcdef01",
406
- };
407
-
408
- // Use directly in resource properties
409
- export const server = new Instance({
410
- imageId: regionAMIs["us-east-1"],
411
- instanceType: "t3.micro",
412
- });
413
- \`\`\`
305
+ {{file:docs-snippets/src/mappings.ts}}
414
306
 
415
307
  For deploy-time region lookups, combine \`AWS.Region\` with \`If\` or use \`Fn::Sub\` with SSM parameter store references.
416
308
 
417
309
  ## Nested stacks
418
310
 
419
- CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) let you decompose large templates into smaller, reusable child templates. Use \`nestedStack()\` to reference a child project directory — a subdirectory with its own barrel file that builds independently:
311
+ CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) let you decompose large templates into smaller, reusable child templates. Use \`nestedStack()\` to reference a child project directory — a subdirectory that builds independently:
420
312
 
421
- \`\`\`typescript
422
- // Child project declares outputs with stackOutput()
423
- // src/network/outputs.ts
424
- export const vpcId = stackOutput(_.$.vpc.vpcId);
425
- export const subnetId = stackOutput(_.$.subnet.subnetId);
426
- export const lambdaSgId = stackOutput(_.$.lambdaSg.groupId);
427
-
428
- // Parent references the child project
429
- // src/app.ts
430
- const network = _.nestedStack("network", import.meta.dir + "/network");
431
-
432
- export const handler = new _.Function({
433
- vpcConfig: {
434
- subnetIds: [network.outputs.subnetId], // cross-stack ref
435
- securityGroupIds: [network.outputs.lambdaSgId],
436
- },
437
- });
438
- \`\`\`
313
+ {{file:nested-stacks/src/network/outputs.ts}}
314
+
315
+ {{file:nested-stacks/src/app.ts}}
439
316
 
440
317
  chant handles the wiring: child template gets an \`Outputs\` section, parent uses \`Fn::GetAtt\` on the stack resource. A \`TemplateBasePath\` parameter lets you configure child template URLs per environment.
441
318
 
@@ -445,26 +322,11 @@ See [Nested Stacks](./nested-stacks) for the full guide.
445
322
 
446
323
  Tags are standard CloudFormation \`Key\`/\`Value\` arrays. Pass them on any resource that supports tagging:
447
324
 
448
- \`\`\`typescript
449
- export const bucket = new Bucket({
450
- bucketName: "my-bucket",
451
- tags: [
452
- { key: "Environment", value: "production" },
453
- { key: "Team", value: "platform" },
454
- ],
455
- });
456
- \`\`\`
325
+ {{file:docs-snippets/src/tagging.ts}}
457
326
 
458
327
  To apply tags across all members of a composite, use [\`propagate\`](./composites#propagate--shared-properties):
459
328
 
460
- \`\`\`typescript
461
- import { propagate } from "@intentius/chant";
462
-
463
- export const api = propagate(
464
- LambdaApi({ name: "myApi", code: lambdaCode }),
465
- { tags: [{ key: "env", value: "prod" }] },
466
- );
467
- \`\`\``,
329
+ {{file:docs-snippets/src/propagate.ts}}`,
468
330
  },
469
331
  {
470
332
  slug: "intrinsics",
@@ -472,26 +334,15 @@ export const api = propagate(
472
334
  description: "CloudFormation intrinsic functions and their chant syntax",
473
335
  content: `CloudFormation intrinsic functions are available as imports from the lexicon. They produce the corresponding \`Fn::\` calls in the serialized template.
474
336
 
475
- \`\`\`typescript
476
- import { Sub, Ref, GetAtt, If, Join, Select, Split, Base64, AWS } from "@intentius/chant-lexicon-aws";
477
- \`\`\`
337
+ Here is a complete example using all intrinsic functions:
338
+
339
+ {{file:docs-snippets/src/intrinsics.ts}}
478
340
 
479
341
  ## \`Sub\` — string substitution
480
342
 
481
343
  Tagged template literal that produces \`Fn::Sub\`. The most common intrinsic — use it for dynamic naming with pseudo-parameters and attribute references:
482
344
 
483
- \`\`\`typescript
484
- // Simple pseudo-parameter substitution
485
- const bucketName = Sub\`\${AWS.StackName}-data\`;
486
- // → { "Fn::Sub": "\${AWS::StackName}-data" }
487
-
488
- // Multiple pseudo-parameters
489
- const arn = Sub\`arn:aws:s3:::\${AWS.AccountId}:\${AWS.Region}:*\`;
490
-
491
- // With resource attribute references
492
- const url = Sub\`https://\${bucket.domainName}/path\`;
493
- // → { "Fn::Sub": "https://\${DataBucket.DomainName}/path" }
494
- \`\`\`
345
+ {{file:docs-snippets/src/intrinsics-detail.ts:3-5}}
495
346
 
496
347
  \`Sub\` is a tagged template — use it with backticks, not as a function call.
497
348
 
@@ -499,89 +350,45 @@ const url = Sub\`https://\${bucket.domainName}/path\`;
499
350
 
500
351
  References a resource's physical ID or a parameter's value:
501
352
 
502
- \`\`\`typescript
503
- // Reference a parameter
504
- const envRef = Ref("Environment");
505
- // → { "Ref": "Environment" }
506
-
507
- // Reference a resource (returns its physical ID)
508
- const bucketRef = Ref("DataBucket");
509
- // → { "Ref": "DataBucket" }
510
- \`\`\`
353
+ {{file:docs-snippets/src/intrinsics-detail.ts:7-9}}
511
354
 
512
- In most cases you don't need \`Ref\` directly — the serializer automatically generates \`Ref\` when you reference a resource via the barrel (e.g. \`_.$.dataBucket\`).
355
+ In most cases you don't need \`Ref\` directly — the serializer automatically generates \`Ref\` when you reference an imported resource (e.g. \`dataBucket\` imported from another file).
513
356
 
514
357
  ## \`GetAtt\` — resource attributes
515
358
 
516
- Retrieves an attribute from a resource:
517
-
518
- \`\`\`typescript
519
- // Explicit GetAtt
520
- const bucketArn = GetAtt("DataBucket", "Arn");
521
- // → { "Fn::GetAtt": ["DataBucket", "Arn"] }
522
- \`\`\`
523
-
524
- **Preferred:** Use AttrRef directly via the resource's typed properties. When you write \`$.dataBucket.arn\`, the serializer automatically emits \`Fn::GetAtt\`. Explicit \`GetAtt\` is only needed for dynamic or imported resource names.
359
+ **Preferred:** Use AttrRef directly via the resource's typed properties. When you write \`dataBucket.arn\` (imported from the file that defines it), the serializer automatically emits \`Fn::GetAtt\`. Explicit \`GetAtt\` is only needed for dynamic or imported resource names.
525
360
 
526
361
  ## \`If\` — conditional values
527
362
 
528
363
  Returns one of two values based on a condition:
529
364
 
530
- \`\`\`typescript
531
- const value = If("IsProduction", "prod-value", "dev-value");
532
- // → { "Fn::If": ["IsProduction", "prod-value", "dev-value"] }
533
- \`\`\`
534
-
535
- Use with \`AWS.NoValue\` to conditionally omit a property:
365
+ {{file:docs-snippets/src/intrinsics-detail.ts:11-12}}
536
366
 
537
- \`\`\`typescript
538
- export const bucket = new Bucket({
539
- bucketName: "my-bucket",
540
- accelerateConfiguration: If("EnableAcceleration",
541
- { accelerationStatus: "Enabled" },
542
- AWS.NoValue,
543
- ),
544
- });
545
- \`\`\`
367
+ Use with \`AWS.NoValue\` to conditionally omit a property — see [Conditions](#conditions) on the CloudFormation Concepts page.
546
368
 
547
369
  ## \`Join\` — join values
548
370
 
549
371
  Joins values with a delimiter:
550
372
 
551
- \`\`\`typescript
552
- const joined = Join("-", ["prefix", AWS.StackName, "suffix"]);
553
- // → { "Fn::Join": ["-", ["prefix", { "Ref": "AWS::StackName" }, "suffix"]] }
554
- \`\`\`
373
+ {{file:docs-snippets/src/intrinsics-detail.ts:14-15}}
555
374
 
556
375
  ## \`Select\` — select by index
557
376
 
558
377
  Selects a value from a list by index:
559
378
 
560
- \`\`\`typescript
561
- const first = Select(0, Split(",", "a,b,c"));
562
- // → { "Fn::Select": [0, { "Fn::Split": [",", "a,b,c"] }] }
563
- \`\`\`
379
+ {{file:docs-snippets/src/intrinsics-detail.ts:17-18}}
564
380
 
565
381
  ## \`Split\` — split string
566
382
 
567
383
  Splits a string by a delimiter:
568
384
 
569
- \`\`\`typescript
570
- const parts = Split(",", "a,b,c");
571
- // → { "Fn::Split": [",", "a,b,c"] }
572
- \`\`\`
385
+ {{file:docs-snippets/src/intrinsics-detail.ts:20-21}}
573
386
 
574
387
  ## \`Base64\` — encode to Base64
575
388
 
576
389
  Encodes a string to Base64, commonly used for EC2 user data:
577
390
 
578
- \`\`\`typescript
579
- const userData = Base64(Sub\`#!/bin/bash
580
- echo "Stack: \${AWS.StackName}"
581
- yum update -y
582
- \`);
583
- // → { "Fn::Base64": { "Fn::Sub": "..." } }
584
- \`\`\``,
391
+ {{file:docs-snippets/src/intrinsics-detail.ts:23-27}}`,
585
392
  },
586
393
  {
587
394
  slug: "composites",
@@ -589,46 +396,11 @@ yum update -y
589
396
  description: "Composite resources, withDefaults presets, and propagate in the AWS CloudFormation lexicon",
590
397
  content: `Composites group related resources into reusable factories. See also the core [Composite Resources](/guide/composite-resources/) guide.
591
398
 
592
- \`\`\`typescript
593
- import * as _ from "./_";
594
-
595
- export const LambdaApi = _.Composite<LambdaApiProps>((props) => {
596
- const role = new _.Role({
597
- assumeRolePolicyDocument: _.$.lambdaTrustPolicy,
598
- managedPolicyArns: [_.$.lambdaBasicExecutionArn],
599
- policies: props.policies,
600
- });
601
-
602
- const func = new _.Function({
603
- functionName: props.name,
604
- runtime: props.runtime,
605
- handler: props.handler,
606
- code: props.code,
607
- role: role.arn,
608
- timeout: props.timeout,
609
- memorySize: props.memorySize,
610
- });
611
-
612
- const permission = new _.Permission({
613
- functionName: func.arn,
614
- action: "lambda:InvokeFunction",
615
- principal: "apigateway.amazonaws.com",
616
- });
617
-
618
- return { role, func, permission };
619
- }, "LambdaApi");
620
- \`\`\`
399
+ {{file:advanced/src/lambda-api.ts}}
621
400
 
622
401
  Instantiate and export:
623
402
 
624
- \`\`\`typescript
625
- export const healthApi = LambdaApi({
626
- name: Sub\`\${AWS.StackName}-health\`,
627
- runtime: "nodejs20.x",
628
- handler: "index.handler",
629
- code: { zipFile: \`exports.handler = async () => ({ statusCode: 200 });\` },
630
- });
631
- \`\`\`
403
+ {{file:advanced/src/health-api.ts}}
632
404
 
633
405
  During build, composites expand to flat CloudFormation resources: \`healthApi_role\` → \`HealthApiRole\`, \`healthApi_func\` → \`HealthApiFunc\`, \`healthApi_permission\` → \`HealthApiPermission\`.
634
406
 
@@ -636,25 +408,7 @@ During build, composites expand to flat CloudFormation resources: \`healthApi_ro
636
408
 
637
409
  Wrap a composite with pre-applied defaults. Defaulted props become optional:
638
410
 
639
- \`\`\`typescript
640
- import { withDefaults } from "@intentius/chant";
641
-
642
- const SecureApi = withDefaults(LambdaApi, {
643
- runtime: "nodejs20.x",
644
- handler: "index.handler",
645
- timeout: 10,
646
- memorySize: 256,
647
- });
648
-
649
- // Only name and code are required now
650
- export const healthApi = SecureApi({
651
- name: Sub\`\${AWS.StackName}-health\`,
652
- code: { zipFile: \`exports.handler = async () => ({ statusCode: 200 });\` },
653
- });
654
-
655
- // Composable — stack defaults on top of defaults
656
- const HighMemoryApi = withDefaults(SecureApi, { memorySize: 2048, timeout: 25 });
657
- \`\`\`
411
+ {{file:docs-snippets/src/with-defaults.ts}}
658
412
 
659
413
  \`withDefaults\` preserves the original composite's identity — same \`_id\` and \`compositeName\`, no new registry entry.
660
414
 
@@ -662,15 +416,7 @@ const HighMemoryApi = withDefaults(SecureApi, { memorySize: 2048, timeout: 25 })
662
416
 
663
417
  Attach properties that merge into every member during expansion:
664
418
 
665
- \`\`\`typescript
666
- import { propagate } from "@intentius/chant";
667
-
668
- export const api = propagate(
669
- LambdaApi({ name: "myApi", code: lambdaCode }),
670
- { tags: [{ key: "env", value: "prod" }] },
671
- );
672
- // role, func, and permission all receive the env tag
673
- \`\`\`
419
+ {{file:docs-snippets/src/propagate.ts}}
674
420
 
675
421
  Merge semantics:
676
422
  - **Scalars** — member-specific value wins over shared
@@ -679,17 +425,9 @@ Merge semantics:
679
425
 
680
426
  ## Nested stacks
681
427
 
682
- When resources should produce a separate CloudFormation template instead of expanding into the parent, use a **child project** — a subdirectory with its own barrel file (\`_.ts\`) that builds independently. The parent references it with \`nestedStack()\`:
428
+ When resources should produce a separate CloudFormation template instead of expanding into the parent, use a **child project** — a subdirectory that builds independently to its own CloudFormation template. The parent references it with \`nestedStack()\`:
683
429
 
684
- \`\`\`typescript
685
- // src/app.ts — parent references child project directory
686
- const network = _.nestedStack("network", import.meta.dir + "/network");
687
-
688
- // Cross-stack reference via outputs proxy
689
- export const handler = new _.Function({
690
- vpcConfig: { subnetIds: [network.outputs.subnetId] },
691
- });
692
- \`\`\`
430
+ {{file:nested-stacks/src/app.ts}}
693
431
 
694
432
  See [Nested Stacks](./nested-stacks) for the full guide.`,
695
433
  },
@@ -697,18 +435,16 @@ See [Nested Stacks](./nested-stacks) for the full guide.`,
697
435
  slug: "nested-stacks",
698
436
  title: "Nested Stacks",
699
437
  description: "Splitting resources into child CloudFormation templates with automatic cross-stack reference wiring",
700
- content: `CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) let you decompose large templates into smaller, reusable child templates. The AWS lexicon's \`nestedStack()\` function references a **child project directory** — a subdirectory with its own barrel file that builds independently to a valid CloudFormation template.
438
+ content: `CloudFormation nested stacks (\`AWS::CloudFormation::Stack\`) let you decompose large templates into smaller, reusable child templates. The AWS lexicon's \`nestedStack()\` function references a **child project directory** — a subdirectory that builds independently to a valid CloudFormation template.
701
439
 
702
440
  ## Project structure
703
441
 
704
- A nested stack is a child project — a subdirectory with its own \`_.ts\` barrel, resource files, and explicit \`stackOutput()\` declarations:
442
+ A nested stack is a child project — a subdirectory with its own resource files and explicit \`stackOutput()\` declarations:
705
443
 
706
444
  \`\`\`
707
445
  src/
708
- _.ts # parent barrel
709
446
  app.ts # parent resources
710
447
  network/ # ← child project (nested stack)
711
- _.ts # its own barrel
712
448
  vpc.ts # VPC, subnet, internet gateway, routing
713
449
  security.ts # security group for Lambda
714
450
  outputs.ts # declares cross-stack outputs
@@ -718,15 +454,7 @@ src/
718
454
 
719
455
  Use \`stackOutput()\` to mark values that the parent can reference. Each \`stackOutput()\` becomes an entry in the child template's \`Outputs\` section:
720
456
 
721
- \`\`\`typescript
722
- // src/network/outputs.ts
723
- import * as _ from "./_";
724
- import { stackOutput } from "@intentius/chant";
725
-
726
- export const vpcId = stackOutput(_.$.vpc.vpcId, { description: "VPC ID" });
727
- export const subnetId = stackOutput(_.$.subnet.subnetId, { description: "Public subnet ID" });
728
- export const lambdaSgId = stackOutput(_.$.lambdaSg.groupId, { description: "Lambda security group ID" });
729
- \`\`\`
457
+ {{file:nested-stacks/src/network/outputs.ts}}
730
458
 
731
459
  The child can be built independently:
732
460
 
@@ -739,24 +467,7 @@ chant build src/network/ -o network.json
739
467
 
740
468
  Use \`nestedStack()\` in the parent to reference a child project directory. It returns an object with an \`outputs\` proxy for cross-stack references:
741
469
 
742
- \`\`\`typescript
743
- // src/app.ts
744
- import * as _ from "./_";
745
-
746
- const network = _.nestedStack("network", import.meta.dir + "/network");
747
-
748
- export const handler = new _.Function({
749
- functionName: _.Sub\`\${_.AWS.StackName}-handler\`,
750
- runtime: "nodejs20.x",
751
- handler: "index.handler",
752
- role: _.Ref("LambdaExecutionRole"),
753
- code: { zipFile: "exports.handler = async () => ({ statusCode: 200 });" },
754
- vpcConfig: {
755
- subnetIds: [network.outputs.subnetId],
756
- securityGroupIds: [network.outputs.lambdaSgId],
757
- },
758
- });
759
- \`\`\`
470
+ {{file:nested-stacks/src/app.ts}}
760
471
 
761
472
  \`network.outputs.subnetId\` produces a \`NestedStackOutputRef\` that serializes to \`{ "Fn::GetAtt": ["Network", "Outputs.SubnetId"] }\`.
762
473
 
@@ -804,7 +515,9 @@ Child templates also receive the \`TemplateBasePath\` parameter so it propagates
804
515
  Pass CloudFormation Parameters to child stacks with the \`parameters\` option:
805
516
 
806
517
  \`\`\`typescript
807
- const network = _.nestedStack("network", import.meta.dir + "/network", {
518
+ import { nestedStack } from "@intentius/chant-lexicon-aws";
519
+
520
+ const network = nestedStack("network", import.meta.dir + "/network", {
808
521
  parameters: { Environment: "prod", CidrBlock: "10.0.0.0/16" },
809
522
  });
810
523
  \`\`\`
@@ -815,16 +528,12 @@ Child projects can themselves reference grandchild projects. Each level produces
815
528
 
816
529
  \`\`\`
817
530
  src/
818
- _.ts
819
531
  app.ts
820
532
  infra/
821
- _.ts
822
533
  network/
823
- _.ts
824
534
  vpc.ts
825
535
  outputs.ts
826
536
  database/
827
- _.ts
828
537
  cluster.ts
829
538
  outputs.ts
830
539
  \`\`\`
@@ -871,13 +580,13 @@ Lint rules analyze your TypeScript source code before build.
871
580
 
872
581
  Flags hardcoded AWS region strings like \`us-east-1\`. Use \`AWS.Region\` instead so templates are portable across regions.
873
582
 
874
- \`\`\`typescript
875
- // Triggers WAW001
876
- const endpoint = "s3.us-east-1.amazonaws.com";
583
+ **Bad** — triggers WAW001:
877
584
 
878
- // Fixed
879
- const endpoint = Sub\`s3.\${AWS.Region}.amazonaws.com\`;
880
- \`\`\`
585
+ {{file:docs-snippets/src/lint-waw001-bad.ts}}
586
+
587
+ **Good** — uses \`AWS.Region\`:
588
+
589
+ {{file:docs-snippets/src/lint-waw001-good.ts}}
881
590
 
882
591
  ### WAW006 — S3 Bucket Encryption
883
592
 
@@ -885,20 +594,13 @@ const endpoint = Sub\`s3.\${AWS.Region}.amazonaws.com\`;
885
594
 
886
595
  Flags S3 buckets that don't configure server-side encryption. AWS recommends enabling encryption on all buckets.
887
596
 
888
- \`\`\`typescript
889
- // Triggers WAW006
890
- export const bucket = new Bucket({ bucketName: "my-bucket" });
891
-
892
- // Fixed add encryption configuration
893
- export const bucket = new Bucket({
894
- bucketName: "my-bucket",
895
- bucketEncryption: {
896
- serverSideEncryptionConfiguration: [{
897
- serverSideEncryptionByDefault: { sseAlgorithm: "AES256" },
898
- }],
899
- },
900
- });
901
- \`\`\`
597
+ **Bad** — triggers WAW006:
598
+
599
+ {{file:docs-snippets/src/lint-waw006-bad.ts}}
600
+
601
+ **Good** — encryption configured:
602
+
603
+ {{file:docs-snippets/src/lint-waw006-good.ts}}
902
604
 
903
605
  ### WAW009 — IAM Wildcard Resource
904
606
 
@@ -906,13 +608,13 @@ export const bucket = new Bucket({
906
608
 
907
609
  Flags IAM policy statements that use \`"Resource": "*"\`. Prefer scoped resource ARNs following the principle of least privilege.
908
610
 
909
- \`\`\`typescript
910
- // Triggers WAW009
911
- { Effect: "Allow", Action: ["s3:GetObject"], Resource: "*" }
611
+ **Bad** — triggers WAW009:
912
612
 
913
- // Fixed — scope to a specific bucket
914
- { Effect: "Allow", Action: ["s3:GetObject"], Resource: Sub\`arn:aws:s3:::\${AWS.StackName}-data/*\` }
915
- \`\`\`
613
+ {{file:docs-snippets/src/lint-waw009-bad.ts}}
614
+
615
+ **Good** — scoped ARN:
616
+
617
+ {{file:docs-snippets/src/lint-waw009-good.ts}}
916
618
 
917
619
  IAM policy documents use PascalCase keys (\`Effect\`, \`Action\`, \`Resource\`) matching the IAM JSON Policy Language spec. The \`PolicyDocument\` and \`IamPolicyStatement\` types provide full autocomplete for these fields.
918
620
 
@@ -993,67 +695,17 @@ See also [Custom Lint Rules](./custom-rules) for writing project-specific rules.
993
695
 
994
696
  ## Anatomy of a lint rule
995
697
 
996
- \`\`\`typescript
997
- import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
998
- import * as ts from "typescript";
999
-
1000
- export const apiTimeoutRule: LintRule = {
1001
- id: "WAW012", // unique ID (WAW = AWS-specific prefix)
1002
- severity: "error", // "error" | "warning"
1003
- category: "correctness", // "correctness" | "style" | "security"
1004
-
1005
- check(context: LintContext): LintDiagnostic[] {
1006
- const { sourceFile } = context;
1007
- const diagnostics: LintDiagnostic[] = [];
1008
-
1009
- function visit(node: ts.Node): void {
1010
- // Walk the AST looking for violations
1011
- if (ts.isCallExpression(node)) {
1012
- // Inspect arguments, report diagnostics
1013
- }
1014
- ts.forEachChild(node, visit);
1015
- }
1016
-
1017
- visit(sourceFile);
1018
- return diagnostics;
1019
- },
1020
- };
1021
- \`\`\`
1022
-
1023
- ## Example: API Gateway timeout (WAW012)
698
+ The advanced example includes a full custom rule implementation:
1024
699
 
1025
- The advanced example includes a rule that flags Lambda API composites with \`timeout > 29\` — API Gateway's synchronous limit:
700
+ {{file:advanced/src/lint/api-timeout.ts}}
1026
701
 
1027
- \`\`\`typescript
1028
- const API_FACTORIES = new Set(["LambdaApi", "SecureApi", "HighMemoryApi"]);
1029
-
1030
- export const apiTimeoutRule: LintRule = {
1031
- id: "WAW012",
1032
- severity: "error",
1033
- category: "correctness",
1034
-
1035
- check(context: LintContext): LintDiagnostic[] {
1036
- // Walks AST for calls to API factory functions,
1037
- // inspects the timeout property, reports if > 29
1038
- },
1039
- };
1040
- \`\`\`
702
+ The \`check\` function receives a \`LintContext\` containing the TypeScript \`sourceFile\` and returns an array of diagnostics with file, line, column, and message.
1041
703
 
1042
704
  ## Registering custom rules
1043
705
 
1044
706
  Add a \`chant.config.ts\` to your project:
1045
707
 
1046
- \`\`\`typescript
1047
- export default {
1048
- lint: {
1049
- extends: ["@intentius/chant/lint/presets/strict"],
1050
- rules: {
1051
- COR004: "off", // disable a built-in rule
1052
- },
1053
- plugins: ["./lint/api-timeout.ts"], // load custom rules
1054
- },
1055
- };
1056
- \`\`\`
708
+ {{file:advanced/src/chant.config.ts}}
1057
709
 
1058
710
  The \`plugins\` array accepts relative paths. Each plugin module should export a \`LintRule\` object.`,
1059
711
  },
@@ -1077,9 +729,8 @@ bun test # runs the example's tests
1077
729
 
1078
730
  \`\`\`
1079
731
  src/
1080
- ├── _.ts # Barrel — re-exports lexicon + auto-discovers siblings
1081
732
  ├── defaults.ts # Shared config: encryption, versioning, public access block
1082
- ├── data-bucket.ts # S3 bucket using barrel defaults
733
+ ├── data-bucket.ts # S3 bucket using shared defaults
1083
734
  ├── logs-bucket.ts # S3 bucket for access logs
1084
735
  ├── role.ts # IAM role with Lambda assume-role policy
1085
736
  └── handler.ts # Lambda function referencing role and bucket
@@ -1087,28 +738,12 @@ src/
1087
738
 
1088
739
  **Patterns demonstrated:**
1089
740
 
1090
- 1. **Barrel file** — \`_.ts\` re-exports the AWS lexicon and creates the \`$\` proxy for cross-file references
1091
- 2. **Shared defaults** — \`defaults.ts\` exports reusable property objects (\`encryptionDefault\`, \`publicAccessBlock\`) that other files reference via \`_.$\`
1092
- 3. **Cross-resource references** — \`_.$.dataBucket.arn\` in \`handler.ts\` serializes to \`Fn::GetAtt\` in the template
741
+ 1. **Direct imports** — lexicon types come from \`@intentius/chant-lexicon-aws\`, sibling exports are imported from the file that defines them
742
+ 2. **Shared defaults** — \`defaults.ts\` exports reusable property objects (\`bucketEncryption\`, \`publicAccessBlock\`) that other files import directly
743
+ 3. **Cross-resource references** — \`dataBucket.arn\` in \`handler.ts\` serializes to \`Fn::GetAtt\` in the template
1093
744
  4. **Intrinsics** — \`Sub\` tagged templates with pseudo-parameters for dynamic naming
1094
745
 
1095
- \`\`\`typescript
1096
- // handler.ts — Lambda function referencing other resources
1097
- import * as _ from "./_";
1098
-
1099
- const lambdaCode = { zipFile: "exports.handler = async () => ({ statusCode: 200 });" };
1100
-
1101
- export const handler = new _.Function({
1102
- functionName: _.Sub\`\${_.AWS.StackName}-handler\`,
1103
- handler: "index.handler",
1104
- runtime: "nodejs20.x",
1105
- role: _.$.functionRole.arn, // → Fn::GetAtt
1106
- code: lambdaCode,
1107
- environment: {
1108
- variables: { BUCKET_ARN: _.$.dataBucket.arn }, // → Fn::GetAtt
1109
- },
1110
- });
1111
- \`\`\`
746
+ {{file:getting-started/src/handler.ts}}
1112
747
 
1113
748
  ## Advanced
1114
749
 
@@ -1116,7 +751,6 @@ export const handler = new _.Function({
1116
751
 
1117
752
  \`\`\`
1118
753
  src/
1119
- ├── _.ts # Barrel + re-exports Composite from core
1120
754
  ├── chant.config.ts # Lint config: strict preset + custom plugin
1121
755
  ├── defaults.ts # Encryption, versioning, access block, Lambda trust policy
1122
756
  ├── data-bucket.ts # S3 bucket
@@ -1144,10 +778,8 @@ The example produces 10 CloudFormation resources: 1 S3 bucket + 3 composites ×
1144
778
 
1145
779
  \`\`\`
1146
780
  src/
1147
- ├── _.ts # Parent barrel
1148
781
  ├── app.ts # Lambda function (references network outputs)
1149
782
  └── network/ # Child project (nested stack)
1150
- ├── _.ts # Child barrel
1151
783
  ├── vpc.ts # VPC, subnet, internet gateway, route table
1152
784
  ├── security.ts # Security group for Lambda
1153
785
  └── outputs.ts # stackOutput() declarations
@@ -1155,39 +787,14 @@ src/
1155
787
 
1156
788
  **Patterns demonstrated:**
1157
789
 
1158
- 1. **Child project** — \`network/\` is a separate project directory with its own barrel, resources, and \`stackOutput()\` exports
790
+ 1. **Child project** — \`network/\` is a separate project directory with its own resources and \`stackOutput()\` exports
1159
791
  2. **Cross-stack references** — \`app.ts\` accesses \`network.outputs.subnetId\` and \`network.outputs.lambdaSgId\`, which serialize to \`Fn::GetAtt\` on the parent's \`AWS::CloudFormation::Stack\` resource
1160
792
  3. **Multi-file output** — build produces \`template.json\` (parent) and \`network.template.json\` (child)
1161
793
  4. **TemplateBasePath** — auto-generated parameter for configuring child template URLs per environment
1162
794
 
1163
- \`\`\`typescript
1164
- // network/outputs.ts — child declares what the parent can reference
1165
- import * as _ from "./_";
1166
- import { stackOutput } from "@intentius/chant";
1167
-
1168
- export const vpcId = stackOutput(_.$.vpc.vpcId, { description: "VPC ID" });
1169
- export const subnetId = stackOutput(_.$.subnet.subnetId, { description: "Public subnet ID" });
1170
- export const lambdaSgId = stackOutput(_.$.lambdaSg.groupId, { description: "Lambda security group ID" });
1171
- \`\`\`
795
+ {{file:nested-stacks/src/network/outputs.ts}}
1172
796
 
1173
- \`\`\`typescript
1174
- // app.ts — parent references child project
1175
- import * as _ from "./_";
1176
-
1177
- const network = _.nestedStack("network", import.meta.dir + "/network");
1178
-
1179
- export const handler = new _.Function({
1180
- functionName: _.Sub\`\${_.AWS.StackName}-handler\`,
1181
- runtime: "nodejs20.x",
1182
- handler: "index.handler",
1183
- role: _.Ref("LambdaExecutionRole"),
1184
- code: { zipFile: "exports.handler = async () => ({ statusCode: 200 });" },
1185
- vpcConfig: {
1186
- subnetIds: [network.outputs.subnetId],
1187
- securityGroupIds: [network.outputs.lambdaSgId],
1188
- },
1189
- });
1190
- \`\`\`
797
+ {{file:nested-stacks/src/app.ts}}
1191
798
 
1192
799
  See [Nested Stacks](./nested-stacks) for the full guide.`,
1193
800
  },