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.
Files changed (161) hide show
  1. package/README.md +84 -0
  2. package/dist/cli/commands/db.d.ts +9 -0
  3. package/dist/cli/commands/db.d.ts.map +1 -0
  4. package/dist/cli/commands/db.js +215 -0
  5. package/dist/cli/commands/db.js.map +1 -0
  6. package/dist/cli/commands/dev.d.ts +6 -0
  7. package/dist/cli/commands/dev.d.ts.map +1 -0
  8. package/dist/cli/commands/dev.js +145 -0
  9. package/dist/cli/commands/dev.js.map +1 -0
  10. package/dist/cli/commands/feedback.d.ts +6 -0
  11. package/dist/cli/commands/feedback.d.ts.map +1 -0
  12. package/dist/cli/commands/feedback.js +116 -0
  13. package/dist/cli/commands/feedback.js.map +1 -0
  14. package/dist/cli/commands/init.d.ts +6 -0
  15. package/dist/cli/commands/init.d.ts.map +1 -0
  16. package/dist/cli/commands/init.js +161 -0
  17. package/dist/cli/commands/init.js.map +1 -0
  18. package/dist/cli/commands/launch.d.ts +12 -0
  19. package/dist/cli/commands/launch.d.ts.map +1 -0
  20. package/dist/cli/commands/launch.js +176 -0
  21. package/dist/cli/commands/launch.js.map +1 -0
  22. package/dist/cli/commands/login.d.ts +6 -0
  23. package/dist/cli/commands/login.d.ts.map +1 -0
  24. package/dist/cli/commands/login.js +90 -0
  25. package/dist/cli/commands/login.js.map +1 -0
  26. package/dist/cli/commands/logout.d.ts +6 -0
  27. package/dist/cli/commands/logout.d.ts.map +1 -0
  28. package/dist/cli/commands/logout.js +32 -0
  29. package/dist/cli/commands/logout.js.map +1 -0
  30. package/dist/cli/commands/logs.d.ts +12 -0
  31. package/dist/cli/commands/logs.d.ts.map +1 -0
  32. package/dist/cli/commands/logs.js +261 -0
  33. package/dist/cli/commands/logs.js.map +1 -0
  34. package/dist/cli/commands/onboard.d.ts +22 -0
  35. package/dist/cli/commands/onboard.d.ts.map +1 -0
  36. package/dist/cli/commands/onboard.js +244 -0
  37. package/dist/cli/commands/onboard.js.map +1 -0
  38. package/dist/cli/commands/project.d.ts +6 -0
  39. package/dist/cli/commands/project.d.ts.map +1 -0
  40. package/dist/cli/commands/project.js +142 -0
  41. package/dist/cli/commands/project.js.map +1 -0
  42. package/dist/cli/commands/push.d.ts +6 -0
  43. package/dist/cli/commands/push.d.ts.map +1 -0
  44. package/dist/cli/commands/push.js +351 -0
  45. package/dist/cli/commands/push.js.map +1 -0
  46. package/dist/cli/commands/register.d.ts +6 -0
  47. package/dist/cli/commands/register.d.ts.map +1 -0
  48. package/dist/cli/commands/register.js +115 -0
  49. package/dist/cli/commands/register.js.map +1 -0
  50. package/dist/cli/commands/secret.d.ts +11 -0
  51. package/dist/cli/commands/secret.d.ts.map +1 -0
  52. package/dist/cli/commands/secret.js +124 -0
  53. package/dist/cli/commands/secret.js.map +1 -0
  54. package/dist/cli/commands/whoami.d.ts +6 -0
  55. package/dist/cli/commands/whoami.d.ts.map +1 -0
  56. package/dist/cli/commands/whoami.js +54 -0
  57. package/dist/cli/commands/whoami.js.map +1 -0
  58. package/dist/cli/deploy/assets.d.ts +73 -0
  59. package/dist/cli/deploy/assets.d.ts.map +1 -0
  60. package/dist/cli/deploy/assets.js +167 -0
  61. package/dist/cli/deploy/assets.js.map +1 -0
  62. package/dist/cli/deploy/base64-stream.d.ts +16 -0
  63. package/dist/cli/deploy/base64-stream.d.ts.map +1 -0
  64. package/dist/cli/deploy/base64-stream.js +44 -0
  65. package/dist/cli/deploy/base64-stream.js.map +1 -0
  66. package/dist/cli/deploy/bucket-stream.d.ts +18 -0
  67. package/dist/cli/deploy/bucket-stream.d.ts.map +1 -0
  68. package/dist/cli/deploy/bucket-stream.js +63 -0
  69. package/dist/cli/deploy/bucket-stream.js.map +1 -0
  70. package/dist/cli/deploy/bundler.d.ts +22 -0
  71. package/dist/cli/deploy/bundler.d.ts.map +1 -0
  72. package/dist/cli/deploy/bundler.js +131 -0
  73. package/dist/cli/deploy/bundler.js.map +1 -0
  74. package/dist/cli/deploy/worker-entry.d.ts +14 -0
  75. package/dist/cli/deploy/worker-entry.d.ts.map +1 -0
  76. package/dist/cli/deploy/worker-entry.js +60 -0
  77. package/dist/cli/deploy/worker-entry.js.map +1 -0
  78. package/dist/cli/deploy/wrangler-config.d.ts +20 -0
  79. package/dist/cli/deploy/wrangler-config.d.ts.map +1 -0
  80. package/dist/cli/deploy/wrangler-config.js +54 -0
  81. package/dist/cli/deploy/wrangler-config.js.map +1 -0
  82. package/dist/cli/index.d.ts +10 -0
  83. package/dist/cli/index.d.ts.map +1 -0
  84. package/dist/cli/index.js +72 -0
  85. package/dist/cli/index.js.map +1 -0
  86. package/dist/cli/utils/config.d.ts +39 -0
  87. package/dist/cli/utils/config.d.ts.map +1 -0
  88. package/dist/cli/utils/config.js +105 -0
  89. package/dist/cli/utils/config.js.map +1 -0
  90. package/dist/cli/utils/duration.d.ts +21 -0
  91. package/dist/cli/utils/duration.d.ts.map +1 -0
  92. package/dist/cli/utils/duration.js +44 -0
  93. package/dist/cli/utils/duration.js.map +1 -0
  94. package/dist/cli/utils/environment.d.ts +17 -0
  95. package/dist/cli/utils/environment.d.ts.map +1 -0
  96. package/dist/cli/utils/environment.js +27 -0
  97. package/dist/cli/utils/environment.js.map +1 -0
  98. package/dist/cli/utils/project.d.ts +24 -0
  99. package/dist/cli/utils/project.d.ts.map +1 -0
  100. package/dist/cli/utils/project.js +47 -0
  101. package/dist/cli/utils/project.js.map +1 -0
  102. package/dist/cli/utils/registry.d.ts +34 -0
  103. package/dist/cli/utils/registry.d.ts.map +1 -0
  104. package/dist/cli/utils/registry.js +94 -0
  105. package/dist/cli/utils/registry.js.map +1 -0
  106. package/dist/cli/utils/token.d.ts +12 -0
  107. package/dist/cli/utils/token.d.ts.map +1 -0
  108. package/dist/cli/utils/token.js +27 -0
  109. package/dist/cli/utils/token.js.map +1 -0
  110. package/dist/index.d.ts +12 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +12 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/runtime/cron.d.ts +50 -0
  115. package/dist/runtime/cron.d.ts.map +1 -0
  116. package/dist/runtime/cron.js +80 -0
  117. package/dist/runtime/cron.js.map +1 -0
  118. package/dist/runtime/db.d.ts +93 -0
  119. package/dist/runtime/db.d.ts.map +1 -0
  120. package/dist/runtime/db.js +137 -0
  121. package/dist/runtime/db.js.map +1 -0
  122. package/dist/runtime/http.d.ts +33 -0
  123. package/dist/runtime/http.d.ts.map +1 -0
  124. package/dist/runtime/http.js +36 -0
  125. package/dist/runtime/http.js.map +1 -0
  126. package/dist/runtime/log.d.ts +90 -0
  127. package/dist/runtime/log.d.ts.map +1 -0
  128. package/dist/runtime/log.js +211 -0
  129. package/dist/runtime/log.js.map +1 -0
  130. package/dist/runtime/migrate.d.ts +54 -0
  131. package/dist/runtime/migrate.d.ts.map +1 -0
  132. package/dist/runtime/migrate.js +130 -0
  133. package/dist/runtime/migrate.js.map +1 -0
  134. package/dist/runtime/secrets.d.ts +36 -0
  135. package/dist/runtime/secrets.d.ts.map +1 -0
  136. package/dist/runtime/secrets.js +75 -0
  137. package/dist/runtime/secrets.js.map +1 -0
  138. package/dist/worker-runtime/index.d.ts +79 -0
  139. package/dist/worker-runtime/index.d.ts.map +1 -0
  140. package/dist/worker-runtime/index.js +203 -0
  141. package/dist/worker-runtime/index.js.map +1 -0
  142. package/package.json +81 -0
  143. package/templates/default/.nvmrc +1 -0
  144. package/templates/default/CLAUDE.md +197 -0
  145. package/templates/default/index.html +13 -0
  146. package/templates/default/package.json +25 -0
  147. package/templates/default/public/vite.svg +1 -0
  148. package/templates/default/skills/fling/API.md +335 -0
  149. package/templates/default/skills/fling/EXAMPLES.md +204 -0
  150. package/templates/default/skills/fling/FEEDBACK.md +73 -0
  151. package/templates/default/skills/fling/SKILL.md +159 -0
  152. package/templates/default/src/react-app/App.css +34 -0
  153. package/templates/default/src/react-app/App.tsx +27 -0
  154. package/templates/default/src/react-app/index.css +15 -0
  155. package/templates/default/src/react-app/main.tsx +10 -0
  156. package/templates/default/src/react-app/vite-env.d.ts +1 -0
  157. package/templates/default/src/worker/index.ts +27 -0
  158. package/templates/default/tsconfig.app.json +22 -0
  159. package/templates/default/tsconfig.json +7 -0
  160. package/templates/default/tsconfig.worker.json +11 -0
  161. 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
+