belt 0.4.4 → 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.
@@ -0,0 +1,129 @@
1
+ # Data Seeding
2
+
3
+ Belt provides two ways to get data into an environment without hand-crafting
4
+ rows: copying data from another environment, and Rails-style seed files.
5
+
6
+ ## `belt db:copy` — copy data between environments
7
+
8
+ Copies DynamoDB table contents from one environment into another, matching
9
+ tables by name after stripping each environment's `<app>-<env>-` prefix.
10
+
11
+ ```bash
12
+ belt db:copy prod dev # copy prod data into dev
13
+ belt db:copy prod dev --force # overwrite dev tables even if non-empty
14
+ ```
15
+
16
+ By default, destination tables that already contain data are skipped — safe
17
+ to re-run against a live environment. `--force` overwrites them instead.
18
+
19
+ ### Cognito identity re-anchoring
20
+
21
+ Cognito identities are **per-environment**: each environment has its own user
22
+ pool, so the same person has a *different* `sub` in every environment. Any row
23
+ that references a user by their `sub` (e.g. a membership's `cognito_sub`) has a
24
+ reference that's meaningless in another environment — copy it verbatim and the
25
+ row points at a user who doesn't exist in the destination pool, so it silently
26
+ disappears (a copied project you can't see, a member who isn't there).
27
+
28
+ `belt db:copy` handles this automatically:
29
+
30
+ - The destination's own `users` table is **left untouched** — the destination
31
+ pool is authoritative for who its users are and what `sub` each one has.
32
+ - For every other table, any row carrying both an `email` and a `cognito_sub`
33
+ has its `cognito_sub` **re-anchored** to the destination user with the same
34
+ email.
35
+ - A row whose email has no destination user yet has its stale `cognito_sub`
36
+ **cleared**, so it reads as unclaimed (e.g. a pending invitation Belt binds
37
+ on that person's first login) rather than dangling.
38
+
39
+ ```bash
40
+ belt db:copy prod dev # re-anchors identities (default)
41
+ belt db:copy prod dev --no-remap-identity # copy cognito_sub refs verbatim
42
+ ```
43
+
44
+ This relies on Belt's `cognito_authenticatable` convention: the users table is
45
+ `<app>-<env>-users`, its primary key is `id` (the Cognito `sub`), it carries an
46
+ `email`, and `cognito_sub` is the foreign-key attribute referencing it. The
47
+ same re-anchoring runs in the nested (PR-preview) environment deploy hook.
48
+
49
+ ### Cross-account copies
50
+
51
+ Source and destination environments often live in different AWS accounts
52
+ (e.g. prod vs. dev). `belt db:copy` resolves the AWS profile for each side
53
+ independently from `infrastructure/<env>/belt.rb` (`config.aws_profile`):
54
+
55
+ ```ruby
56
+ # infrastructure/prod/belt.rb
57
+ Belt.configure do |config|
58
+ config.aws_profile = "prod-readonly"
59
+ end
60
+ ```
61
+
62
+ Override either side explicitly if you don't want to rely on `belt.rb`:
63
+
64
+ ```bash
65
+ belt db:copy prod dev --from-profile prod-readonly --to-profile dev
66
+ ```
67
+
68
+ ### How it works
69
+
70
+ 1. Lists tables under each environment's prefix (`<app>-<env>-`) using the
71
+ AWS CLI (`aws dynamodb list-tables`)
72
+ 2. Pairs up tables by matching suffix (e.g. `myapp-prod-posts` ↔ `myapp-dev-posts`)
73
+ 3. Scans the source table and `batch-write-item`s into the destination,
74
+ re-anchoring Cognito-sub foreign keys to the destination's users by email
75
+ (unless `--no-remap-identity`; see above)
76
+ 4. Skips (or overwrites, with `--force`) destination tables that already
77
+ have items
78
+
79
+ This is the same mechanism used by nested (PR-preview) environment deploys
80
+ to seed a preview environment's tables from its parent — `belt db:copy` just
81
+ exposes it as a standalone command for any two environments.
82
+
83
+ ## `belt db:seed` — Rails-style seed file
84
+
85
+ Mirrors `rails db:seed`. Loads `config/seeds.rb` in the same booted context
86
+ `belt console` uses — your models (ActiveItem) are available, targeting the
87
+ resolved environment's tables.
88
+
89
+ ```bash
90
+ belt db:seed # seeds dev, or $BELT_ENV if set
91
+ belt db:seed dev01 # explicit environment
92
+ belt db:seed prod # prompts for confirmation, like belt console prod
93
+ ```
94
+
95
+ `config/seeds.rb` is a plain Ruby file:
96
+
97
+ ```ruby
98
+ # frozen_string_literal: true
99
+
100
+ post = Post.create!(title: "Hello, world", body: "Seeded post")
101
+ puts "Created post: #{post.id}"
102
+ ```
103
+
104
+ ### Safety
105
+
106
+ `belt db:seed` refuses to run if the target environment's tables already
107
+ have data, to avoid clobbering a live environment (or accidentally reseeding
108
+ one that's already loaded). Pass `--force` to seed anyway:
109
+
110
+ ```bash
111
+ belt db:seed dev01 --force
112
+ ```
113
+
114
+ Because of this guard, seeds are typically run once against a fresh
115
+ environment. If you want `seeds.rb` to be safely re-runnable regardless,
116
+ write it idempotently (`find_or_create_by`-style) — `belt db:seed --force`
117
+ does not enforce idempotency for you.
118
+
119
+ ### Scaffolding
120
+
121
+ `belt new` generates a starter `config/seeds.rb` with usage notes and an
122
+ example. Existing apps can add the file manually — it's just a plain Ruby
123
+ file, no generator required.
124
+
125
+ ## See Also
126
+
127
+ - `belt explain backups` — recovery-point snapshots, not data seeding
128
+ - `belt explain console` — the app-booting mechanism `db:seed` reuses
129
+ - `belt explain deployment` — nested environments and the parent → child copy hook
data/lib/belt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Belt
4
- VERSION = '0.4.4'
4
+ VERSION = '0.4.6'
5
5
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Seed data for <%= @app_name %>. Run with:
4
+ #
5
+ # belt db:seed # seeds dev (or BELT_ENV)
6
+ # belt db:seed prod # explicit environment (prompts for confirmation)
7
+ # BELT_ENV=dev01 belt db:seed
8
+ #
9
+ # This file is loaded in the same booted context as `belt console` — your
10
+ # models (ActiveItem) are available, and they target the resolved
11
+ # environment's DynamoDB tables (<%= @app_name %>-<env>-*).
12
+ #
13
+ # `belt db:seed` refuses to run against an environment that already has
14
+ # data, to avoid clobbering something real. Pass --force to seed anyway.
15
+ # Keep this file idempotent if you expect to re-run it (e.g. find_or_create
16
+ # rather than create).
17
+ #
18
+ # Example:
19
+ #
20
+ # post = Post.create!(
21
+ # title: "Hello, world",
22
+ # body: "This post was created by config/seeds.rb"
23
+ # )
24
+ # puts "Created post: #{post.id}"
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
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
@@ -119,6 +120,8 @@ files:
119
120
  - lib/belt/cli/cognito_sharer.rb
120
121
  - lib/belt/cli/console_command.rb
121
122
  - lib/belt/cli/contracts_command.rb
123
+ - lib/belt/cli/db_copy_command.rb
124
+ - lib/belt/cli/db_seed_command.rb
122
125
  - lib/belt/cli/deploy_command.rb
123
126
  - lib/belt/cli/destroy_command.rb
124
127
  - lib/belt/cli/dns_command.rb
@@ -160,6 +163,7 @@ files:
160
163
  - lib/belt/docs/backups.md
161
164
  - lib/belt/docs/console.md
162
165
  - lib/belt/docs/controllers.md
166
+ - lib/belt/docs/data_seeding.md
163
167
  - lib/belt/docs/deployment.md
164
168
  - lib/belt/docs/frontend.md
165
169
  - lib/belt/docs/generators.md
@@ -231,6 +235,7 @@ files:
231
235
  - lib/templates/new_app/config/contracts.rb.erb
232
236
  - lib/templates/new_app/config/lambda/api.yml.erb
233
237
  - lib/templates/new_app/config/routes.rb.erb
238
+ - lib/templates/new_app/config/seeds.rb.erb
234
239
  - lib/templates/new_app/gitignore.erb
235
240
  - lib/templates/new_app/lambda/api.rb.erb
236
241
  - lib/templates/new_app/lambda/config/environment.rb.erb
@@ -258,6 +263,12 @@ files:
258
263
  - lib/templates/views/Index.jsx.erb
259
264
  - lib/templates/views/New.jsx.erb
260
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
261
272
  homepage: https://github.com/stowzilla/belt
262
273
  licenses:
263
274
  - MIT