belt 0.3.2 → 0.3.4

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.

Potentially problematic release.


This version of belt might be problematic. Click here for more details.

@@ -0,0 +1,141 @@
1
+ # Deployment
2
+
3
+ Belt deploys serverless applications to AWS using Terraform. The CLI wraps
4
+ `terraform init/plan/apply` with conventions for environment management,
5
+ Lambda packaging, and pre-deploy backups.
6
+
7
+ ## Quick Deploy
8
+
9
+ ```bash
10
+ belt deploy <env> # init → plan → apply (interactive)
11
+ belt deploy prod --auto # skip confirmation prompt
12
+ belt deploy prod --skip-backup # skip pre-deploy backup
13
+ belt deploy --backup-only # create recovery point without deploying
14
+ ```
15
+
16
+ ## First-Time Setup
17
+
18
+ ```bash
19
+ # 1. Create S3 bucket for Terraform state
20
+ belt setup state
21
+
22
+ # 2. Scaffold an environment
23
+ belt generate environment dev01
24
+
25
+ # 3. Generate DynamoDB table definitions from schema
26
+ belt setup tables dev01
27
+
28
+ # 4. Initialize and deploy
29
+ belt deploy dev01
30
+ ```
31
+
32
+ ## Environment Structure
33
+
34
+ Each environment lives in `infrastructure/<env>/`:
35
+
36
+ ```
37
+ infrastructure/
38
+ ├── modules/
39
+ │ └── main/ # Shared Terraform module
40
+ ├── dev01/
41
+ │ ├── main.tf # Module reference + provider
42
+ │ ├── variables.tf # Variable declarations
43
+ │ ├── terraform.tfvars # Environment-specific values
44
+ │ ├── backend.tf # S3 state backend
45
+ │ └── outputs.tf # Exported values
46
+ └── prod/
47
+ └── ...
48
+ ```
49
+
50
+ ## What Gets Deployed
51
+
52
+ The Conveyor Belt Terraform provider reads your Ruby DSL and creates:
53
+
54
+ 1. **API Gateway** — HTTP API with routes matching your DSL
55
+ 2. **Lambda functions** — packaged Ruby code (one per `gateway`/`function` block)
56
+ 3. **IAM roles** — least-privilege policies for DynamoDB table access
57
+ 4. **CloudWatch logs** — log groups for each Lambda
58
+ 5. **DynamoDB tables** — from your schema definition
59
+ 6. **Custom domain** — if configured (Route53 + ACM certificate)
60
+
61
+ ## Lambda Packaging
62
+
63
+ Belt packages the `lambda/` directory plus vendored gems. For `path:` gems
64
+ (local development), Belt materializes them into `vendor/cache` automatically.
65
+
66
+ The Lambda entry point is specified in `config/lambda/<name>.yml`:
67
+
68
+ ```yaml
69
+ handler: lambda/<name>.lambda_handler
70
+ runtime: ruby3.3
71
+ timeout: 30
72
+ memory: 256
73
+ environment:
74
+ ENVIRONMENT: ${var.environment}
75
+ ```
76
+
77
+ ## Route Manifests
78
+
79
+ Before deploying, generate the route manifest used at runtime:
80
+
81
+ ```bash
82
+ belt routes --namespace api
83
+ # → writes lambda/lib/routes/api_routes.rb
84
+ ```
85
+
86
+ This is typically done automatically by `belt deploy`.
87
+
88
+ ## Terraform Commands
89
+
90
+ Belt wraps Terraform with environment awareness:
91
+
92
+ ```bash
93
+ belt init <env> # terraform init with correct backend
94
+ belt plan <env> # terraform plan
95
+ belt apply <env> # terraform apply
96
+ belt destroy <env> # terraform destroy (careful!)
97
+ belt output <env> # terraform output
98
+ ```
99
+
100
+ Or use `belt deploy <env>` which runs init → plan → apply in sequence.
101
+
102
+ ## Pre-Deploy Backups
103
+
104
+ Configure in `infrastructure/<env>/belt.rb`:
105
+
106
+ ```ruby
107
+ Belt.configure do |config|
108
+ config.backups do
109
+ dynamodb :all
110
+ retention snapshots: 90
111
+ end
112
+ end
113
+ ```
114
+
115
+ Backups run automatically before each deploy (DynamoDB snapshots, Cognito
116
+ exports, S3 syncs). See `belt explain backups` for full documentation.
117
+
118
+ ## Environment Variables
119
+
120
+ Set `BELT_ENV` to avoid typing the environment every time:
121
+
122
+ ```bash
123
+ export BELT_ENV=dev01
124
+ belt deploy # uses BELT_ENV
125
+ belt deploy prod # explicit arg wins
126
+ ```
127
+
128
+ ## Frontend Deployment
129
+
130
+ For apps with a frontend:
131
+
132
+ ```bash
133
+ belt deploy frontend <env> # build + deploy to S3/CloudFront
134
+ belt frontend env <env> # generate .env from Terraform outputs
135
+ ```
136
+
137
+ ## See Also
138
+
139
+ - `belt explain routing` — how routes map to infrastructure
140
+ - `belt explain backups` — pre-deploy backup configuration
141
+ - `belt doctor` — check system dependencies before deploying
@@ -0,0 +1,128 @@
1
+ # Generators
2
+
3
+ Belt generators scaffold code and infrastructure for common patterns.
4
+ Run `belt generate --help` to see all available generators (built-in + plugins).
5
+
6
+ ## Built-in Generators
7
+
8
+ ### Resource (scaffold)
9
+
10
+ Creates model + controller + routes + schema entry:
11
+
12
+ ```bash
13
+ belt generate resource post title:string body:text user_id:string status:string
14
+ belt g resource comment body:text author:string post_id:string
15
+ ```
16
+
17
+ This is the most common generator — it sets up everything for a new REST endpoint.
18
+
19
+ ### Model
20
+
21
+ Creates just the model file + schema entry:
22
+
23
+ ```bash
24
+ belt generate model post title:string body:text user_id:string
25
+ belt g model payment amount:number currency:string
26
+ ```
27
+
28
+ ### Controller
29
+
30
+ Creates just the controller:
31
+
32
+ ```bash
33
+ belt generate controller posts
34
+ belt g controller admin/users
35
+ ```
36
+
37
+ ### Environment
38
+
39
+ Scaffolds a new Terraform environment directory:
40
+
41
+ ```bash
42
+ belt generate environment staging
43
+ belt g environment prod
44
+ ```
45
+
46
+ ### Frontend
47
+
48
+ Adds a frontend framework to the project:
49
+
50
+ ```bash
51
+ belt generate frontend react
52
+ belt generate frontend vue
53
+ belt generate frontend svelte
54
+ ```
55
+
56
+ ### Views
57
+
58
+ Generates React pages for a resource's REST actions:
59
+
60
+ ```bash
61
+ belt generate views post title:string body:text status:string
62
+ belt g views comment body:text author:string
63
+ ```
64
+
65
+ ### Auth
66
+
67
+ Sets up Cognito authentication:
68
+
69
+ ```bash
70
+ belt generate auth
71
+ belt g auth --provider cognito
72
+ ```
73
+
74
+ ## Field Types
75
+
76
+ When specifying fields, use these types:
77
+
78
+ | Type | DynamoDB Type | Notes |
79
+ |------|---------------|-------|
80
+ | `string` | S | Default if no type given |
81
+ | `text` | S | Same as string (semantic hint) |
82
+ | `number` | N | Numeric values |
83
+ | `boolean` | BOOL | True/false |
84
+ | `list` | L | Array values |
85
+ | `map` | M | Nested objects |
86
+
87
+ ## Destroying Generated Code
88
+
89
+ Every generator has a matching destroy command:
90
+
91
+ ```bash
92
+ belt destroy resource post
93
+ belt destroy model comment
94
+ belt destroy controller admin/users
95
+ belt destroy environment staging
96
+ belt destroy frontend
97
+ belt destroy views post
98
+ ```
99
+
100
+ ## Plugin Generators
101
+
102
+ Gems that follow the Belt plugin contract are auto-discovered:
103
+
104
+ ```bash
105
+ belt generate messaging # from belt-messaging gem
106
+ belt generate pay # from belt-pay gem
107
+ belt generate --help # lists all available generators
108
+ ```
109
+
110
+ ## Generator Workflow (Common Pattern)
111
+
112
+ ```bash
113
+ # 1. Generate the resource
114
+ belt generate resource order item_id:string quantity:number total:number status:string
115
+
116
+ # 2. Generate DynamoDB table from schema
117
+ belt setup tables dev01
118
+
119
+ # 3. Deploy
120
+ belt deploy dev01
121
+ ```
122
+
123
+ ## See Also
124
+
125
+ - `belt explain models` — how generated models work
126
+ - `belt explain controllers` — how generated controllers work
127
+ - `belt explain routing` — how generated routes work
128
+ - `belt explain deployment` — deploying generated code
@@ -0,0 +1,105 @@
1
+ # Lambda Handler
2
+
3
+ `Belt::LambdaHandler` is the module you include in your Lambda entry point.
4
+ It provides the `lambda_handler` method that AWS Lambda invokes, wrapping
5
+ your application logic with observability, CORS, and error handling.
6
+
7
+ ## Basic Usage
8
+
9
+ ```ruby
10
+ require "belt"
11
+
12
+ include Belt::LambdaHandler
13
+
14
+ ROUTER = Belt::ActionRouter.new(routes: Routes::API, gateway: "api")
15
+
16
+ def execute(path:, body:, event:)
17
+ ROUTER.route(event: event, body: body)
18
+ end
19
+ ```
20
+
21
+ ## What It Does
22
+
23
+ When a request arrives, `lambda_handler` automatically:
24
+
25
+ 1. **Initializes observability** — structured logging + CloudWatch metrics
26
+ 2. **Handles OPTIONS preflight** — returns CORS headers immediately
27
+ 3. **Parses the request body** — JSON string → Ruby hash
28
+ 4. **Calls your `execute` method** — with `path:`, `body:`, and `event:`
29
+ 5. **Catches unhandled errors** — returns a CORS-enabled error response
30
+ 6. **Emits metrics** — request count, latency, error count via EMF
31
+
32
+ ## The `execute` Method
33
+
34
+ You must define `execute` in your Lambda file. It receives:
35
+
36
+ | Param | Description |
37
+ |-------|-------------|
38
+ | `path:` | The request path (e.g., `/posts/123`) |
39
+ | `body:` | Parsed request body (Hash or nil) |
40
+ | `event:` | Full API Gateway event (for headers, query params, auth context) |
41
+
42
+ Return value should be a response hash: `{ statusCode:, headers:, body: }`.
43
+ Typically you just call `ROUTER.route(...)` which returns the correct format.
44
+
45
+ ## Multiple Lambdas
46
+
47
+ If your app has multiple Lambda functions (via `function` in routes):
48
+
49
+ ```ruby
50
+ # lambda/worker.rb
51
+ require "belt"
52
+
53
+ include Belt::LambdaHandler
54
+
55
+ ROUTER = Belt::ActionRouter.new(routes: Routes::WORKER, gateway: "worker")
56
+
57
+ def execute(path:, body:, event:)
58
+ ROUTER.route(event: event, body: body)
59
+ end
60
+ ```
61
+
62
+ Each Lambda entry point gets its own route manifest and gateway name.
63
+
64
+ ## Configuration
65
+
66
+ Configure via `config/lambda/<name>.yml`:
67
+
68
+ ```yaml
69
+ handler: lambda/api.lambda_handler
70
+ runtime: ruby3.3
71
+ timeout: 30
72
+ memory: 256
73
+ layers:
74
+ - ${var.ruby_layer_arn}
75
+ environment:
76
+ ENVIRONMENT: ${var.environment}
77
+ APP_NAME: my-app
78
+ ERROR_NOTIFICATION_TOPIC_ARN: ${var.sns_topic_arn}
79
+ ```
80
+
81
+ ## Observability
82
+
83
+ The handler sets up `Belt::Observability::Logger` and `Belt::Observability::Metrics`
84
+ automatically. Use them anywhere in your app:
85
+
86
+ ```ruby
87
+ Belt::Observability::Logger.info("Order created", order_id: order.id)
88
+ Belt::Observability::Metrics.track_event("OrderCreated", model: "Order")
89
+ ```
90
+
91
+ ## Environment Variables
92
+
93
+ | Variable | Purpose |
94
+ |----------|---------|
95
+ | `ENVIRONMENT` | Controls error verbosity (`dev*`, `local`, `test` = verbose) |
96
+ | `BELT_METRICS_NAMESPACE` | CloudWatch namespace (default: `Belt`) |
97
+ | `ACTION` | Service name for logging |
98
+ | `ERROR_NOTIFICATION_TOPIC_ARN` | SNS topic for error alerts |
99
+ | `CORS_ALLOWED_ORIGINS` | Comma-separated allowed origins |
100
+
101
+ ## See Also
102
+
103
+ - `belt explain routing` — how ActionRouter dispatches requests
104
+ - `belt explain controllers` — how dispatched requests are handled
105
+ - `belt explain deployment` — Lambda packaging and configuration
@@ -0,0 +1,160 @@
1
+ # Models
2
+
3
+ Belt uses **ActiveItem** as its ORM for DynamoDB. Models inherit from
4
+ `ActiveItem::Base` (typically via an `ApplicationRecord` base class).
5
+
6
+ ## Basic Model
7
+
8
+ ```ruby
9
+ class Post < ApplicationRecord
10
+ attr_accessor :id, :user_id, :title, :body, :status, :created_at, :updated_at
11
+
12
+ validates :title, presence: true
13
+ validates :status, inclusion: { in: %w[draft published] }, allow_nil: true
14
+
15
+ before_create { self.id ||= SecureRandom.uuid }
16
+ before_save { self.updated_at = Time.now.iso8601 }
17
+ end
18
+ ```
19
+
20
+ ## Table Naming
21
+
22
+ Table names are derived from the environment:
23
+ `{APP_NAME}-{ENVIRONMENT}-{pluralized_model}`
24
+
25
+ Example: app "blog", environment "prod", model "Post" → `blog-prod-posts`
26
+
27
+ ## Schema Definition
28
+
29
+ Define table structure in `infrastructure/schema.tf.rb`:
30
+
31
+ ```ruby
32
+ Belt.application.schema.define do
33
+ model :post do
34
+ partition_key :id, :string
35
+ global_secondary_index :UserIndex, partition_key: :user_id
36
+ global_secondary_index :StatusIndex, partition_key: :status, sort_key: :created_at
37
+ end
38
+ end
39
+ ```
40
+
41
+ ## CRUD Operations
42
+
43
+ ### Create
44
+
45
+ ```ruby
46
+ post = Post.create!(title: "Hello", body: "World", user_id: "u-123")
47
+ # or
48
+ post = Post.new(title: "Hello")
49
+ post.save!
50
+ ```
51
+
52
+ ### Read
53
+
54
+ ```ruby
55
+ post = Post.find("post-id-123") # by primary key
56
+ posts = Post.all # scan (use sparingly)
57
+ posts = Post.where(user_id: "u-123", index: "UserIndex")
58
+ post = Post.find_by(user_id: "u-123", index: "UserIndex") # first match
59
+ ```
60
+
61
+ ### Update
62
+
63
+ ```ruby
64
+ post = Post.find("post-id-123")
65
+ post.update(title: "New Title")
66
+ # or
67
+ post.title = "New Title"
68
+ post.save!
69
+ ```
70
+
71
+ ### Delete
72
+
73
+ ```ruby
74
+ post = Post.find("post-id-123")
75
+ post.destroy
76
+ ```
77
+
78
+ ## Query Patterns
79
+
80
+ ### Using Indexes
81
+
82
+ ```ruby
83
+ # Query a GSI
84
+ Post.where(user_id: "u-123", index: "UserIndex")
85
+
86
+ # With sort key conditions
87
+ Post.where(
88
+ status: "published",
89
+ created_at: { gte: "2024-01-01" },
90
+ index: "StatusIndex"
91
+ )
92
+ ```
93
+
94
+ ### Count and Existence
95
+
96
+ ```ruby
97
+ Post.count # total items (scan)
98
+ Post.exists?("post-id-123") # check by primary key
99
+ ```
100
+
101
+ ## Validations
102
+
103
+ ActiveItem supports ActiveModel-style validations:
104
+
105
+ ```ruby
106
+ validates :title, presence: true
107
+ validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
108
+ validates :status, inclusion: { in: %w[active inactive] }
109
+ validates :age, numericality: { greater_than: 0 }
110
+ ```
111
+
112
+ ## Callbacks
113
+
114
+ ```ruby
115
+ before_create { self.id ||= SecureRandom.uuid }
116
+ before_save { self.updated_at = Time.now.iso8601 }
117
+ after_create { notify_subscribers }
118
+ before_destroy { cleanup_associations }
119
+ ```
120
+
121
+ ## Associations (Manual)
122
+
123
+ DynamoDB doesn't have joins. Model relationships manually:
124
+
125
+ ```ruby
126
+ class Post < ApplicationRecord
127
+ def comments
128
+ Comment.where(post_id: id, index: "PostIndex")
129
+ end
130
+
131
+ def author
132
+ User.find(user_id)
133
+ end
134
+ end
135
+ ```
136
+
137
+ ## Transactions
138
+
139
+ ```ruby
140
+ ActiveItem::Transaction.write do |tx|
141
+ tx.put(post)
142
+ tx.put(comment)
143
+ tx.delete(draft)
144
+ end
145
+ ```
146
+
147
+ ## Generating Models
148
+
149
+ ```bash
150
+ belt generate model post title:string body:text user_id:string
151
+ belt generate resource comment body:text author:string post_id:string
152
+ ```
153
+
154
+ The resource generator creates model + controller + routes + schema entry.
155
+
156
+ ## See Also
157
+
158
+ - `belt explain controllers` — using models in controllers
159
+ - `belt explain deployment` — how schema becomes infrastructure
160
+ - `belt explain queries` — advanced DynamoDB query patterns
@@ -0,0 +1,94 @@
1
+ # Observability
2
+
3
+ Belt provides structured logging and CloudWatch metrics out of the box.
4
+ These are initialized automatically by `Belt::LambdaHandler` — no setup required.
5
+
6
+ ## Logging
7
+
8
+ Access the logger from anywhere:
9
+
10
+ ```ruby
11
+ Belt::Observability::Logger.info("Order placed", order_id: "o-123", total: 49.99)
12
+ Belt::Observability::Logger.warn("Retry attempt", attempt: 3, service: "payments")
13
+ Belt::Observability::Logger.error("Payment failed", error: e.message, order_id: "o-123")
14
+ ```
15
+
16
+ Logs are structured JSON, compatible with CloudWatch Logs Insights:
17
+
18
+ ```json
19
+ {
20
+ "level": "INFO",
21
+ "message": "Order placed",
22
+ "order_id": "o-123",
23
+ "total": 49.99,
24
+ "timestamp": "2024-01-15T10:30:00Z",
25
+ "service": "api"
26
+ }
27
+ ```
28
+
29
+ ## Metrics
30
+
31
+ Track custom events via CloudWatch Embedded Metric Format (EMF):
32
+
33
+ ```ruby
34
+ Belt::Observability::Metrics.track_event("OrderCreated", model: "Order")
35
+ Belt::Observability::Metrics.track_event("PaymentProcessed", amount: 49.99)
36
+ ```
37
+
38
+ ### Built-in Metrics
39
+
40
+ The Lambda handler automatically emits:
41
+ - `RequestCount` — per invocation
42
+ - `ErrorCount` — on unhandled exceptions
43
+ - `Latency` — request duration in milliseconds
44
+
45
+ ### Namespace
46
+
47
+ Metrics are published under the namespace set by `BELT_METRICS_NAMESPACE`
48
+ (defaults to `Belt`). View them in CloudWatch → Metrics → Custom namespaces.
49
+
50
+ ## Error Alerting
51
+
52
+ Set `ERROR_NOTIFICATION_TOPIC_ARN` to an SNS topic ARN. Unhandled errors
53
+ will publish a notification with error details (message, backtrace, request context).
54
+
55
+ ```yaml
56
+ # config/lambda/api.yml
57
+ environment:
58
+ ERROR_NOTIFICATION_TOPIC_ARN: ${var.sns_topic_arn}
59
+ ```
60
+
61
+ ## CloudWatch Logs Insights
62
+
63
+ Query structured logs:
64
+
65
+ ```
66
+ fields @timestamp, message, order_id
67
+ | filter level = "ERROR"
68
+ | sort @timestamp desc
69
+ | limit 20
70
+ ```
71
+
72
+ ## Viewing Logs Locally
73
+
74
+ ```bash
75
+ belt logs api # tail logs for the "api" Lambda
76
+ belt logs api -f # follow (live tail)
77
+ belt logs api -s 30m # last 30 minutes
78
+ belt logs worker -e prod # specific environment
79
+ ```
80
+
81
+ ## Environment Variables
82
+
83
+ | Variable | Purpose |
84
+ |----------|---------|
85
+ | `BELT_METRICS_NAMESPACE` | CloudWatch metrics namespace (default: `Belt`) |
86
+ | `ACTION` | Service name in log entries (falls back to function name) |
87
+ | `ERROR_NOTIFICATION_TOPIC_ARN` | SNS topic for error alerts |
88
+ | `ENVIRONMENT` | Controls verbose errors (`dev*`/`local`/`test` = verbose) |
89
+
90
+ ## See Also
91
+
92
+ - `belt explain lambda_handler` — how observability is initialized
93
+ - `belt explain deployment` — configuring environment variables
94
+ - `belt logs --help` — full CLI options for log viewing