[READ ONLY MIRROR] Spark Social AppView Server github.com/sprksocial/server
atproto deno hono lexicon
1
fork

Configure Feed

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

refactor: logging

+123 -188
+1 -1
api/so/sprk/actor/getPreferences.ts
··· 127 127 }, 128 128 }; 129 129 } catch (error) { 130 - ctx.logger.error("Failed to get preferences", { error, userDid }); 130 + console.error("Failed to get preferences", { error, userDid }); 131 131 throw error; 132 132 } 133 133 },
+1 -1
api/so/sprk/actor/putPreferences.ts
··· 131 131 132 132 return; 133 133 } catch (error) { 134 - ctx.logger.error("Failed to put preferences", { error, userDid }); 134 + console.error("Failed to put preferences", { error, userDid }); 135 135 throw error; 136 136 } 137 137 },
+1 -1
api/so/sprk/notification/updateSeen.ts
··· 25 25 // Reset badge count on iOS devices 26 26 // Fire and forget - don't block the response 27 27 ctx.pushService.sendBadgeReset(viewer).catch((err) => { 28 - ctx.logger.error("Failed to send badge reset", { err, viewer }); 28 + console.error("Failed to send badge reset", { err, viewer }); 29 29 }); 30 30 }, 31 31 });
-2
context.ts
··· 1 - import { Logger } from "@logtape/logtape"; 2 1 import { Database } from "./data-plane/db/index.ts"; 3 2 import { DataPlane } from "./data-plane/index.ts"; 4 3 import { Hydrator } from "./hydration/index.ts"; ··· 14 13 dataplane: DataPlane; 15 14 hydrator: Hydrator; 16 15 views: Views; 17 - logger: Logger; 18 16 idResolver: IdResolver; 19 17 authVerifier: AuthVerifier; 20 18 cfg: ServerConfig;
+5 -6
data-plane/background.ts
··· 1 1 import PQueue from "p-queue"; 2 2 import { Database } from "./db/index.ts"; 3 - import { Logger } from "@logtape/logtape"; 4 3 5 4 // A simple queue for in-process, out-of-band/backgrounded work 6 5 ··· 10 9 private processAllInterval: number | null = null; 11 10 private isProcessingAll = false; 12 11 13 - constructor(public db: Database, public logger: Logger) {} 12 + constructor(public db: Database) {} 14 13 15 14 add(task: Task) { 16 15 if (this.destroyed) { ··· 23 22 if ( 24 23 err.message?.includes("Client must be connected") && this.destroyed 25 24 ) { 26 - this.logger.debug( 25 + console.debug( 27 26 "Ignoring MongoDB connection error during shutdown", 28 27 { err: err.message }, 29 28 ); ··· 33 32 // Check for MongoDB duplicate key errors 34 33 const mongoError = err as { code?: number }; 35 34 if (mongoError.code === 11000) { 36 - this.logger.warn( 35 + console.warn( 37 36 "Ignoring duplicate key error in background task", 38 37 { err: err.message }, 39 38 ); 40 39 return; 41 40 } 42 41 43 - this.logger.error("background queue task failed", { err }); 42 + console.error("background queue task failed", { err }); 44 43 }); 45 44 } 46 45 ··· 73 72 74 73 await Promise.race([processPromise, timeoutPromise]); 75 74 } catch (error) { 76 - this.logger.error( 75 + console.error( 77 76 "Background queue processing failed or timed out", 78 77 { error }, 79 78 );
+12 -13
data-plane/db/index.ts
··· 2 2 import { IdResolver, MemoryCache } from "@atp/identity"; 3 3 import * as models from "./models.ts"; 4 4 import { getResultFromDoc } from "../util.ts"; 5 - import { getLogger } from "@logtape/logtape"; 6 5 import { ServerConfig } from "../../config.ts"; 7 6 8 7 const HOUR = 60 * 60 * 1000; ··· 11 10 export class Database { 12 11 private connection!: Connection; 13 12 public models!: models.DatabaseModels; 14 - public logger = getLogger(["appview", "database"]); 13 + 15 14 public idResolver: IdResolver; 16 15 17 16 constructor(private cfg: ServerConfig) { ··· 28 27 if (!uri) { 29 28 throw new Error("No database URI provided"); 30 29 } 31 - this.logger.info(`Connecting to ${uri}`); 30 + console.info(`Connecting to ${uri}`); 32 31 33 32 try { 34 33 this.connection = mongoose.createConnection(uri, { ··· 41 40 42 41 // Attach basic listeners for visibility 43 42 this.connection.on("connected", () => { 44 - this.logger.info("MongoDB connection established"); 43 + console.info("MongoDB connection established"); 45 44 }); 46 45 this.connection.on("disconnected", () => { 47 - this.logger.warn("MongoDB connection disconnected"); 46 + console.warn("MongoDB connection disconnected"); 48 47 }); 49 48 this.connection.on("error", (err) => { 50 - this.logger.error("MongoDB connection error", { err }); 49 + console.error("MongoDB connection error", { err }); 51 50 }); 52 51 53 52 // Initialize models ··· 150 149 ), 151 150 }; 152 151 153 - this.logger.info("Started connection to MongoDB"); 152 + console.info("Started connection to MongoDB"); 154 153 } catch (error) { 155 - this.logger.error("Failed to start connection to MongoDB", { error }); 154 + console.error("Failed to start connection to MongoDB", { error }); 156 155 throw error; 157 156 } 158 157 } ··· 160 159 async disconnect(): Promise<void> { 161 160 if (this.connection) { 162 161 await this.connection.close(); 163 - this.logger.info("Disconnected from MongoDB"); 162 + console.info("Disconnected from MongoDB"); 164 163 } 165 164 } 166 165 ··· 177 176 try { 178 177 return await this.idResolver.handle.resolve(handle); 179 178 } catch (err) { 180 - this.logger.error("Failed to resolve handle", { err, handle }); 179 + console.error("Failed to resolve handle", { err, handle }); 181 180 return undefined; 182 181 } 183 182 } ··· 192 191 handle: data.handle, 193 192 }; 194 193 } catch (err) { 195 - this.logger.error("Failed to resolve DID", { err, did }); 194 + console.error("Failed to resolve DID", { err, did }); 196 195 return undefined; 197 196 } 198 197 } ··· 215 214 }); 216 215 return cursorState?.cursorValue || null; 217 216 } catch (error) { 218 - this.logger.error("Failed to get cursor state", { error }); 217 + console.error("Failed to get cursor state", { error }); 219 218 return null; 220 219 } 221 220 } ··· 231 230 { upsert: true }, 232 231 ); 233 232 } catch (error) { 234 - this.logger.error( 233 + console.error( 235 234 "Failed to save cursor state", 236 235 { error, cursorPosition }, 237 236 );
-3
data-plane/index.ts
··· 1 1 import { IdResolver } from "@atp/identity"; 2 2 import { Database } from "./db/index.ts"; 3 - import { getLogger, Logger } from "@logtape/logtape"; 4 3 import { Blocks } from "./routes/blocks.ts"; 5 4 import { FeedGens } from "./routes/feed-gens.ts"; 6 5 import { Feeds } from "./routes/feeds.ts"; ··· 32 31 33 32 export class DataPlane { 34 33 private db: Database; 35 - public logger: Logger; 36 34 private idResolver?: IdResolver; 37 35 38 36 // Route handlers as root-level properties ··· 64 62 ) { 65 63 this.db = db; 66 64 this.idResolver = idResolver; 67 - this.logger = getLogger(["appview", "data-plane"]); 68 65 69 66 // Initialize all route handlers 70 67 this.blocks = new Blocks(db);
+7 -10
data-plane/indexing/index.ts
··· 27 27 import * as Audio from "./plugins/audio.ts"; 28 28 import * as Labeler from "./plugins/labeler.ts"; 29 29 import { RecordProcessor } from "./processor.ts"; 30 - import { getLogger, Logger } from "@logtape/logtape"; 31 30 import { ServerConfig } from "../../config.ts"; 32 31 import { PushService } from "../../utils/push.ts"; 33 32 ··· 45 44 audio: Audio.PluginType; 46 45 labeler: Labeler.PluginType; 47 46 }; 48 - logger: Logger; 49 47 private pushService?: PushService; 50 48 51 49 constructor( ··· 55 53 public background: BackgroundQueue, 56 54 pushService?: PushService, 57 55 ) { 58 - this.logger = getLogger(["appview", "indexer"]); 59 56 this.pushService = pushService; 60 57 this.records = { 61 58 post: Post.makePlugin(this.db, this.background), ··· 143 140 ); 144 141 } catch (err) { 145 142 // Log the error but don't throw - this prevents the firehose from crashing 146 - this.logger.warn( 143 + console.warn( 147 144 "Failed to index handle, skipping", 148 145 { err, did, timestamp }, 149 146 ); ··· 157 154 { upsert: true, new: true }, 158 155 ); 159 156 } catch (dbErr) { 160 - this.logger.error( 157 + console.error( 161 158 "Failed to update actor record after handle resolution failure", 162 159 { err: dbErr, did }, 163 160 ); ··· 170 167 171 168 const actorExists = await this.db.models.Actor.findOne({ did }).lean(); 172 169 if (!actorExists) { 173 - this.logger.info( 170 + console.info( 174 171 `indexRepo: No actor record found for ${did}, indexing handle first`, 175 172 ); 176 173 await this.indexHandle(did, now); ··· 192 189 const repoRecords = formatCheckout(did, verifiedRepo); 193 190 const diff = findDiffFromCheckout(currRecords, repoRecords); 194 191 195 - this.logger.info(`Indexing ${diff.length} records for ${did}:`); 192 + console.info(`Indexing ${diff.length} records for ${did}:`); 196 193 197 194 await Promise.all( 198 195 diff.map(async (op) => { ··· 212 209 } 213 210 } catch (err) { 214 211 if (err instanceof ValidationError) { 215 - this.logger.warn( 212 + console.warn( 216 213 "skipping indexing of invalid record", 217 214 { did, commit, uri: uri.toString(), cid: cid.toString() }, 218 215 ); 219 216 } else { 220 - this.logger.error( 217 + console.error( 221 218 "skipping indexing due to error processing record", 222 219 { err, did, commit, uri: uri.toString(), cid: cid.toString() }, 223 220 ); ··· 304 301 return null; 305 302 } 306 303 } catch (err) { 307 - this.logger.warn( 304 + console.warn( 308 305 "Failed to check if actor is hosted, assuming not hosted", 309 306 { err, did }, 310 307 );
+9 -15
data-plane/subscription.ts
··· 4 4 import { BackgroundQueue } from "./background.ts"; 5 5 import { Database } from "./db/index.ts"; 6 6 import { IndexingService } from "./indexing/index.ts"; 7 - import { getLogger, Logger } from "@logtape/logtape"; 8 7 import { ServerConfig } from "../config.ts"; 9 8 import { PushService } from "../utils/push.ts"; 10 9 import { PushTokens } from "./routes/push-tokens.ts"; ··· 14 13 runner: MemoryRunner; 15 14 background: BackgroundQueue; 16 15 indexingSvc: IndexingService; 17 - logger: Logger; 18 16 pushService: PushService; 19 17 private firehoseRunning = false; 20 18 ··· 27 25 }, 28 26 ) { 29 27 const { db, idResolver, startCursor, cfg } = opts; 30 - this.logger = getLogger(["appview", "subscription"]); 31 - this.background = new BackgroundQueue(db, this.logger); 28 + this.background = new BackgroundQueue(db); 32 29 33 30 // Create push service (FCM handles both iOS and Android) 34 31 const pushTokens = new PushTokens(db); ··· 49 46 idResolver, 50 47 service: cfg.relayUrl, 51 48 indexingSvc: this.indexingSvc, 52 - logger: this.logger, 53 49 db, 54 50 startCursor, 55 51 }); ··· 58 54 } 59 55 60 56 start() { 61 - this.logger.info("Starting firehose subscription"); 57 + console.info("Starting firehose subscription"); 62 58 this.firehoseRunning = true; 63 59 this.firehose.start(); 64 60 } ··· 74 70 idResolver: this.opts.idResolver, 75 71 service: this.opts.cfg.relayUrl, 76 72 indexingSvc: this.indexingSvc, 77 - logger: this.logger, 78 73 db: this.opts.db, 79 74 startCursor, 80 75 }); ··· 94 89 await this.firehose.destroy(); 95 90 this.firehoseRunning = false; 96 91 } 97 - this.logger.info("Processing remaining runner tasks..."); 92 + console.info("Processing remaining runner tasks..."); 98 93 if (this.opts.cfg.debugMode) { 99 94 const timeoutMs = 10000; 100 95 // Runner destroy with timeout and proper timer cleanup ··· 108 103 }); 109 104 await Promise.race([this.runner.destroy(), timeoutPromise]); 110 105 } catch (e) { 111 - this.logger.warn("Runner destroy timed out; continuing shutdown", { 106 + console.warn("Runner destroy timed out; continuing shutdown", { 112 107 e, 113 108 }); 114 109 } finally { ··· 128 123 }); 129 124 await Promise.race([this.background.processAll(), timeoutPromise]); 130 125 } catch (e) { 131 - this.logger.warn("Runner destroy timed out; continuing shutdown", { 126 + console.warn("Runner destroy timed out; continuing shutdown", { 132 127 e, 133 128 }); 134 129 } finally { ··· 142 137 await this.background.processAll(); 143 138 } 144 139 } catch (error) { 145 - this.logger.error("Error during subscription destroy", { error }); 140 + console.error("Error during subscription destroy", { error }); 146 141 throw error; 147 142 } 148 143 } ··· 152 147 idResolver: IdResolver; 153 148 service?: string; 154 149 indexingSvc: IndexingService; 155 - logger: Logger; 156 150 db: Database; 157 151 startCursor?: number; 158 152 }): { firehose: Firehose; runner: MemoryRunner } { 159 - const { idResolver, service, indexingSvc, logger, db, startCursor } = opts; 153 + const { idResolver, service, indexingSvc, db, startCursor } = opts; 160 154 161 155 const runner = new MemoryRunner({ 162 156 startCursor, 163 157 setCursorInterval: 30000, // Save cursor every 30 seconds 164 158 setCursor: async (cursor: number) => { 165 159 await db.saveCursorState(cursor); 166 - logger.info("Cursor saved to database", { cursor }); 160 + console.info("Cursor saved to database", { cursor }); 167 161 }, 168 162 }); 169 163 const firehose = new Firehose({ 170 164 idResolver, 171 165 runner, 172 166 service, 173 - onError: (err: Error) => logger.error("error in subscription", { err }), 167 + onError: (err: Error) => console.error("error in subscription", { err }), 174 168 handleEvent: async (evt: FirehoseEvent) => { 175 169 if (evt.event === "identity") { 176 170 await indexingSvc.indexHandle(evt.did, evt.time, true);
+3 -5
deno.json
··· 17 17 "@atp/identity": "jsr:@atp/identity@^0.1.0-alpha.2", 18 18 "@atp/lexicon": "jsr:@atp/lexicon@^0.1.0-alpha.4", 19 19 "@atp/repo": "jsr:@atp/repo@^0.1.0-alpha.5", 20 - "@atp/sync": "jsr:@atp/sync@^0.1.0-alpha.8", 20 + "@atp/sync": "jsr:@atp/sync@^0.1.0-alpha.9", 21 21 "@atp/syntax": "jsr:@atp/syntax@^0.1.0-alpha.2", 22 22 "@atp/xrpc": "jsr:@atp/xrpc@^0.1.0-alpha.4", 23 23 "@atp/xrpc-server": "jsr:@atp/xrpc-server@^0.1.0-alpha.9", 24 - "@logtape/logtape": "jsr:@logtape/logtape@^1.3.7", 25 - "@logtape/pretty": "jsr:@logtape/pretty@^1.3.7", 26 24 "@std/assert": "jsr:@std/assert@^1.0.18", 27 25 "dotenv": "npm:dotenv@^17.2.4", 28 26 "hono": "jsr:@hono/hono@^4.11.9", 29 27 "@std/encoding": "jsr:@std/encoding@^1.0.10", 30 - "@atproto/api": "npm:@atproto/api@^0.16.11", 28 + "@atproto/api": "npm:@atproto/api@^0.18.21", 31 29 "jose": "npm:jose@^6.1.3", 32 30 "mongoose": "npm:mongoose@^8.23.0", 33 31 "multiformats": "npm:multiformats@^13.4.2", 34 - "p-queue": "npm:p-queue@^8.1.1", 32 + "p-queue": "npm:p-queue@^9.1.0", 35 33 "mongodb-memory-server-core": "npm:mongodb-memory-server-core@^11.0.1", 36 34 "structured-headers": "npm:structured-headers@^2.0.2" 37 35 },
+64 -61
deno.lock
··· 8 8 "jsr:@atp/identity@~0.1.0-alpha.2": "0.1.0-alpha.2", 9 9 "jsr:@atp/lexicon@~0.1.0-alpha.4": "0.1.0-alpha.4", 10 10 "jsr:@atp/repo@~0.1.0-alpha.5": "0.1.0-alpha.5", 11 - "jsr:@atp/sync@~0.1.0-alpha.8": "0.1.0-alpha.8", 11 + "jsr:@atp/sync@~0.1.0-alpha.9": "0.1.0-alpha.9", 12 12 "jsr:@atp/syntax@~0.1.0-alpha.2": "0.1.0-alpha.2", 13 13 "jsr:@atp/xrpc-server@~0.1.0-alpha.9": "0.1.0-alpha.9", 14 14 "jsr:@atp/xrpc@~0.1.0-alpha.4": "0.1.0-alpha.4", 15 15 "jsr:@hono/hono@^4.10.8": "4.11.9", 16 16 "jsr:@hono/hono@^4.11.9": "4.11.9", 17 - "jsr:@logtape/file@^1.2.2": "1.3.5", 17 + "jsr:@logtape/file@^1.2.2": "1.3.7", 18 18 "jsr:@logtape/logtape@^1.2.2": "1.3.7", 19 - "jsr:@logtape/logtape@^1.3.5": "1.3.7", 20 19 "jsr:@logtape/logtape@^1.3.7": "1.3.7", 21 - "jsr:@logtape/pretty@^1.3.7": "1.3.7", 22 20 "jsr:@noble/curves@^2.0.1": "2.0.1", 23 21 "jsr:@noble/hashes@2": "2.0.1", 24 22 "jsr:@noble/hashes@^2.0.1": "2.0.1", ··· 27 25 "jsr:@std/bytes@^1.0.6": "1.0.6", 28 26 "jsr:@std/cbor@~0.1.9": "0.1.9", 29 27 "jsr:@std/encoding@^1.0.10": "1.0.10", 30 - "jsr:@std/fs@^1.0.20": "1.0.21", 28 + "jsr:@std/fs@^1.0.20": "1.0.22", 31 29 "jsr:@std/internal@^1.0.12": "1.0.12", 32 - "jsr:@std/streams@^1.0.14": "1.0.16", 33 - "jsr:@zod/zod@^4.1.13": "4.2.1", 34 - "npm:@atproto/api@~0.16.11": "0.16.11", 30 + "jsr:@std/streams@^1.0.14": "1.0.17", 31 + "jsr:@zod/zod@^4.1.13": "4.3.6", 32 + "npm:@atproto/api@~0.18.21": "0.18.21", 35 33 "npm:@atproto/sync@*": "0.1.39", 36 34 "npm:@bufbuild/protobuf@1.5.0": "1.5.0", 37 35 "npm:@ipld/dag-cbor@^9.2.5": "9.2.5", 38 - "npm:@types/node@24.0.7": "24.0.7", 36 + "npm:@opentelemetry/api@^1.9.0": "1.9.0", 39 37 "npm:dotenv@^17.2.4": "17.2.4", 40 38 "npm:jose@^6.1.3": "6.1.3", 41 39 "npm:lodash@*": "4.17.21", ··· 44 42 "npm:multiformats@^13.4.1": "13.4.2", 45 43 "npm:multiformats@^13.4.2": "13.4.2", 46 44 "npm:p-queue@^8.1.1": "8.1.1", 45 + "npm:p-queue@^9.1.0": "9.1.0", 47 46 "npm:rate-limiter-flexible@9": "9.1.1", 48 47 "npm:structured-headers@^2.0.2": "2.0.2" 49 48 }, ··· 106 105 "npm:multiformats@^13.4.1" 107 106 ] 108 107 }, 109 - "@atp/sync@0.1.0-alpha.8": { 110 - "integrity": "c471fef8fd3e2ab16a565791700967df0b48316e894e005769d4269e3db12dbf", 108 + "@atp/sync@0.1.0-alpha.9": { 109 + "integrity": "7a2c84f69bafc80cf705db7921a7aa12a659d4f82e187b5403be7bf426d28536", 111 110 "dependencies": [ 112 111 "jsr:@atp/common@~0.1.0-alpha.9", 113 112 "jsr:@atp/identity", ··· 115 114 "jsr:@atp/repo", 116 115 "jsr:@atp/syntax", 117 116 "jsr:@atp/xrpc-server", 117 + "npm:@opentelemetry/api", 118 118 "npm:multiformats@^13.4.1", 119 - "npm:p-queue" 119 + "npm:p-queue@^8.1.1" 120 120 ] 121 121 }, 122 122 "@atp/syntax@0.1.0-alpha.2": { ··· 149 149 "@hono/hono@4.11.9": { 150 150 "integrity": "c82c6b846abc3c1879d921d8365287d77cdef8073019f509ff80bf53033bdcba" 151 151 }, 152 - "@logtape/file@1.3.5": { 153 - "integrity": "6e5248e873e260109267b79bd3fb19f307a664a2233c5ae6f699d697549db985", 152 + "@logtape/file@1.3.7": { 153 + "integrity": "8cacd752ac49671135e80abc8cf4a843c377ab80906d075ece0e9105fc24677e", 154 154 "dependencies": [ 155 - "jsr:@logtape/logtape@^1.3.5" 155 + "jsr:@logtape/logtape@^1.3.7" 156 156 ] 157 157 }, 158 158 "@logtape/logtape@1.3.7": { 159 159 "integrity": "d9dc1f8c7e2e1e4e3998006ea84eaf4054e40ad39325b056b3f517c013286bed" 160 160 }, 161 - "@logtape/pretty@1.3.7": { 162 - "integrity": "f13f1e151158d76e5b8e352b7d1a6308bf3a65d14d70f50703f8f2fca125250c", 163 - "dependencies": [ 164 - "jsr:@logtape/logtape@^1.3.7", 165 - "npm:@types/node" 166 - ] 167 - }, 168 161 "@noble/curves@2.0.1": { 169 162 "integrity": "21ef41d207a203f60ba37a4fdcbc4f4a545b10c5dab7f293889f18292f81ab23", 170 163 "dependencies": [ ··· 196 189 "@std/fs@1.0.21": { 197 190 "integrity": "d720fe1056d78d43065a4d6e0eeb2b19f34adb8a0bc7caf3a4dbf1d4178252cd" 198 191 }, 192 + "@std/fs@1.0.22": { 193 + "integrity": "de0f277a58a867147a8a01bc1b181d0dfa80bfddba8c9cf2bacd6747bcec9308" 194 + }, 199 195 "@std/internal@1.0.12": { 200 196 "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" 201 197 }, 202 198 "@std/streams@1.0.16": { 203 199 "integrity": "85030627befb1767c60d4f65cb30fa2f94af1d6ee6e5b2515b76157a542e89c4" 200 + }, 201 + "@std/streams@1.0.17": { 202 + "integrity": "7859f3d9deed83cf4b41f19223d4a67661b3d3819e9fc117698f493bf5992140" 204 203 }, 205 204 "@zod/zod@4.2.1": { 206 205 "integrity": "693a557fccaf73bfcbcd132ca286929f3343cda9efb56e9780985aa41d229b38" 206 + }, 207 + "@zod/zod@4.3.6": { 208 + "integrity": "7144e5e11f8ffc3cf6e2fca624f6597a8762898aac9868cc8938e9398b96ffe4" 207 209 } 208 210 }, 209 211 "npm": { 210 - "@atproto/api@0.16.11": { 211 - "integrity": "sha512-1dhfQNHiclb102RW+Ea8Nft5olfqU0Ev/vlQaSX6mWNo1aP5zT+sPODJ8+BTUOYk3vcuvL7QMkqA/rLYy2PMyw==", 212 + "@atproto/api@0.18.21": { 213 + "integrity": "sha512-s35MIJerGT/pKe2xJtKKswqlIr/ola2r2iURBKBL0Mk1OKe6jP4YvTMh1N2d2PEANFzNNTbKoDaLfJPo2Uvc/w==", 212 214 "dependencies": [ 213 215 "@atproto/common-web", 214 - "@atproto/lexicon@0.5.1", 216 + "@atproto/lexicon", 215 217 "@atproto/syntax", 216 218 "@atproto/xrpc", 217 219 "await-lock", ··· 220 222 "zod" 221 223 ] 222 224 }, 223 - "@atproto/common-web@0.4.15": { 224 - "integrity": "sha512-A4l9gyqUNez8CjZp/Trypz/D3WIQsNj8dN05WR6+RoBbvwc9JhWjKPrm+WoVYc/F16RPdXHLkE3BEJlGIyYIiA==", 225 + "@atproto/common-web@0.4.16": { 226 + "integrity": "sha512-Ufvaff5JgxUyUyTAG0/3o7ltpy3lnZ1DvLjyAnvAf+hHfiK7OMQg+8byr+orN+KP9MtIQaRTsCgYPX+PxMKUoA==", 225 227 "dependencies": [ 226 - "@atproto/lex-data", 228 + "@atproto/lex-data@0.0.11", 227 229 "@atproto/lex-json", 228 230 "@atproto/syntax", 229 231 "zod" ··· 234 236 "dependencies": [ 235 237 "@atproto/common-web", 236 238 "@atproto/lex-cbor", 237 - "@atproto/lex-data", 239 + "@atproto/lex-data@0.0.10", 238 240 "iso-datestring-validator", 239 241 "multiformats@9.9.0", 240 242 "pino" ··· 258 260 "@atproto/lex-cbor@0.0.10": { 259 261 "integrity": "sha512-5RtV90iIhRNCXXvvETd3KlraV8XGAAAgOmiszUb+l8GySDU/sGk7AlVvArFfXnj/S/GXJq8DP6IaUxCw/sPASA==", 260 262 "dependencies": [ 261 - "@atproto/lex-data", 263 + "@atproto/lex-data@0.0.10", 262 264 "tslib" 263 265 ] 264 266 }, ··· 271 273 "unicode-segmenter" 272 274 ] 273 275 }, 274 - "@atproto/lex-json@0.0.10": { 275 - "integrity": "sha512-L6MyXU17C5ODMeob8myQ2F3xvgCTvJUtM0ew8qSApnN//iDasB/FDGgd7ty4UVNmx4NQ/rtvz8xV94YpG6kneQ==", 276 + "@atproto/lex-data@0.0.11": { 277 + "integrity": "sha512-4+KTtHdqwlhiTKA7D4SACea4jprsNpCQsNALW09wsZ6IHhCDGO5tr1cmV+QnLYe3G3mu1E1yXHXbPUHrUUDT/A==", 276 278 "dependencies": [ 277 - "@atproto/lex-data", 278 - "tslib" 279 + "multiformats@9.9.0", 280 + "tslib", 281 + "uint8arrays", 282 + "unicode-segmenter" 279 283 ] 280 284 }, 281 - "@atproto/lexicon@0.5.1": { 282 - "integrity": "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A==", 285 + "@atproto/lex-json@0.0.11": { 286 + "integrity": "sha512-2IExAoQ4KsR5fyPa1JjIvtR316PvdgRH/l3BVGLBd3cSxM3m5MftIv1B6qZ9HjNiK60SgkWp0mi9574bTNDhBQ==", 283 287 "dependencies": [ 284 - "@atproto/common-web", 285 - "@atproto/syntax", 286 - "iso-datestring-validator", 287 - "multiformats@9.9.0", 288 - "zod" 288 + "@atproto/lex-data@0.0.11", 289 + "tslib" 289 290 ] 290 291 }, 291 292 "@atproto/lexicon@0.6.1": { ··· 304 305 "@atproto/common", 305 306 "@atproto/common-web", 306 307 "@atproto/crypto", 307 - "@atproto/lexicon@0.6.1", 308 + "@atproto/lexicon", 308 309 "@ipld/dag-cbor@7.0.3", 309 310 "multiformats@9.9.0", 310 311 "uint8arrays", ··· 317 318 "dependencies": [ 318 319 "@atproto/common", 319 320 "@atproto/identity", 320 - "@atproto/lexicon@0.6.1", 321 + "@atproto/lexicon", 321 322 "@atproto/repo", 322 323 "@atproto/syntax", 323 324 "@atproto/xrpc-server", ··· 345 346 "@atproto/common", 346 347 "@atproto/crypto", 347 348 "@atproto/lex-cbor", 348 - "@atproto/lex-data", 349 - "@atproto/lexicon@0.6.1", 349 + "@atproto/lex-data@0.0.10", 350 + "@atproto/lexicon", 350 351 "@atproto/ws-client", 351 352 "@atproto/xrpc", 352 353 "express", ··· 360 361 "@atproto/xrpc@0.7.7": { 361 362 "integrity": "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA==", 362 363 "dependencies": [ 363 - "@atproto/lexicon@0.6.1", 364 + "@atproto/lexicon", 364 365 "zod" 365 366 ] 366 367 }, ··· 396 397 "@noble/hashes@1.8.0": { 397 398 "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==" 398 399 }, 399 - "@types/node@24.0.7": { 400 - "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", 401 - "dependencies": [ 402 - "undici-types" 403 - ] 400 + "@opentelemetry/api@1.9.0": { 401 + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" 404 402 }, 405 403 "@types/webidl-conversions@7.0.3": { 406 404 "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" ··· 936 934 "p-timeout@6.1.4" 937 935 ] 938 936 }, 937 + "p-queue@9.1.0": { 938 + "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", 939 + "dependencies": [ 940 + "eventemitter3@5.0.4", 941 + "p-timeout@7.0.1" 942 + ] 943 + }, 939 944 "p-timeout@3.2.0": { 940 945 "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", 941 946 "dependencies": [ ··· 944 949 }, 945 950 "p-timeout@6.1.4": { 946 951 "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==" 952 + }, 953 + "p-timeout@7.0.1": { 954 + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==" 947 955 }, 948 956 "p-try@2.2.0": { 949 957 "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" ··· 1190 1198 "real-require" 1191 1199 ] 1192 1200 }, 1193 - "tlds@1.260.0": { 1194 - "integrity": "sha512-78+28EWBhCEE7qlyaHA9OR3IPvbCLiDh3Ckla593TksfFc9vfTsgvH7eS+dr3o9qr31gwGbogcI16yN91PoRjQ==", 1201 + "tlds@1.261.0": { 1202 + "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", 1195 1203 "bin": true 1196 1204 }, 1197 1205 "toidentifier@1.0.1": { ··· 1218 1226 "dependencies": [ 1219 1227 "multiformats@9.9.0" 1220 1228 ] 1221 - }, 1222 - "undici-types@7.8.0": { 1223 - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==" 1224 1229 }, 1225 1230 "unicode-segmenter@0.14.5": { 1226 1231 "integrity": "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==" ··· 1269 1274 "jsr:@atp/identity@~0.1.0-alpha.2", 1270 1275 "jsr:@atp/lexicon@~0.1.0-alpha.4", 1271 1276 "jsr:@atp/repo@~0.1.0-alpha.5", 1272 - "jsr:@atp/sync@~0.1.0-alpha.8", 1277 + "jsr:@atp/sync@~0.1.0-alpha.9", 1273 1278 "jsr:@atp/syntax@~0.1.0-alpha.2", 1274 1279 "jsr:@atp/xrpc-server@~0.1.0-alpha.9", 1275 1280 "jsr:@atp/xrpc@~0.1.0-alpha.4", 1276 1281 "jsr:@hono/hono@^4.11.9", 1277 - "jsr:@logtape/logtape@^1.3.7", 1278 - "jsr:@logtape/pretty@^1.3.7", 1279 1282 "jsr:@std/assert@^1.0.18", 1280 1283 "jsr:@std/encoding@^1.0.10", 1281 - "npm:@atproto/api@~0.16.11", 1284 + "npm:@atproto/api@~0.18.21", 1282 1285 "npm:dotenv@^17.2.4", 1283 1286 "npm:jose@^6.1.3", 1284 1287 "npm:mongodb-memory-server-core@^11.0.1", 1285 1288 "npm:mongoose@^8.23.0", 1286 1289 "npm:multiformats@^13.4.2", 1287 - "npm:p-queue@^8.1.1", 1290 + "npm:p-queue@^9.1.0", 1288 1291 "npm:structured-headers@^2.0.2" 1289 1292 ] 1290 1293 }
+2 -4
hydration/index.ts
··· 48 48 RecordInfo, 49 49 urisByCollection, 50 50 } from "./util.ts"; 51 - import { getLogger } from "@logtape/logtape"; 51 + 52 52 import { 53 53 LabelerAggs, 54 54 Labelers, ··· 143 143 144 144 export type BidirectionalBlocks = HydrationMap<HydrationMap<boolean>>; 145 145 146 - const hydrationLogger = getLogger(["appview", "hydrator"]); 147 - 148 146 export class Hydrator { 149 147 actor: ActorHydrator; 150 148 feed: FeedHydrator; ··· 237 235 try { 238 236 knownFollowers = await this.actor.getKnownFollowers(dids, ctx.viewer); 239 237 } catch (err) { 240 - hydrationLogger.error( 238 + console.error( 241 239 "Failed to get known followers for profiles", 242 240 { err }, 243 241 );
+1 -6
ingest.ts
··· 2 2 import { IdResolver } from "@atp/identity"; 3 3 import { ServerConfig } from "./config.ts"; 4 4 import { Database } from "./data-plane/db/index.ts"; 5 - import { getLogger } from "@logtape/logtape"; 6 - import { configureLogger } from "./utils/logger.ts"; 7 5 8 - await configureLogger(); 9 - 10 - const logger = getLogger(["ingester"]); 11 6 const cfg = ServerConfig.readEnv(); 12 7 13 8 const idResolver = new IdResolver({ plcUrl: cfg.plcUrl }); ··· 25 20 }); 26 21 27 22 sub.start(); 28 - logger.info("Subscription started"); 23 + console.info("Subscription started");
+5 -12
main.ts
··· 9 9 import health from "./api/health.ts"; 10 10 import { IdResolver, MemoryCache } from "@atp/identity"; 11 11 import { DataPlane } from "./data-plane/index.ts"; 12 - import { getLogger } from "@logtape/logtape"; 13 - import { configureLogger } from "./utils/logger.ts"; 14 12 import { Hydrator } from "./hydration/index.ts"; 15 13 import { Views } from "./views/index.ts"; 16 14 import { AppContext, AppEnv } from "./context.ts"; 17 15 import { ServerConfig } from "./config.ts"; 18 16 import { defaultLabelerHeader, parseLabelerHeader } from "./util.ts"; 19 17 import { PushService } from "./utils/push.ts"; 20 - 21 - await configureLogger(); 22 18 23 19 // Create app without starting services 24 20 export function createApp(ctx: AppContext): Hono<AppEnv> { ··· 44 40 45 41 // Setup function to create context and app 46 42 export function setupApp(): { app: Hono<AppEnv>; ctx: AppContext } { 47 - // Setup logger and database 48 - const appLogger = getLogger(["appview"]); 49 43 const cfg = ServerConfig.readEnv(); 50 44 const db = new Database(cfg); 51 45 db.connect(); ··· 90 84 dataplane, 91 85 hydrator, 92 86 views, 93 - logger: appLogger, 94 87 idResolver, 95 88 cfg, 96 89 authVerifier, ··· 111 104 Deno.serve({ 112 105 port, 113 106 onListen: (info) => { 114 - ctx.logger.info(`Server listening on ${info.hostname}:${info.port}`); 107 + console.info(`Server listening on ${info.hostname}:${info.port}`); 115 108 }, 116 109 }, app.fetch); 117 110 118 111 // Handle shutdown 119 112 const shutdown = async (signal: string) => { 120 - ctx.logger.info(`Received ${signal}; shutting down...`); 113 + console.info(`Received ${signal}; shutting down...`); 121 114 try { 122 - ctx.logger.info("Disconnecting database..."); 115 + console.info("Disconnecting database..."); 123 116 await ctx.db.disconnect(); 124 117 } catch (err) { 125 - ctx.logger.error("Error disconnecting database during shutdown", { err }); 118 + console.error("Error disconnecting database during shutdown", { err }); 126 119 } 127 - ctx.logger.info("Shutdown complete"); 120 + console.info("Shutdown complete"); 128 121 Deno.exit(0); 129 122 }; 130 123
-6
tests/util.ts
··· 6 6 import { AppContext, AppEnv } from "../context.ts"; 7 7 import { Database } from "../data-plane/db/index.ts"; 8 8 import { createAuthVerifier } from "../auth-verifier.ts"; 9 - import { getLogger } from "@logtape/logtape"; 10 9 import { DataPlane } from "../data-plane/index.ts"; 11 10 import { Hydrator } from "../hydration/index.ts"; 12 11 import { Views } from "../views/index.ts"; ··· 265 264 configOverrides: Partial<ServerConfigValues> = {}, 266 265 ): AppContext { 267 266 const cfg = new ServerConfig({ ...DEFAULT_TEST_CONFIG, ...configOverrides }); 268 - const appLogger = getLogger(["appview"]); 269 267 const idResolver = new IdResolver(); 270 268 271 269 // Create mock database that doesn't actually connect ··· 298 296 dataplane, 299 297 hydrator, 300 298 views, 301 - logger: appLogger, 302 299 idResolver, 303 300 cfg, 304 301 authVerifier, ··· 319 316 ): Promise<{ ctx: AppContext; cleanup: () => Promise<void> }> { 320 317 const testDb = await createTestDatabase(options); 321 318 const cfg = new ServerConfig({ ...DEFAULT_TEST_CONFIG, ...configOverrides }); 322 - const appLogger = getLogger(["appview"]); 323 319 const idResolver = new IdResolver(); 324 320 325 321 // Create a wrapper Database object that uses the test connection and models ··· 327 323 connection: testDb.connection, 328 324 models: testDb.models, 329 325 idResolver, 330 - logger: getLogger(["appview", "database"]), 331 326 connect: () => Promise.resolve(), 332 327 disconnect: async () => { 333 328 await testDb.cleanup(); ··· 381 376 dataplane, 382 377 hydrator, 383 378 views, 384 - logger: appLogger, 385 379 idResolver, 386 380 cfg, 387 381 authVerifier,
-27
utils/logger.ts
··· 1 - import { configure, getConsoleSink } from "@logtape/logtape"; 2 - import { getPrettyFormatter } from "@logtape/pretty"; 3 - 4 - export async function configureLogger() { 5 - await configure({ 6 - sinks: { 7 - console: getConsoleSink({ 8 - formatter: getPrettyFormatter({ 9 - properties: true, 10 - categoryStyle: "underline", 11 - messageColor: "rgb(255, 255, 255)", 12 - categoryColor: "rgb(255, 255, 255)", 13 - messageStyle: "reset", 14 - }), 15 - }), 16 - }, 17 - loggers: [ 18 - { category: "appview", lowestLevel: "info", sinks: ["console"] }, 19 - { category: "ingester", lowestLevel: "info", sinks: ["console"] }, 20 - { 21 - category: ["logtape", "meta"], 22 - lowestLevel: "error", 23 - sinks: ["console"], 24 - }, 25 - ], 26 - }); 27 - }
+12 -15
utils/push.ts
··· 1 - import { getLogger, Logger } from "@logtape/logtape"; 2 1 import { jsonStringToLex } from "@atp/lexicon"; 3 2 import { PushToken, PushTokens } from "../data-plane/routes/push-tokens.ts"; 4 3 import { Database } from "../data-plane/db/index.ts"; ··· 23 22 } 24 23 25 24 export class PushService { 26 - private logger: Logger; 27 25 private pushTokens: PushTokens; 28 26 private db: Database; 29 27 private config: PushConfig; ··· 32 30 private fcmServiceAccount: FcmServiceAccount | null = null; 33 31 34 32 constructor(pushTokens: PushTokens, db: Database, config: PushConfig) { 35 - this.logger = getLogger(["appview", "push"]); 36 33 this.pushTokens = pushTokens; 37 34 this.db = db; 38 35 this.config = config; ··· 41 38 try { 42 39 this.fcmServiceAccount = JSON.parse(config.fcmServiceAccount); 43 40 } catch { 44 - this.logger.error("Failed to parse FCM service account JSON"); 41 + console.error("Failed to parse FCM service account JSON"); 45 42 } 46 43 } 47 44 } ··· 72 69 invalidTokens.push(token.token); 73 70 } 74 71 } catch (err) { 75 - this.logger.error("Failed to send push notification", { 72 + console.error("Failed to send push notification", { 76 73 err, 77 74 platform: token.platform, 78 75 did, ··· 83 80 // Clean up invalid tokens 84 81 if (invalidTokens.length > 0) { 85 82 await this.pushTokens.deleteInvalidTokens(invalidTokens); 86 - this.logger.info("Removed invalid push tokens", { 83 + console.info("Removed invalid push tokens", { 87 84 count: invalidTokens.length, 88 85 }); 89 86 } ··· 117 114 invalidTokens.push(token.token); 118 115 } 119 116 } catch (err) { 120 - this.logger.error("Failed to send badge reset", { 117 + console.error("Failed to send badge reset", { 121 118 err, 122 119 did, 123 120 }); ··· 148 145 const count = await this.db.models.Notification.countDocuments(filter); 149 146 return count; 150 147 } catch (err) { 151 - this.logger.error("Failed to get unread count", { err, did }); 148 + console.error("Failed to get unread count", { err, did }); 152 149 return 1; // Default to 1 if we can't get the count 153 150 } 154 151 } ··· 213 210 ) { 214 211 return false; 215 212 } 216 - this.logger.error("Badge reset FCM request failed", { 213 + console.error("Badge reset FCM request failed", { 217 214 error, 218 215 status: response.status, 219 216 }); ··· 221 218 222 219 return true; 223 220 } catch (err) { 224 - this.logger.error("Badge reset FCM request error", { err }); 221 + console.error("Badge reset FCM request error", { err }); 225 222 return true; 226 223 } 227 224 } ··· 232 229 badgeCount: number, 233 230 ): Promise<boolean> { 234 231 if (!this.fcmServiceAccount) { 235 - this.logger.warn("FCM service account not configured"); 232 + console.warn("FCM service account not configured"); 236 233 return true; // Don't mark as invalid if not configured 237 234 } 238 235 ··· 306 303 ) { 307 304 return false; // Mark as invalid 308 305 } 309 - this.logger.error("FCM request failed", { 306 + console.error("FCM request failed", { 310 307 error, 311 308 status: response.status, 312 309 }); ··· 314 311 315 312 return true; 316 313 } catch (err) { 317 - this.logger.error("FCM request error", { err }); 314 + console.error("FCM request error", { err }); 318 315 return true; // Don't mark as invalid on network errors 319 316 } 320 317 } ··· 465 462 }); 466 463 467 464 if (!response.ok) { 468 - this.logger.error("Failed to get FCM access token", { 465 + console.error("Failed to get FCM access token", { 469 466 status: response.status, 470 467 }); 471 468 return null; ··· 477 474 478 475 return this.fcmAccessToken; 479 476 } catch (err) { 480 - this.logger.error("Error getting FCM access token", { err }); 477 + console.error("Error getting FCM access token", { err }); 481 478 return null; 482 479 } 483 480 }