flingit 0.0.1
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.
- package/README.md +84 -0
- package/dist/cli/commands/db.d.ts +9 -0
- package/dist/cli/commands/db.d.ts.map +1 -0
- package/dist/cli/commands/db.js +215 -0
- package/dist/cli/commands/db.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +6 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +145 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/feedback.d.ts +6 -0
- package/dist/cli/commands/feedback.d.ts.map +1 -0
- package/dist/cli/commands/feedback.js +116 -0
- package/dist/cli/commands/feedback.js.map +1 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +161 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/launch.d.ts +12 -0
- package/dist/cli/commands/launch.d.ts.map +1 -0
- package/dist/cli/commands/launch.js +176 -0
- package/dist/cli/commands/launch.js.map +1 -0
- package/dist/cli/commands/login.d.ts +6 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +90 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/logout.d.ts +6 -0
- package/dist/cli/commands/logout.d.ts.map +1 -0
- package/dist/cli/commands/logout.js +32 -0
- package/dist/cli/commands/logout.js.map +1 -0
- package/dist/cli/commands/logs.d.ts +12 -0
- package/dist/cli/commands/logs.d.ts.map +1 -0
- package/dist/cli/commands/logs.js +261 -0
- package/dist/cli/commands/logs.js.map +1 -0
- package/dist/cli/commands/onboard.d.ts +22 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -0
- package/dist/cli/commands/onboard.js +244 -0
- package/dist/cli/commands/onboard.js.map +1 -0
- package/dist/cli/commands/project.d.ts +6 -0
- package/dist/cli/commands/project.d.ts.map +1 -0
- package/dist/cli/commands/project.js +142 -0
- package/dist/cli/commands/project.js.map +1 -0
- package/dist/cli/commands/push.d.ts +6 -0
- package/dist/cli/commands/push.d.ts.map +1 -0
- package/dist/cli/commands/push.js +351 -0
- package/dist/cli/commands/push.js.map +1 -0
- package/dist/cli/commands/register.d.ts +6 -0
- package/dist/cli/commands/register.d.ts.map +1 -0
- package/dist/cli/commands/register.js +115 -0
- package/dist/cli/commands/register.js.map +1 -0
- package/dist/cli/commands/secret.d.ts +11 -0
- package/dist/cli/commands/secret.d.ts.map +1 -0
- package/dist/cli/commands/secret.js +124 -0
- package/dist/cli/commands/secret.js.map +1 -0
- package/dist/cli/commands/whoami.d.ts +6 -0
- package/dist/cli/commands/whoami.d.ts.map +1 -0
- package/dist/cli/commands/whoami.js +54 -0
- package/dist/cli/commands/whoami.js.map +1 -0
- package/dist/cli/deploy/assets.d.ts +73 -0
- package/dist/cli/deploy/assets.d.ts.map +1 -0
- package/dist/cli/deploy/assets.js +167 -0
- package/dist/cli/deploy/assets.js.map +1 -0
- package/dist/cli/deploy/base64-stream.d.ts +16 -0
- package/dist/cli/deploy/base64-stream.d.ts.map +1 -0
- package/dist/cli/deploy/base64-stream.js +44 -0
- package/dist/cli/deploy/base64-stream.js.map +1 -0
- package/dist/cli/deploy/bucket-stream.d.ts +18 -0
- package/dist/cli/deploy/bucket-stream.d.ts.map +1 -0
- package/dist/cli/deploy/bucket-stream.js +63 -0
- package/dist/cli/deploy/bucket-stream.js.map +1 -0
- package/dist/cli/deploy/bundler.d.ts +22 -0
- package/dist/cli/deploy/bundler.d.ts.map +1 -0
- package/dist/cli/deploy/bundler.js +131 -0
- package/dist/cli/deploy/bundler.js.map +1 -0
- package/dist/cli/deploy/worker-entry.d.ts +14 -0
- package/dist/cli/deploy/worker-entry.d.ts.map +1 -0
- package/dist/cli/deploy/worker-entry.js +60 -0
- package/dist/cli/deploy/worker-entry.js.map +1 -0
- package/dist/cli/deploy/wrangler-config.d.ts +20 -0
- package/dist/cli/deploy/wrangler-config.d.ts.map +1 -0
- package/dist/cli/deploy/wrangler-config.js +54 -0
- package/dist/cli/deploy/wrangler-config.js.map +1 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +72 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/config.d.ts +39 -0
- package/dist/cli/utils/config.d.ts.map +1 -0
- package/dist/cli/utils/config.js +105 -0
- package/dist/cli/utils/config.js.map +1 -0
- package/dist/cli/utils/duration.d.ts +21 -0
- package/dist/cli/utils/duration.d.ts.map +1 -0
- package/dist/cli/utils/duration.js +44 -0
- package/dist/cli/utils/duration.js.map +1 -0
- package/dist/cli/utils/environment.d.ts +17 -0
- package/dist/cli/utils/environment.d.ts.map +1 -0
- package/dist/cli/utils/environment.js +27 -0
- package/dist/cli/utils/environment.js.map +1 -0
- package/dist/cli/utils/project.d.ts +24 -0
- package/dist/cli/utils/project.d.ts.map +1 -0
- package/dist/cli/utils/project.js +47 -0
- package/dist/cli/utils/project.js.map +1 -0
- package/dist/cli/utils/registry.d.ts +34 -0
- package/dist/cli/utils/registry.d.ts.map +1 -0
- package/dist/cli/utils/registry.js +94 -0
- package/dist/cli/utils/registry.js.map +1 -0
- package/dist/cli/utils/token.d.ts +12 -0
- package/dist/cli/utils/token.d.ts.map +1 -0
- package/dist/cli/utils/token.js +27 -0
- package/dist/cli/utils/token.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/cron.d.ts +50 -0
- package/dist/runtime/cron.d.ts.map +1 -0
- package/dist/runtime/cron.js +80 -0
- package/dist/runtime/cron.js.map +1 -0
- package/dist/runtime/db.d.ts +93 -0
- package/dist/runtime/db.d.ts.map +1 -0
- package/dist/runtime/db.js +137 -0
- package/dist/runtime/db.js.map +1 -0
- package/dist/runtime/http.d.ts +33 -0
- package/dist/runtime/http.d.ts.map +1 -0
- package/dist/runtime/http.js +36 -0
- package/dist/runtime/http.js.map +1 -0
- package/dist/runtime/log.d.ts +90 -0
- package/dist/runtime/log.d.ts.map +1 -0
- package/dist/runtime/log.js +211 -0
- package/dist/runtime/log.js.map +1 -0
- package/dist/runtime/migrate.d.ts +54 -0
- package/dist/runtime/migrate.d.ts.map +1 -0
- package/dist/runtime/migrate.js +130 -0
- package/dist/runtime/migrate.js.map +1 -0
- package/dist/runtime/secrets.d.ts +36 -0
- package/dist/runtime/secrets.d.ts.map +1 -0
- package/dist/runtime/secrets.js +75 -0
- package/dist/runtime/secrets.js.map +1 -0
- package/dist/worker-runtime/index.d.ts +79 -0
- package/dist/worker-runtime/index.d.ts.map +1 -0
- package/dist/worker-runtime/index.js +203 -0
- package/dist/worker-runtime/index.js.map +1 -0
- package/package.json +81 -0
- package/templates/default/.nvmrc +1 -0
- package/templates/default/CLAUDE.md +197 -0
- package/templates/default/index.html +13 -0
- package/templates/default/package.json +25 -0
- package/templates/default/public/vite.svg +1 -0
- package/templates/default/skills/fling/API.md +335 -0
- package/templates/default/skills/fling/EXAMPLES.md +204 -0
- package/templates/default/skills/fling/FEEDBACK.md +73 -0
- package/templates/default/skills/fling/SKILL.md +159 -0
- package/templates/default/src/react-app/App.css +34 -0
- package/templates/default/src/react-app/App.tsx +27 -0
- package/templates/default/src/react-app/index.css +15 -0
- package/templates/default/src/react-app/main.tsx +10 -0
- package/templates/default/src/react-app/vite-env.d.ts +1 -0
- package/templates/default/src/worker/index.ts +27 -0
- package/templates/default/tsconfig.app.json +22 -0
- package/templates/default/tsconfig.json +7 -0
- package/templates/default/tsconfig.worker.json +11 -0
- package/templates/default/vite.config.ts +21 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Fling Project
|
|
2
|
+
|
|
3
|
+
This is a Fling project - a personal software platform for building and deploying personal tools with a React frontend and Hono API backend.
|
|
4
|
+
|
|
5
|
+
## IMPORTANT: Do NOT ask about deployment, hosting, or technology choices!
|
|
6
|
+
|
|
7
|
+
Fling handles all of this automatically. When the user asks you to build something:
|
|
8
|
+
1. Write backend code in `src/worker/index.ts` using the Fling API
|
|
9
|
+
2. Write frontend code in `src/react-app/` using React
|
|
10
|
+
3. Run `npm start` to test it locally (you have bash access - run commands yourself!)
|
|
11
|
+
4. When it works, run `npm exec fling push` to deploy
|
|
12
|
+
|
|
13
|
+
**IMPORTANT: Run CLI commands yourself.** You have bash access. Do NOT ask the user to run commands like `fling push`, `fling dev`, or `npm start`. Execute them directly and report the results.
|
|
14
|
+
|
|
15
|
+
## Project Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/
|
|
19
|
+
worker/
|
|
20
|
+
index.ts # Backend API entry point (routes, migrations)
|
|
21
|
+
react-app/
|
|
22
|
+
main.tsx # React entry point
|
|
23
|
+
App.tsx # Main React component
|
|
24
|
+
App.css # Component styles
|
|
25
|
+
index.css # Global styles
|
|
26
|
+
public/ # Static assets (served by Vite)
|
|
27
|
+
index.html # Vite entry HTML
|
|
28
|
+
vite.config.ts # Vite configuration
|
|
29
|
+
.fling/secrets # Local secrets (gitignored)
|
|
30
|
+
.fling/data/ # SQLite database (gitignored)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
|
|
35
|
+
Run `npm start` (or `fling dev`) to start development:
|
|
36
|
+
- **Frontend**: http://localhost:5173 (Vite with React HMR)
|
|
37
|
+
- **API**: http://localhost:3210 (Hono backend)
|
|
38
|
+
- Vite proxies `/api/*` requests to the API server
|
|
39
|
+
|
|
40
|
+
## Fling API (Backend)
|
|
41
|
+
|
|
42
|
+
All primitives are imported from `"fling"` in the backend:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { app, db, migrate, secrets } from "fling";
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### HTTP Routes (Hono)
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// API routes should use /api prefix for Vite proxy
|
|
52
|
+
app.get("/api/hello", (c) => c.json({ message: "Hello!" }));
|
|
53
|
+
app.post("/api/data", async (c) => {
|
|
54
|
+
const body = await c.req.json();
|
|
55
|
+
return c.json({ received: body });
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Migrations
|
|
60
|
+
|
|
61
|
+
**IMPORTANT: Migrations MUST be idempotent** (safe to run multiple times).
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
migrate("001_create_users", async () => {
|
|
65
|
+
await db.prepare(`
|
|
66
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
67
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
68
|
+
name TEXT NOT NULL,
|
|
69
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
70
|
+
)
|
|
71
|
+
`).run();
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Database (SQLite, D1-compatible)
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Query
|
|
79
|
+
const item = await db.prepare("SELECT * FROM items WHERE id = ?").bind(id).first();
|
|
80
|
+
const all = await db.prepare("SELECT * FROM items").all();
|
|
81
|
+
|
|
82
|
+
// Insert/Update/Delete
|
|
83
|
+
await db.prepare("INSERT INTO items (name) VALUES (?)").bind(name).run();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Secrets
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const apiKey = secrets.get("API_KEY"); // Throws if not set
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## React Frontend
|
|
93
|
+
|
|
94
|
+
The frontend is a standard React + Vite setup. Call the backend API:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// In React components
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
fetch("/api/hello")
|
|
100
|
+
.then(res => res.json())
|
|
101
|
+
.then(data => setMessage(data.message));
|
|
102
|
+
}, []);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## CLI Commands
|
|
106
|
+
|
|
107
|
+
Always use `npm exec` to run the project's Fling:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npm exec fling dev # Start local dev server (API + Vite)
|
|
111
|
+
npm exec fling dev -- --port 8080 # Custom API port
|
|
112
|
+
npm exec fling db sql "SELECT * FROM users" # Query local SQLite
|
|
113
|
+
npm exec fling db reset # Wipe local database
|
|
114
|
+
npm exec fling db sql "SELECT * FROM users" # Query local SQLite
|
|
115
|
+
npm exec fling secret set K=V # Set local secret
|
|
116
|
+
npm exec fling secret list # List local secrets
|
|
117
|
+
npm exec fling logs # View local logs
|
|
118
|
+
npm exec fling push # Build and deploy to production
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Local vs Production (`--prod`)
|
|
122
|
+
|
|
123
|
+
Most CLI commands operate on the **local** environment by default. Add `--prod` for production (logs and db only):
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Local (default)
|
|
127
|
+
npm exec fling secret list # Local secrets
|
|
128
|
+
npm exec fling logs # Local logs
|
|
129
|
+
npm exec fling db sql "SELECT 1" # Local SQLite
|
|
130
|
+
|
|
131
|
+
# Production (requires login)
|
|
132
|
+
npm exec fling -- --prod logs # Deployed logs
|
|
133
|
+
npm exec fling -- --prod db sql "SELECT 1" # Deployed D1
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Secrets:** Always stored locally in `.fling/secrets`. Use `fling push` to deploy secrets to production along with your code.
|
|
137
|
+
|
|
138
|
+
## Deployment
|
|
139
|
+
|
|
140
|
+
`fling push` handles everything automatically:
|
|
141
|
+
|
|
142
|
+
1. Builds React frontend with Vite → `dist/client/`
|
|
143
|
+
2. Uploads static assets (HTML, JS, CSS, images, fonts)
|
|
144
|
+
3. Bundles backend code with esbuild
|
|
145
|
+
4. Deploys to Cloudflare Workers
|
|
146
|
+
|
|
147
|
+
**Limits:** 25MB per file, 100MB total assets.
|
|
148
|
+
|
|
149
|
+
**Routing in production:**
|
|
150
|
+
- `/api/*` → Backend worker code
|
|
151
|
+
- Everything else → Static assets from `dist/client/`
|
|
152
|
+
- SPA fallback: Unknown paths serve `index.html`
|
|
153
|
+
|
|
154
|
+
## Platform Limitations
|
|
155
|
+
|
|
156
|
+
Be aware of these constraints when building:
|
|
157
|
+
|
|
158
|
+
### Feature Scope
|
|
159
|
+
- **Supported:** Frontend (React), backend (Hono API), database (SQLite/D1)
|
|
160
|
+
- **Not yet supported:** Cron jobs, file/blob storage, websockets
|
|
161
|
+
- If the user needs unsupported features, encourage them to send feedback via `npm exec fling feedback`
|
|
162
|
+
|
|
163
|
+
### Backend Memory (128MB limit)
|
|
164
|
+
- The backend runs in a Cloudflare Worker with ~128MB memory
|
|
165
|
+
- Cannot process large datasets in memory all at once
|
|
166
|
+
- For large data: use streaming, pagination, or chunked processing
|
|
167
|
+
- Example: process files line-by-line instead of loading entirely into memory
|
|
168
|
+
|
|
169
|
+
### Cloudflare Workers Runtime
|
|
170
|
+
- Workers do NOT have the full Node.js API
|
|
171
|
+
- Some npm packages won't work (those requiring `fs`, `path`, `child_process`, native modules)
|
|
172
|
+
- Must use Workers-compatible packages (check package docs for "Cloudflare Workers" or "edge runtime" support)
|
|
173
|
+
- Web standard APIs work fine (fetch, crypto.subtle, TextEncoder, etc.)
|
|
174
|
+
|
|
175
|
+
**If the user's request might hit these limitations, warn them early and suggest alternatives.**
|
|
176
|
+
|
|
177
|
+
## Key Points
|
|
178
|
+
|
|
179
|
+
- **Don't ask about hosting** - Fling handles deployment
|
|
180
|
+
- **Don't ask about databases** - Use `db` from fling
|
|
181
|
+
- **Don't suggest external services** - Fling provides what you need
|
|
182
|
+
- **Use /api prefix** - For Vite proxy to work in development
|
|
183
|
+
- **Run commands yourself** - You have bash access, don't ask the user to run CLI commands
|
|
184
|
+
|
|
185
|
+
## After First Deployment
|
|
186
|
+
|
|
187
|
+
After your first `fling push`, consider asking the user if they want a custom URL.
|
|
188
|
+
Their project was auto-assigned a slug like `proj-abc123`.
|
|
189
|
+
|
|
190
|
+
To change it: `npm exec fling project slug:set my-custom-slug`
|
|
191
|
+
|
|
192
|
+
Slugs must be:
|
|
193
|
+
- More than 4 characters
|
|
194
|
+
- Lowercase alphanumeric with optional hyphens
|
|
195
|
+
- Globally unique across all Fling projects
|
|
196
|
+
|
|
197
|
+
See `.claude/skills/fling/` for detailed API documentation.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Fling App</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/react-app/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fling-project",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "fling dev",
|
|
7
|
+
"start": "fling dev",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"flingit": "^0.0.1",
|
|
13
|
+
"react": "^18.3.1",
|
|
14
|
+
"react-dom": "^18.3.1"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"vite": "^6.0.5",
|
|
18
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
19
|
+
"@tailwindcss/vite": "^4",
|
|
20
|
+
"tailwindcss": "^4",
|
|
21
|
+
"@types/react": "^18.3.12",
|
|
22
|
+
"@types/react-dom": "^18.3.1",
|
|
23
|
+
"typescript": "~5.6.2"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFBD4F"></stop><stop offset="100%" stop-color="#FF980E"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Fling API Reference
|
|
2
|
+
|
|
3
|
+
## HTTP (Hono)
|
|
4
|
+
|
|
5
|
+
Fling uses [Hono](https://hono.dev) for HTTP routing. The `app` export is a configured Hono instance.
|
|
6
|
+
|
|
7
|
+
### Basic Routes
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { app } from "fling";
|
|
11
|
+
|
|
12
|
+
// GET request
|
|
13
|
+
app.get("/path", (c) => {
|
|
14
|
+
return c.text("Plain text response");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// POST request with JSON body
|
|
18
|
+
app.post("/api/items", async (c) => {
|
|
19
|
+
const body = await c.req.json();
|
|
20
|
+
return c.json({ id: 1, ...body });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// URL parameters
|
|
24
|
+
app.get("/items/:id", (c) => {
|
|
25
|
+
const id = c.req.param("id");
|
|
26
|
+
return c.json({ id });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Query parameters
|
|
30
|
+
app.get("/search", (c) => {
|
|
31
|
+
const q = c.req.query("q");
|
|
32
|
+
return c.json({ query: q });
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Response Types
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Plain text
|
|
40
|
+
return c.text("Hello");
|
|
41
|
+
|
|
42
|
+
// JSON
|
|
43
|
+
return c.json({ key: "value" });
|
|
44
|
+
|
|
45
|
+
// HTML
|
|
46
|
+
return c.html("<h1>Hello</h1>");
|
|
47
|
+
|
|
48
|
+
// Custom content type
|
|
49
|
+
return c.text(xmlContent, 200, { "Content-Type": "application/rss+xml" });
|
|
50
|
+
|
|
51
|
+
// Binary data
|
|
52
|
+
return c.body(buffer, 200, { "Content-Type": "image/png" });
|
|
53
|
+
|
|
54
|
+
// Status codes
|
|
55
|
+
return c.text("Not found", 404);
|
|
56
|
+
return c.json({ error: "Bad request" }, 400);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Headers and Cookies
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// Read headers
|
|
63
|
+
const auth = c.req.header("Authorization");
|
|
64
|
+
|
|
65
|
+
// Set headers
|
|
66
|
+
return c.text("OK", 200, { "X-Custom": "value" });
|
|
67
|
+
|
|
68
|
+
// Cookies
|
|
69
|
+
const session = c.req.cookie("session");
|
|
70
|
+
c.cookie("session", "value", { httpOnly: true });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Migrations
|
|
74
|
+
|
|
75
|
+
Database migrations run automatically on app startup. Each migration executes exactly once and is tracked in the `_migrations` table.
|
|
76
|
+
|
|
77
|
+
### Basic Usage
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { migrate, db } from "fling";
|
|
81
|
+
|
|
82
|
+
// Migrations run in alphabetical order by name
|
|
83
|
+
migrate("001_create_users", async () => {
|
|
84
|
+
await db.prepare(`
|
|
85
|
+
CREATE TABLE users (
|
|
86
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
87
|
+
name TEXT NOT NULL,
|
|
88
|
+
email TEXT UNIQUE,
|
|
89
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
90
|
+
)
|
|
91
|
+
`).run();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
migrate("002_create_posts", async () => {
|
|
95
|
+
await db.prepare(`
|
|
96
|
+
CREATE TABLE posts (
|
|
97
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
98
|
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
99
|
+
title TEXT NOT NULL,
|
|
100
|
+
content TEXT,
|
|
101
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
102
|
+
)
|
|
103
|
+
`).run();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
migrate("003_add_user_avatar", async () => {
|
|
107
|
+
await db.prepare(`ALTER TABLE users ADD COLUMN avatar_url TEXT`).run();
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Naming Convention
|
|
112
|
+
|
|
113
|
+
Use numeric prefixes to ensure correct execution order:
|
|
114
|
+
- `001_create_users`
|
|
115
|
+
- `002_create_posts`
|
|
116
|
+
- `003_add_user_avatar`
|
|
117
|
+
- `010_add_comments` (leaves room for insertions)
|
|
118
|
+
|
|
119
|
+
Migrations run in **alphabetical order**, so numeric prefixes guarantee the intended sequence.
|
|
120
|
+
|
|
121
|
+
### Startup Sequence
|
|
122
|
+
|
|
123
|
+
When your app starts:
|
|
124
|
+
|
|
125
|
+
1. **Import phase**: Routes and migrations are registered (not executed)
|
|
126
|
+
2. **Migration phase**: All pending migrations run in order
|
|
127
|
+
3. **Server phase**: HTTP server begins accepting requests
|
|
128
|
+
|
|
129
|
+
**Critical**: The HTTP server does NOT start until all migrations complete successfully. If any migration fails, the entire startup aborts.
|
|
130
|
+
|
|
131
|
+
### Failure Behavior
|
|
132
|
+
|
|
133
|
+
If a migration fails:
|
|
134
|
+
- A detailed error is logged with the migration name and stack trace
|
|
135
|
+
- The failed migration is NOT recorded (allowing retry on restart)
|
|
136
|
+
- The server does NOT start
|
|
137
|
+
- Previously successful migrations in the same run ARE recorded
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
============================================================
|
|
141
|
+
MIGRATION FAILED: 003_add_user_avatar
|
|
142
|
+
============================================================
|
|
143
|
+
Type: SqliteError
|
|
144
|
+
Message: duplicate column name: avatar_url
|
|
145
|
+
Code: SQLITE_ERROR
|
|
146
|
+
|
|
147
|
+
Stack trace:
|
|
148
|
+
SqliteError: duplicate column name: avatar_url
|
|
149
|
+
at Database.prepare (...)
|
|
150
|
+
at Object.handler (file:///path/to/src/app.ts:25:9)
|
|
151
|
+
at runMigrations (...)
|
|
152
|
+
============================================================
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The error output includes:
|
|
156
|
+
- **Type**: The error class (e.g., `SqliteError`)
|
|
157
|
+
- **Message**: Human-readable description
|
|
158
|
+
- **Code**: SQLite error code (e.g., `SQLITE_ERROR`, `SQLITE_CONSTRAINT_UNIQUE`)
|
|
159
|
+
- **Stack trace**: Points to the exact line that failed
|
|
160
|
+
|
|
161
|
+
### Idempotent Migrations
|
|
162
|
+
|
|
163
|
+
Each migration runs exactly once. Use `IF NOT EXISTS` and `IF EXISTS` for safety:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
migrate("001_create_users", async () => {
|
|
167
|
+
// Safe to run multiple times if migration tracking fails
|
|
168
|
+
await db.prepare(`
|
|
169
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
170
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
171
|
+
name TEXT NOT NULL
|
|
172
|
+
)
|
|
173
|
+
`).run();
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Data Migrations
|
|
178
|
+
|
|
179
|
+
Migrations can also modify data:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
migrate("004_normalize_emails", async () => {
|
|
183
|
+
await db.prepare(`
|
|
184
|
+
UPDATE users SET email = LOWER(email) WHERE email IS NOT NULL
|
|
185
|
+
`).run();
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Important Notes
|
|
190
|
+
|
|
191
|
+
- Migrations MUST be declared at module top-level (not inside functions)
|
|
192
|
+
- Each migration must have a unique name
|
|
193
|
+
- Always use `await db.prepare(...).run()` for DDL statements (not `db.exec()` - it doesn't work in production D1)
|
|
194
|
+
- Tables starting with `_` are reserved for the system (`_migrations`, `_fling_logs`)
|
|
195
|
+
- Do NOT perform database operations at module top-level outside of migrations
|
|
196
|
+
- The `_migrations` table is created automatically on first migration
|
|
197
|
+
|
|
198
|
+
### Migration vs Top-Level Code
|
|
199
|
+
|
|
200
|
+
**Wrong** - DB access at module load time:
|
|
201
|
+
```typescript
|
|
202
|
+
// DON'T DO THIS - runs during import, before migrations
|
|
203
|
+
const users = await db.prepare("SELECT * FROM users").all();
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Correct** - DB access in migrations and handlers:
|
|
207
|
+
```typescript
|
|
208
|
+
// Migrations run at startup in correct order
|
|
209
|
+
migrate("001_create_users", async () => {
|
|
210
|
+
await db.prepare(`CREATE TABLE users ...`).run();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Route handlers run after migrations complete
|
|
214
|
+
app.get("/users", async (c) => {
|
|
215
|
+
const { results } = await db.prepare("SELECT * FROM users").all();
|
|
216
|
+
return c.json(results);
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Database
|
|
221
|
+
|
|
222
|
+
SQLite locally, Cloudflare D1 in production. API matches D1.
|
|
223
|
+
|
|
224
|
+
### Queries
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { db } from "fling";
|
|
228
|
+
|
|
229
|
+
// Single row
|
|
230
|
+
const user = await db.prepare("SELECT * FROM users WHERE id = ?")
|
|
231
|
+
.bind(userId)
|
|
232
|
+
.first();
|
|
233
|
+
// Returns: { id: 1, name: "Alice" } or null
|
|
234
|
+
|
|
235
|
+
// All rows
|
|
236
|
+
const users = await db.prepare("SELECT * FROM users ORDER BY name")
|
|
237
|
+
.all();
|
|
238
|
+
// Returns: { results: [{ id: 1, name: "Alice" }, ...] }
|
|
239
|
+
|
|
240
|
+
// With multiple parameters
|
|
241
|
+
const items = await db.prepare("SELECT * FROM items WHERE status = ? AND category = ?")
|
|
242
|
+
.bind("active", "tools")
|
|
243
|
+
.all();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Mutations
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Insert
|
|
250
|
+
const result = await db.prepare("INSERT INTO users (name, email) VALUES (?, ?)")
|
|
251
|
+
.bind("Alice", "alice@example.com")
|
|
252
|
+
.run();
|
|
253
|
+
// Returns: { success: true, meta: { changes: 1, last_row_id: 5 } }
|
|
254
|
+
|
|
255
|
+
// Update
|
|
256
|
+
await db.prepare("UPDATE users SET name = ? WHERE id = ?")
|
|
257
|
+
.bind("Bob", 1)
|
|
258
|
+
.run();
|
|
259
|
+
|
|
260
|
+
// Delete
|
|
261
|
+
await db.prepare("DELETE FROM users WHERE id = ?")
|
|
262
|
+
.bind(1)
|
|
263
|
+
.run();
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Schema (DDL)
|
|
267
|
+
|
|
268
|
+
Schema changes should be done in migrations (see Migrations section above):
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
migrate("001_create_users", async () => {
|
|
272
|
+
await db.prepare(`
|
|
273
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
274
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
275
|
+
name TEXT NOT NULL,
|
|
276
|
+
email TEXT UNIQUE,
|
|
277
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
278
|
+
)
|
|
279
|
+
`).run();
|
|
280
|
+
|
|
281
|
+
await db.prepare(`
|
|
282
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)
|
|
283
|
+
`).run();
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Important**: Always use `db.prepare(...).run()` for DDL statements. The `db.exec()` method does not work in production D1 databases.
|
|
288
|
+
|
|
289
|
+
### Important Notes
|
|
290
|
+
|
|
291
|
+
- Schema changes should be done in migrations (see Migrations section)
|
|
292
|
+
- Database queries should be in route handlers, not at module top-level
|
|
293
|
+
- Use parameterized queries (?) to prevent SQL injection
|
|
294
|
+
- `first()` returns null if no row found (undefined in local SQLite)
|
|
295
|
+
- `all()` always returns `{ results: [...] }`
|
|
296
|
+
- Tables starting with `_` are reserved for the system
|
|
297
|
+
|
|
298
|
+
## Secrets
|
|
299
|
+
|
|
300
|
+
Secure credential management. Throws if secret not set.
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { secrets } from "fling";
|
|
304
|
+
|
|
305
|
+
// Get a secret (throws if not set)
|
|
306
|
+
const apiKey = secrets.get("API_KEY");
|
|
307
|
+
|
|
308
|
+
// Check if secret exists
|
|
309
|
+
if (secrets.has("OPTIONAL_KEY")) {
|
|
310
|
+
const key = secrets.get("OPTIONAL_KEY");
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Managing Secrets
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# Set/manage secrets locally
|
|
318
|
+
fling secret set GITHUB_TOKEN=ghp_xxxx # Set a secret
|
|
319
|
+
fling secret list # List secret names
|
|
320
|
+
fling secret remove GITHUB_TOKEN # Remove a secret
|
|
321
|
+
|
|
322
|
+
# Secrets are deployed automatically with:
|
|
323
|
+
fling push
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Secrets are always stored locally in `.fling/secrets`. When you run `fling push`, secrets are automatically synced to production along with your code.
|
|
327
|
+
|
|
328
|
+
### Naming Convention
|
|
329
|
+
|
|
330
|
+
Secret names must be uppercase with underscores:
|
|
331
|
+
- `API_KEY` ✓
|
|
332
|
+
- `GITHUB_TOKEN` ✓
|
|
333
|
+
- `apiKey` ✗
|
|
334
|
+
- `github-token` ✗
|
|
335
|
+
|