AWS Provider
@rank-lang/plugin-aws is a first-party provider package for AWS-backed getters and mutations. It registers the aws namespace and ships ordinary Rank source modules alongside provider exports, so you can import typed helpers such as DynamoDB::createClient and DynamoDB::Table<...> directly from the provider package.
Like other providers, it is not built into the compiler. Register it in your project's manifest and Rank will materialize it through the same cache and lockfile flow used for registry-backed dependencies.
Start here if you want typed AWS reads and explicit write plans from Rank. For runnable setups, see Secrets-Backed Config, LocalStack Bootstrap, DynamoDB Report, and S3 Logs.
Install
Register the package in rank.toml:
manifestVersion = 1
[package]
name = "aws-example"
version = "0.1.0"
source = "src"
[providers]
aws = { registry = "npm", package = "@rank-lang/plugin-aws", version = "0.1.0" }
The package exposes three public modules:
use aws::{ DynamoDB, S3, SecretsManager }
For provider registration rules, registry configuration, and host security defaults, see the manifest reference.
Current Surface
@rank-lang/plugin-aws currently provides:
DynamoDB::ItemandDynamoDB::QuerygettersS3::Object<T>andSecretsManager::Secret<T>gettersDynamoDB::Put,DynamoDB::Update, andS3::Putmutation exports- provider-owned Rank modules for
aws::DynamoDB,aws::S3, andaws::SecretsManager - typed helper aliases such as
Table<T, PK>,UpdatePatch<Table>, andcreateClient(...)
The provider runtime memoizes live AWS SDK clients per provider session. The createClient(...) helpers themselves return plain serializable config objects, so they remain safe to use in pure preparation and serve-time setup flows.
Quick Start
Create typed client configs in ordinary Rank code:
use aws::{ DynamoDB, S3, SecretsManager }
dynamoClient: aws::DynamoDB::ClientConfig = DynamoDB::createClient({
region: `us-east-1`,
})
s3Client: aws::S3::ClientConfig = S3::createClient({
region: `us-east-1`,
})
secretsClient: aws::SecretsManager::ClientConfig = SecretsManager::createClient({
region: `us-east-1`,
})
These values are serializable configs, not live SDK clients. The live client instances exist only inside the admitted provider runtime.
Getter Example
use aws::{ DynamoDB, S3, SecretsManager }
User = Object {
userId: string,
email: string,
status: `active` | `inactive`,
}
UsersTable = DynamoDB::Table<User, `userId`>
dynamoClient: aws::DynamoDB::ClientConfig = DynamoDB::createClient({
region: `us-east-1`,
})
{ item, metadata } = DynamoDB::Item<UsersTable> {
client: dynamoClient,
tableName: `Users`,
key: {
userId: `123`,
},
}
Additional getter notes:
DynamoDB::Query<Resource>uses explicittargetmetadata and narrowsindexNamebased on the selected table or index resource.S3::Object<T>reads UTF-8 JSON objects.SecretsManager::Secret<T>currently supportsformat: stringandformat: json.
Declaring Effects
use aws::DynamoDB::{ Table, UpdatePatch }
use std::Mutation
User = Object {
userId: string,
email: string,
status: `active` | `inactive`,
}
UsersTable = Table<User, `userId`>
UserPatch = UpdatePatch<UsersTable>
dynamoClient: aws::DynamoDB::ClientConfig = aws::DynamoDB::createClient({
region: `us-east-1`,
})
s3Client: aws::S3::ClientConfig = aws::S3::createClient({
region: `us-east-1`,
})
putUser: Mutation::Effect<User> = aws::DynamoDB::Put<UsersTable> {
client: dynamoClient,
tableName: `Users`,
}
activateUser: Mutation::Effect<UserPatch> = aws::DynamoDB::Update<UsersTable, UserPatch> {
client: dynamoClient,
tableName: `Users`,
key: {
userId: `123`,
},
}
uploadUser: Mutation::Effect<User> = aws::S3::Put<User> {
client: s3Client,
bucket: `profiles`,
key: `users/123.json`,
}
created = putUser({
userId: `123`,
email: `alice@example.com`,
status: `inactive`,
})
activated = activateUser({
status: `active`,
})
uploaded = uploadUser({
userId: created.userId,
email: created.email,
status: created.status,
})
Fulfillment returns the declared payload value rather than the provider receipt. Receipt metadata is still retained for commit summaries and Mutation::commit(...) projections.
Complete Project Example
The earlier snippet shows how AWS-backed effects are declared and fulfilled, but it still does not commit anything by itself. A complete runnable project also needs:
- provider registration in
rank.toml - host admission for
networkplus the exact mutation export names you call - a top-level
pub mainthat returnsMutation::commit(...)
rank.toml:
manifestVersion = 1
[package]
name = "aws-mutation-example"
version = "0.1.0"
source = "src"
[providers]
aws = { registry = "npm", package = "@rank-lang/plugin-aws", version = "0.1.0" }
[security]
allow-provider-capabilities = ["network"]
allow-provider-mutation-exports = [
"aws::DynamoDB::Put",
]
src/main.rank:
use aws::DynamoDB::{ Table }
use std::Mutation
User = Object {
userId: string,
email: string,
status: `active` | `inactive`,
}
UsersTable = Table<User, `userId`>
dynamoClient: aws::DynamoDB::ClientConfig = aws::DynamoDB::createClient({
region: `us-east-1`,
})
putUser: Mutation::Effect<User> = aws::DynamoDB::Put<UsersTable> {
client: dynamoClient,
tableName: `Users`,
}
plan = Mutation::plan({
created: putUser({
userId: `123`,
email: `alice@example.com`,
status: `active`,
}),
})
pub main = ||
Mutation::commit(
plan,
|report| {
status: report.status,
created: report.operations.created,
},
)
This example is structurally complete. To run it against real AWS or LocalStack, the target Users table must exist and the credentials or explicit endpoints passed to createClient(...) must be valid for that environment.
Running rank src/main.rank now evaluates the program, commits the plan, and prints the projected commit result as YAML by default. Pass --format json if you want JSON instead. rank check still only type-checks the graph and does not perform the write.
Commit Timing
Provider mutators are intentionally explicit about when I/O happens:
- declaring
putUser: Mutation::Effect<User>is static - calling
putUser({...})fulfills the payload and returns a commit-localUservalue Mutation::plan(...)stays pure and only records required operations- the actual AWS write happens only when a host realizes the top-level
Mutation::commit(...)descriptor
That explicit commit boundary is what lets Rank keep ordinary evaluation deterministic while still making writes typed, host-admitted, and projectable into ordinary output.
For the language-level mutation flow, see Mutations.
Emit And Serve
The same top-level Mutation::commit(...) descriptor can also be returned from HTTP::Response.body. That means AWS mutation outcomes can flow into CLI output, manifest emission, or serve responses without manually unpacking provider receipts.
use std::HTTP
use std::Mutation
use std::Runtime
use aws::DynamoDB::{ Table, createClient }
HealthRoute = HTTP::Route {
method: `GET`,
path: `/health`
}
Routes = HealthRoute
Entry = Object {
id: string,
active: bool,
}
UsersTable = Table<Entry, `id`>
dynamoClient: aws::DynamoDB::ClientConfig = createClient({
region: `us-east-1`,
})
putUser: Mutation::Effect<Entry> = aws::DynamoDB::Put<UsersTable> {
client: dynamoClient,
tableName: `Users`,
}
pub main = |req: Runtime::ExecutionContext<Routes>| {
status: 200,
body: Mutation::commit(
Mutation::plan({
createdUser: putUser({ id: `1`, active: true })
}),
|report| {
commitStatus: report.status,
createdUser: report.operations.createdUser,
}
)
}
For more on output shapes, see Output and manifests. For serve-specific runtime behavior, see Server.
Security And Operations
This provider is intentionally explicit about host admission:
- getter and mutation exports require the
networkprovider capability - mutation exports are separately default-denied and must be explicitly allowlisted by exact name
- the host does not currently add AWS-specific table, bucket, or secret allowlists on top of IAM
Typical host security config:
[security]
allow-provider-capabilities = ["network"]
allow-provider-mutation-exports = [
"aws::DynamoDB::Put",
"aws::DynamoDB::Update",
"aws::S3::Put",
]
IAM remains the real resource boundary. Use least-privilege policies for the tables, buckets, and secrets your program touches.
If credentials enter through Env<T>, destructure them through @sensitive bindings so they stay redacted in diagnostics:
use std::Env
SysEnvironment = Object {
AWS_ACCESS_KEY_ID: string,
AWS_SECRET_ACCESS_KEY: string,
...: string,
}
@sensitive
{ AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } = Env<SysEnvironment> {}
Typed Helpers
The provider-owned source modules expose additional schema helpers beyond the runtime exports.
DynamoDB
Table<T, PK, SK?>declares a base table schemaGSI<BaseTable, Name, IndexTable>andLSI<BaseTable, Name, IndexTable>declare query-only secondary-index schemasKeyInput<Table>derives the exact key object from the table schemaUpdatePatch<Table>is shallow and excludes declared key fieldsQueryInput<Resource>specializes table, GSI, and LSI query inputs
Receipts And Metadata
- getter results return request metadata structures
DynamoDB::PutReceiptandDynamoDB::UpdateReceiptincluderequestIdand optional consumed-capacity summariesS3::PutReceiptincludesrequestIdand optionaleTag/versionId
LocalStack And Development
The package keeps a pinned LocalStack harness in packages/plugins/aws/test/localstack/docker-compose.yml for DynamoDB, S3, and Secrets Manager.
Typical workflow from the repo root:
npm run localstack:up -w @rank-lang/plugin-aws
npm run test:localstack -w @rank-lang/plugin-aws
npm run localstack:down -w @rank-lang/plugin-aws
General package development commands:
npm run build -w @rank-lang/plugin-aws
npm run typecheck -w @rank-lang/plugin-aws
npm run test -w @rank-lang/plugin-aws
Known v1 Limitations
S3::Object<T>andS3::Put<T>are JSON-only in v1SecretsManager::Secret<T>supportsSecretStringonly;SecretBinaryis rejectedDynamoDB::Update<Table, Patch>currently lowers shallowSETupdates onlyDynamoDB::Query<Resource>uses native DynamoDB expression strings rather than a typed DSL- resource-scoped host allowlists for AWS tables, buckets, and secrets are still deferred
For provider packaging and publication workflows, see the provider authoring guide.