@everystack/mcp 0.2.2 → 0.3.0

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.
Files changed (44) hide show
  1. package/LICENSE +681 -0
  2. package/README.md +45 -10
  3. package/dist/adding-database.md +169 -0
  4. package/dist/admin.md +81 -0
  5. package/dist/auth.md +115 -0
  6. package/dist/aws-setup.md +276 -0
  7. package/dist/cli.md +108 -0
  8. package/dist/client-api.md +145 -0
  9. package/dist/core.md +196 -0
  10. package/dist/deployment.md +146 -0
  11. package/dist/events.md +87 -0
  12. package/dist/first-run.md +100 -0
  13. package/dist/getting-started.md +75 -0
  14. package/dist/handler-options.md +114 -0
  15. package/dist/images.md +93 -0
  16. package/dist/index.cjs +23726 -0
  17. package/dist/jobs.md +97 -0
  18. package/dist/logging.md +91 -0
  19. package/dist/plugins.md +68 -0
  20. package/dist/project-claude-md.md +102 -0
  21. package/dist/query-protocol.md +129 -0
  22. package/dist/schema-patterns.md +167 -0
  23. package/dist/security-device.md +99 -0
  24. package/dist/security.md +270 -0
  25. package/dist/ssr.md +82 -0
  26. package/dist/storage.md +63 -0
  27. package/dist/testing.md +118 -0
  28. package/package.json +26 -14
  29. package/src/gates/detectors/embedded-data-bundle.ts +58 -0
  30. package/src/gates/detectors/hand-written-migration.ts +42 -0
  31. package/src/gates/detectors/secret-in-public-env.ts +41 -0
  32. package/src/gates/engine.ts +80 -0
  33. package/src/gates/registry.ts +25 -0
  34. package/src/gates/telemetry.ts +143 -0
  35. package/src/gates/types.ts +70 -0
  36. package/src/governance/cli.ts +193 -0
  37. package/src/governance/grounding.ts +344 -0
  38. package/src/index.ts +97 -50
  39. package/src/prompts/claude-md.ts +90 -0
  40. package/src/prompts/governance-setup.ts +85 -0
  41. package/src/prompts/index.ts +4 -0
  42. package/src/prompts/new-app.ts +4 -1
  43. package/src/resources/project-claude-md.md +69 -94
  44. package/src/tools/index.ts +6 -39
