@everystack/mcp 0.2.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 (40) hide show
  1. package/README.md +100 -0
  2. package/package.json +39 -0
  3. package/src/index.ts +58 -0
  4. package/src/prompts/add-feature.ts +163 -0
  5. package/src/prompts/debug.ts +136 -0
  6. package/src/prompts/deploy.ts +131 -0
  7. package/src/prompts/design-schema.ts +104 -0
  8. package/src/prompts/index.ts +16 -0
  9. package/src/prompts/new-app.ts +211 -0
  10. package/src/prompts/secure.ts +231 -0
  11. package/src/resources/adding-database.md +169 -0
  12. package/src/resources/admin.md +81 -0
  13. package/src/resources/auth.md +115 -0
  14. package/src/resources/aws-setup.md +173 -0
  15. package/src/resources/cli.md +108 -0
  16. package/src/resources/client-api.md +145 -0
  17. package/src/resources/core.md +196 -0
  18. package/src/resources/deployment.md +146 -0
  19. package/src/resources/events.md +87 -0
  20. package/src/resources/first-run.md +100 -0
  21. package/src/resources/getting-started.md +75 -0
  22. package/src/resources/handler-options.md +114 -0
  23. package/src/resources/images.md +73 -0
  24. package/src/resources/index.ts +224 -0
  25. package/src/resources/jobs.md +97 -0
  26. package/src/resources/logging.md +91 -0
  27. package/src/resources/plugins.md +68 -0
  28. package/src/resources/project-claude-md.md +127 -0
  29. package/src/resources/query-protocol.md +129 -0
  30. package/src/resources/schema-patterns.md +167 -0
  31. package/src/resources/security-device.md +99 -0
  32. package/src/resources/security.md +270 -0
  33. package/src/resources/ssr.md +82 -0
  34. package/src/resources/storage.md +63 -0
  35. package/src/resources/testing.md +118 -0
  36. package/src/tools/check-environment.ts +319 -0
  37. package/src/tools/index.ts +58 -0
  38. package/src/tools/project-status.ts +183 -0
  39. package/src/tools/project-validate.ts +369 -0
  40. package/src/tools/schema-analyze.ts +410 -0
