🌿 Collaborative wiki on ATProto lichen.wiki
atproto
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

Resolve dev env issues

juprodh 544c7ef7 ea632848

+49 -26
+4 -1
scripts/dev-full.ts
··· 28 28 let buffer = ""; 29 29 let pdsInfo: { 30 30 pdsUrl: string; 31 + plcUrl: string; 31 32 accounts: Record<string, { did: string; handle: string; password: string }>; 32 33 }; 33 34 ··· 45 46 } 46 47 } 47 48 48 - const { pdsUrl, accounts } = pdsInfo as { 49 + const { pdsUrl, plcUrl, accounts } = pdsInfo as { 49 50 pdsUrl: string; 51 + plcUrl: string; 50 52 accounts: Record<string, { did: string; handle: string; password: string }>; 51 53 }; 52 54 ··· 56 58 RELAY_URL: pdsUrl.replace("http://", "ws://"), 57 59 HANDLE_RESOLVER_URL: pdsUrl, 58 60 DEV_PDS_URL: pdsUrl, 61 + DEV_PLC_URL: plcUrl, 59 62 DEV_ACCOUNTS: JSON.stringify(accounts), 60 63 DB_PATH: "atwiki-dev.db", 61 64 };
+4
src/atproto/env.ts
··· 30 30 return process.env.DEV_PDS_URL ?? null; 31 31 } 32 32 33 + export function getDevPlcUrl(): string | null { 34 + return process.env.DEV_PLC_URL ?? null; 35 + } 36 + 33 37 export interface DevAccount { 34 38 did: string; 35 39 handle: string;
+13
src/atproto/session.ts
··· 69 69 } 70 70 71 71 /** 72 + * Get a fully authenticated agent for the session. 73 + * In production: wraps the OAuth session. 74 + * In dev-full mode (no oauthSession): logs in with app password. 75 + * Use this in orchestrators instead of createAgent(). 76 + */ 77 + export async function getAgent(session: Session): Promise<Agent> { 78 + if (session.oauthSession) { 79 + return new Agent(session.oauthSession); 80 + } 81 + return loginDevAgent(session); 82 + } 83 + 84 + /** 72 85 * Create and log in a dev-mode agent. Use this instead of createAgent() 73 86 * when in dev mode (session.oauthSession is undefined). 74 87 */
+8 -4
src/firehose/index.ts
··· 2 2 3 3 import { IdResolver } from "@atproto/identity"; 4 4 import { type Event, Firehose, MemoryRunner } from "@atproto/sync"; 5 - import { getRelayUrl } from "../atproto/env.ts"; 5 + import { getDevPdsUrl, getDevPlcUrl, getRelayUrl } from "../atproto/env.ts"; 6 6 import { COLLECTIONS } from "../lib/constants.ts"; 7 7 import { getCursor, setCursor } from "../server/db/queries/index.ts"; 8 8 import { handleCommitEvent } from "./handlers.ts"; 9 9 10 10 const relayUrl = getRelayUrl(); 11 - const savedCursor = getCursor(); 11 + // In dev mode the PDS is ephemeral — each run starts fresh from seq 0. 12 + // Persisting the cursor across sessions causes "FutureCursor" errors. 13 + const isDevMode = !!getDevPdsUrl(); 14 + const savedCursor = isDevMode ? null : getCursor(); 12 15 13 16 console.log(`Firehose connecting to ${relayUrl}`); 14 17 if (savedCursor) { 15 18 console.log(`Resuming from cursor ${savedCursor}`); 16 19 } 17 20 18 - const idResolver = new IdResolver(); 21 + const plcUrl = getDevPlcUrl() ?? undefined; 22 + const idResolver = new IdResolver({ plcUrl }); 19 23 20 24 const runner = new MemoryRunner({ 21 25 startCursor: savedCursor ?? undefined, 22 26 setCursor: async (cursor: number) => { 23 - setCursor(cursor); 27 + if (!isDevMode) setCursor(cursor); 24 28 }, 25 29 }); 26 30
+4 -4
src/lib/orchestrators/membership.ts
··· 3 3 writeMemberRequestRecord, 4 4 writeMembershipRecord, 5 5 } from "../../atproto/pds.ts"; 6 - import { createAgent } from "../../atproto/session.ts"; 6 + import { getAgent } from "../../atproto/session.ts"; 7 7 import { 8 8 deleteMembership, 9 9 deleteRequest, ··· 39 39 40 40 // PDS write 41 41 try { 42 - const agent = createAgent(ctx.session); 42 + const agent = await getAgent(ctx.session); 43 43 await writeMemberRequestRecord( 44 44 agent, 45 45 ctx.session.did, ··· 73 73 // PDS write first (skip in dev mode) 74 74 if (ctx.session) { 75 75 try { 76 - const agent = createAgent(ctx.session); 76 + const agent = await getAgent(ctx.session); 77 77 await writeMembershipRecord( 78 78 agent, 79 79 ctx.session.did, ··· 120 120 const parsed = parseAtUri(atUri); 121 121 if (parsed) { 122 122 try { 123 - const agent = createAgent(ctx.session); 123 + const agent = await getAgent(ctx.session); 124 124 await deleteRecord( 125 125 agent, 126 126 parsed.did,
+3 -3
src/lib/orchestrators/note.ts
··· 1 1 import { writeNoteRecord, writeRevisionRecord } from "../../atproto/pds.ts"; 2 - import { createAgent } from "../../atproto/session.ts"; 2 + import { getAgent } from "../../atproto/session.ts"; 3 3 import { 4 4 createNote, 5 5 getCurrentNote, ··· 63 63 64 64 // PDS write (skip in dev mode without session) 65 65 if (ctx.session) { 66 - const agent = createAgent(ctx.session); 66 + const agent = await getAgent(ctx.session); 67 67 const now = new Date().toISOString(); 68 68 const noteTid = generateTid(); 69 69 const revisionTid = generateTid(); ··· 135 135 136 136 // PDS write (skip in dev mode without session) 137 137 if (ctx.session) { 138 - const agent = createAgent(ctx.session); 138 + const agent = await getAgent(ctx.session); 139 139 const now = new Date().toISOString(); 140 140 const currentContent = currentNote?.content ?? ""; 141 141 const diff = createDiff(currentContent, fields.content);
+7 -8
src/lib/orchestrators/wiki.ts
··· 5 5 writeRevisionRecord, 6 6 writeWikiRecord, 7 7 } from "../../atproto/pds.ts"; 8 - import { createAgent } from "../../atproto/session.ts"; 8 + import { getAgent } from "../../atproto/session.ts"; 9 9 import { 10 10 createNote, 11 11 deleteWikiByAtUri, ··· 73 73 74 74 // PDS write (skip in dev mode without session) 75 75 let atUri = `at://${did}/pub.coral.wiki/${slug}`; 76 - if (ctx.session) { 76 + const agent = ctx.session ? await getAgent(ctx.session) : null; 77 + 78 + if (agent) { 77 79 try { 78 - const agent = createAgent(ctx.session); 79 80 const result = await writeWikiRecord( 80 81 agent, 81 82 did, ··· 107 108 const membershipAtUri = `at://${did}/pub.coral.membership/${membershipTid}`; 108 109 upsertMembership(slug, did, "admin", membershipAtUri, now); 109 110 110 - if (ctx.session) { 111 + if (agent) { 111 112 try { 112 - const agent = createAgent(ctx.session); 113 113 await writeMembershipRecord( 114 114 agent, 115 115 did, ··· 129 129 // Create home note: PDS write → DB write (same lifecycle as wiki creation) 130 130 const homeContent = `# Welcome to ${fields.name}\n\nThis is the home page of your wiki. Edit it to get started.`; 131 131 132 - if (ctx.session) { 132 + if (agent) { 133 133 try { 134 - const agent = createAgent(ctx.session); 135 134 const noteTid = generateTid(); 136 135 const revisionTid = generateTid(); 137 136 const noteAtUri = `at://${did}/pub.coral.note/${noteTid}`; ··· 171 170 172 171 // PDS cleanup (skip in dev mode) 173 172 if (ctx.session) { 174 - const agent = createAgent(ctx.session); 173 + const agent = await getAgent(ctx.session); 175 174 176 175 // Delete wiki record 177 176 const wikiParsed = parseAtUri(ctx.wiki.at_uri);
+2 -2
tests/lib/orchestrators/membership.test.ts
··· 16 16 cid: "bafyrei456", 17 17 })); 18 18 const mockDeleteRecord = mock(async () => {}); 19 - const mockCreateAgent = mock(() => ({}) as never); 19 + const mockGetAgent = mock(async () => ({}) as never); 20 20 21 21 mock.module("../../../src/atproto/pds.ts", () => ({ 22 22 ...realPds, ··· 26 26 })); 27 27 mock.module("../../../src/atproto/session.ts", () => ({ 28 28 ...realSession, 29 - createAgent: mockCreateAgent, 29 + getAgent: mockGetAgent, 30 30 })); 31 31 32 32 const { requestAccessAction, approveMemberAction, removeMemberAction } =
+2 -2
tests/lib/orchestrators/note.test.ts
··· 15 15 uri: "at://did:plc:test/pub.coral.noteRevision/def", 16 16 cid: "bafyrei456", 17 17 })); 18 - const mockCreateAgent = mock(() => ({}) as never); 18 + const mockGetAgent = mock(async () => ({}) as never); 19 19 20 20 mock.module("../../../src/atproto/pds.ts", () => ({ 21 21 ...realPds, ··· 24 24 })); 25 25 mock.module("../../../src/atproto/session.ts", () => ({ 26 26 ...realSession, 27 - createAgent: mockCreateAgent, 27 + getAgent: mockGetAgent, 28 28 })); 29 29 30 30 const { createNoteAction, editNoteAction } = await import(
+2 -2
tests/lib/orchestrators/wiki.test.ts
··· 27 27 cid: "bafyreirev", 28 28 })); 29 29 const mockDeleteRecord = mock(async () => {}); 30 - const mockCreateAgent = mock(() => ({}) as never); 30 + const mockGetAgent = mock(async () => ({}) as never); 31 31 32 32 mock.module("../../../src/atproto/pds.ts", () => ({ 33 33 ...realPds, ··· 39 39 })); 40 40 mock.module("../../../src/atproto/session.ts", () => ({ 41 41 ...realSession, 42 - createAgent: mockCreateAgent, 42 + getAgent: mockGetAgent, 43 43 })); 44 44 45 45 const { createWikiAction, deleteWikiAction } = await import(