Services
MiniStack emulates each service’s behaviour, not just its API: S3 keeps the objects you put, SQS delivers and redelivers messages, and Kinesis stores records on shards.
These work as they do in MiniStack. Pocket Region’s tests have run each one here and found nothing that behaves differently. Each links to an example you can run below, and where Pocket Region runs part of a service its own way, such as MiniStack’s background threads, its section says so.
The rest:
| Service | Status |
|---|---|
| Lambda | Node functions, run by Pocket Region in Node and in a page. See Lambda |
| Step Functions | A Pass state machine is tested. The rest works as it does in MiniStack, untested here |
| RDS, ElastiCache, ECS, EKS, Batch, OpenSearch, Athena | Stubs: they answer, but nothing runs behind them. Databases, caches, and clusters report ready with no endpoint, tasks and jobs report running or done, and Athena returns made-up rows |
| The rest of MiniStack’s services | Not run here yet. They may work, but nothing here checks them |
Examples
Section titled “Examples”The examples are written as they would be for AWS, in JavaScript with clients built from {} and
in Python with boto3. The JavaScript and Python buttons under an example switch every example on
this page, and the choice is remembered. Clicking Run executes an example here in your browser
tab, against a region that’s emptied before each run, as Examples in your docs
describes. To run a JavaScript one in your own code, give its clients a region first:
import { clientConfig, createRegion } from 'pocket-region/browser';
const region = await createRegion();const config = clientConfig(region);// then new S3Client(config) wherever an example has new S3Client({})Without that, a client built from {} outside this page reaches AWS with whatever credentials
your environment has. A Python example reaches a region from your own code over HTTP: serve
one from Node and build each client with its URL as endpoint_url.
Uploading an object sends a notification to a queue. MiniStack sends notifications from a
background thread, which Pocket Region runs inside the upload request instead, so the notification
is already queued when PutObject returns. MiniStack’s S3 page
lists what it supports.
import { S3Client, CreateBucketCommand, PutBucketNotificationConfigurationCommand, PutObjectCommand } from '@aws-sdk/client-s3';import { SQSClient, CreateQueueCommand, GetQueueAttributesCommand, ReceiveMessageCommand } from '@aws-sdk/client-sqs';
const s3 = new S3Client({});const sqs = new SQSClient({});
const { QueueUrl } = await sqs.send(new CreateQueueCommand({ QueueName: 'uploads' }));const { Attributes } = await sqs.send(new GetQueueAttributesCommand({ QueueUrl, AttributeNames: ['QueueArn'] }));
await s3.send(new CreateBucketCommand({ Bucket: 'photos' }));await s3.send(new PutBucketNotificationConfigurationCommand({ Bucket: 'photos', NotificationConfiguration: { QueueConfigurations: [{ QueueArn: Attributes.QueueArn, Events: ['s3:ObjectCreated:*'] }], },}));await s3.send(new PutObjectCommand({ Bucket: 'photos', Key: 'cat.jpg', Body: 'meow' }));
const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({ QueueUrl, MaxNumberOfMessages: 10 }));for (const record of Messages.flatMap((message) => JSON.parse(message.Body).Records ?? [])) { console.log(record.eventName, record.s3.bucket.name, record.s3.object.key);}import json
import boto3
s3 = boto3.client('s3')sqs = boto3.client('sqs')
queue_url = sqs.create_queue(QueueName='uploads')['QueueUrl']queue_arn = sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=['QueueArn'])['Attributes']['QueueArn']
s3.create_bucket(Bucket='photos')s3.put_bucket_notification_configuration( Bucket='photos', NotificationConfiguration={ 'QueueConfigurations': [{'QueueArn': queue_arn, 'Events': ['s3:ObjectCreated:*']}], },)s3.put_object(Bucket='photos', Key='cat.jpg', Body=b'meow')
messages = sqs.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=10).get('Messages', [])for message in messages: for record in json.loads(message['Body']).get('Records', []): print(record['eventName'], record['s3']['bucket']['name'], record['s3']['object']['key'])Messages sent to a queue come back from a receive. MiniStack’s SQS page lists what it supports.
import { SQSClient, CreateQueueCommand, SendMessageCommand, ReceiveMessageCommand } from '@aws-sdk/client-sqs';
const sqs = new SQSClient({});
const { QueueUrl } = await sqs.send(new CreateQueueCommand({ QueueName: 'orders' }));await sqs.send(new SendMessageCommand({ QueueUrl, MessageBody: 'order 41' }));await sqs.send(new SendMessageCommand({ QueueUrl, MessageBody: 'order 42' }));
const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({ QueueUrl, MaxNumberOfMessages: 10 }));console.log(Messages.map((message) => message.Body));import boto3
sqs = boto3.client('sqs')
queue_url = sqs.create_queue(QueueName='orders')['QueueUrl']sqs.send_message(QueueUrl=queue_url, MessageBody='order 41')sqs.send_message(QueueUrl=queue_url, MessageBody='order 42')
messages = sqs.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=10).get('Messages', [])print([message['Body'] for message in messages])DynamoDB
Section titled “DynamoDB”An item whose TTL has passed is removed within about a minute, when MiniStack’s reaper next runs, as scheduled work describes. MiniStack’s DynamoDB page lists what it supports.
import { DynamoDBClient, CreateTableCommand, PutItemCommand, ScanCommand } from '@aws-sdk/client-dynamodb';
const dynamodb = new DynamoDBClient({});
await dynamodb.send(new CreateTableCommand({ TableName: 'sessions', KeySchema: [{ AttributeName: 'id', KeyType: 'HASH' }], AttributeDefinitions: [{ AttributeName: 'id', AttributeType: 'S' }], BillingMode: 'PAY_PER_REQUEST',}));
await dynamodb.send(new PutItemCommand({ TableName: 'sessions', Item: { id: { S: 'alice' } } }));await dynamodb.send(new PutItemCommand({ TableName: 'sessions', Item: { id: { S: 'bob' } } }));
const { Items } = await dynamodb.send(new ScanCommand({ TableName: 'sessions' }));console.log('sessions:', Items.map((item) => item.id.S).sort());import boto3
dynamodb = boto3.client('dynamodb')
dynamodb.create_table( TableName='sessions', KeySchema=[{'AttributeName': 'id', 'KeyType': 'HASH'}], AttributeDefinitions=[{'AttributeName': 'id', 'AttributeType': 'S'}], BillingMode='PAY_PER_REQUEST',)
dynamodb.put_item(TableName='sessions', Item={'id': {'S': 'alice'}})dynamodb.put_item(TableName='sessions', Item={'id': {'S': 'bob'}})
items = dynamodb.scan(TableName='sessions')['Items']print('sessions:', sorted(item['id']['S'] for item in items))A message published to a topic arrives in a subscribed queue. MiniStack’s SNS page lists what it supports.
import { SNSClient, CreateTopicCommand, SubscribeCommand, PublishCommand } from '@aws-sdk/client-sns';import { SQSClient, CreateQueueCommand, GetQueueAttributesCommand, ReceiveMessageCommand } from '@aws-sdk/client-sqs';
const sns = new SNSClient({});const sqs = new SQSClient({});
const { QueueUrl } = await sqs.send(new CreateQueueCommand({ QueueName: 'order-emails' }));const { Attributes } = await sqs.send(new GetQueueAttributesCommand({ QueueUrl, AttributeNames: ['QueueArn'] }));
const { TopicArn } = await sns.send(new CreateTopicCommand({ Name: 'orders' }));await sns.send(new SubscribeCommand({ TopicArn, Protocol: 'sqs', Endpoint: Attributes.QueueArn }));await sns.send(new PublishCommand({ TopicArn, Subject: 'placed', Message: 'order 42' }));
const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({ QueueUrl }));const { Subject, Message } = JSON.parse(Messages[0].Body);console.log(Subject, Message);import json
import boto3
sns = boto3.client('sns')sqs = boto3.client('sqs')
queue_url = sqs.create_queue(QueueName='order-emails')['QueueUrl']queue_arn = sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=['QueueArn'])['Attributes']['QueueArn']
topic_arn = sns.create_topic(Name='orders')['TopicArn']sns.subscribe(TopicArn=topic_arn, Protocol='sqs', Endpoint=queue_arn)sns.publish(TopicArn=topic_arn, Subject='placed', Message='order 42')
messages = sqs.receive_message(QueueUrl=queue_url)['Messages']notification = json.loads(messages[0]['Body'])print(notification['Subject'], notification['Message'])EventBridge
Section titled “EventBridge”Only the event that matches the rule reaches its queue. MiniStack’s EventBridge page lists what it supports.
import { EventBridgeClient, PutRuleCommand, PutTargetsCommand, PutEventsCommand } from '@aws-sdk/client-eventbridge';import { SQSClient, CreateQueueCommand, GetQueueAttributesCommand, ReceiveMessageCommand } from '@aws-sdk/client-sqs';
const events = new EventBridgeClient({});const sqs = new SQSClient({});
const { QueueUrl } = await sqs.send(new CreateQueueCommand({ QueueName: 'large-orders' }));const { Attributes } = await sqs.send(new GetQueueAttributesCommand({ QueueUrl, AttributeNames: ['QueueArn'] }));
await events.send(new PutRuleCommand({ Name: 'large-orders', EventPattern: JSON.stringify({ source: ['shop'], detail: { total: [{ numeric: ['>', 100] }] } }),}));await events.send(new PutTargetsCommand({ Rule: 'large-orders', Targets: [{ Id: 'queue', Arn: Attributes.QueueArn }] }));await events.send(new PutEventsCommand({ Entries: [ { Source: 'shop', DetailType: 'order placed', Detail: JSON.stringify({ total: 20 }) }, { Source: 'shop', DetailType: 'order placed', Detail: JSON.stringify({ total: 250 }) }, ],}));
const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({ QueueUrl, MaxNumberOfMessages: 10 }));console.log(Messages.map((message) => JSON.parse(message.Body).detail));import json
import boto3
events = boto3.client('events')sqs = boto3.client('sqs')
queue_url = sqs.create_queue(QueueName='large-orders')['QueueUrl']queue_arn = sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=['QueueArn'])['Attributes']['QueueArn']
events.put_rule( Name='large-orders', EventPattern=json.dumps({'source': ['shop'], 'detail': {'total': [{'numeric': ['>', 100]}]}}),)events.put_targets(Rule='large-orders', Targets=[{'Id': 'queue', 'Arn': queue_arn}])events.put_events( Entries=[ {'Source': 'shop', 'DetailType': 'order placed', 'Detail': json.dumps({'total': 20})}, {'Source': 'shop', 'DetailType': 'order placed', 'Detail': json.dumps({'total': 250})}, ])
messages = sqs.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=10).get('Messages', [])print([json.loads(message['Body'])['detail'] for message in messages])Secrets Manager
Section titled “Secrets Manager”A second value becomes the secret’s current version. MiniStack’s Secrets Manager page lists what it supports.
import { SecretsManagerClient, CreateSecretCommand, PutSecretValueCommand, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const secrets = new SecretsManagerClient({});
await secrets.send(new CreateSecretCommand({ Name: 'db-password', SecretString: 'first' }));await secrets.send(new PutSecretValueCommand({ SecretId: 'db-password', SecretString: 'second' }));
const { SecretString, VersionStages } = await secrets.send(new GetSecretValueCommand({ SecretId: 'db-password' }));console.log(SecretString, VersionStages);import boto3
secrets = boto3.client('secretsmanager')
secrets.create_secret(Name='db-password', SecretString='first')secrets.put_secret_value(SecretId='db-password', SecretString='second')
secret = secrets.get_secret_value(SecretId='db-password')print(secret['SecretString'], secret['VersionStages'])SSM Parameter Store
Section titled “SSM Parameter Store”Parameters listed by path, with a SecureString decrypted. MiniStack’s SSM page lists what it supports.
import { SSMClient, PutParameterCommand, GetParametersByPathCommand } from '@aws-sdk/client-ssm';
const ssm = new SSMClient({});
await ssm.send(new PutParameterCommand({ Name: '/app/api-key', Value: 'abc', Type: 'SecureString' }));await ssm.send(new PutParameterCommand({ Name: '/app/region', Value: 'us-east-1', Type: 'String' }));
const { Parameters } = await ssm.send(new GetParametersByPathCommand({ Path: '/app', WithDecryption: true }));console.log(Parameters.map(({ Name, Value }) => `${Name} = ${Value}`));import boto3
ssm = boto3.client('ssm')
ssm.put_parameter(Name='/app/api-key', Value='abc', Type='SecureString')ssm.put_parameter(Name='/app/region', Value='us-east-1', Type='String')
parameters = ssm.get_parameters_by_path(Path='/app', WithDecryption=True)['Parameters']print([f"{parameter['Name']} = {parameter['Value']}" for parameter in parameters])Text encrypted with a new key decrypts back. MiniStack’s KMS page lists what it supports.
import { KMSClient, CreateKeyCommand, EncryptCommand, DecryptCommand } from '@aws-sdk/client-kms';
const kms = new KMSClient({});
const { KeyMetadata } = await kms.send(new CreateKeyCommand({}));const { CiphertextBlob } = await kms.send(new EncryptCommand({ KeyId: KeyMetadata.KeyId, Plaintext: new TextEncoder().encode('hello'),}));const { Plaintext } = await kms.send(new DecryptCommand({ CiphertextBlob }));console.log(new TextDecoder().decode(Plaintext));import boto3
kms = boto3.client('kms')
key_id = kms.create_key()['KeyMetadata']['KeyId']ciphertext = kms.encrypt(KeyId=key_id, Plaintext=b'hello')['CiphertextBlob']print(kms.decrypt(CiphertextBlob=ciphertext)['Plaintext'].decode())CloudWatch Logs
Section titled “CloudWatch Logs”A filter pattern finds the matching log event. MiniStack’s CloudWatch Logs page lists what it supports.
import { CloudWatchLogsClient, CreateLogGroupCommand, CreateLogStreamCommand, PutLogEventsCommand, FilterLogEventsCommand } from '@aws-sdk/client-cloudwatch-logs';
const logs = new CloudWatchLogsClient({});
await logs.send(new CreateLogGroupCommand({ logGroupName: '/app/web' }));await logs.send(new CreateLogStreamCommand({ logGroupName: '/app/web', logStreamName: 'instance-1' }));await logs.send(new PutLogEventsCommand({ logGroupName: '/app/web', logStreamName: 'instance-1', logEvents: [ { timestamp: Date.now(), message: 'GET / 200' }, { timestamp: Date.now(), message: 'GET /cart 500' }, ],}));
const { events } = await logs.send(new FilterLogEventsCommand({ logGroupName: '/app/web', filterPattern: '500' }));console.log(events.map((event) => event.message));import time
import boto3
logs = boto3.client('logs')
logs.create_log_group(logGroupName='/app/web')logs.create_log_stream(logGroupName='/app/web', logStreamName='instance-1')now = int(time.time() * 1000)logs.put_log_events( logGroupName='/app/web', logStreamName='instance-1', logEvents=[ {'timestamp': now, 'message': 'GET / 200'}, {'timestamp': now, 'message': 'GET /cart 500'}, ],)
events = logs.filter_log_events(logGroupName='/app/web', filterPattern='500')['events']print([event['message'] for event in events])Kinesis
Section titled “Kinesis”A record put on a stream reads back from its shard. MiniStack’s Kinesis page lists what it supports.
import { KinesisClient, CreateStreamCommand, PutRecordCommand, ListShardsCommand, GetShardIteratorCommand, GetRecordsCommand } from '@aws-sdk/client-kinesis';
const kinesis = new KinesisClient({});
await kinesis.send(new CreateStreamCommand({ StreamName: 'clicks', ShardCount: 1 }));await kinesis.send(new PutRecordCommand({ StreamName: 'clicks', PartitionKey: 'user-1', Data: new TextEncoder().encode('click'),}));
const { Shards } = await kinesis.send(new ListShardsCommand({ StreamName: 'clicks' }));const { ShardIterator } = await kinesis.send(new GetShardIteratorCommand({ StreamName: 'clicks', ShardId: Shards[0].ShardId, ShardIteratorType: 'TRIM_HORIZON',}));const { Records } = await kinesis.send(new GetRecordsCommand({ ShardIterator }));console.log(Records.map((record) => new TextDecoder().decode(record.Data)));import boto3
kinesis = boto3.client('kinesis')
kinesis.create_stream(StreamName='clicks', ShardCount=1)kinesis.put_record(StreamName='clicks', PartitionKey='user-1', Data=b'click')
shard_id = kinesis.list_shards(StreamName='clicks')['Shards'][0]['ShardId']shard_iterator = kinesis.get_shard_iterator( StreamName='clicks', ShardId=shard_id, ShardIteratorType='TRIM_HORIZON',)['ShardIterator']records = kinesis.get_records(ShardIterator=shard_iterator)['Records']print([record['Data'].decode() for record in records])Scheduled work
Section titled “Scheduled work”MiniStack runs scheduled EventBridge rules, EventBridge Scheduler schedules, and the DynamoDB TTL
reaper on background threads. Pocket Region runs each thread as a task that sleeps as MiniStack’s
code says, so they keep MiniStack’s timing: a one-time schedule in the past fires within ten
seconds, and an expired TTL is removed within a minute. rate() and cron() wait real time, so a
rate(1 minute) rule first fires a minute after it’s created. Schedules that invoke Lambda are
untested.