@@ -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.
@@ -0,0 +1,196 @@
1
+ # everystack
2
+
3
+ Self-hosted application stack for Expo apps on AWS. Start with a static site, add database/auth/jobs as you grow. Each tier builds on the previous -- you never rip out what you have, you add to it.
4
+
5
+ ## Tiers
6
+
7
+ ### V1: Static Site (no database)
8
+ - Expo app exported to static assets, served from CloudFront
9
+ - Lambda SSR for SEO (server-rendered HTML with OG meta, JSON-LD)
10
+ - OTA updates via `everystack update` (instant deploys without redeploying infrastructure)
11
+ - **Packages:** `@everystack/server`, `@everystack/cli`, `@everystack/ui`
12
+ - **Infrastructure:** S3 + CloudFront + Lambda + S3 (updates bucket)
13
+
14
+ ### V2: Dynamic App (database + auth)
15
+ - PostgreSQL via RDS Aurora Serverless
16
+ - PostgREST-compatible REST API (filters, pagination, relations, RPC)
17
+ - JWT auth (signup, signin, refresh, edge verification)
18
+ - Admin dashboard (config-driven, EverystackAdapter)
19
+ - Structured logging and analytics
20
+ - **Adds:** `@everystack/api`, `@everystack/auth`, `@everystack/admin`, `@everystack/logging`, `@everystack/query`, `@everystack/security`
21
+ - **Infrastructure:** V1 + RDS Aurora Serverless
22
+
23
+ ### V3: Full Platform (jobs + media)
24
+ - SQS background workers with dead letter queue
25
+ - On-demand image resizing via Sharp on Lambda
26
+ - S3 file uploads with presigned URLs and CDN delivery
27
+ - **Adds:** `@everystack/jobs`, `@everystack/storage`, `@everystack/images`
28
+ - **Infrastructure:** V2 + SQS + Worker Lambda + Image Lambda + Media S3
29
+
30
+ ## Package Map
31
+
32
+ | Package | Purpose |
33
+ |---------|---------|
34
+ | `@everystack/server` | Lambda handler, router, DB connection, SSR, image processing, worker, plugin system |
35
+ | `@everystack/api` | PostgREST-compatible handler (`createHandler`) + typed client (`createClient`) |
36
+ | `@everystack/auth` | JWT auth flows, OAuth, password hashing, edge verification, React AuthProvider |
37
+ | `@everystack/cli` | CLI binary (`everystack`), OTA updates handler, storage adapters |
38
+ | `@everystack/admin` | Declarative admin dashboard, EverystackAdapter, presets |
39
+ | `@everystack/logging` | Structured logging, crash reports, analytics, S3 storage, CloudWatch trigger |
40
+ | `@everystack/query` | React Query hooks for PostgREST APIs |
41
+ | `@everystack/security` | Device attestation (Apple/Google), biometric auth, RS256 keys |
42
+ | `@everystack/jobs` | SQS background workers, job dispatch, Postgres materialization |
43
+ | `@everystack/storage` | S3 uploads, presigned URLs, MIME validation, ownership enforcement |
44
+ | `@everystack/images` | Sharp image processing, variant generation, EXIF extraction |
45
+ | `@everystack/events` | PostgreSQL LISTEN/NOTIFY, WebSocket fan-out, Lambda event bridge |
46
+ | `@everystack/ui` | React Native components, charts, MDX support |
47
+
48
+ ## Core Insight: Two Paths, One Database
49
+
50
+ The central design decision (V2+). SSR and mobile share the same PostgreSQL database but access it differently:
51
+
52
+ - **Mobile app** -> HTTP -> PostgREST handler -> Drizzle -> PostgreSQL
53
+ - **SSR loader** -> Drizzle directly -> PostgreSQL (same Lambda process, zero network overhead)
54
+
55
+ Both paths use the same Drizzle schema as source of truth. One deployment unit (single Lambda), shared connection pool, no inter-service latency.
56
+
57
+ ## Project Structure Conventions
58
+
59
+ ```
60
+ my-app/
61
+ ├── app/ # Expo Router pages + API routes
62
+ │ ├── api/
63
+ │ │ └── [...path]+api.ts # Catch-all API route (mounts handler)
64
+ │ ├── admin/ # Admin dashboard pages
65
+ │ └── (tabs)/ # App screens
66
+ ├── db/
67
+ │ └── schema.ts # Drizzle schema (your tables)
68
+ ├── drizzle/ # SQL migration files (generated by drizzle-kit)
69
+ ├── server/
70
+ │ ├── api.ts # Main Lambda handler (createLambdaHandler)
71
+ │ ├── worker.ts # Background worker handler (V3)
72
+ │ ├── image.ts # Image processing handler (V3)
73
+ │ ├── rpc/ # Custom RPC functions
74
+ │ └── plugins/ # Plugin compositions
75
+ ├── lib/
76
+ │ ├── api.ts # Client re-exports (createClient config)
77
+ │ └── auth-context.tsx # Auth context provider
78
+ ├── sst.config.ts # Infrastructure definition
79
+ ├── app.json # Expo config
80
+ └── package.json
81
+ ```
82
+
83
+ ## Handler Quick Reference
84
+
85
+ ```typescript
86
+ import { createHandler } from '@everystack/api/handler';
87
+
88
+ const handler = createHandler(db, schema, {
89
+ basePath: '/api', // URL prefix to strip
90
+ auth: {
91
+ verifyToken: async (token) => payload, // JWT verification
92
+ publicRoutes: ['GET'], // Methods that skip auth
93
+ publicRpc: ['health'], // RPC functions that skip auth
94
+ roleHierarchy: ['public', 'authenticated', 'admin'],
95
+ },
96
+ pgSettings: (user, client) => ({ // RLS context injection
97
+ role: user?.role === 'admin' ? 'admin' : user ? 'authenticated' : 'anon',
98
+ 'request.jwt.claims': JSON.stringify(user || { role: 'anon' }),
99
+ }),
100
+ relations: { posts: { author: { table: 'users', from: 'authorId', to: 'id' } } },
101
+ rpc: { timeline: { fn: async (body, user) => {}, role: 'authenticated' } },
102
+ exposedTables: ['posts', 'profiles'], // Whitelist (404 for others)
103
+ hiddenColumns: { users: ['passwordHash'] }, // Strip from responses
104
+ protectedFields: { profiles: ['role'] }, // Strip from writes
105
+ rowOwnership: { posts: { column: 'authorId', userField: 'sub' } },
106
+ hooks: { posts: { beforeCreate: async (body, user) => ({ ...body, authorId: user?.sub }) } },
107
+ softDelete: { column: 'deletedAt', tables: ['posts'] },
108
+ maxEmbedDepth: 3,
109
+ maxLimit: 1000,
110
+ });
111
+ ```
112
+
113
+ Returns `(request: Request) => Promise<Response>` -- Web Standard interface. Works with Expo Router, Cloudflare Workers, Deno, Bun.
114
+
115
+ ## Lambda Handler
116
+
117
+ ```typescript
118
+ import { createLambdaHandler } from '@everystack/server';
119
+
120
+ export const handler = createLambdaHandler({
121
+ init: async () => ({ api, auth }), // Lazy init, returns handler map
122
+ routes: (h) => [ // Route dispatch
123
+ { path: '/api/auth/signup', method: 'POST', exact: true, handler: h.signup },
124
+ { path: '/api', handler: h.api }, // Catch-all for PostgREST
125
+ ],
126
+ onAction: async (action, payload) => { // CLI invocations via IAM
127
+ if (action === 'migrate') return runMigrations();
128
+ if (action === 'seed') return runSeed();
129
+ },
130
+ });
131
+ ```
132
+
133
+ ## Client
134
+
135
+ ```typescript
136
+ import { createClient } from '@everystack/api/client';
137
+
138
+ const api = createClient({
139
+ baseUrl: '/api',
140
+ getToken: () => token,
141
+ onTokenExpired: async () => newToken, // Auto-refresh with deduplication
142
+ });
143
+
144
+ // Queries
145
+ const { data } = await api.from('posts').eq('status', 'published').order('createdAt', 'desc').limit(10).execute();
146
+ const { data } = await api.from('posts').select('*,author(email)').execute();
147
+
148
+ // Mutations
149
+ await api.from('posts').insert({ body: 'Hello', authorId: user.sub });
150
+ await api.from('posts').eq('id', 1).update({ body: 'Updated' });
151
+ await api.from('posts').eq('id', 1).delete();
152
+
153
+ // RPC
154
+ const { data } = await api.rpc('timeline', { limit: 20 });
155
+ ```
156
+
157
+ ## CLI Commands
158
+
159
+ All infrastructure commands use **AWS IAM credentials** (not shared secrets).
160
+
161
+ | Command | What it does |
162
+ |---------|-------------|
163
+ | `everystack update --channel production` | OTA deploy (no infrastructure changes) |
164
+ | `everystack db:migrate` | Run Drizzle migrations via Lambda invoke |
165
+ | `everystack db:seed` | Seed database via Lambda invoke (dev only) |
166
+ | `everystack db:psql --stage dev -c "SQL"` | Execute read-only SQL via Lambda |
167
+ | `everystack console --stage dev` | Interactive REPL with db + schema in scope |
168
+ | `everystack logs:errors --stage dev` | Query recent error logs |
169
+ | `everystack logs:tail --stage dev` | Tail CloudWatch Lambda logs |
170
+ | `everystack logs:query --stage dev` | Flexible log search with filters |
171
+ | `everystack cache:purge` | Bust CloudFront cache via KVS |
172
+ | `everystack certs:generate` | Generate RSA key pair for code signing |
173
+ | `everystack diag URL` | Diagnose deployed page freshness |
174
+ | `everystack analyze:ssr` | Static analysis for SSR anti-patterns |
175
+
176
+ ## Key Patterns
177
+
178
+ **Schema-agnostic.** The library knows nothing about your tables. You pass your Drizzle schema to `createHandler()`. Your schema, your migrations, your database.
179
+
180
+ **Web Standards.** Handler uses Request/Response. No Express, no Fastify. Works with any Web Standard runtime.
181
+
182
+ **SST for infrastructure.** Resource linking injects secrets at deploy time (no env vars). `_action` dispatch lets the CLI invoke Lambda directly via IAM.
183
+
184
+ **Progressive complexity.** Install only what you need. V1 has zero database code. V2 adds it. V3 adds workers. Each tier is additive.
185
+
186
+ **Plugin architecture.** Packages provide plugins (`@everystack/auth/plugin`, `@everystack/jobs/plugin`) that compose into a single Lambda handler via `createPluginLambdaHandler`.
187
+
188
+ ## Conventions
189
+
190
+ - TypeScript strict mode, named exports only (no default exports)
191
+ - Files: `kebab-case.ts`. Types: `PascalCase`. Functions: `camelCase`
192
+ - Tests in `__tests__/` mirroring source, named `{feature}.test.ts`
193
+ - Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `test:`
194
+ - TDD: write failing test first, implement, refactor
195
+ - Zero runtime dependencies in the API library
196
+ - Peer dependencies for optional integrations
@@ -0,0 +1,146 @@
1
+ # Deployment Guide
2
+
3
+ > SST deployment: infrastructure setup, secrets, stages, CloudFront configuration.
4
+
5
+ ## When to Use
6
+ Read this when deploying an everystack app to AWS for the first time or adding a new stage.
7
+
8
+ ## Prerequisites
9
+
10
+ - Node.js 20+, pnpm
11
+ - AWS account with credentials (`aws configure`)
12
+ - SST CLI (`pnpm add -g sst`)
13
+
14
+ ## SST Config
15
+
16
+ ```typescript
17
+ // sst.config.ts
18
+ export default $config({
19
+ app(input) {
20
+ return {
21
+ name: 'my-app',
22
+ removal: input?.stage === 'production' ? 'retain' : 'remove',
23
+ home: 'aws',
24
+ };
25
+ },
26
+ async run() {
27
+ // V1: Static site
28
+ const updates = new sst.aws.Bucket('Updates');
29
+
30
+ // V2: Add database
31
+ const vpc = new sst.aws.Vpc('Vpc');
32
+ const database = new sst.aws.Postgres('Database', {
33
+ vpc,
34
+ scaling: { min: '0.5 ACU', max: '2 ACU' },
35
+ });
36
+
37
+ // V3: Add queues + media
38
+ const media = new sst.aws.Bucket('Media');
39
+ const dlq = new sst.aws.Queue('DeadLetterQueue');
40
+ const jobs = new sst.aws.Queue('Jobs', { dlq: dlq.arn });
41
+
42
+ // Lambda functions
43
+ const api = new sst.aws.Function('Api', {
44
+ handler: 'server/api.handler',
45
+ link: [database, updates],
46
+ vpc,
47
+ });
48
+
49
+ // V3: Worker + Image Lambdas
50
+ const worker = new sst.aws.Function('Worker', {
51
+ handler: 'server/worker.handler',
52
+ link: [database, jobs, media],
53
+ vpc,
54
+ });
55
+
56
+ // CDN
57
+ const router = new sst.aws.Router('Router', {
58
+ routes: { '/*': api.url },
59
+ });
60
+
61
+ // CLI needs these outputs
62
+ return {
63
+ routerUrl: router.url,
64
+ apiFunctionName: api.name,
65
+ updatesBucket: updates.name,
66
+ clientBundlesBucket: updates.name,
67
+ };
68
+ },
69
+ });
70
+ ```
71
+
72
+ ## Secrets
73
+
74
+ SST secrets are per-stage, encrypted, stored in S3:
75
+
76
+ ```bash
77
+ pnpm sst secret set JwtSecret "$(openssl rand -base64 32)" --stage dev
78
+ pnpm sst secret set JwtSecret "$(openssl rand -base64 64)" --stage production
79
+ ```
80
+
81
+ Access in code via Resource linking: `Resource.JwtSecret.value`. Never in env vars or code.
82
+
83
+ ## Stages
84
+
85
+ ```bash
86
+ pnpm sst deploy --stage dev # Development
87
+ pnpm sst deploy --stage production # Production
88
+ ```
89
+
90
+ Each stage gets isolated resources (separate database, buckets, Lambda functions). Use `removal: 'retain'` for production to prevent accidental deletion.
91
+
92
+ ## Deploy Flow
93
+
94
+ ```bash
95
+ # 1. Export Expo app
96
+ pnpm export
97
+
98
+ # 2. Deploy infrastructure + code
99
+ pnpm sst deploy --stage dev
100
+
101
+ # 3. Run migrations
102
+ everystack db:migrate
103
+
104
+ # 4. Seed database (dev only)
105
+ everystack db:seed
106
+
107
+ # 5. Subsequent content updates (no deploy needed)
108
+ everystack update --channel production
109
+ ```
110
+
111
+ ## Resource Linking
112
+
113
+ SST injects resource details into Lambda at deploy time:
114
+
115
+ ```typescript
116
+ import { Resource } from 'sst';
117
+
118
+ // Database credentials (automatic from sst.aws.Postgres)
119
+ Resource.Database.host
120
+ Resource.Database.port
121
+ Resource.Database.database
122
+ Resource.Database.username
123
+ Resource.Database.password
124
+
125
+ // Secrets
126
+ Resource.JwtSecret.value
127
+
128
+ // Buckets
129
+ Resource.Updates.name
130
+ Resource.Media.name
131
+ ```
132
+
133
+ No env vars needed. `@everystack/server/db` provides `createDb()` which reads these automatically.
134
+
135
+ ## AWS IAM Profiles
136
+
137
+ See everystack://security for the three-profile model (everystack-create, everystack-manage, everystack-deploy).
138
+
139
+ ## Gotchas
140
+
141
+ - First deploy creates VPC + RDS (can take 10-15 minutes)
142
+ - Database credentials are managed by SST/RDS (no manual password management)
143
+ - `sst dev` provides live Lambda debugging with hot reload
144
+ - `removal: 'remove'` deletes all resources when the stage is removed
145
+ - `removal: 'retain'` keeps RDS and S3 even if the stage is removed
146
+ - Outputs are written to `.sst/outputs.json` (used by CLI for auto-discovery)
@@ -0,0 +1,87 @@
1
+ # Events & WebSocket
2
+
3
+ > Real-time events via PostgreSQL LISTEN/NOTIFY and WebSocket fan-out. Import from `@everystack/events`.
4
+
5
+ ## When to Use
6
+ Read this when adding real-time updates: live feeds, notifications, collaborative editing.
7
+
8
+ ## Setup
9
+
10
+ ```typescript
11
+ import { createEventHandler } from '@everystack/events/handler';
12
+
13
+ const events = createEventHandler({
14
+ db,
15
+ channels: ['table:posts', 'table:profiles', 'user:notifications'],
16
+ });
17
+ ```
18
+
19
+ ## How It Works
20
+
21
+ 1. Database change triggers NOTIFY on a channel
22
+ 2. Listener Lambda receives the notification
23
+ 3. Fan-out via WebSocket to connected clients
24
+ 4. Client `useSignal` hook fires callback
25
+
26
+ ## Client Hook
27
+
28
+ ```typescript
29
+ import { useSignal } from '@everystack/events/client';
30
+
31
+ function PostList() {
32
+ const [posts, setPosts] = useState(initialPosts);
33
+
34
+ useSignal('table:posts', () => {
35
+ // Refetch when posts table changes
36
+ refetchPosts().then(setPosts);
37
+ });
38
+
39
+ return <FlatList data={posts} />;
40
+ }
41
+ ```
42
+
43
+ ## Database Trigger
44
+
45
+ ```sql
46
+ CREATE OR REPLACE FUNCTION notify_table_change()
47
+ RETURNS trigger AS $$
48
+ BEGIN
49
+ PERFORM pg_notify('table:' || TG_TABLE_NAME, json_build_object(
50
+ 'operation', TG_OP,
51
+ 'id', COALESCE(NEW.id, OLD.id)
52
+ )::text);
53
+ RETURN COALESCE(NEW, OLD);
54
+ END;
55
+ $$ LANGUAGE plpgsql;
56
+
57
+ CREATE TRIGGER posts_notify
58
+ AFTER INSERT OR UPDATE OR DELETE ON posts
59
+ FOR EACH ROW EXECUTE FUNCTION notify_table_change();
60
+ ```
61
+
62
+ ## Actions
63
+
64
+ ```typescript
65
+ import { createEventActions } from '@everystack/events/actions';
66
+
67
+ const actions = createEventActions({
68
+ invokeFunction: Resource.ListenerFunction.name,
69
+ });
70
+
71
+ // Trigger from API handler
72
+ await actions.notify('user:notifications', { userId: 'abc', message: 'New follower' });
73
+ ```
74
+
75
+ ## Schema
76
+
77
+ ```typescript
78
+ import { eventsSchema } from '@everystack/events/schema';
79
+ // Adds: event_subscriptions table for persistent subscriptions
80
+ ```
81
+
82
+ ## Gotchas
83
+
84
+ - LISTEN/NOTIFY is per-database-connection (not per-Lambda)
85
+ - The listener Lambda must maintain a persistent connection
86
+ - WebSocket connections are managed separately from HTTP
87
+ - Payload size limit for NOTIFY is 8000 bytes
@@ -0,0 +1,100 @@
1
+ # Running Your App Locally
2
+
3
+ You have a project. Now let's see it work.
4
+
5
+ ## Starting the dev server
6
+
7
+ Run this command from your project directory:
8
+
9
+ ```bash
10
+ npx expo start
11
+ ```
12
+
13
+ Or if you have pnpm installed:
14
+
15
+ ```bash
16
+ pnpm dev
17
+ ```
18
+
19
+ ## What you'll see
20
+
21
+ The terminal will show output like this:
22
+
23
+ ```
24
+ Starting Metro Bundler
25
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
26
+ █ QR CODE HERE █
27
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
28
+
29
+ › Press w │ open web
30
+ › Press i │ open iOS simulator
31
+ › Press a │ open Android emulator
32
+ › Press r │ reload app
33
+ › Press j │ open debugger
34
+ ```
35
+
36
+ **Metro Bundler** is the tool that packages your code so a browser or phone can run it. It starts automatically.
37
+
38
+ ## Opening in a browser
39
+
40
+ Press **w** in the terminal. Your browser will open to something like `http://localhost:8081`. You should see your app's home screen.
41
+
42
+ If `w` doesn't work, open your browser manually and go to `http://localhost:8081`.
43
+
44
+ ## Opening on your phone
45
+
46
+ Install the **Expo Go** app from the App Store (iPhone) or Google Play (Android). Then scan the QR code shown in the terminal with your phone's camera. The app will load on your phone over your local Wi-Fi network.
47
+
48
+ Your phone and computer must be on the same Wi-Fi network for this to work.
49
+
50
+ ## Hot reload
51
+
52
+ When you change a file and save it, the app updates automatically. You don't need to restart anything. This is called **hot reload**.
53
+
54
+ Try it: open `app/index.tsx` in your editor, change some text, save the file, and watch the browser or phone update.
55
+
56
+ ## Stopping the server
57
+
58
+ Press **Ctrl+C** in the terminal. This stops the dev server. Your app will stop working in the browser until you start it again.
59
+
60
+ ## Common issues
61
+
62
+ ### "Port 8081 is already in use"
63
+
64
+ Something else is using that port. Either stop the other program, or start Expo on a different port:
65
+
66
+ ```bash
67
+ npx expo start --port 8082
68
+ ```
69
+
70
+ ### "Unable to resolve module"
71
+
72
+ A package is missing. Run:
73
+
74
+ ```bash
75
+ pnpm install
76
+ ```
77
+
78
+ Then try starting again.
79
+
80
+ ### The app is stuck or shows an old version
81
+
82
+ Clear the Metro cache and restart:
83
+
84
+ ```bash
85
+ npx expo start --clear
86
+ ```
87
+
88
+ ### "Network response timed out" on phone
89
+
90
+ Your phone and computer aren't on the same network, or a firewall is blocking the connection. Make sure both are on the same Wi-Fi.
91
+
92
+ ### White screen or "Something went wrong"
93
+
94
+ Check the terminal for red error text. The error message will tell you what's wrong. Copy the error and ask Claude for help.
95
+
96
+ ## What's next
97
+
98
+ Once your app runs locally and you can see changes in real time, you're ready to start building. Describe what you want to add and Claude will help you implement it.
99
+
100
+ When you're ready to put your app on the internet so others can use it, you'll set up AWS and deploy. That's a separate step — there's no rush. Build locally first, deploy when you're ready.