belt 0.4.5 → 0.4.6
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -1
- data/SKILL.md +144 -0
- data/lib/belt/version.rb +1 -1
- data/references/cli.md +76 -0
- data/references/controllers.md +73 -0
- data/references/deploy-and-ops.md +77 -0
- data/references/models-and-auth.md +71 -0
- data/references/plugins.md +64 -0
- data/references/routing.md +78 -0
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f29c42b3eb7869a90ab1a0a22d56fd6b1304dbd6e22f32503669b3c9b0974890
|
|
4
|
+
data.tar.gz: f313115ee77ceafb82a4c424c6721f6776f3b869c1b71cc1e458332bc16b53fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57150cb2d0934d723b6fafb9eaee652dc933c4522fcae787518d2e03990da13e1c9f229d498b990a86fb1daa90ec3916e4e049e411a418a4534e76f4274a658e
|
|
7
|
+
data.tar.gz: 8e2d42b8ba9417dacdf725a66cc0e0a0d37e3a7ae59c3085a50f40c7c1e38eb298be49fa0a0ac3d3489e445e3907d85174bc07f45959a385ccbe55a605a5016d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.4.6
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Agent Skill (`SKILL.md`) — installable via `npx skills add stowzilla/belt`.**
|
|
8
|
+
A root-level `SKILL.md` with trigger-rich frontmatter, a lean activation body
|
|
9
|
+
(core patterns + decision guide), and on-demand `references/` docs (CLI, routing,
|
|
10
|
+
controllers, models+auth, deploy/ops, plugins). A CI workflow validates the
|
|
11
|
+
frontmatter and that every reference link resolves.
|
|
12
|
+
- **`SKILL.md` + `references/` now ship inside the `belt` gem.** The gemspec
|
|
13
|
+
`files` manifest was extended so a `gem install belt` lands the skill on disk
|
|
14
|
+
alongside the runtime. The `skills` CLI resolves git repos and local paths (not
|
|
15
|
+
RubyGems), so a gem install won't *auto-register* the skill — but the files are
|
|
16
|
+
present and can be picked up via a local-path install
|
|
17
|
+
(`npx skills add <gem-install-dir>`), and belt.dev tooling can vendor them.
|
|
4
18
|
|
|
5
19
|
### Bug Fix
|
|
6
20
|
|
data/SKILL.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: belt
|
|
3
|
+
license: MIT
|
|
4
|
+
description: >-
|
|
5
|
+
Build, run, and deploy serverless Ruby apps on AWS Lambda with the Belt
|
|
6
|
+
framework (Rails-inspired). Use when working in a Belt app or the belt gem:
|
|
7
|
+
scaffolding apps/models/controllers/frontends, defining routes with the
|
|
8
|
+
Belt routing DSL, writing BeltController actions, modeling DynamoDB data
|
|
9
|
+
with ActiveItem, wiring Cognito auth, configuring the Lambda handler,
|
|
10
|
+
running `belt` CLI commands (new, generate, deploy, console, routes, logs,
|
|
11
|
+
server, setup, plugin, explain, db:copy/db:seed), Terraform via belt,
|
|
12
|
+
backups, observability, or authoring Belt plugins. Triggers on "belt new",
|
|
13
|
+
"belt deploy", "belt generate", "belt routes", "BeltController", "ActiveItem",
|
|
14
|
+
"Belt::LambdaHandler", "belt console", "belt server", "conveyor-belt". Do NOT
|
|
15
|
+
activate for physical belts, conveyor hardware, or unrelated Ruby web
|
|
16
|
+
frameworks like Rails/Sinatra outside a Belt project.
|
|
17
|
+
metadata:
|
|
18
|
+
author: stowzilla
|
|
19
|
+
version: "0.1.0"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Belt
|
|
23
|
+
|
|
24
|
+
Belt is a Rails-inspired framework for serverless Ruby on AWS Lambda. It ships a
|
|
25
|
+
runtime (`BeltController`, `Belt::LambdaHandler`, `Belt::ActionRouter`,
|
|
26
|
+
`Belt::Authentication`), a DynamoDB ORM (`ActiveItem`), logging/metrics
|
|
27
|
+
(`lambda_loadout`), a routing DSL consumed by the **conveyor-belt** Terraform
|
|
28
|
+
provider, and a `belt` CLI. Optional features (`belt-messaging`, `belt-pay`)
|
|
29
|
+
ship as separate plugin gems.
|
|
30
|
+
|
|
31
|
+
## First: orient yourself
|
|
32
|
+
|
|
33
|
+
Belt has an authoritative, in-repo docs system. **Use it before guessing.**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
belt explain <topic> # routing controllers models deployment generators
|
|
37
|
+
# lambda_handler observability console backups
|
|
38
|
+
# data_seeding plugins structure frontend authentication
|
|
39
|
+
belt --help # full command list
|
|
40
|
+
belt routes # list every endpoint (verb, path, controller#action)
|
|
41
|
+
belt doctor # check deps + AWS config
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- **Working in a Belt app?** Read the app's own `AGENTS.md` (scaffolded by `belt new`).
|
|
45
|
+
- **Working on the belt gem itself?** Read `AGENTS.md` + `README.md` at the gem root.
|
|
46
|
+
- The `belt explain` docs live at `lib/belt/docs/*.md` in the gem — the single
|
|
47
|
+
source of truth for CLI behavior, controller lifecycle, and routing.
|
|
48
|
+
|
|
49
|
+
## Decision guide
|
|
50
|
+
|
|
51
|
+
| I want to… | Do this |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Scaffold an app | `belt new <name> --frontend react` |
|
|
54
|
+
| Add a resource | `belt generate scaffold post title:string body:text` |
|
|
55
|
+
| See all routes | `belt routes` (add `-f json` for tooling) |
|
|
56
|
+
| Deploy to AWS | `belt deploy <env>` (`--auto` to skip prompt) |
|
|
57
|
+
| Poke at data live | `belt console <env>` |
|
|
58
|
+
| Run a local frontend | `belt server` (see references for env targeting) |
|
|
59
|
+
| Add Cognito auth | `belt generate auth` → `cognito_authenticatable` in a model |
|
|
60
|
+
| Build a plugin | `belt plugin new <name>` |
|
|
61
|
+
| Learn a concept | `belt explain <topic>` |
|
|
62
|
+
|
|
63
|
+
## Core patterns (memorize these)
|
|
64
|
+
|
|
65
|
+
**Controller** — assigns become the JSON body; explicit helpers win.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
class PostsController < BeltController::Base
|
|
69
|
+
before_action :authenticate_user!
|
|
70
|
+
|
|
71
|
+
def index
|
|
72
|
+
@posts = Post.where(user_id: current_user.id, index: "UserIndex")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def create
|
|
76
|
+
@post = Post.create!(params.require(:post).permit(:title, :body).to_h)
|
|
77
|
+
response_status :created # → 201 + { post: {...} }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def destroy
|
|
81
|
+
Post.find(params["id"]).destroy
|
|
82
|
+
head :no_content
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Model** — ActiveItem over DynamoDB.
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
class Post < ActiveItem::Base
|
|
91
|
+
self.primary_key = :id
|
|
92
|
+
attr_accessor :id, :user_id, :title, :body, :created_at
|
|
93
|
+
validates :title, presence: true
|
|
94
|
+
before_create { self.id ||= SecureRandom.uuid }
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Routes DSL** — `infrastructure/routes.tf.rb`. `gateway`/`function` pick the
|
|
99
|
+
Lambda; `namespace`/`scope` only affect paths + controller module.
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
Belt.application.routes.draw do
|
|
103
|
+
gateway :api, auth: :cognito do
|
|
104
|
+
resources :posts do
|
|
105
|
+
member { post :publish } # /posts/:post_id/publish
|
|
106
|
+
collection { get :recent } # /posts/recent
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Lambda entry point** — `Belt::LambdaHandler` gives observability, CORS
|
|
113
|
+
preflight, JSON parsing, and error wrapping for free.
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
require "belt"
|
|
117
|
+
include Belt::LambdaHandler
|
|
118
|
+
ROUTER = Belt::ActionRouter.new(routes: Routes::API, gateway: "api")
|
|
119
|
+
|
|
120
|
+
def execute(path:, body:, event:)
|
|
121
|
+
ROUTER.route(event: event, body: body)
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Golden rules for agents
|
|
126
|
+
|
|
127
|
+
1. **`belt explain <topic>` before inventing behavior.** The docs are canonical.
|
|
128
|
+
2. **`namespace`/`scope` never change which Lambda serves a route** — only
|
|
129
|
+
`gateway` and `function` do. Do not conflate them.
|
|
130
|
+
3. **Controllers need no registration.** Belt discovers them by convention.
|
|
131
|
+
4. **Runtime code stays in gems; generators copy only what the app must own.**
|
|
132
|
+
5. **Never commit secrets** (`.env`, AWS keys, real account IDs).
|
|
133
|
+
6. **`rubocop` + `rspec` must pass** before a PR on the belt gem.
|
|
134
|
+
|
|
135
|
+
## Deeper references
|
|
136
|
+
|
|
137
|
+
Load these only when the task needs them:
|
|
138
|
+
|
|
139
|
+
- [references/cli.md](references/cli.md) — every `belt` command, flags, env vars, Terraform shorthand
|
|
140
|
+
- [references/routing.md](references/routing.md) — full routing DSL, nested resources, request/response model inference
|
|
141
|
+
- [references/controllers.md](references/controllers.md) — callbacks, strong params, responses, error handling, formats
|
|
142
|
+
- [references/models-and-auth.md](references/models-and-auth.md) — ActiveItem + Cognito authentication
|
|
143
|
+
- [references/deploy-and-ops.md](references/deploy-and-ops.md) — deploy lifecycle, environments, backups, seeding, observability
|
|
144
|
+
- [references/plugins.md](references/plugins.md) — authoring Belt plugin gems and the GeneratorRegistry contract
|
data/lib/belt/version.rb
CHANGED
data/references/cli.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Belt CLI reference
|
|
2
|
+
|
|
3
|
+
Run `belt --help` for the live list, or `belt <command> --help` for a specific
|
|
4
|
+
command. `belt explain <topic>` gives conceptual docs. `BELT_ENV` sets the
|
|
5
|
+
default environment so you can omit the `<env>` argument.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
| Command | What it does |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `belt new <app> [--frontend react]` | Create a new Belt app. `-v` lists every created file. |
|
|
12
|
+
| `belt generate <thing> <name>` (alias `g`) | Generate `scaffold`, `model`, `controller`, `frontend`, `views`, `environment`, `dns`, `auth`, or a plugin generator. |
|
|
13
|
+
| `belt destroy <thing> <name>` (alias `d`) | Remove what `generate` created. |
|
|
14
|
+
| `belt routes [-g PATTERN] [-f json] [--namespace N]` | Show/inspect routes; generate Ruby route constants for the runtime router. |
|
|
15
|
+
| `belt contracts [-g PATTERN] [-f json]` | Show API request/response contracts. |
|
|
16
|
+
| `belt lambda-config [-e ENV] [-f json\|terraform]` | Show merged Lambda configuration. |
|
|
17
|
+
| `belt console [env]` (alias `c`) | Interactive IRB with the app booted. `--run "expr"` for runner mode. |
|
|
18
|
+
| `belt logs [lambda] [-f] [-s 5m] [-e env]` | Tail Lambda logs. |
|
|
19
|
+
| `belt tasks [-g PATTERN] [-a]` (alias `-T`) | List rake tasks. Any rake task can be run directly: `belt lambda:build_layer`. |
|
|
20
|
+
| `belt setup <state\|tables <env>\|frontend>` | Create S3 state bucket / generate DynamoDB tables / frontend infra. |
|
|
21
|
+
| `belt doctor` | Check system deps + AWS config. |
|
|
22
|
+
| `belt plugin new <name>` | Scaffold a Belt plugin gem. |
|
|
23
|
+
| `belt explain <topic>` | Explain a concept (see topic list below). |
|
|
24
|
+
| `belt deploy [env] [--auto] [--skip-backup] [--backup-only]` | Deploy to AWS (init → plan → apply, runs backups first if configured). |
|
|
25
|
+
| `belt deploy frontend <env> [--frontend NAME]` | Build + deploy frontend(s). |
|
|
26
|
+
| `belt dns <deploy\|add <env>\|show>` | Manage the root DNS zone. |
|
|
27
|
+
| `belt frontend <env <env>\|list>` | Write `<frontend>/.env` from TF outputs, or list frontends. |
|
|
28
|
+
| `belt server [--frontend NAME]` (alias `s`) | Start local dev server. |
|
|
29
|
+
| `belt db:copy <from> <to> [--force]` | Copy DynamoDB data between environments. |
|
|
30
|
+
| `belt db:seed [env] [--force]` | Run `config/seeds.rb` against an environment. |
|
|
31
|
+
| `belt version` | Show Belt version. |
|
|
32
|
+
|
|
33
|
+
### Terraform shorthand
|
|
34
|
+
|
|
35
|
+
`belt <action> [env]` maps to Terraform: `init`, `plan`, `apply`, `destroy`,
|
|
36
|
+
`output`. Example: `belt apply wups`, `belt output prod`.
|
|
37
|
+
|
|
38
|
+
> ⚠ `belt destroy` is ambiguous: `belt destroy <env>` runs terraform destroy,
|
|
39
|
+
> while `belt destroy scaffold post` removes generated code. Belt disambiguates
|
|
40
|
+
> by argument shape.
|
|
41
|
+
|
|
42
|
+
## `belt explain` topics
|
|
43
|
+
|
|
44
|
+
`routing`, `controllers`, `models`, `deployment`, `generators`,
|
|
45
|
+
`lambda_handler`, `observability`, `console`, `backups`, `data_seeding`,
|
|
46
|
+
`plugins`, `structure`, `frontend`, `authentication`.
|
|
47
|
+
|
|
48
|
+
## Standalone vs project commands
|
|
49
|
+
|
|
50
|
+
These run anywhere (no Belt project needed): `new`, `version`, `doctor`,
|
|
51
|
+
`explain`. All others chdir to the detected project root first.
|
|
52
|
+
|
|
53
|
+
## Environment variables
|
|
54
|
+
|
|
55
|
+
| Variable | Purpose |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `BELT_ENV` | Default environment for env-scoped commands |
|
|
58
|
+
| `ENVIRONMENT` | Verbose error responses (`dev*`, `local`, `test`) |
|
|
59
|
+
| `BELT_METRICS_NAMESPACE` | CloudWatch metrics namespace (default `Belt`) |
|
|
60
|
+
| `ACTION` | Service name for logging (falls back to function name) |
|
|
61
|
+
| `ERROR_NOTIFICATION_TOPIC_ARN` | SNS topic for error alerts |
|
|
62
|
+
| `CORS_ALLOWED_ORIGINS` | Comma-separated origins (overrides domain vars) |
|
|
63
|
+
| `CUSTOMER_APP_DOMAIN` / `OPS_APP_DOMAIN` | CORS domains |
|
|
64
|
+
|
|
65
|
+
## Common flows
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
belt new blog --frontend react
|
|
69
|
+
belt generate scaffold post title:string content:text
|
|
70
|
+
belt routes
|
|
71
|
+
belt deploy dev
|
|
72
|
+
belt deploy prod --auto
|
|
73
|
+
belt console prod --run "Post.count"
|
|
74
|
+
belt logs api -f -e prod
|
|
75
|
+
belt db:copy prod dev
|
|
76
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# BeltController
|
|
2
|
+
|
|
3
|
+
`BeltController::Base` gives Rails-like callbacks, strong params, response
|
|
4
|
+
helpers, and error handling. Run `belt explain controllers` for canonical docs.
|
|
5
|
+
|
|
6
|
+
## Implicit responses
|
|
7
|
+
|
|
8
|
+
Instance variables assigned in an action become the JSON body by default:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
def index
|
|
12
|
+
@posts = Post.all # → { "posts": [ ... ] }
|
|
13
|
+
end
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Explicit helpers always override implicit assigns.
|
|
17
|
+
|
|
18
|
+
## Callbacks
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
before_action :authenticate_user!
|
|
22
|
+
before_action :require_admin!, except: [:health]
|
|
23
|
+
skip_before_action :authenticate_user!, only: [:health]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Strong parameters
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
params.require(:user).permit(:name, :email, address: [:street, :city])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Response helpers
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
success_response({ id: "123" }) # 200 JSON + CORS
|
|
36
|
+
success_response({ id: "123" }, :created) # 201 (symbol or int)
|
|
37
|
+
error_response("Not found", :not_found) # 404 JSON error
|
|
38
|
+
error_response("Nope", :unprocessable_entity) # 422
|
|
39
|
+
html_response("<h1>Hi</h1>") # 200 HTML + CORS
|
|
40
|
+
head :no_content # 204 empty
|
|
41
|
+
response_status :created # 201 + implicit assigns
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Error handling
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
rescue_from MyError, with: :handle_it
|
|
48
|
+
|
|
49
|
+
def handle_it(exception, _context = {})
|
|
50
|
+
error_response(exception.message, 422)
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Default format (JSON vs HTML)
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
# App-wide (lambda/config/environment.rb)
|
|
58
|
+
Belt.configure { |c| c.default_format = :json } # default
|
|
59
|
+
|
|
60
|
+
# Per-controller
|
|
61
|
+
class PagesController < ApplicationController
|
|
62
|
+
self.default_format = :html # implicitly renders views/<controller>/<action>.html.erb
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- `:json` (default): assigns → `success_response({ ... })`.
|
|
67
|
+
- `:html`: Belt implicitly renders the ERB template. Missing template raises
|
|
68
|
+
`Belt::TemplateNotFound` (no silent JSON fallback).
|
|
69
|
+
|
|
70
|
+
## Controller discovery
|
|
71
|
+
|
|
72
|
+
No registration needed. Belt looks in the app namespace module first, then
|
|
73
|
+
`Belt.all_controller_paths`.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Deploy, environments, backups, seeding & observability
|
|
2
|
+
|
|
3
|
+
Run `belt explain deployment`, `belt explain backups`, `belt explain
|
|
4
|
+
data_seeding`, and `belt explain observability` for canonical docs.
|
|
5
|
+
|
|
6
|
+
## Deploy lifecycle
|
|
7
|
+
|
|
8
|
+
`belt deploy [env]` runs pre-deploy backups (if configured) → terraform init →
|
|
9
|
+
plan → apply. The **conveyor-belt** Terraform provider packages Ruby into
|
|
10
|
+
Lambdas, creates API Gateway routes from the routing DSL, generates IAM for
|
|
11
|
+
DynamoDB access, and sets up CloudWatch log groups.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
belt deploy dev
|
|
15
|
+
belt deploy prod --auto # skip confirmation
|
|
16
|
+
belt deploy prod --skip-backup # CI re-runs
|
|
17
|
+
belt deploy prod --backup-only # recovery point, no deploy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Provider config (Terraform):
|
|
21
|
+
|
|
22
|
+
```hcl
|
|
23
|
+
terraform {
|
|
24
|
+
required_providers {
|
|
25
|
+
conveyor-belt = { source = "stowzilla/conveyor-belt", version = "~> 0.0.1" }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Environments
|
|
31
|
+
|
|
32
|
+
Each env has `infrastructure/<env>/` (main.tf, backend.tf, variables.tf,
|
|
33
|
+
terraform.tfvars, outputs.tf, belt.rb). Create with `belt generate environment
|
|
34
|
+
<name> [parent]`. Terraform shorthand: `belt init|plan|apply|destroy|output <env>`.
|
|
35
|
+
Set `BELT_ENV` to omit the env arg.
|
|
36
|
+
|
|
37
|
+
## Backups (pre-deploy, config-driven)
|
|
38
|
+
|
|
39
|
+
`infrastructure/<env>/belt.rb`:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
Belt.configure do |config|
|
|
43
|
+
config.backups do
|
|
44
|
+
dynamodb :all # PITR check + on-demand snapshot per table
|
|
45
|
+
cognito :users, :pool_config # export to backup bucket
|
|
46
|
+
s3 :legal_documents # sync to backup bucket
|
|
47
|
+
retention snapshots: 90, cognito: 10, s3: 10
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Simple mode: `config.backups = true` (DynamoDB, all tables, 90-day retention).
|
|
53
|
+
Omit the block entirely for lightweight dev envs. Belt auto-creates
|
|
54
|
+
`<app>-backups-<env>` (versioned, public access blocked) on first run. Table
|
|
55
|
+
names come from `terraform output`, so the first-ever deploy skips backups.
|
|
56
|
+
|
|
57
|
+
## Data seeding
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
belt db:copy prod dev [--force] # copy DynamoDB between envs (matches by stripped prefix)
|
|
61
|
+
belt db:seed [env] [--force] # run config/seeds.rb in the booted console context
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`db:copy` skips non-empty destination tables by default. `db:seed` refuses to
|
|
65
|
+
run against an env that already has data unless `--force`.
|
|
66
|
+
|
|
67
|
+
## Observability
|
|
68
|
+
|
|
69
|
+
`Belt::LambdaHandler` wires these global facades automatically:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
Belt::Observability::Logger.info("Something happened", user_id: "123")
|
|
73
|
+
Belt::Observability::Metrics.track_event("OrderCreated", model: "Order")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Backed by `lambda_loadout` (structured logging + CloudWatch EMF metrics + error
|
|
77
|
+
alerting via `ERROR_NOTIFICATION_TOPIC_ARN`).
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Models (ActiveItem) & Cognito authentication
|
|
2
|
+
|
|
3
|
+
Run `belt explain models` and `belt explain authentication` for canonical docs.
|
|
4
|
+
|
|
5
|
+
## ActiveItem (DynamoDB ORM)
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
require "activeitem"
|
|
9
|
+
|
|
10
|
+
class Post < ActiveItem::Base
|
|
11
|
+
self.primary_key = :id
|
|
12
|
+
attr_accessor :id, :user_id, :title, :body, :created_at
|
|
13
|
+
|
|
14
|
+
validates :title, presence: true
|
|
15
|
+
before_create { self.id ||= SecureRandom.uuid }
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Supports queries, validations, associations, and transactions. Query a GSI:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
Post.where(user_id: current_user.id, index: "UserIndex")
|
|
23
|
+
Post.find(id)
|
|
24
|
+
Post.create!(attrs)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Table schema is declared in `infrastructure/schema.tf.rb`:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
Belt.application.schema.define do
|
|
31
|
+
model :post do
|
|
32
|
+
partition_key :id, :string
|
|
33
|
+
global_secondary_index :UserIndex, partition_key: :user_id
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
DynamoDB tables generated by Belt default to PITR enabled and (in prod)
|
|
39
|
+
deletion protection enabled.
|
|
40
|
+
|
|
41
|
+
## Authentication — Cognito owns auth, Belt owns the record
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
class User < ApplicationRecord
|
|
45
|
+
cognito_authenticatable
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
One line supplies: Cognito `sub` as primary key, identity attributes (`email`,
|
|
50
|
+
`name`, `role`, `email_verified`, `last_seen_on`), an `EmailIndex` GSI,
|
|
51
|
+
just-in-time provisioning from a token, and `#admin?` for platform staff.
|
|
52
|
+
|
|
53
|
+
Controllers get helpers for free — no `include`, no config:
|
|
54
|
+
|
|
55
|
+
| Helper | Meaning |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `current_user` | User record or nil (memoized per request) |
|
|
58
|
+
| `user_signed_in?` | Is there a Cognito identity on this request? |
|
|
59
|
+
| `authenticate_user!` | `before_action` guard → 401 |
|
|
60
|
+
| `cognito_admin?` | Does the token carry a staff Cognito group? |
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
class ProfilesController < ApplicationController
|
|
64
|
+
before_action :authenticate_user!
|
|
65
|
+
def show = @profile = current_user
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`belt generate auth` creates the user pool **and** scaffolds the model + table.
|
|
70
|
+
See `belt explain authentication` for the `after_cognito_sync` hook, platform
|
|
71
|
+
staff handling, and both token shapes. Upgrading an existing app? See `UPGRADING.md`.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Authoring Belt plugins
|
|
2
|
+
|
|
3
|
+
Belt stays lean; optional capabilities ship as **separate gems** that plug into
|
|
4
|
+
the CLI and runtime. Run `belt explain plugins` for canonical docs. Reference
|
|
5
|
+
implementations: `belt-messaging`, `belt-pay`.
|
|
6
|
+
|
|
7
|
+
## Discovery contract (GeneratorRegistry)
|
|
8
|
+
|
|
9
|
+
No central registry, no initializer. Belt discovers a generator when:
|
|
10
|
+
|
|
11
|
+
1. The gem is in the app's `Gemfile` and bundled.
|
|
12
|
+
2. It ships `lib/belt/generators/<name>_generator.rb`.
|
|
13
|
+
3. The class is `Belt::Generators::<Name>Generator`.
|
|
14
|
+
4. It implements `.run(args)` (required); optionally `.destroy(args)` and `.description`.
|
|
15
|
+
|
|
16
|
+
After `bundle install`, `belt generate <name>` and `belt destroy <name>` just work.
|
|
17
|
+
|
|
18
|
+
## Scaffold a plugin
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
belt plugin new notifications # → ./belt-notifications/
|
|
22
|
+
belt plugin new pay --path ~/Code --summary "Stripe payments for Belt"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Point an app at a local plugin while developing:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
# app Gemfile
|
|
29
|
+
gem "belt-notifications", path: "../belt-notifications"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`belt deploy` vendors `path:` gems into `vendor/cache` so conveyor-belt can
|
|
33
|
+
package them.
|
|
34
|
+
|
|
35
|
+
## Canonical layout
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
belt-messaging/
|
|
39
|
+
├── belt-messaging.gemspec
|
|
40
|
+
├── lib/
|
|
41
|
+
│ ├── belt-messaging.rb # require entrypoint
|
|
42
|
+
│ └── belt/
|
|
43
|
+
│ ├── messaging.rb # Belt::Messaging API
|
|
44
|
+
│ ├── messaging/{configuration,version}.rb
|
|
45
|
+
│ ├── messaging/controllers/ # default controllers (optional)
|
|
46
|
+
│ ├── messaging/templates/ # ERB for the generator
|
|
47
|
+
│ └── generators/messaging_generator.rb # ← auto-discovered
|
|
48
|
+
└── spec/
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Runtime code stays in the gem.** Generators copy only what the host app must
|
|
52
|
+
own — Terraform modules, Lambda entrypoints, optional controller overrides.
|
|
53
|
+
Prefer gem defaults + `belt g <plugin> --controllers` over dumping everything
|
|
54
|
+
into the app.
|
|
55
|
+
|
|
56
|
+
## Generator checklist
|
|
57
|
+
|
|
58
|
+
1. Terraform module → `infrastructure/modules/<name>/`
|
|
59
|
+
2. Lambda config → `config/lambda/<name>.yml`
|
|
60
|
+
3. Lambda entrypoint → `lambda/<name>.rb` via `Belt::LambdaHandler`
|
|
61
|
+
4. Routes/schema injection when needed
|
|
62
|
+
5. Optional `--controllers` for app-local overrides
|
|
63
|
+
6. Matching `destroy` path
|
|
64
|
+
7. `.description` + `--help`
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Belt routing DSL
|
|
2
|
+
|
|
3
|
+
Routes live in `infrastructure/routes.tf.rb` and are read both by the
|
|
4
|
+
**conveyor-belt** Terraform provider (for infra) and by `belt routes` (which
|
|
5
|
+
generates the runtime route constants at `lambda/lib/routes/<namespace>_routes.rb`).
|
|
6
|
+
|
|
7
|
+
Run `belt explain routing` for the canonical docs.
|
|
8
|
+
|
|
9
|
+
## Four keywords
|
|
10
|
+
|
|
11
|
+
| Keyword | Purpose | Changes which Lambda? |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `gateway` | API Gateway + default Lambda | **Yes** — sets default for routes inside |
|
|
14
|
+
| `function` | Route to a different Lambda | **Yes** — overrides gateway default |
|
|
15
|
+
| `namespace` | Path prefix + controller module | No — code organization only |
|
|
16
|
+
| `scope` | Path/module/auth grouping | No — grouping + shared options |
|
|
17
|
+
|
|
18
|
+
**Critical:** `namespace` and `scope` are purely organizational. Only `gateway`
|
|
19
|
+
and `function` determine the serving Lambda.
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
Belt.application.routes.draw do
|
|
23
|
+
gateway :api, auth: :cognito do
|
|
24
|
+
resources :posts # lambda: api, /posts, posts controller
|
|
25
|
+
|
|
26
|
+
namespace :admin do
|
|
27
|
+
resources :users # /admin/users, admin/users controller
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
function :worker do
|
|
31
|
+
resources :jobs # lambda: worker, /jobs, jobs controller
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
scope path: 'v2', module: 'legacy' do
|
|
35
|
+
resources :widgets # /v2/widgets, legacy/widgets controller
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Nested resources
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
resources :projects do
|
|
45
|
+
resource :billing, only: [:show], tables: [:memberships] # singular, no :id
|
|
46
|
+
|
|
47
|
+
resources :webhooks do
|
|
48
|
+
member { post :test } # POST /projects/:project_id/webhooks/:webhook_id/test
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
resources :surfaces do
|
|
52
|
+
collection { get :teams } # GET /projects/:project_id/surfaces/teams
|
|
53
|
+
member { put :assign } # PUT /projects/:project_id/surfaces/:surface_id/assign
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
scope path: 'billing', controller: :billing, tables: [:memberships] do
|
|
57
|
+
get '/', action: :show
|
|
58
|
+
post :checkout
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- **Action inference:** `post :checkout` uses `checkout` as both path segment and action.
|
|
64
|
+
- **Controller inheritance:** `member`/`collection` inherit the parent resource's controller.
|
|
65
|
+
- **`tables:`** declares DynamoDB access for IAM generation.
|
|
66
|
+
|
|
67
|
+
## Request/response model inference (for `belt routes` / contracts)
|
|
68
|
+
|
|
69
|
+
Resolution order (highest first):
|
|
70
|
+
|
|
71
|
+
1. **Explicit per-route:** `put "/items/:id", request_model: :update_item`
|
|
72
|
+
2. **Hash per-action:** `resources :items, request_model: { create: :create_item }`
|
|
73
|
+
3. **Convention cascade** (POST/PUT/PATCH only):
|
|
74
|
+
- `:<verb>_<gateway>_<singular>` → e.g. `:create_customer_item`
|
|
75
|
+
- `:<verb>_<singular>` → e.g. `:create_item`
|
|
76
|
+
|
|
77
|
+
**Response model:** singular of the resource name → `resources :items` looks for
|
|
78
|
+
`model :item` in `contracts.rb`. Applies to all verbs. No match = no model documented.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: belt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
@@ -97,6 +97,7 @@ files:
|
|
|
97
97
|
- CHANGELOG.md
|
|
98
98
|
- LICENSE.txt
|
|
99
99
|
- README.md
|
|
100
|
+
- SKILL.md
|
|
100
101
|
- exe/belt
|
|
101
102
|
- lib/belt.rb
|
|
102
103
|
- lib/belt/action_router.rb
|
|
@@ -262,6 +263,12 @@ files:
|
|
|
262
263
|
- lib/templates/views/Index.jsx.erb
|
|
263
264
|
- lib/templates/views/New.jsx.erb
|
|
264
265
|
- lib/templates/views/Show.jsx.erb
|
|
266
|
+
- references/cli.md
|
|
267
|
+
- references/controllers.md
|
|
268
|
+
- references/deploy-and-ops.md
|
|
269
|
+
- references/models-and-auth.md
|
|
270
|
+
- references/plugins.md
|
|
271
|
+
- references/routing.md
|
|
265
272
|
homepage: https://github.com/stowzilla/belt
|
|
266
273
|
licenses:
|
|
267
274
|
- MIT
|