// STRYKE-AWS — AWS CLIENT FOR STRYKE // S3 + DYNAMODB + SQS + LAMBDA + STS + SNS + SSM + SECRETS + SES + CLOUDWATCH + LOGS + EC2 + KMS + IAM + KINESIS + ECR + STEP FUNCTIONS

stryke package · cdylib libstryke_aws.{dylib,so} · dlopen'd in-process on use AWS · opt-in (kept out of stryke core) · 17 services

Report GitHub Issues
// Color scheme

>_STRYKE-AWS

The cloud, one stryke pipe away. AWS client for stryke across 17 services — S3, DynamoDB, SQS, Lambda, STS, SNS, SSM Parameter Store, Secrets Manager, SES, CloudWatch, CloudWatch Logs, EC2, KMS, IAM, Kinesis, ECR, and Step Functions. Opt-in package, kept out of the stryke core binary so the daily-driver install stays slim. The 17 SDKs combined are too much to bake into stryke core, so they ship as a Rust cdylib that stryke dlopens in-process on first use AWS.

Contents

Install

# from a release (no rustc on the consumer machine)
s pkg install -g github.com/MenkeTechnologies/stryke-aws

# from a local checkout
cd ~/projects/stryke-aws
cargo build --release            # produces target/release/libstryke_aws.{dylib,so}
s pkg install -g .               # cdylib lands in ~/.stryke/store/aws@<version>/

# one-liner
make install

The cdylib is dlopened in-process on first use AWS — no helper-binary fork per call (the v1 helper-binary model was replaced in v0.2.0). Any stryke script that declares use AWS (or a submodule like use AWS::S3) resolves the package automatically. Credentials and region come from the standard AWS chain (env vars, ~/.aws/config|credentials, IMDS) — same as the aws CLI.

Quick start: use AWS

identity checkp to_json AWS::STS::caller_identity()
list S3 keysval @keys = AWS::S3::ls "s3://my-bucket/prefix/", delimiter => "/"
get an objectp AWS::S3::get "s3://my-bucket/hello.txt"
put an objectAWS::S3::put "s3://my-bucket/hello.txt", data => "hi"
DynamoDB itemAWS::Dynamo::put "users", { id => "u-42", name => "alice" }
send SQS messageAWS::SQS::send $queue_url, "payload"
invoke Lambda + captureval $reply = AWS::Lambda::call "my-fn", { hello => "world" }
publish to SNSAWS::SNS::publish "alert", topic_arn => $arn
read a parameterp AWS::SSM::get "/app/db/url"
fetch a secretp AWS::Secrets::get "prod/api-key"
LocalStack endpoint overrideAWS::S3::ls "s3://...", endpoint => "http://localhost:4566"

Every public fn accepts per-call connection overrides as named options — region, profile, and endpoint (for LocalStack / MinIO):

AWS::S3::ls "s3://...",
    region   => "us-west-2",
    profile  => "prod",
    endpoint => "http://localstack:4566"

The full surface is mirrored below and in the README "API reference" section.

Configuration

SourceEffect
standard AWS chainCredentials + region from env vars, ~/.aws/config|credentials, IMDS — same resolution order as the aws CLI
region => "..."Per-call region override; a separate SdkConfig is cached per region
profile => "..."Per-call named profile from ~/.aws/config
endpoint => "..."Per-call endpoint override (LocalStack, MinIO, VPC endpoints)
$ENV{AWS_ENDPOINT_URL}Global default endpoint for all calls

API reference

Each row is a public stryke fn. %opts on any fn carries the named connection overrides above plus the per-fn options noted. Responses are plain stryke values (scalars / arrayrefs / hashrefs). Deferred ops die with a message naming the cdylib export that needs adding.

use AWS — plumbing

FnReturns / notes
AWS::version()cdylib package version string
AWS::ping(%opts)STS-backed connectivity probe

Plus the flat AWS::<service>_<op> fns that the namespaced wrappers below delegate to.

use AWS::S3

FnReturns / notes
S3::ls $uri, %opts@entries — {type=>"object", key, size, etag, last_modified, storage_class} or {type=>"prefix", key} when delimiter is set
S3::get $uri, %opts$body (or $path when output=>"PATH")
S3::put $uri, %opts\%resp — data=>$bytes | input=>"PATH"
S3::head $uri, %opts\%resp (content_length, content_type, etag, …)
S3::rm $uri, %opts\%resp
S3::buckets %opts@buckets
S3::versions $uri, %opts\%resp — { versions, delete_markers }
S3::location $uri, %opts\%resp — { bucket, location_constraint, region }
S3::tags $uri, %opts\%tags — { key => value }
S3::mb $uri, %opts\%resp — CreateBucket; { bucket, location, created }
S3::rb $uri, %opts1 | 0 — DeleteBucket (bucket must be empty)
S3::presign $uri, %optsdies — deferred in the cdylib

