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,204 @@
|
|
|
1
|
+
# Fling Examples
|
|
2
|
+
|
|
3
|
+
Common patterns for building personal tools with Fling.
|
|
4
|
+
|
|
5
|
+
## Simple API with Authentication
|
|
6
|
+
|
|
7
|
+
Protected endpoints with API key auth.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { app, db, secrets } from "fling";
|
|
11
|
+
|
|
12
|
+
const API_KEY = secrets.get("API_KEY");
|
|
13
|
+
|
|
14
|
+
// Auth middleware
|
|
15
|
+
function requireAuth(c: any, next: () => Promise<void>) {
|
|
16
|
+
const auth = c.req.header("Authorization");
|
|
17
|
+
if (auth !== `Bearer ${API_KEY}`) {
|
|
18
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
19
|
+
}
|
|
20
|
+
return next();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Public endpoint
|
|
24
|
+
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
25
|
+
|
|
26
|
+
// Protected endpoints
|
|
27
|
+
app.post("/api/items", requireAuth, async (c) => {
|
|
28
|
+
const { name, value } = await c.req.json();
|
|
29
|
+
|
|
30
|
+
const result = await db.prepare(
|
|
31
|
+
"INSERT INTO items (name, value) VALUES (?, ?)"
|
|
32
|
+
).bind(name, value).run();
|
|
33
|
+
|
|
34
|
+
console.log("Created item", name, result.meta.last_row_id);
|
|
35
|
+
|
|
36
|
+
return c.json({
|
|
37
|
+
id: result.meta.last_row_id,
|
|
38
|
+
name,
|
|
39
|
+
value
|
|
40
|
+
}, 201);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
app.get("/api/items", requireAuth, async (c) => {
|
|
44
|
+
const { results } = await db.prepare("SELECT * FROM items").all();
|
|
45
|
+
return c.json(results);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
app.get("/api/items/:id", requireAuth, async (c) => {
|
|
49
|
+
const id = c.req.param("id");
|
|
50
|
+
const item = await db.prepare(
|
|
51
|
+
"SELECT * FROM items WHERE id = ?"
|
|
52
|
+
).bind(id).first();
|
|
53
|
+
|
|
54
|
+
if (!item) {
|
|
55
|
+
return c.json({ error: "Not found" }, 404);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return c.json(item);
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Webhook Receiver
|
|
63
|
+
|
|
64
|
+
Process incoming webhooks and store data.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { app, db, secrets } from "fling";
|
|
68
|
+
|
|
69
|
+
async function initDb() {
|
|
70
|
+
await db.prepare(`
|
|
71
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
72
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
73
|
+
source TEXT NOT NULL,
|
|
74
|
+
type TEXT,
|
|
75
|
+
payload TEXT NOT NULL,
|
|
76
|
+
received_at TEXT DEFAULT (datetime('now'))
|
|
77
|
+
)
|
|
78
|
+
`).run();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Verify GitHub webhook signature using Web Crypto API
|
|
82
|
+
async function verifyGitHubSignature(body: string, signature: string, secret: string): Promise<boolean> {
|
|
83
|
+
const encoder = new TextEncoder();
|
|
84
|
+
const key = await crypto.subtle.importKey(
|
|
85
|
+
"raw",
|
|
86
|
+
encoder.encode(secret),
|
|
87
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
88
|
+
false,
|
|
89
|
+
["sign"]
|
|
90
|
+
);
|
|
91
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
92
|
+
const expected = "sha256=" + Array.from(new Uint8Array(sig))
|
|
93
|
+
.map(b => b.toString(16).padStart(2, "0")).join("");
|
|
94
|
+
return signature === expected;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// GitHub webhook
|
|
98
|
+
app.post("/webhooks/github", async (c) => {
|
|
99
|
+
const signature = c.req.header("X-Hub-Signature-256");
|
|
100
|
+
const event = c.req.header("X-GitHub-Event");
|
|
101
|
+
const body = await c.req.text();
|
|
102
|
+
|
|
103
|
+
// Optional: Verify signature (uncomment to enable)
|
|
104
|
+
// const secret = secrets.get("GITHUB_WEBHOOK_SECRET");
|
|
105
|
+
// if (signature && !await verifyGitHubSignature(body, signature, secret)) {
|
|
106
|
+
// return c.text("Invalid signature", 401);
|
|
107
|
+
// }
|
|
108
|
+
|
|
109
|
+
await db.prepare(
|
|
110
|
+
"INSERT INTO events (source, type, payload) VALUES (?, ?, ?)"
|
|
111
|
+
).bind("github", event, body).run();
|
|
112
|
+
|
|
113
|
+
console.log("GitHub webhook received", event);
|
|
114
|
+
|
|
115
|
+
return c.json({ ok: true });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Generic webhook
|
|
119
|
+
app.post("/webhooks/:source", async (c) => {
|
|
120
|
+
const source = c.req.param("source");
|
|
121
|
+
const body = await c.req.text();
|
|
122
|
+
|
|
123
|
+
await db.prepare(
|
|
124
|
+
"INSERT INTO events (source, payload) VALUES (?, ?)"
|
|
125
|
+
).bind(source, body).run();
|
|
126
|
+
|
|
127
|
+
console.log("Webhook received", source);
|
|
128
|
+
|
|
129
|
+
return c.json({ ok: true });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// View recent events
|
|
133
|
+
app.get("/events", async (c) => {
|
|
134
|
+
const { results } = await db.prepare(
|
|
135
|
+
"SELECT * FROM events ORDER BY received_at DESC LIMIT 100"
|
|
136
|
+
).all();
|
|
137
|
+
return c.json(results);
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Data Sync API
|
|
142
|
+
|
|
143
|
+
Pull data from an external API and store it.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { app, db, secrets, migrate } from "fling";
|
|
147
|
+
|
|
148
|
+
const API_TOKEN = secrets.get("DATA_API_TOKEN");
|
|
149
|
+
|
|
150
|
+
migrate("001_create_sync_data", async () => {
|
|
151
|
+
await db.prepare(`
|
|
152
|
+
CREATE TABLE IF NOT EXISTS sync_data (
|
|
153
|
+
id TEXT PRIMARY KEY,
|
|
154
|
+
data TEXT NOT NULL,
|
|
155
|
+
synced_at TEXT DEFAULT (datetime('now'))
|
|
156
|
+
)
|
|
157
|
+
`).run();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Sync data from external API
|
|
161
|
+
async function syncData() {
|
|
162
|
+
console.log("Starting data sync");
|
|
163
|
+
|
|
164
|
+
const response = await fetch("https://api.example.com/data", {
|
|
165
|
+
headers: { "Authorization": `Bearer ${API_TOKEN}` }
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
throw new Error(`Sync failed: ${response.status}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const items = await response.json();
|
|
173
|
+
let synced = 0;
|
|
174
|
+
|
|
175
|
+
for (const item of items) {
|
|
176
|
+
await db.prepare(
|
|
177
|
+
"INSERT OR REPLACE INTO sync_data (id, data, synced_at) VALUES (?, ?, datetime('now'))"
|
|
178
|
+
).bind(item.id, JSON.stringify(item)).run();
|
|
179
|
+
synced++;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log("Sync completed", synced);
|
|
183
|
+
return synced;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// View synced data
|
|
187
|
+
app.get("/api/data", async (c) => {
|
|
188
|
+
const { results } = await db.prepare(
|
|
189
|
+
"SELECT * FROM sync_data ORDER BY synced_at DESC"
|
|
190
|
+
).all();
|
|
191
|
+
|
|
192
|
+
return c.json(results.map((r: any) => ({
|
|
193
|
+
id: r.id,
|
|
194
|
+
data: JSON.parse(r.data),
|
|
195
|
+
synced_at: r.synced_at
|
|
196
|
+
})));
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Trigger sync via API
|
|
200
|
+
app.post("/api/sync", async (c) => {
|
|
201
|
+
const count = await syncData();
|
|
202
|
+
return c.json({ message: "Sync completed", count });
|
|
203
|
+
});
|
|
204
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Feedback Skill
|
|
2
|
+
|
|
3
|
+
This skill helps you collect and submit user feedback about Fling to the team.
|
|
4
|
+
|
|
5
|
+
## When to Offer Feedback
|
|
6
|
+
|
|
7
|
+
Proactively offer to send feedback in these situations:
|
|
8
|
+
|
|
9
|
+
1. **User frustration** - When the user expresses frustration with Fling
|
|
10
|
+
2. **Product limitations** - When Fling can't do something the user needs
|
|
11
|
+
3. **After difficulties** - When you've helped resolve a confusing issue
|
|
12
|
+
4. **Feature requests** - When the user wishes Fling could do something differently
|
|
13
|
+
|
|
14
|
+
## Critical Rule: Always Ask First
|
|
15
|
+
|
|
16
|
+
**NEVER send feedback without explicit user approval.** Always:
|
|
17
|
+
|
|
18
|
+
1. Ask if they'd like to send feedback
|
|
19
|
+
2. Show them exactly what will be sent
|
|
20
|
+
3. Wait for their confirmation before running the command
|
|
21
|
+
|
|
22
|
+
Example flow:
|
|
23
|
+
```
|
|
24
|
+
User: "This is so frustrating, the logs command never shows what I need!"
|
|
25
|
+
|
|
26
|
+
You: "I understand that's frustrating. Would you like me to send this feedback
|
|
27
|
+
to the Fling team? Here's what I'd send:
|
|
28
|
+
|
|
29
|
+
'The logs command output doesn't include enough context. When debugging
|
|
30
|
+
deployment issues, users need to see request IDs and timestamps together.'
|
|
31
|
+
|
|
32
|
+
Should I submit this?"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Command Usage
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Simple feedback (single line)
|
|
39
|
+
npm exec fling feedback "Your feedback message here"
|
|
40
|
+
|
|
41
|
+
# Interactive mode (for longer, multi-line feedback)
|
|
42
|
+
npm exec fling feedback -i
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The user must be logged in (`npm exec fling login`) for feedback to work.
|
|
46
|
+
|
|
47
|
+
## Writing Good Feedback
|
|
48
|
+
|
|
49
|
+
Help users write **actionable** feedback:
|
|
50
|
+
|
|
51
|
+
**Good feedback includes:**
|
|
52
|
+
- What they were trying to do
|
|
53
|
+
- What happened vs. what they expected
|
|
54
|
+
- Specific details (commands, error messages)
|
|
55
|
+
|
|
56
|
+
**Less useful feedback:**
|
|
57
|
+
- Vague complaints ("this is broken")
|
|
58
|
+
- No context about the situation
|
|
59
|
+
- Just emotional venting without specifics
|
|
60
|
+
|
|
61
|
+
## Example Good Feedback
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
"When running 'fling push' after adding a new secret, the deployment succeeds
|
|
65
|
+
but the secret isn't available until a second deploy. Expected the secret to
|
|
66
|
+
be available immediately. Workaround: run 'fling push' twice."
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Feedback Requirements
|
|
70
|
+
|
|
71
|
+
- Minimum 10 characters (to encourage meaningful feedback)
|
|
72
|
+
- Maximum 5000 characters
|
|
73
|
+
- User must be logged in to the platform
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Fling Skill
|
|
2
|
+
|
|
3
|
+
You are working on a Fling project - a personal software platform for building and deploying personal tools with a React frontend and Hono API backend.
|
|
4
|
+
|
|
5
|
+
## Core Concepts
|
|
6
|
+
|
|
7
|
+
Fling provides four primitives that work identically in local development and production:
|
|
8
|
+
|
|
9
|
+
1. **HTTP** - Expose endpoints via Hono
|
|
10
|
+
2. **Database** - SQLite locally, D1 in production
|
|
11
|
+
3. **Secrets** - Secure credential management
|
|
12
|
+
4. **Migrations** - Version your database schema
|
|
13
|
+
|
|
14
|
+
## Project Structure
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
src/
|
|
18
|
+
worker/
|
|
19
|
+
index.ts # Backend API entry point
|
|
20
|
+
react-app/
|
|
21
|
+
main.tsx # React entry point
|
|
22
|
+
App.tsx # Main React component
|
|
23
|
+
App.css # Component styles
|
|
24
|
+
index.css # Global styles
|
|
25
|
+
public/ # Static assets (served by Vite)
|
|
26
|
+
index.html # Vite entry HTML
|
|
27
|
+
vite.config.ts # Vite configuration
|
|
28
|
+
.fling/
|
|
29
|
+
secrets # Local secrets (gitignored)
|
|
30
|
+
data/
|
|
31
|
+
local.db # SQLite database (gitignored)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Reference
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// Backend (src/worker/index.ts)
|
|
38
|
+
import { app, db, secrets } from "fling";
|
|
39
|
+
|
|
40
|
+
// HTTP routes - use /api prefix for Vite proxy
|
|
41
|
+
app.get("/api/hello", (c) => c.json({ message: "Hello!" }));
|
|
42
|
+
app.post("/api/webhook", async (c) => {
|
|
43
|
+
const body = await c.req.json();
|
|
44
|
+
return c.json({ ok: true });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Database (D1-compatible API)
|
|
48
|
+
const row = await db.prepare("SELECT * FROM items WHERE id = ?").bind(id).first();
|
|
49
|
+
const all = await db.prepare("SELECT * FROM items").all();
|
|
50
|
+
await db.prepare("INSERT INTO items (name) VALUES (?)").bind(name).run();
|
|
51
|
+
await db.prepare("CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)").run();
|
|
52
|
+
|
|
53
|
+
// Secrets (throws if not set)
|
|
54
|
+
const token = secrets.get("API_TOKEN");
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Frontend (src/react-app/App.tsx)
|
|
59
|
+
import { useState, useEffect } from "react";
|
|
60
|
+
|
|
61
|
+
function App() {
|
|
62
|
+
const [data, setData] = useState(null);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
fetch("/api/hello")
|
|
66
|
+
.then(res => res.json())
|
|
67
|
+
.then(setData);
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
return <div>{data?.message}</div>;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## CLI Commands
|
|
75
|
+
|
|
76
|
+
Always use `npm exec` to run the project's installed Fling (not global):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm exec fling dev # Start local server (API + Vite)
|
|
80
|
+
npm exec fling dev -- --port 8080 # Custom API port
|
|
81
|
+
npm exec fling db sql "SELECT * FROM users" # Query local SQLite
|
|
82
|
+
npm exec fling db reset # Wipe local database
|
|
83
|
+
npm exec fling db sql "SELECT 1" # Query local SQLite
|
|
84
|
+
npm exec fling secret set KEY=value
|
|
85
|
+
npm exec fling secret list
|
|
86
|
+
npm exec fling secret remove KEY
|
|
87
|
+
npm exec fling logs # View local logs
|
|
88
|
+
npm exec fling push # Build and deploy to Cloudflare Workers
|
|
89
|
+
npm exec fling project slug # Show current project slug and URL
|
|
90
|
+
npm exec fling project slug:set <new-slug> # Change project slug (affects URL)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Local vs Production (`--prod`)
|
|
94
|
+
|
|
95
|
+
Commands default to local environment. Use `--prod` for production:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Local (default)
|
|
99
|
+
npm exec fling secret list # Local secrets
|
|
100
|
+
npm exec fling logs # Local logs
|
|
101
|
+
npm exec fling db sql "SELECT 1" # Local SQLite
|
|
102
|
+
|
|
103
|
+
# Production (requires login)
|
|
104
|
+
npm exec fling -- --prod secret list # Deployed secrets
|
|
105
|
+
npm exec fling -- --prod logs # Deployed logs
|
|
106
|
+
npm exec fling -- --prod db sql "SELECT 1" # Deployed D1
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
Run `npm start` (or `fling dev`) to start development:
|
|
112
|
+
- **Frontend**: http://localhost:5173 (Vite with React HMR)
|
|
113
|
+
- **API**: http://localhost:3210 (Hono backend)
|
|
114
|
+
- Vite proxies `/api/*` requests to the API server
|
|
115
|
+
|
|
116
|
+
## Deployment
|
|
117
|
+
|
|
118
|
+
When the user's request is complete and working locally, deploy it:
|
|
119
|
+
**Run `npm exec fling push` directly** - you have bash access, don't ask the user to run commands.
|
|
120
|
+
|
|
121
|
+
### What `fling push` does:
|
|
122
|
+
|
|
123
|
+
1. **Builds frontend** - Runs `vite build`, outputs to `dist/client/`
|
|
124
|
+
2. **Uploads static assets** - HTML, JS, CSS, images from `dist/client/`
|
|
125
|
+
3. **Bundles backend** - Compiles `src/worker/index.ts` with esbuild
|
|
126
|
+
4. **Deploys to Cloudflare** - Both frontend and backend go live
|
|
127
|
+
|
|
128
|
+
### Static Asset Limits
|
|
129
|
+
|
|
130
|
+
- **25MB** per file maximum
|
|
131
|
+
- **100MB** total assets maximum
|
|
132
|
+
- Supported: HTML, CSS, JS, images, fonts, video, audio, WebAssembly
|
|
133
|
+
- MIME types are detected automatically from file extensions
|
|
134
|
+
|
|
135
|
+
### Routing
|
|
136
|
+
|
|
137
|
+
- `/api/*` routes are handled by your backend code
|
|
138
|
+
- All other paths serve static assets from `dist/client/`
|
|
139
|
+
- If no asset matches, `index.html` is served (SPA fallback)
|
|
140
|
+
|
|
141
|
+
## Important Constraints
|
|
142
|
+
|
|
143
|
+
1. **Backend code runs in Cloudflare Workers** - This is NOT a Node.js environment. You cannot use Node.js-specific APIs (`fs`, `path`, `child_process`, etc.) or npm packages that depend on them. Only use packages that explicitly support Cloudflare Workers or are pure JavaScript/TypeScript with no Node.js dependencies.
|
|
144
|
+
|
|
145
|
+
2. **Memory limit (~128MB)** - Workers have limited memory. Cannot process large datasets in memory. Use streaming, pagination, or chunked processing for large data.
|
|
146
|
+
|
|
147
|
+
3. **Feature scope** - Fling supports frontend, backend, and database. Not yet supported: cron jobs, file/blob storage, websockets. If users need these, encourage feedback via `npm exec fling feedback`.
|
|
148
|
+
|
|
149
|
+
4. **Database operations cannot be at module top-level** - They must be inside route handlers or functions called by them.
|
|
150
|
+
|
|
151
|
+
5. **Secrets throw on missing** - No default values. Use `fling secret set` to configure.
|
|
152
|
+
|
|
153
|
+
6. **Use /api prefix for backend routes** - Required for Vite proxy during development.
|
|
154
|
+
|
|
155
|
+
7. **Run commands yourself** - You have bash access. Don't ask the user to run `fling push`, `fling dev`, `npm start`, etc. Execute them directly.
|
|
156
|
+
|
|
157
|
+
**If the user's request might hit platform limitations, warn them early and suggest alternatives.**
|
|
158
|
+
|
|
159
|
+
See API.md for detailed API reference and EXAMPLES.md for common patterns.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.app {
|
|
2
|
+
max-width: 600px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
padding: 2rem;
|
|
5
|
+
text-align: center;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
h1 {
|
|
9
|
+
font-size: 2.5rem;
|
|
10
|
+
margin-bottom: 1rem;
|
|
11
|
+
color: #333;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.api-message {
|
|
15
|
+
font-size: 1.25rem;
|
|
16
|
+
color: #0066cc;
|
|
17
|
+
background: #f0f8ff;
|
|
18
|
+
padding: 1rem;
|
|
19
|
+
border-radius: 8px;
|
|
20
|
+
margin: 1.5rem 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.hint {
|
|
24
|
+
color: #666;
|
|
25
|
+
font-size: 0.9rem;
|
|
26
|
+
line-height: 1.6;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.hint code {
|
|
30
|
+
background: #f4f4f4;
|
|
31
|
+
padding: 0.2rem 0.4rem;
|
|
32
|
+
border-radius: 4px;
|
|
33
|
+
font-family: monospace;
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import "./App.css";
|
|
3
|
+
|
|
4
|
+
function App() {
|
|
5
|
+
const [message, setMessage] = useState<string>("Loading...");
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
fetch("/api/hello")
|
|
9
|
+
.then((res) => res.json())
|
|
10
|
+
.then((data: { message: string }) => setMessage(data.message))
|
|
11
|
+
.catch(() => setMessage("Failed to connect to API"));
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="app">
|
|
16
|
+
<h1>Fling App</h1>
|
|
17
|
+
<p className="api-message">{message}</p>
|
|
18
|
+
<p className="hint">
|
|
19
|
+
Edit <code>src/react-app/App.tsx</code> for the frontend
|
|
20
|
+
<br />
|
|
21
|
+
Edit <code>src/worker/index.ts</code> for the API
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default App;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
5
|
+
Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue",
|
|
6
|
+
sans-serif;
|
|
7
|
+
color: #213547;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
min-height: 100vh;
|
|
12
|
+
display: flex;
|
|
13
|
+
place-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fling Backend Worker
|
|
3
|
+
*
|
|
4
|
+
* This is your backend API entry point. Define HTTP routes and database
|
|
5
|
+
* migrations here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { app, migrate, db } from "fling";
|
|
9
|
+
|
|
10
|
+
// Database migrations - run automatically on startup
|
|
11
|
+
// IMPORTANT: Migrations MUST be idempotent (safe to run multiple times).
|
|
12
|
+
// Due to distributed execution, a migration might run more than once.
|
|
13
|
+
// Use "CREATE TABLE IF NOT EXISTS", "CREATE INDEX IF NOT EXISTS", etc.
|
|
14
|
+
migrate("001_create_example", async () => {
|
|
15
|
+
await db.prepare(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS example (
|
|
17
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
18
|
+
name TEXT NOT NULL,
|
|
19
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
20
|
+
)
|
|
21
|
+
`).run();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// API endpoint - the React frontend calls this
|
|
25
|
+
app.get("/api/hello", (c) => {
|
|
26
|
+
return c.json({ message: "Hello from Fling API!" });
|
|
27
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "ES2020",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"moduleDetection": "force",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"strict": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"noUncheckedSideEffectImports": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["src/react-app"]
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
3
|
+
import react from "@vitejs/plugin-react";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
base: process.env["VITE_BASE"] || "/",
|
|
7
|
+
plugins: [tailwindcss(), react()],
|
|
8
|
+
build: {
|
|
9
|
+
outDir: "dist/client",
|
|
10
|
+
emptyOutDir: true,
|
|
11
|
+
},
|
|
12
|
+
server: {
|
|
13
|
+
port: 5173,
|
|
14
|
+
proxy: {
|
|
15
|
+
"/api": {
|
|
16
|
+
target: "http://localhost:3210",
|
|
17
|
+
changeOrigin: true,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|