Skip to content

Resource Naming

chant discovers resources by scanning exports from your src/ files. The export name becomes the logical resource name — it’s what you see in lint output, build logs, and cross-file imports. Choosing clear, consistent names makes your project easier to navigate and maintain.

Exports must use camelCase — the lint rule COR005 enforces this automatically.

// ✅ correct
export const appBucket = new Bucket({ ... });
export const taskQueue = new Queue({ ... });
// ❌ rejected by COR005
export const AppBucket = new Bucket({ ... });
export const app_bucket = new Bucket({ ... });

Name exports after what the resource does, not what it is. Suffix with the resource’s role to distinguish resources of the same type.

// ✅ clear purpose
export const uploadBucket = new Bucket({ ... });
export const taskQueue = new Queue({ ... });
export const apiRole = new Role({ ... });
// ❌ ambiguous
export const bucket1 = new Bucket({ ... });
export const myQueue = new Queue({ ... });

When multiple resources share naming prefixes or tags, extract them into a const object. Template literals keep physical names consistent across the stack.

src/naming-shared-config.ts
import { Bucket, Queue } from "@intentius/chant-lexicon-aws";
const app = { name: "myapp", team: "platform" } as const;
export const sharedDataBucket = new Bucket({
BucketName: `${app.name}-data`,
Tags: [{ Key: "Team", Value: app.team }],
PublicAccessBlockConfiguration: {
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: true,
RestrictPublicBuckets: true,
},
});
export const taskQueue = new Queue({
QueueName: `${app.name}-tasks`,
Tags: [{ Key: "Team", Value: app.team }],
});

Because the config object is as const, chant’s evaluator can resolve every value at build time.

TypeScript’s native constructs replace the need for framework-level count or for_each. Use Array.map to create a set of resources, then destructure the result into named exports:

src/naming-sequential.ts
import { Bucket } from "@intentius/chant-lexicon-aws";
const envs = ["dev", "staging", "prod"] as const;
const buckets = envs.map(
(env) =>
new Bucket({
BucketName: `myapp-${env}-data`,
PublicAccessBlockConfiguration: {
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: true,
RestrictPublicBuckets: true,
},
})
);
export const [devData, stagingData, prodData] = buckets;

Each destructured export (devData, stagingData, prodData) is a distinct named resource that can be referenced from other files via import.

Two builds of the same stack produce identical physical names, so deploying both into one account collides. When a project declares more than one environment, put the environment identity into the names. There are two working shapes.

Declare an env build parameter, bind the ownership marker to it, and interpolate params.env into every name that must be unique per instance:

chant.config.ts
export default {
ownership: { stack: "billing", env: { param: "env" } },
buildParams: {
env: { type: "string", default: "dev" },
},
environments: ["dev", "staging", "prod"],
} satisfies ChantConfig;
src/storage.ts
import { params } from "@intentius/chant/params";
export const uploadBucket = new Bucket({
bucketName: `billing-${params.env}-uploads`,
});

params.env resolves before any file is imported, so the template literal folds to a plain string — billing-staging-uploads — with no new machinery. Build once per environment:

Terminal window
chant build --param env=staging
chant build --param env=prod

The two builds yield disjoint physical names and disjoint ownership markers (chant.intentius.io/env follows the same parameter — see Build-Time Parameters), so --owned filtering, drift, and delete stay scoped to one instance each.

The alternative keeps every environment in a single build and hand-threads the name per instance — instantiate the same composite once per environment, each call passing its own name (web-dev, web-staging, web-prod). This is the layered configuration pattern: a base config, per-environment overrides via object spread, one manifest containing all instances.

Choose per-env builds when environments deploy independently (separate accounts, separate pipelines, short-lived copies); choose the all-in-one shape when one apply should converge every environment at once.

chant build --env <name> sets the CHANT_ENV environment variable before the project is discovered. A build parameter can declare that variable as its fallback, which turns --env into a validated way to supply params.env:

chant.config.ts
buildParams: {
env: { type: "string", default: "dev", env: "CHANT_ENV" },
},
environments: ["dev", "staging", "prod"],

Now chant build --env staging and chant build --param env=staging bind the same value. The --env route has one extra property: the CLI rejects an --env value that is not among the declared environments, so a typo like --env stagign fails before anything builds. --param env= takes precedence over the CHANT_ENV fallback when both are supplied, per the normal precedence rules.

Group related resources in the same file and keep unrelated ones separate. The lint rule COR009 warns when a single file exports eight or more resources — a signal that it may be doing too much.

A typical layout:

src/
storage.ts # buckets, tables
compute.ts # functions, containers
networking.ts # VPCs, subnets, security groups
iam.ts # roles, policies