@@ -0,0 +1,276 @@
1
+ # Setting Up AWS for everystack
2
+
3
+ AWS (Amazon Web Services) rents you computers on the internet. When you deploy your app, it runs on AWS servers so anyone in the world can use it. This guide walks you through creating an account and giving your computer permission to deploy.
4
+
5
+ ## What does everystack use on AWS?
6
+
7
+ Your app is made up of a few building blocks. Each one is an AWS service:
8
+
9
+ | Service | What it does | Plain English | Tier | Typical cost |
10
+ |---------|-------------|---------------|------|-------------|
11
+ | **S3** | File storage | Holds your app's files (HTML, CSS, JavaScript, images) | V1+ | Pennies/month |
12
+ | **CloudFront** | Content delivery network | Makes your app load fast for users worldwide | V1+ | Free tier covers most small apps |
13
+ | **Lambda** | Serverless compute | Runs your server code only when someone visits | V1+ | Free tier: 1M requests/month |
14
+ | **RDS** | Managed database | Stores your users, posts, and data (Aurora Serverless) | V2+ | ~$15/month minimum |
15
+ | **SQS** | Message queue | Processes background tasks like sending emails | V3 | Pennies/month |
16
+
17
+ **Total cost for a small app with low traffic:** Under $5/month for V1, ~$20/month for V2+ (the database is the main cost).
18
+
19
+ AWS has a **free tier** for the first 12 months that covers most of what everystack uses at low traffic. You will not be charged anything significant while learning.
20
+
21
+ ## Step 1: Create an AWS account
22
+
23
+ 1. Go to https://aws.amazon.com/free
24
+ 2. Click **Create a Free Account**
25
+ 3. You will need:
26
+ - An email address
27
+ - A credit card (required for verification, but free tier usage will not be charged)
28
+ - A phone number for verification
29
+ 4. Choose the **Basic Support** plan (free)
30
+ 5. After account creation, sign in to the AWS Console at https://console.aws.amazon.com to confirm it works
31
+
32
+ ## Step 2: Create an IAM user for everystack
33
+
34
+ Your AWS account has a "root user" (the email you signed up with). The root user has unlimited power over your account. You should never use it for daily work. Instead, create a dedicated user just for everystack.
35
+
36
+ **IAM** stands for Identity and Access Management. It controls who can do what in your AWS account.
37
+
38
+ ### Create the user
39
+
40
+ 1. Sign in to the **AWS Console** at https://console.aws.amazon.com
41
+ 2. In the top search bar, type **IAM** and click on it
42
+ 3. Click **Users** in the left sidebar
43
+ 4. Click **Create user**
44
+ 5. Enter the user name: `everystack-dev`
45
+ 6. Click **Next**
46
+
47
+ ### Create the permissions policy
48
+
49
+ This policy gives the user the permissions SST needs to deploy your app, scoped to your app's resources. Every statement targets your app name prefix so this user can't touch other apps in the same account.
50
+
51
+ 7. Select **Attach policies directly**
52
+ 8. Click **Create policy** (this opens a new tab)
53
+ 9. Click the **JSON** tab
54
+ 10. Delete anything in the editor and paste this (replacing the placeholders — see table below):
55
+
56
+ ```json
57
+ {
58
+ "Version": "2012-10-17",
59
+ "Statement": [
60
+ {
61
+ "Sid": "SSM",
62
+ "Effect": "Allow",
63
+ "Action": "ssm:*",
64
+ "Resource": [
65
+ "arn:aws:ssm:{REGION}:{ACCOUNT_ID}:parameter/sst/*",
66
+ "arn:aws:ssm:{REGION}:{ACCOUNT_ID}:parameter/{APP_NAME}/*"
67
+ ]
68
+ },
69
+ {
70
+ "Sid": "S3",
71
+ "Effect": "Allow",
72
+ "Action": "s3:*",
73
+ "Resource": [
74
+ "arn:aws:s3:::{APP_NAME}-*-*",
75
+ "arn:aws:s3:::{APP_NAME}-*-*/*",
76
+ "arn:aws:s3:::sst-asset-*",
77
+ "arn:aws:s3:::sst-asset-*/*",
78
+ "arn:aws:s3:::sst-state-*",
79
+ "arn:aws:s3:::sst-state-*/*"
80
+ ]
81
+ },
82
+ {
83
+ "Sid": "S3Protect",
84
+ "Effect": "Deny",
85
+ "Action": [
86
+ "s3:DeleteBucket",
87
+ "s3:PutBucketAcl",
88
+ "s3:PutBucketVersioning",
89
+ "s3:PutBucketEncryption",
90
+ "s3:PutBucketLogging"
91
+ ],
92
+ "Resource": "*"
93
+ },
94
+ {
95
+ "Sid": "CF",
96
+ "Effect": "Allow",
97
+ "Action": [
98
+ "cloudfront:*",
99
+ "cloudfront-keyvaluestore:*"
100
+ ],
101
+ "Resource": "*"
102
+ },
103
+ {
104
+ "Sid": "Lambda",
105
+ "Effect": "Allow",
106
+ "Action": "lambda:*",
107
+ "Resource": "arn:aws:lambda:{REGION}:{ACCOUNT_ID}:function:{APP_NAME}-*"
108
+ },
109
+ {
110
+ "Sid": "LambdaGlobal",
111
+ "Effect": "Allow",
112
+ "Action": [
113
+ "lambda:CreateEventSourceMapping",
114
+ "lambda:DeleteEventSourceMapping",
115
+ "lambda:GetEventSourceMapping",
116
+ "lambda:ListEventSourceMappings",
117
+ "lambda:UpdateEventSourceMapping",
118
+ "lambda:ListFunctions",
119
+ "lambda:GetFunctionConfiguration",
120
+ "lambda:TagResource",
121
+ "lambda:UntagResource",
122
+ "lambda:ListTags"
123
+ ],
124
+ "Resource": "*"
125
+ },
126
+ {
127
+ "Sid": "SQS",
128
+ "Effect": "Allow",
129
+ "Action": "sqs:*",
130
+ "Resource": "arn:aws:sqs:{REGION}:{ACCOUNT_ID}:{APP_NAME}-*"
131
+ },
132
+ {
133
+ "Sid": "IAM",
134
+ "Effect": "Allow",
135
+ "Action": "iam:*Role*",
136
+ "Resource": [
137
+ "arn:aws:iam::{ACCOUNT_ID}:role/{APP_NAME}-*",
138
+ "arn:aws:iam::{ACCOUNT_ID}:role/SchedulerRole-*"
139
+ ]
140
+ },
141
+ {
142
+ "Sid": "Scheduler",
143
+ "Effect": "Allow",
144
+ "Action": [
145
+ "scheduler:CreateScheduleGroup",
146
+ "scheduler:DeleteScheduleGroup",
147
+ "scheduler:GetScheduleGroup",
148
+ "scheduler:ListTagsForResource",
149
+ "scheduler:TagResource",
150
+ "scheduler:UntagResource"
151
+ ],
152
+ "Resource": "arn:aws:scheduler:{REGION}:{ACCOUNT_ID}:schedule-group/{APP_NAME}-*"
153
+ },
154
+ {
155
+ "Sid": "Logs",
156
+ "Effect": "Allow",
157
+ "Action": "logs:*",
158
+ "Resource": [
159
+ "arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:/aws/lambda/{APP_NAME}-*",
160
+ "arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:/aws/lambda/{APP_NAME}-*:*"
161
+ ]
162
+ },
163
+ {
164
+ "Sid": "LogsList",
165
+ "Effect": "Allow",
166
+ "Action": "logs:DescribeLogGroups",
167
+ "Resource": "arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:*"
168
+ },
169
+ {
170
+ "Sid": "Discovery",
171
+ "Effect": "Allow",
172
+ "Action": "s3:ListAllMyBuckets",
173
+ "Resource": "*"
174
+ },
175
+ {
176
+ "Sid": "STS",
177
+ "Effect": "Allow",
178
+ "Action": "sts:GetCallerIdentity",
179
+ "Resource": "*"
180
+ }
181
+ ]
182
+ }
183
+ ```
184
+
185
+ Replace the placeholders with your values:
186
+
187
+ | Placeholder | Example | Where to find it |
188
+ |-------------|---------|-------------------|
189
+ | `{ACCOUNT_ID}` | `123456789012` | `aws sts get-caller-identity` |
190
+ | `{REGION}` | `us-east-1` | Your default AWS region |
191
+ | `{APP_NAME}` | `myapp` | The `name` in your `sst.config.ts` |
192
+
193
+ If your app creates additional S3 buckets outside the `{APP_NAME}-*` pattern, add them to the S3 statement.
194
+
195
+ 11. Click **Next**
196
+ 12. Name the policy: `everystack-dev-policy`
197
+ 13. Optionally add a description: "Permissions for everystack SST deploys"
198
+ 14. Click **Create policy**
199
+
200
+ ### Attach the policy to the user
201
+
202
+ 15. Go back to the **Create user** tab in your browser
203
+ 16. Click the refresh button on the policy list
204
+ 17. Search for `everystack-dev-policy`
205
+ 18. Check the box next to it
206
+ 19. Click **Next**, then **Create user**
207
+
208
+ ### Create an access key
209
+
210
+ 20. Click on the user name **everystack-dev**
211
+ 21. Go to the **Security credentials** tab
212
+ 22. Under **Access keys**, click **Create access key**
213
+ 23. Select **Command Line Interface (CLI)**
214
+ 24. Check the confirmation checkbox, click **Next**, then **Create access key**
215
+ 25. **Save both keys now.** You will not see the secret key again after this page:
216
+ - **Access key ID** (looks like: `AKIAIOSFODNN7EXAMPLE`)
217
+ - **Secret access key** (looks like: `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`)
218
+
219
+ ## Step 3: Configure your computer
220
+
221
+ Open a terminal and run:
222
+
223
+ ```bash
224
+ aws configure
225
+ ```
226
+
227
+ It will ask four questions:
228
+
229
+ - **AWS Access Key ID:** Paste the access key ID from Step 2
230
+ - **AWS Secret Access Key:** Paste the secret access key from Step 2
231
+ - **Default region name:** `us-east-1` (recommended, or choose your nearest region)
232
+ - **Default output format:** `json`
233
+
234
+ ## Step 4: Verify it works
235
+
236
+ Run this command:
237
+
238
+ ```bash
239
+ aws sts get-caller-identity
240
+ ```
241
+
242
+ You should see something like:
243
+
244
+ ```json
245
+ {
246
+ "UserId": "AIDAIOSFODNN7EXAMPLE",
247
+ "Account": "123456789012",
248
+ "Arn": "arn:aws:iam::123456789012:user/everystack-dev"
249
+ }
250
+ ```
251
+
252
+ If you see your account number and `everystack-dev`, your credentials are working. You are ready to deploy.
253
+
254
+ ### What each statement does
255
+
256
+ | Statement | Actions | Scope | Purpose |
257
+ |-----------|---------|-------|---------|
258
+ | **SSM** | `ssm:*` | `sst/*` + `{APP_NAME}/*` params | SST state management + app secrets |
259
+ | **S3** | `s3:*` | App buckets + SST state/asset buckets | Media, client bundles, OTA updates, SST state |
260
+ | **S3Protect** | Deny destructive bucket ops | `*` | Prevents `DeleteBucket`, ACL/versioning/encryption/logging changes |
261
+ | **CF** | `cloudfront:*`, `cloudfront-keyvaluestore:*` | `*` | Router distribution + KVS cache versioning |
262
+ | **Lambda** | `lambda:*` | `{APP_NAME}-*` functions | API, Image, Worker Lambdas |
263
+ | **LambdaGlobal** | Event source mappings, list, tags | `*` | SQS-to-Lambda subscriptions, resource discovery |
264
+ | **SQS** | `sqs:*` | `{APP_NAME}-*` queues | Background jobs + dead-letter queues (V3) |
265
+ | **IAM** | `iam:*Role*` | `{APP_NAME}-*` + `SchedulerRole-*` | Lambda execution roles + EventBridge Scheduler role |
266
+ | **Scheduler** | Schedule group CRUD + tags | `{APP_NAME}-*` groups | EventBridge Scheduler for delayed tasks |
267
+ | **Logs** | `logs:*` | `/aws/lambda/{APP_NAME}-*` | CloudWatch log groups for Lambda functions |
268
+ | **LogsList** | `logs:DescribeLogGroups` | `*` | Log group enumeration |
269
+ | **Discovery** | `s3:ListAllMyBuckets` | `*` | CLI resource discovery |
270
+ | **STS** | `sts:GetCallerIdentity` | `*` | Identity verification |
271
+
272
+ **V2-only apps** can omit `SQS` and `Scheduler`. **V1-only apps** can omit `SQS`, `Scheduler`, `IAM`, and `Logs`.
273
+
274
+ ## Production hardening
275
+
276
+ For production, read `everystack://security` for a three-profile model that separates infrastructure creation, day-to-day operations, and CI/CD into distinct IAM users with minimal permissions.
package/dist/cli.md ADDED
@@ -0,0 +1,108 @@
1
+ # CLI Reference
2
+
3
+ > Full command reference for the `everystack` CLI. Import from `@everystack/cli`.
4
+
5
+ ## When to Use
6
+ Read this when deploying, debugging, or managing a deployed everystack app. All commands use AWS IAM credentials (not shared secrets).
7
+
8
+ ## Authentication
9
+
10
+ | Operation | Auth Mechanism |
11
+ |-----------|---------------|
12
+ | `db:migrate`, `db:seed`, `console` | IAM -> Lambda invoke |
13
+ | `update --platform web` | IAM -> S3 PutObject + Lambda invoke |
14
+ | `update --platform ios/android` | `EVERYSTACK_TOKEN` -> HTTP POST |
15
+ | `logs:errors`, `logs:query` | IAM -> Lambda invoke |
16
+ | `logs:tail` | IAM -> CloudWatch FilterLogEvents |
17
+ | `cache:purge` | IAM -> CloudFront KVS |
18
+ | `certs:*` | Local only (no network) |
19
+
20
+ ## Commands
21
+
22
+ ### OTA Updates
23
+ ```bash
24
+ everystack update --channel production --message "Fix login bug"
25
+ everystack update --channel production --platform ios --message "iOS fix"
26
+ everystack update --channel staging --platform web
27
+ ```
28
+ Flags: `--channel` (default: production), `--message`, `--platform` (ios/android/web/all), `--skip-export`.
29
+
30
+ ### Database
31
+ ```bash
32
+ everystack db:migrate # Run migrations via Lambda
33
+ everystack db:seed # Seed database (dev only)
34
+ everystack db:psql --stage dev -c "SELECT * FROM posts" # Read-only SQL via Lambda
35
+ everystack console --stage dev # Interactive REPL with db + schema
36
+ ```
37
+
38
+ The REPL has `db`, `schema`, `eq`, `and`, `or`, `gt`, `lt`, `count`, `sum`, `avg`, `sql`, `desc`, `asc` in scope.
39
+
40
+ ### Logs
41
+ ```bash
42
+ everystack logs:errors --stage dev # Recent errors (DB -> S3 fallback)
43
+ everystack logs:errors --stage dev --limit 50 --level fatal
44
+ everystack logs:tail --stage dev # CloudWatch Lambda output
45
+ everystack logs:tail --stage dev --filter ERROR --since 1h
46
+ everystack logs:query --stage dev --level error --source api
47
+ everystack logs:query --stage dev --traceId "abc123"
48
+ everystack logs:query --stage dev --userId "user_123" --since 30m
49
+ ```
50
+
51
+ ### Cache
52
+ ```bash
53
+ everystack cache:purge # Global cache bust
54
+ everystack cache:purge --origin web # Per-origin
55
+ everystack cache:purge --path "/api/posts" # Per-path
56
+ ```
57
+
58
+ ### Code Signing
59
+ ```bash
60
+ everystack certs:generate --output ./certs # Generate RSA key pair
61
+ everystack certs:configure --input ./certs --keyid main # Add to app.json
62
+ ```
63
+
64
+ ### Channels
65
+ ```bash
66
+ everystack channels list
67
+ everystack channels create --name staging
68
+ ```
69
+
70
+ ### SSR Debugging
71
+ ```bash
72
+ everystack diag https://myapp.com # Version + cache check
73
+ everystack diag https://myapp.com/page --hydration # Runtime hydration analysis
74
+ everystack analyze:ssr # Static analysis for SSR anti-patterns
75
+ ```
76
+
77
+ ## Configuration
78
+
79
+ The CLI auto-discovers resources from `.sst/outputs.json` (written by `sst deploy`). Required outputs:
80
+ ```typescript
81
+ return {
82
+ routerUrl: router.url,
83
+ apiFunctionName: api.name,
84
+ updatesBucket: updates.name,
85
+ clientBundlesBucket: clientBundles.name,
86
+ };
87
+ ```
88
+
89
+ Use `--stage` flag to target specific deployments: reads from `.sst/outputs.{stage}.json`.
90
+
91
+ ## Environment Variables
92
+
93
+ | Variable | Purpose |
94
+ |----------|---------|
95
+ | `AWS_REGION` | AWS region (default: us-east-1) |
96
+ | `AWS_PROFILE` | AWS credentials profile |
97
+ | `EVERYSTACK_TOKEN` | JWT for authenticated mobile uploads |
98
+
99
+ ## Lambda onAction Integration
100
+
101
+ The CLI invokes Lambda with `_action` payloads. Your handler must dispatch them:
102
+ ```typescript
103
+ onAction: async (action, payload) => {
104
+ if (action === 'migrate') return runMigrations(db, './drizzle');
105
+ if (action === 'seed') return runSeed(db, schema);
106
+ // ... console, db:psql, logs:errors, logs:query, etc.
107
+ }
108
+ ```
@@ -0,0 +1,145 @@
1
+ # Client API
2
+
3
+ > Typed query builder for everystack PostgREST APIs. Import from `@everystack/api/client`.
4
+
5
+ ## When to Use
6
+ Read this when building client-side data fetching in React Native, Expo, or web apps.
7
+
8
+ ## Setup
9
+
10
+ ```typescript
11
+ import { createClient } from '@everystack/api/client';
12
+
13
+ const api = createClient({
14
+ baseUrl: '/api',
15
+ getToken: () => localStorage.getItem('everystack-token'),
16
+ onTokenExpired: async () => {
17
+ const res = await fetch('/api/auth/refresh', {
18
+ method: 'POST',
19
+ headers: { 'Content-Type': 'application/json' },
20
+ body: JSON.stringify({ refreshToken: localStorage.getItem('everystack-refresh') }),
21
+ });
22
+ const { token } = await res.json();
23
+ localStorage.setItem('everystack-token', token);
24
+ return token;
25
+ },
26
+ });
27
+ ```
28
+
29
+ ### Options
30
+
31
+ | Option | Type | Description |
32
+ |--------|------|-------------|
33
+ | `baseUrl` | `string` | API base URL (e.g., `/api` or `https://api.example.com`) |
34
+ | `getToken` | `() => string \| null` | Returns current JWT token |
35
+ | `onTokenExpired` | `() => Promise<string>` | Called on 401 to refresh token. Concurrent 401s deduplicated (one refresh per batch) |
36
+ | `headers` | `Record<string, string>` | Additional headers for every request |
37
+
38
+ ## Queries (GET)
39
+
40
+ ```typescript
41
+ // Basic query
42
+ const { data, error } = await api.from('posts').execute();
43
+
44
+ // Filters
45
+ const { data } = await api.from('posts')
46
+ .eq('status', 'published')
47
+ .neq('authorId', blockedUser)
48
+ .gt('createdAt', yesterday)
49
+ .order('createdAt', 'desc')
50
+ .limit(10)
51
+ .offset(20)
52
+ .execute();
53
+
54
+ // Column selection
55
+ const { data } = await api.from('posts').select('id,title,body').execute();
56
+
57
+ // Relation embedding
58
+ const { data } = await api.from('posts').select('*,author(name,email)').execute();
59
+
60
+ // Logical groups
61
+ const { data } = await api.from('posts')
62
+ .or('status.eq.draft,status.eq.pending')
63
+ .execute();
64
+
65
+ // Exact count
66
+ const { data, count } = await api.from('posts')
67
+ .eq('status', 'published')
68
+ .count()
69
+ .execute();
70
+ ```
71
+
72
+ ### Filter Methods
73
+
74
+ | Method | Example |
75
+ |--------|---------|
76
+ | `.eq(col, val)` | `?col=eq.val` |
77
+ | `.neq(col, val)` | `?col=neq.val` |
78
+ | `.gt(col, val)` | `?col=gt.val` |
79
+ | `.gte(col, val)` | `?col=gte.val` |
80
+ | `.lt(col, val)` | `?col=lt.val` |
81
+ | `.lte(col, val)` | `?col=lte.val` |
82
+ | `.like(col, pattern)` | `?col=like.pattern` |
83
+ | `.ilike(col, pattern)` | `?col=ilike.pattern` |
84
+ | `.is(col, val)` | `?col=is.val` (null/not.null) |
85
+ | `.in(col, vals)` | `?col=in.(a,b,c)` |
86
+ | `.not(col, op, val)` | `?col=not.op.val` |
87
+ | `.or(expr)` | `?or=(a.eq.1,b.eq.2)` |
88
+ | `.select(cols)` | `?select=cols` |
89
+ | `.order(col, dir)` | `?order=col.dir` |
90
+ | `.limit(n)` | `?limit=n` |
91
+ | `.offset(n)` | `?offset=n` |
92
+ | `.count()` | Adds `Prefer: count=exact` |
93
+
94
+ ## Mutations
95
+
96
+ ```typescript
97
+ // Insert
98
+ const { data, error } = await api.from('posts')
99
+ .insert({ body: 'Hello world', authorId: user.sub });
100
+
101
+ // Update
102
+ const { data, error } = await api.from('posts')
103
+ .eq('id', postId)
104
+ .update({ body: 'Updated content' });
105
+
106
+ // Delete
107
+ const { data, error } = await api.from('posts')
108
+ .eq('id', postId)
109
+ .delete();
110
+ ```
111
+
112
+ ## RPC
113
+
114
+ ```typescript
115
+ const { data, error } = await api.rpc('timeline', { limit: 20, offset: 0 });
116
+ const { data } = await api.rpc('search', { query: 'hiking trails', limit: 10 });
117
+ ```
118
+
119
+ ## Response Shape
120
+
121
+ All methods return `{ data, error, count? }`:
122
+
123
+ ```typescript
124
+ interface ApiResponse<T> {
125
+ data: T | null;
126
+ error: { status: number; message: string; details?: string } | null;
127
+ count?: number; // Only with .count()
128
+ }
129
+ ```
130
+
131
+ ## Token Refresh
132
+
133
+ When a request returns 401:
134
+ 1. Client calls `onTokenExpired()` to get a new token
135
+ 2. If multiple concurrent 401s fire, they all wait for the SAME refresh (deduplication)
136
+ 3. Client retries the original request with the new token
137
+ 4. If refresh fails, returns `{ data: null, error: { status: 401, ... } }`
138
+
139
+ ## Gotchas
140
+
141
+ - `.execute()` is required to send the request. The builder is lazy.
142
+ - `.insert()`, `.update()`, `.delete()` send immediately (no `.execute()` needed).
143
+ - Filters on `.update()` and `.delete()` scope which rows are affected.
144
+ - The client auto-adds `Content-Type: application/json` for mutations.
145
+ - `baseUrl` should NOT include a trailing slash.