Skip to content

Commit a446c3f

Browse files
just-be-devclaude
andcommitted
refactor(micro): replace D1/Drizzle with R2 JSONL and HTTP API
Replace the D1 database and Drizzle ORM with a JSONL file stored in a new R2 bucket named "micro". All R2 and syndication logic moves to the Astro site; the micro CLI becomes a thin HTTP client. - Create `micro` R2 bucket and bind as MICRO_BUCKET in wrangler.toml - Add src/lib/micro-r2.ts: createMicroStorage(bucket) with read/write/ add/update/delete using read-modify-write on micro-posts.jsonl - Add src/lib/micro-syndicate.ts: syndication logic (moved from micro pkg) - Add src/env.d.ts: typed Worker env for MICRO_BUCKET, MICRO_SECRET, and syndication credential bindings - POST /micro (Authorization: Bearer): creates post + syndicates - GET /micro (Authorization + Accept: application/json): returns JSON list - DELETE /micro/:id (Authorization: Bearer): deletes post from R2 - Update micro-loader to use createMicroStorage + getPlatformProxy directly - Slim micro CLI to HTTP fetch calls using MICRO_SECRET + MICRO_SITE_URL - Remove drizzle-orm, drizzle-kit, wrangler, @atproto/api, twitter-api-v2 from micro package; move atproto/twitter deps to root package.json - Delete schema.ts, db.ts, drizzle.config.ts, migrations, syndicate.ts, loader-db.ts, r2.ts from micro package - Remove micro:db:generate, micro:db:migrate, deploy:micro mise tasks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 11ccbca commit a446c3f

21 files changed

Lines changed: 497 additions & 817 deletions

bun.lock

Lines changed: 2 additions & 246 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mise.toml

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,3 @@ description = "Preview release notes for a package version"
9393
run = "bun packages/micro/index.ts"
9494
description = "Micro blog CLI (browse posts or create new ones)"
9595
raw = true
96-
97-
[tasks."micro:db:generate"]
98-
dir = "packages/micro"
99-
run = "bun run db:generate"
100-
description = "Generate drizzle migrations for micro blog"
101-
102-
[tasks."micro:db:migrate"]
103-
dir = "packages/micro"
104-
run = "bun run db:migrate"
105-
description = "Apply drizzle migrations for micro blog"
106-
107-
[tasks."deploy:micro"]
108-
run = "wrangler d1 migrations apply micro-blog --remote"
109-
description = "Apply D1 migrations to remote micro-blog database"

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"@astrojs/mdx": "5.0.0",
3434
"@astrojs/rss": "4.0.17",
3535
"@astrojs/sitemap": "3.7.1",
36+
"@atproto/api": "^0.15.3",
3637
"@iconify-json/pixel": "^1.2.1",
3738
"@types/p5": "^1.7.6",
3839
"@unocss/reset": "^66.6.0",
@@ -45,6 +46,7 @@
4546
"p5": "^1.11.3",
4647
"remark-mdc": "^3.9.0",
4748
"sharp": "^0.34.3",
49+
"twitter-api-v2": "^1.17.3",
4850
"unist-util-visit": "^5.0.0",
4951
"zod": "^4.1.13"
5052
},

packages/micro/db/migrations/0000_red_mother_askani.sql

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/micro/db/migrations/meta/0000_snapshot.json

Lines changed: 0 additions & 66 deletions
This file was deleted.

packages/micro/db/migrations/meta/_journal.json

Lines changed: 0 additions & 13 deletions
This file was deleted.

packages/micro/drizzle.config.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