use AWS::Dynamo

FnReturns / notes
Dynamo::get $table, $key, %opts\%item | undef
Dynamo::put $table, $item, %opts{ ok: 1 }
Dynamo::delete $table, $key, %opts1 | 0
Dynamo::query $table, %opts@items — opts: key_condition, values, names, filter, index, limit
Dynamo::scan $table, %opts@items — opts: filter, values, limit
Dynamo::describe $table, %opts\%info — status, item_count, key_schema, arn
Dynamo::tables %opts@names
Dynamo::transact %opts$count — opts: puts, deletes; atomic
Dynamo::ttl $table, %opts\%info — { status, attribute_name }
Dynamo::create $table, $hash_key, %opts\%resp — CreateTable (on-demand); opts: hash_key_type, range_key, range_key_type
Dynamo::drop $table, %opts\%resp — DeleteTable; { table, status, deleted }
Dynamo::batch_write $table, $rows, %optsdies — deferred in the cdylib
Dynamo::scan_stream $table, %optsdies — deferred in the cdylib

query/scan take a DynamoDB expression plus a values hashref of :placeholder => value bindings (and optional names for #alias). Nested maps/lists round-trip recursively; binary attributes come back as "base64:…".

use AWS::SQS

FnReturns / notes
SQS::send $queue, $body, %opts\%resp — opts: delay_seconds, dedup_id, group_id
SQS::receive $queue, %opts@messages — opts: max, wait, visibility
SQS::delete $queue, $receipt, %opts{ ok: 1 }
SQS::list %opts@urls (prefix filter deferred)
SQS::purge $queue, %opts1 | 0 — delete all messages
SQS::attrs $queue, %opts\%attributes — GetQueueAttributes (All)
SQS::set_attrs $queue, $attributes, %opts\%resp — SetQueueAttributes
SQS::change_visibility $queue, $receipt, $timeout, %opts\%resp
SQS::pump $queue, %opts$count — callback + auto-delete on success

use AWS::Lambda

FnReturns / notes
Lambda::invoke $name, $payload, %opts\%resp — { function, status_code, result }
Lambda::call $name, $payload, %opts$result — just .result (undef unless status_code == 200)
Lambda::list %opts@functions
Lambda::get $name, %opts\%resp — GetFunction: { runtime, handler, arn, memory_size, timeout, … }

invocation_type => "event" (fire-and-forget) is deferred in the cdylib.

use AWS::STS

FnReturns / notes
STS::caller_identity %opts{ account, arn, user_id }
STS::assume_role $role_arn, %opts\%creds — opts: session_name; temp credentials

use AWS::SNS

FnReturns / notes
SNS::topics %opts@topic_arns
SNS::create $name, %opts$topic_arn
SNS::publish $message, %opts$message_id — opts: topic_arn | target_arn | phone_number, subject
SNS::subscribe $topic_arn, $protocol, $endpoint, %opts$subscription_arn
SNS::unsubscribe $subscription_arn, %opts1 | 0
SNS::subscriptions $topic_arn, %opts@{ {subscription_arn, protocol, endpoint, owner} }
SNS::delete $topic_arn, %opts1 | 0 — DeleteTopic

use AWS::SSM — Parameter Store

FnReturns / notes
SSM::get $name, %opts$value — opts: with_decryption
SSM::put $name, $value, %opts$version — opts: type (String|StringList|SecureString), overwrite
SSM::by_path $path, %opts@{ {name, value} } — opts: recursive, with_decryption
SSM::delete $name, %opts1 | 0
SSM::get_many $names_or_aref, %opts\%resp — GetParameters (≤10): { parameters, invalid_parameters }

use AWS::Secrets — Secrets Manager

FnReturns / notes
Secrets::get $secret_id, %opts$secret_string
Secrets::create $name, $secret_string, %opts{ arn, name }
Secrets::put $secret_id, $secret_string, %opts$version_id
Secrets::list %opts@{ {name, arn} }

use AWS::SES — email (v2)

FnReturns / notes
SES::send $from, $to_or_aref, %opts$message_id — opts: subject, body, html

use AWS::CloudWatch

FnReturns / notes
CloudWatch::put $namespace, $metric_name, $value, %opts1 — opts: unit
CloudWatch::list %opts@{ {namespace, metric_name, dimensions} } — opts: namespace, metric_name

use AWS::Logs — CloudWatch Logs

FnReturns / notes
Logs::groups %opts@groups — DescribeLogGroups; opts: prefix
Logs::create $name, %opts1 — CreateLogGroup
Logs::filter $log_group, %opts@events — opts: filter_pattern, start_time, end_time, limit
Logs::events $log_group, $log_stream, %opts@events — opts: limit, start_from_head
Logs::put $log_group, $log_stream, $events, %opts$next_token — PutLogEvents; events: [{message, timestamp(ms)}]; opts: sequence_token

use AWS::EC2

FnReturns / notes
EC2::instances %opts@instances — DescribeInstances; opts: instance_ids
EC2::start $ids, %opts@changes — StartInstances
EC2::stop $ids, %opts@changes — StopInstances; opts: force
EC2::security_groups %opts@groups — opts: group_ids
EC2::vpcs %opts@vpcs — opts: vpc_ids
EC2::reboot $ids, %opts@ids — RebootInstances

use AWS::KMS — Key Management Service

FnReturns / notes
KMS::keys %opts@{ {key_id, key_arn} } — ListKeys
KMS::describe $key_id, %opts\%info — DescribeKey
KMS::encrypt $key_id, $plaintext, %opts$ciphertext (base64)
KMS::decrypt $ciphertext, %opts$plaintext — opts: key_id
KMS::data_key $key_id, %opts{ key_id, plaintext, ciphertext } — opts: key_spec (default AES_256)

use AWS::IAM

FnReturns / notes
IAM::users %opts@users — ListUsers
IAM::roles %opts@roles — ListRoles
IAM::user %opts\%user — GetUser; opts: user_name
IAM::role_policies $role_name, %opts@{ {policy_name, policy_arn} }
IAM::role $role_name, %opts\%role — GetRole; { role_name, role_id, arn, path, description, max_session_duration }

use AWS::Kinesis

FnReturns / notes
Kinesis::list %opts@stream_names — ListStreams
Kinesis::describe $stream_name, %opts\%info — DescribeStream
Kinesis::put $stream_name, $partition_key, $data, %opts\%resp — PutRecord

use AWS::ECR — Elastic Container Registry

FnReturns / notes
ECR::repositories %opts@repos — DescribeRepositories; opts: repository_names
ECR::images $repository_name, %opts@{ {image_digest, image_tag} } — ListImages

use AWS::StepFunctions

FnReturns / notes
StepFunctions::list %opts@{ {name, arn, type, creation_date} } — ListStateMachines
StepFunctions::start $state_machine_arn, %opts{ execution_arn, start_date } — opts: name, input
StepFunctions::describe $execution_arn, %opts\%info — DescribeExecution

Flat extras on use AWS

FnReturns / notes
AWS::s3_copy_object $src_bucket, $src_key, $bucket, $key, %opts\%resp
AWS::s3_delete_objects $bucket, \@keys, %opts@deleted — batch delete (≤1000)
AWS::ddb_update_item $table, \%key, \%updates, %opts1 | 0 — SET each attr
AWS::ddb_batch_get_item $table, \@keys, %opts@items — ≤100 keys
AWS::ddb_batch_write_item $table, %opts$count — opts: puts (≤25), deletes

DynamoDB type encoding

DynamoDB items are plain JSON in and out — no AttributeValue wrappers. The cdylib maps plain JSON to DynamoDB AttributeValue automatically:

JSONAttributeValue
nullNULL(true)
boolBOOL
numberN (stringified to preserve precision)
stringS (or B when the string is "base64:...")
arrayL (heterogeneous)
objectM

Set types (SS / NS / BS) round-trip out as JSON arrays. On the write path, pass them as an array (stored as L), or extend the cdylib export list if you need a typed set.

LocalStack / MinIO

val %ls = (endpoint => "http://localhost:4566", region => "us-east-1")
AWS::S3::buckets(%ls) |> ep
AWS::SQS::list(%ls)   |> ep

$ENV{AWS_ENDPOINT_URL} works as a global default endpoint for every call.

Pure helpers (no AWS calls)

These open no client — pure string parsing / validation, so they run with no credentials. Available on use AWS.

FnReturns
AWS::parse_arn($arn){ partition, service, region, account_id, resource, resource_type, resource_id }
AWS::build_arn(%opts)$arn — parts → ARN; inverse of parse_arn
AWS::parse_s3_uri($uri){ bucket, key }
AWS::build_s3_uri($b, $k?)$uri — inverse of parse_s3_uri
AWS::s3_uri_to_arn($uri, $partition?){ arn, bucket, key } — s3://b/k → arn:aws:s3:::b/k
AWS::arn_to_s3_uri($arn){ uri, bucket, key } — inverse of s3_uri_to_arn
AWS::valid_bucket_name($n){ name, valid, reason }
AWS::valid_s3_key($key){ key, valid, reason, bytes } — non-empty, ≤1024 UTF-8 bytes
AWS::valid_sqs_queue_name($n){ name, valid, reason, fifo } — ≤80 chars, FIFO ends .fifo
AWS::valid_account_id($id){ account_id, valid, reason } — exactly 12 decimal digits
AWS::valid_arn($arn){ arn, valid, reason } — non-throwing structure check
AWS::arn_matches($pattern, $arn){ pattern, arn, matches } — IAM policy resource matching (* / ?)
AWS::partition_for_region($r){ region, partition } — cn-*→aws-cn, us-gov-*→aws-us-gov, …
AWS::valid_region($r){ region, valid, partition, reason } — botocore regionRegex per partition
AWS::dns_suffix_for_partition($p){ partition, dns_suffix } — aws→amazonaws.com, aws-cn→amazonaws.com.cn, …
AWS::service_endpoint($svc, $region){ service, region, partition, dns_suffix, endpoint, url }
AWS::parse_service_endpoint($endpoint)inverse of service_endpoint; host or URL → parts
AWS::s3_object_url($bucket, $region, $key?){ url, bucket, region, partition, host } — virtual-hosted https URL
AWS::parse_s3_url($url)inverse of s3_object_url; virtual-hosted + path styles
AWS::region_for_az($az){ az, region, zone_letter } — us-east-1a → us-east-1

Tests

cargo test                                          # compiles, no live calls
s test t/                                           # creds-aware end-to-end

# Opt into per-service round-trips with a writable resource:
export STRYKE_AWS_TEST_BUCKET=my-test-bucket
export STRYKE_AWS_TEST_TABLE=stryke-aws-demo        # PK `id: S`
export STRYKE_AWS_TEST_QUEUE=https://sqs.us-east-1.amazonaws.com/.../my-q
s test t/

The suite skips cleanly when the cdylib isn't installed, when credentials are missing, or when the per-service env vars are unset. The dev workflow targets — make (release build), make debug, make test, make install, make clean — wrap the same steps.

Why a package, not a builtin

The official aws-sdk-rust crates pull in tokio, hyper, rustls, and a fat chain of smithy / signing / endpoint-resolution support code. The 17 SDKs combined are too much to bake into stryke core. This package ships them once, opt-in.

The stryke side is a thin .stk wrapper that calls aws__* FFI symbols on the cdylib; the heavy code lives in libstryke_aws.{dylib,so}, dlopened in-process on first use AWS. Core stryke is never linked against this package's deps.

FFI layer

Each AWS::* wrapper builds a JSON args dict and calls a sibling aws__* symbol resolved out of libstryke_aws.{dylib,so}, listed in the [ffi] exports table in stryke.toml. The cdylib is dlopened in-process on first use AWS (via stryke's pkg::commands::try_load_ffi_for resolver hook); a shared tokio runtime + a per-region aws_config::SdkConfig cache is held in OnceCell — no fork-per-call, no full IMDS/SSO/env creds chain on each call. Responses are JSON; errors come back as {"error": "<msg>"} and the wrapper dies with the message.

Layout

stryke-aws/
├── Cargo.toml             # cdylib crate manifest (crate-type = ["cdylib"], publish = false)
├── src/
│   └── lib.rs            # cdylib — aws__* extern "C" exports
├── lib/                   # stryke .stk wrappers — AWS, AWS::S3, AWS::Dynamo, … (17 services)
├── stryke.toml            # stryke package manifest ([ffi] exports table)
├── t/                     # zunit-style tests
├── examples/              # runnable .stk examples
├── Makefile               # `make install` builds + installs
└── docs/                  # this site (GitHub Pages)

Sibling packages

Part of the stryke package family. Browse the others via the MenkeTechnologiesMeta umbrella repo: