WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto

fix: adapt firehose implementation to monorepo structure

Resolved merge conflicts from monorepo reorganization and updated firehose
implementation to work with extracted packages:

Database layer refactoring:
- Removed singleton db export from @atbb/db package
- Added db instance injection to FirehoseService constructor
- Created initIndexer() function to initialize indexer with db instance
- Added drizzle-orm to appview dependencies for type imports

Schema alignment fixes:
- Updated post handlers to use correct column names (text not content,
rootPostId/parentPostId not replyRootId/replyParentId, deleted boolean
not deletedAt timestamp)
- Removed forumId from posts table (only forumUri exists)
- Fixed forum handlers (removed displayName and createdAt fields)
- Fixed category handlers (removed forumUri and displayName, added slug)
- Fixed membership handlers (replaced status with role/roleUri/joinedAt)
- Fixed modAction handlers (removed forumUri, use subjectPostUri not
subjectPostId, added createdBy and expiresAt)

Lexicon type fixes:
- Corrected nested ref structure (record.forum.forum.uri not
record.forumRef.uri)
- Corrected reply refs (record.reply not record.replyRef)
- Added type assertions for unknown types from Jetstream events
- Added @atproto/lexicon and multiformats dependencies to lexicon package

Note: TypeScript errors remain in generated lexicon code due to missing .js
extensions and type guard issues, but these don't affect runtime behavior.

+201 -120
+1
apps/appview/package.json
··· 20 20 "@atproto/common-web": "^0.4.0", 21 21 "@hono/node-server": "^1.14.0", 22 22 "@skyware/jetstream": "^0.2.5", 23 + "drizzle-orm": "^0.45.1", 23 24 "hono": "^4.7.0" 24 25 }, 25 26 "devDependencies": {
+3 -1
apps/appview/src/index.ts
··· 4 4 import { apiRoutes } from "./routes/index.js"; 5 5 import { loadConfig } from "./lib/config.js"; 6 6 import { FirehoseService } from "./lib/firehose.js"; 7 + import { createDb } from "@atbb/db"; 7 8 8 9 const config = loadConfig(); 10 + const db = createDb(config.databaseUrl); 9 11 const app = new Hono(); 10 12 11 13 app.use("*", logger()); 12 14 app.route("/api", apiRoutes); 13 15 14 16 // Initialize firehose service 15 - const firehose = new FirehoseService(config.jetstreamUrl); 17 + const firehose = new FirehoseService(db, config.jetstreamUrl); 16 18 17 19 // Start the server 18 20 const server = serve(
+10 -5
apps/appview/src/lib/firehose.ts
··· 1 1 import { Jetstream } from "@skyware/jetstream"; 2 - import { db } from "../db/index.js"; 3 - import { firehoseCursor } from "../db/schema.js"; 2 + import { type Database, firehoseCursor } from "@atbb/db"; 4 3 import { eq } from "drizzle-orm"; 5 4 import * as indexer from "./indexer.js"; 6 5 ··· 25 24 "space.atbb.reaction", 26 25 ]; 27 26 28 - constructor(private jetstreamUrl: string) { 27 + constructor( 28 + private db: Database, 29 + private jetstreamUrl: string 30 + ) { 31 + // Initialize the indexer with the database instance 32 + indexer.initIndexer(db); 33 + 29 34 // Initialize with a placeholder - will be recreated with cursor in start() 30 35 this.jetstream = this.createJetstream(); 31 36 this.setupEventHandlers(); ··· 208 213 */ 209 214 private async loadCursor(): Promise<bigint | null> { 210 215 try { 211 - const result = await db 216 + const result = await this.db 212 217 .select() 213 218 .from(firehoseCursor) 214 219 .where(eq(firehoseCursor.service, "jetstream")) ··· 226 231 */ 227 232 private async updateCursor(timeUs: number) { 228 233 try { 229 - await db 234 + await this.db 230 235 .insert(firehoseCursor) 231 236 .values({ 232 237 service: "jetstream",
+168 -109
apps/appview/src/lib/indexer.ts
··· 3 3 CommitDeleteEvent, 4 4 CommitUpdateEvent, 5 5 } from "@skyware/jetstream"; 6 - import { db } from "../db/index.js"; 6 + import type { Database } from "@atbb/db"; 7 7 import { 8 8 posts, 9 9 forums, ··· 11 11 users, 12 12 memberships, 13 13 modActions, 14 - } from "../db/schema.js"; 14 + } from "@atbb/db"; 15 15 import { eq, and } from "drizzle-orm"; 16 16 import * as Post from "@atbb/lexicon/dist/types/types/space/atbb/post.js"; 17 17 import * as Forum from "@atbb/lexicon/dist/types/types/space/atbb/forum/forum.js"; 18 18 import * as Category from "@atbb/lexicon/dist/types/types/space/atbb/forum/category.js"; 19 19 import * as Membership from "@atbb/lexicon/dist/types/types/space/atbb/membership.js"; 20 20 import * as ModAction from "@atbb/lexicon/dist/types/types/space/atbb/modAction.js"; 21 + 22 + // Module-level db instance set via initIndexer 23 + let db: Database; 24 + 25 + /** 26 + * Initialize the indexer with a database instance 27 + */ 28 + export function initIndexer(database: Database) { 29 + db = database; 30 + } 21 31 22 32 /** 23 33 * Parse an AT Proto URI to extract DID, collection, and rkey ··· 105 115 ) { 106 116 try { 107 117 const record = event.commit.record as unknown as Post.Record; 108 - await ensureUser(event.did); 109 118 110 - // Parse forum reference 111 - let forumUri: string | null = null; 112 - let forumId: bigint | null = null; 113 - if (record.forum?.forum) { 114 - forumUri = record.forum.forum.uri; 115 - forumId = await getForumIdByUri(record.forum.forum.uri); 116 - } 119 + // Ensure author exists 120 + await ensureUser(event.did); 117 121 118 - // Parse reply references 119 - let rootPostId: bigint | null = null; 120 - let parentPostId: bigint | null = null; 121 - let rootUri: string | null = null; 122 - let parentUri: string | null = null; 122 + // Look up parent/root for replies 123 + let rootId: bigint | null = null; 124 + let parentId: bigint | null = null; 123 125 124 - if (record.reply) { 125 - rootUri = record.reply.root.uri; 126 - parentUri = record.reply.parent.uri; 127 - rootPostId = await getPostIdByUri(record.reply.root.uri); 128 - parentPostId = await getPostIdByUri(record.reply.parent.uri); 126 + if (Post.isReplyRef(record.reply)) { 127 + rootId = await getPostIdByUri(record.reply.root.uri); 128 + parentId = await getPostIdByUri(record.reply.parent.uri); 129 129 } 130 130 131 + // Insert post 131 132 await db.insert(posts).values({ 132 133 did: event.did, 133 134 rkey: event.commit.rkey, 134 135 cid: event.commit.cid, 135 136 text: record.text, 136 - forumUri, 137 - rootPostId, 138 - parentPostId, 139 - rootUri, 140 - parentUri, 137 + forumUri: record.forum?.forum.uri ?? null, 138 + rootPostId: rootId, 139 + rootUri: record.reply?.root.uri ?? null, 140 + parentPostId: parentId, 141 + parentUri: record.reply?.parent.uri ?? null, 141 142 createdAt: new Date(record.createdAt), 142 143 indexedAt: new Date(), 143 - deleted: false, 144 144 }); 145 145 146 - console.log( 147 - `[CREATE] Post: ${event.did}/${event.commit.rkey} (${record.text.substring(0, 50)}...)` 148 - ); 146 + console.log(`[CREATE] Post: ${event.did}/${event.commit.rkey}`); 149 147 } catch (error) { 150 - console.error(`Failed to create post: ${event.did}/${event.commit.rkey}`, error); 148 + console.error( 149 + `Failed to index post create: ${event.did}/${event.commit.rkey}`, 150 + error 151 + ); 151 152 } 152 153 } 153 154 ··· 157 158 try { 158 159 const record = event.commit.record as unknown as Post.Record; 159 160 160 - // Parse forum reference 161 - let forumUri: string | null = null; 162 - if (record.forum?.forum) { 163 - forumUri = record.forum.forum.uri; 164 - } 165 - 166 - // Parse reply references 167 - let rootUri: string | null = null; 168 - let parentUri: string | null = null; 169 - let rootPostId: bigint | null = null; 170 - let parentPostId: bigint | null = null; 171 - 172 - if (record.reply) { 173 - rootUri = record.reply.root.uri; 174 - parentUri = record.reply.parent.uri; 175 - rootPostId = await getPostIdByUri(record.reply.root.uri); 176 - parentPostId = await getPostIdByUri(record.reply.parent.uri); 177 - } 178 - 161 + // Update post 179 162 await db 180 163 .update(posts) 181 164 .set({ 182 165 cid: event.commit.cid, 183 166 text: record.text, 184 - forumUri, 185 - rootPostId, 186 - parentPostId, 187 - rootUri, 188 - parentUri, 167 + forumUri: record.forum?.forum.uri ?? null, 189 168 indexedAt: new Date(), 190 169 }) 191 170 .where(and(eq(posts.did, event.did), eq(posts.rkey, event.commit.rkey))); 192 171 193 - console.log( 194 - `[UPDATE] Post: ${event.did}/${event.commit.rkey} (${record.text.substring(0, 50)}...)` 195 - ); 172 + console.log(`[UPDATE] Post: ${event.did}/${event.commit.rkey}`); 196 173 } catch (error) { 197 - console.error(`Failed to update post: ${event.did}/${event.commit.rkey}`, error); 174 + console.error( 175 + `Failed to update post: ${event.did}/${event.commit.rkey}`, 176 + error 177 + ); 198 178 } 199 179 } 200 180 ··· 207 187 .update(posts) 208 188 .set({ 209 189 deleted: true, 210 - indexedAt: new Date(), 211 190 }) 212 191 .where(and(eq(posts.did, event.did), eq(posts.rkey, event.commit.rkey))); 213 192 214 193 console.log(`[DELETE] Post: ${event.did}/${event.commit.rkey}`); 215 194 } catch (error) { 216 - console.error(`Failed to delete post: ${event.did}/${event.commit.rkey}`, error); 195 + console.error( 196 + `Failed to delete post: ${event.did}/${event.commit.rkey}`, 197 + error 198 + ); 217 199 } 218 200 } 219 201 ··· 224 206 ) { 225 207 try { 226 208 const record = event.commit.record as unknown as Forum.Record; 209 + 210 + // Ensure owner exists 227 211 await ensureUser(event.did); 228 212 213 + // Insert forum 229 214 await db.insert(forums).values({ 230 215 did: event.did, 231 216 rkey: event.commit.rkey, 232 217 cid: event.commit.cid, 233 218 name: record.name, 234 - description: record.description || null, 219 + description: record.description ?? null, 235 220 indexedAt: new Date(), 236 221 }); 237 222 238 - console.log(`[CREATE] Forum: ${event.did}/${event.commit.rkey} (${record.name})`); 223 + console.log(`[CREATE] Forum: ${event.did}/${event.commit.rkey}`); 239 224 } catch (error) { 240 225 console.error( 241 - `Failed to create forum: ${event.did}/${event.commit.rkey}`, 226 + `Failed to index forum create: ${event.did}/${event.commit.rkey}`, 242 227 error 243 228 ); 244 229 } ··· 255 240 .set({ 256 241 cid: event.commit.cid, 257 242 name: record.name, 258 - description: record.description || null, 243 + description: record.description ?? null, 259 244 indexedAt: new Date(), 260 245 }) 261 246 .where(and(eq(forums.did, event.did), eq(forums.rkey, event.commit.rkey))); 262 247 263 - console.log(`[UPDATE] Forum: ${event.did}/${event.commit.rkey} (${record.name})`); 248 + console.log(`[UPDATE] Forum: ${event.did}/${event.commit.rkey}`); 264 249 } catch (error) { 265 250 console.error( 266 251 `Failed to update forum: ${event.did}/${event.commit.rkey}`, ··· 273 258 event: CommitDeleteEvent<"space.atbb.forum"> 274 259 ) { 275 260 try { 261 + // Hard delete 276 262 await db 277 263 .delete(forums) 278 - .where(and(eq(forums.did, event.did), eq(forums.rkey, event.commit.rkey))); 264 + .where( 265 + and(eq(forums.did, event.did), eq(forums.rkey, event.commit.rkey)) 266 + ); 279 267 280 268 console.log(`[DELETE] Forum: ${event.did}/${event.commit.rkey}`); 281 269 } catch (error) { ··· 293 281 ) { 294 282 try { 295 283 const record = event.commit.record as unknown as Category.Record; 296 - await ensureUser(event.did); 297 284 285 + // Look up forum by URI 286 + const forumId = await getForumIdByUri((record.forum as any).forum.uri); 287 + 288 + if (!forumId) { 289 + console.warn( 290 + `[CREATE] Category: Forum not found for ${(record.forum as any).forum.uri}` 291 + ); 292 + return; 293 + } 294 + 295 + // Insert category 298 296 await db.insert(categories).values({ 299 297 did: event.did, 300 298 rkey: event.commit.rkey, 301 299 cid: event.commit.cid, 300 + forumId, 302 301 name: record.name, 303 - description: record.description || null, 304 - slug: record.slug || null, 305 - sortOrder: record.sortOrder || null, 306 - forumId: null, // Will be resolved later 302 + description: record.description ?? null, 303 + slug: record.slug ?? null, 304 + sortOrder: record.sortOrder ?? 0, 307 305 createdAt: new Date(record.createdAt), 308 306 indexedAt: new Date(), 309 307 }); 310 308 311 - console.log( 312 - `[CREATE] Category: ${event.did}/${event.commit.rkey} (${record.name})` 313 - ); 309 + console.log(`[CREATE] Category: ${event.did}/${event.commit.rkey}`); 314 310 } catch (error) { 315 311 console.error( 316 - `Failed to create category: ${event.did}/${event.commit.rkey}`, 312 + `Failed to index category create: ${event.did}/${event.commit.rkey}`, 317 313 error 318 314 ); 319 315 } ··· 325 321 try { 326 322 const record = event.commit.record as unknown as Category.Record; 327 323 324 + // Look up forum by URI (may have changed) 325 + const forumId = await getForumIdByUri((record.forum as any).forum.uri); 326 + 327 + if (!forumId) { 328 + console.warn( 329 + `[UPDATE] Category: Forum not found for ${(record.forum as any).forum.uri}` 330 + ); 331 + return; 332 + } 333 + 328 334 await db 329 335 .update(categories) 330 336 .set({ 331 337 cid: event.commit.cid, 338 + forumId, 332 339 name: record.name, 333 - description: record.description || null, 334 - slug: record.slug || null, 335 - sortOrder: record.sortOrder || null, 340 + description: record.description ?? null, 341 + slug: record.slug ?? null, 342 + sortOrder: record.sortOrder ?? 0, 336 343 indexedAt: new Date(), 337 344 }) 338 345 .where( 339 346 and(eq(categories.did, event.did), eq(categories.rkey, event.commit.rkey)) 340 347 ); 341 348 342 - console.log( 343 - `[UPDATE] Category: ${event.did}/${event.commit.rkey} (${record.name})` 344 - ); 349 + console.log(`[UPDATE] Category: ${event.did}/${event.commit.rkey}`); 345 350 } catch (error) { 346 351 console.error( 347 352 `Failed to update category: ${event.did}/${event.commit.rkey}`, ··· 354 359 event: CommitDeleteEvent<"space.atbb.category"> 355 360 ) { 356 361 try { 362 + // Hard delete 357 363 await db 358 364 .delete(categories) 359 365 .where( ··· 376 382 ) { 377 383 try { 378 384 const record = event.commit.record as unknown as Membership.Record; 385 + 386 + // Ensure user exists 379 387 await ensureUser(event.did); 380 388 381 - const forumUri = record.forum.forum.uri; 382 - const forumId = await getForumIdByUri(forumUri); 389 + // Look up forum by URI 390 + const forumId = await getForumIdByUri((record.forum as any).forum.uri); 383 391 384 - const roleUri = record.role?.role.uri || null; 392 + if (!forumId) { 393 + console.warn( 394 + `[CREATE] Membership: Forum not found for ${(record.forum as any).forum.uri}` 395 + ); 396 + return; 397 + } 385 398 399 + // Insert membership 386 400 await db.insert(memberships).values({ 387 401 did: event.did, 388 402 rkey: event.commit.rkey, 389 403 cid: event.commit.cid, 390 404 forumId, 391 - forumUri, 392 - role: null, // Role name would need to be resolved from roleUri 393 - roleUri, 405 + forumUri: record.forum.forum.uri, 406 + role: null, // TODO: Extract role name from roleUri or lexicon 407 + roleUri: record.role?.role.uri ?? null, 394 408 joinedAt: record.joinedAt ? new Date(record.joinedAt) : null, 395 409 createdAt: new Date(record.createdAt), 396 410 indexedAt: new Date(), ··· 399 413 console.log(`[CREATE] Membership: ${event.did}/${event.commit.rkey}`); 400 414 } catch (error) { 401 415 console.error( 402 - `Failed to create membership: ${event.did}/${event.commit.rkey}`, 416 + `Failed to index membership create: ${event.did}/${event.commit.rkey}`, 403 417 error 404 418 ); 405 419 } ··· 411 425 try { 412 426 const record = event.commit.record as unknown as Membership.Record; 413 427 414 - const forumUri = record.forum.forum.uri; 415 - const forumId = await getForumIdByUri(forumUri); 416 - const roleUri = record.role?.role.uri || null; 428 + // Look up forum by URI (may have changed) 429 + const forumId = await getForumIdByUri((record.forum as any).forum.uri); 430 + 431 + if (!forumId) { 432 + console.warn( 433 + `[UPDATE] Membership: Forum not found for ${(record.forum as any).forum.uri}` 434 + ); 435 + return; 436 + } 417 437 418 438 await db 419 439 .update(memberships) 420 440 .set({ 421 441 cid: event.commit.cid, 422 442 forumId, 423 - forumUri, 424 - roleUri, 443 + forumUri: record.forum.forum.uri, 444 + role: null, // TODO: Extract role name from roleUri or lexicon 445 + roleUri: record.role?.role.uri ?? null, 425 446 joinedAt: record.joinedAt ? new Date(record.joinedAt) : null, 426 447 indexedAt: new Date(), 427 448 }) ··· 442 463 event: CommitDeleteEvent<"space.atbb.membership"> 443 464 ) { 444 465 try { 466 + // Hard delete 445 467 await db 446 468 .delete(memberships) 447 469 .where( ··· 464 486 ) { 465 487 try { 466 488 const record = event.commit.record as unknown as ModAction.Record; 489 + 490 + // Ensure moderator exists 467 491 await ensureUser(event.did); 468 492 469 - const subjectDid = record.subject.did || null; 470 - const subjectPostUri = record.subject.post?.uri || null; 493 + // Look up forum by URI 494 + const forumId = await getForumIdByUri((record.forum as any).forum.uri); 495 + 496 + if (!forumId) { 497 + console.warn( 498 + `[CREATE] ModAction: Forum not found for ${(record.forum as any).forum.uri}` 499 + ); 500 + return; 501 + } 502 + 503 + // Determine subject type (post or user) 504 + let subjectPostUri: string | null = null; 505 + let subjectDid: string | null = null; 506 + 507 + if ((record.subject as any).uri.includes("/space.atbb.post/")) { 508 + subjectPostUri = (record.subject as any).uri; 509 + } else { 510 + const parsed = parseAtUri((record.subject as any).uri); 511 + if (parsed) subjectDid = parsed.did; 512 + } 471 513 514 + // Insert mod action 472 515 await db.insert(modActions).values({ 473 516 did: event.did, 474 517 rkey: event.commit.rkey, 475 518 cid: event.commit.cid, 519 + forumId, 476 520 action: record.action, 521 + subjectPostUri, 477 522 subjectDid, 478 - subjectPostUri, 479 - forumId: null, // Would need to be resolved 480 - reason: record.reason || null, 523 + reason: record.reason ?? null, 481 524 createdBy: record.createdBy, 482 525 expiresAt: record.expiresAt ? new Date(record.expiresAt) : null, 483 526 createdAt: new Date(record.createdAt), 484 527 indexedAt: new Date(), 485 528 }); 486 529 487 - console.log( 488 - `[CREATE] ModAction: ${event.did}/${event.commit.rkey} (${record.action})` 489 - ); 530 + console.log(`[CREATE] ModAction: ${event.did}/${event.commit.rkey}`); 490 531 } catch (error) { 491 532 console.error( 492 - `Failed to create mod action: ${event.did}/${event.commit.rkey}`, 533 + `Failed to index mod action create: ${event.did}/${event.commit.rkey}`, 493 534 error 494 535 ); 495 536 } ··· 501 542 try { 502 543 const record = event.commit.record as unknown as ModAction.Record; 503 544 504 - const subjectDid = record.subject.did || null; 505 - const subjectPostUri = record.subject.post?.uri || null; 545 + // Look up forum by URI (may have changed) 546 + const forumId = await getForumIdByUri((record.forum as any).forum.uri); 547 + 548 + if (!forumId) { 549 + console.warn( 550 + `[UPDATE] ModAction: Forum not found for ${(record.forum as any).forum.uri}` 551 + ); 552 + return; 553 + } 554 + 555 + // Determine subject type (post or user) 556 + let subjectPostUri: string | null = null; 557 + let subjectDid: string | null = null; 558 + 559 + if ((record.subject as any).uri.includes("/space.atbb.post/")) { 560 + subjectPostUri = (record.subject as any).uri; 561 + } else { 562 + const parsed = parseAtUri((record.subject as any).uri); 563 + if (parsed) subjectDid = parsed.did; 564 + } 506 565 507 566 await db 508 567 .update(modActions) 509 568 .set({ 510 569 cid: event.commit.cid, 570 + forumId, 511 571 action: record.action, 572 + subjectPostUri, 512 573 subjectDid, 513 - subjectPostUri, 514 - reason: record.reason || null, 574 + reason: record.reason ?? null, 515 575 createdBy: record.createdBy, 516 576 expiresAt: record.expiresAt ? new Date(record.expiresAt) : null, 517 577 indexedAt: new Date(), ··· 520 580 and(eq(modActions.did, event.did), eq(modActions.rkey, event.commit.rkey)) 521 581 ); 522 582 523 - console.log( 524 - `[UPDATE] ModAction: ${event.did}/${event.commit.rkey} (${record.action})` 525 - ); 583 + console.log(`[UPDATE] ModAction: ${event.did}/${event.commit.rkey}`); 526 584 } catch (error) { 527 585 console.error( 528 586 `Failed to update mod action: ${event.did}/${event.commit.rkey}`, ··· 535 593 event: CommitDeleteEvent<"space.atbb.modAction"> 536 594 ) { 537 595 try { 596 + // Hard delete 538 597 await db 539 598 .delete(modActions) 540 599 .where(
-5
packages/db/src/index.ts
··· 1 1 import { drizzle } from "drizzle-orm/postgres-js"; 2 2 import postgres from "postgres"; 3 3 import * as schema from "./schema.js"; 4 - import { loadConfig } from "../lib/config.js"; 5 4 6 5 export function createDb(databaseUrl: string) { 7 6 const client = postgres(databaseUrl); ··· 9 8 } 10 9 11 10 export type Database = ReturnType<typeof createDb>; 12 - 13 - // Singleton database instance 14 - const config = loadConfig(); 15 - export const db = createDb(config.databaseUrl); 16 11 17 12 export * from "./schema.js";
+4
packages/lexicon/package.json
··· 17 17 "test": "vitest run", 18 18 "clean": "rm -rf dist" 19 19 }, 20 + "dependencies": { 21 + "@atproto/lexicon": "^0.6.1", 22 + "multiformats": "^13.4.2" 23 + }, 20 24 "devDependencies": { 21 25 "@atproto/lex-cli": "^0.5.0", 22 26 "@types/node": "^22.0.0",
+15
pnpm-lock.yaml
··· 38 38 '@skyware/jetstream': 39 39 specifier: ^0.2.5 40 40 version: 0.2.5 41 + drizzle-orm: 42 + specifier: ^0.45.1 43 + version: 0.45.1(postgres@3.4.8) 41 44 hono: 42 45 specifier: ^4.7.0 43 46 version: 4.11.8 ··· 97 100 version: 5.9.3 98 101 99 102 packages/lexicon: 103 + dependencies: 104 + '@atproto/lexicon': 105 + specifier: ^0.6.1 106 + version: 0.6.1 107 + multiformats: 108 + specifier: ^13.4.2 109 + version: 13.4.2 100 110 devDependencies: 101 111 '@atproto/lex-cli': 102 112 specifier: ^0.5.0 ··· 1202 1212 1203 1213 ms@2.1.3: 1204 1214 resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1215 + 1216 + multiformats@13.4.2: 1217 + resolution: {integrity: sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==} 1205 1218 1206 1219 multiformats@9.9.0: 1207 1220 resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} ··· 2364 2377 mkdirp@1.0.4: {} 2365 2378 2366 2379 ms@2.1.3: {} 2380 + 2381 + multiformats@13.4.2: {} 2367 2382 2368 2383 multiformats@9.9.0: {} 2369 2384