packages/micro/package.json

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,17 @@
1919
},
2020
"files": [
2121
"index.ts",
22-
"src",
23-
"db"
22+
"src"
2423
],
2524
"type": "module",
2625
"exports": {
27-
".": "./index.ts",
28-
"./loader-db": "./src/loader-db.ts"
26+
".": "./index.ts"
2927
},
3028
"publishConfig": {
3129
"access": "public"
3230
},
33-
"scripts": {
34-
"db:generate": "drizzle-kit generate",
35-
"db:migrate": "drizzle-kit migrate"
36-
},
3731
"dependencies": {
38-
"@atproto/api": "^0.15.3",
39-
"@clack/prompts": "^0.11.0",
40-
"drizzle-kit": "^0.31.9",
41-
"drizzle-orm": "^0.45.1",
42-
"twitter-api-v2": "^1.17.3",
43-
"wrangler": "4.48.0"
32+
"@clack/prompts": "^0.11.0"
4433
},
4534
"engines": {
4635
"bun": ">=1.0.0"

packages/micro/src/browse.ts

Lines changed: 87 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,102 @@
11
import * as p from "@clack/prompts";
2-
import { desc, eq } from "drizzle-orm";
3-
import { getD1Database, microPosts } from "./db.ts";
42

5-
type Post = typeof microPosts.$inferSelect;
3+
interface Post {
4+
id: number;
5+
content: string;
6+
createdAt: string;
7+
updatedAt: string;
8+
syndicatedTo: Array<{ platform: string; id: string; url: string }>;
9+
}
10+
11+
const DEFAULT_SITE_URL = "https://just-be.dev";
12+
13+
function getConfig() {
14+
const secret = process.env.MICRO_SECRET;
15+
if (!secret) {
16+
console.error("Error: MICRO_SECRET environment variable not set");
17+
process.exit(1);
18+
}
19+
return {
20+
secret,
21+
siteUrl: process.env.MICRO_SITE_URL ?? DEFAULT_SITE_URL,
22+
};
23+
}
624

725
export async function browse() {
826
p.intro("Micro Blog Browser");
927

1028
const spinner = p.spinner();
1129
spinner.start("Loading posts...");
1230

31+
const { secret, siteUrl } = getConfig();
32+
33+
let posts: Post[];
1334
try {
14-
const { db, dispose } = await getD1Database();
35+
const res = await fetch(`${siteUrl}/micro`, {
36+
headers: {
37+
Authorization: `Bearer ${secret}`,
38+
Accept: "application/json",
39+
},
40+
});
41+
if (!res.ok) throw new Error(`Server returned ${res.status}`);
42+
posts = (await res.json()) as Post[];
43+
} catch (error) {
44+
spinner.stop("Failed to load posts");
45+
throw error;
46+
}
1547

16-
// Fetch all posts, newest first
17-
const posts = await db.select().from(microPosts).orderBy(desc(microPosts.createdAt));
48+
spinner.stop(`Found ${posts.length} post(s)`);
1849

19-
spinner.stop(`Found ${posts.length} post(s)`);
50+
if (posts.length === 0) {
51+
p.note("No posts yet. Create one with 'micro post'", "Empty");
52+
return;
53+
}
2054

21-
if (posts.length === 0) {
22-
p.note("No posts yet. Create one with 'micro post'", "Empty");
23-
await dispose();
24-
return;
55+
let shouldContinue = true;
56+
57+
while (shouldContinue) {
58+
const options = posts.map((post) => {
59+
const preview =
60+
post.content.length > 60 ? post.content.substring(0, 60) + "..." : post.content;
61+
const date = new Date(post.createdAt).toLocaleDateString();
62+
return {
63+
value: post.id,
64+
label: `[${post.id}] ${preview}`,
65+
hint: date,
66+
};
67+
});
68+
69+
options.push({ value: -1, label: "Exit", hint: "" });
70+
71+
const selectedId = await p.select({
72+
message: "Select a post to view options",
73+
options,
74+
});
75+
76+
if (p.isCancel(selectedId) || selectedId === -1) {
77+
shouldContinue = false;
78+
continue;
2579
}
2680

27-
// Show posts in a loop until user exits
28-
let shouldContinue = true;
29-
30-
while (shouldContinue) {
31-
const options = posts.map((post) => {
32-
const preview =
33-
post.content.length > 60 ? post.content.substring(0, 60) + "..." : post.content;
34-
const date = post.createdAt.toLocaleDateString();
35-
return {
36-
value: post.id,
37-
label: `[${post.id}] ${preview}`,
38-
hint: date,
39-
};
40-
});
41-
42-
options.push({
43-
value: -1,
44-
label: "Exit",
45-
hint: "",
46-
});
47-
48-
const selectedId = await p.select({
49-
message: "Select a post to view options",
50-
options,
51-
});
52-
53-
if (p.isCancel(selectedId) || selectedId === -1) {
54-
shouldContinue = false;
55-
continue;
56-
}
57-
58-
const selectedPost = posts.find((p) => p.id === selectedId);
59-
if (!selectedPost) continue;
60-
61-
// Show post details and actions
62-
await showPostActions(db, selectedPost, posts);
63-
}
81+
const selectedPost = posts.find((p) => p.id === selectedId);
82+
if (!selectedPost) continue;
6483

65-
await dispose();
66-
p.outro("Goodbye!");
67-
} catch (error) {
68-
spinner.stop("Failed to load posts");
69-
throw error;
84+
await showPostActions({ siteUrl, secret }, selectedPost, posts);
7085
}
86+
87+
p.outro("Goodbye!");
7188
}
7289

7390
async function showPostActions(
74-
db: ReturnType<typeof getD1Database> extends Promise<{ db: infer D }> ? D : never,
91+
config: { siteUrl: string; secret: string },
7592
post: Post,
7693
allPosts: Post[],
7794
) {
78-
const syndicatedData =
79-
(post.syndicatedTo as Array<{ platform: string; id: string; url: string }> | null) || [];
80-
const syndicatedPlatforms = syndicatedData.map((s) => s.platform);
95+
const syndicatedPlatforms = post.syndicatedTo.map((s) => s.platform);
8196
const syndicatedText = syndicatedPlatforms.length > 0 ? syndicatedPlatforms.join(", ") : "None";
8297

8398
p.note(
84-
`${post.content}\n\nCreated: ${post.createdAt.toLocaleString()}\nSyndicated to: ${syndicatedText}`,
99+
`${post.content}\n\nCreated: ${new Date(post.createdAt).toLocaleString()}\nSyndicated to: ${syndicatedText}`,
85100
`Post #${post.id}`,
86101
);
87102

@@ -110,14 +125,13 @@ async function showPostActions(
110125

111126
switch (action) {
112127
case "open-web": {
113-
// Open in browser - construct URL based on post ID
114-
const url = `https://just-be.dev/micro#post-${post.id}`;
128+
const url = `${config.siteUrl}/micro#post-${post.id}`;
115129
console.log(`Opening: ${url}`);
116130
await Bun.spawn(["open", url]);
117131
break;
118132
}
119133
case "open-bluesky": {
120-
const blueskyData = syndicatedData.find((s) => s.platform === "bluesky");
134+
const blueskyData = post.syndicatedTo.find((s) => s.platform === "bluesky");
121135
if (!blueskyData) {
122136
p.note("This post hasn't been syndicated to Bluesky yet", "Not available");
123137
break;
@@ -127,7 +141,7 @@ async function showPostActions(
127141
break;
128142
}
129143
case "open-twitter": {
130-
const twitterData = syndicatedData.find((s) => s.platform === "twitter");
144+
const twitterData = post.syndicatedTo.find((s) => s.platform === "twitter");
131145
if (!twitterData) {
132146
p.note("This post hasn't been syndicated to Twitter yet", "Not available");
133147
break;
@@ -150,13 +164,19 @@ async function showPostActions(
150164
const spinner = p.spinner();
151165
spinner.start("Deleting post...");
152166

153-
await db.delete(microPosts).where(eq(microPosts.id, post.id));
167+
const res = await fetch(`${config.siteUrl}/micro/${post.id}`, {
168+
method: "DELETE",
169+
headers: { Authorization: `Bearer ${config.secret}` },
170+
});
171+
172+
if (!res.ok) {
173+
spinner.stop("Delete failed");
174+
p.log.warn(`Server returned ${res.status}`);
175+
break;
176+
}
154177

155-
// Remove from the posts array
156178
const index = allPosts.findIndex((p) => p.id === post.id);
157-
if (index > -1) {
158-
allPosts.splice(index, 1);
159-
}
179+
if (index > -1) allPosts.splice(index, 1);
160180

161181
spinner.stop(`Post #${post.id} deleted successfully`);
162182
break;

packages/micro/src/db.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)