Shows how to get repo export and walk it in TypeScript walktherepo.wisp.place

I think thats about it

-12
lexicons.json
··· 1 - { 2 - "version": 1, 3 - "lexicons": [ 4 - "com.atproto.sync.getRepo" 5 - ], 6 - "resolutions": { 7 - "com.atproto.sync.getRepo": { 8 - "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.getRepo", 9 - "cid": "bafyreieyt5x6wf4pgcn56tb2724uplmwcqjivkubmi25km3xrvroz6dw5q" 10 - } 11 - } 12 - }
+2 -1
package.json
··· 19 19 "vite": "^7.2.4" 20 20 }, 21 21 "dependencies": { 22 - "@atproto-labs/did-resolver": "^0.2.5", 22 + "@atcute/car": "^5.0.0", 23 + "@atcute/cbor": "^2.2.8", 23 24 "@atproto-labs/handle-resolver": "^0.3.5", 24 25 "@atproto/identity": "^0.4.10", 25 26 "@atproto/lex": "^0.0.10"
+50 -3
pnpm-lock.yaml
··· 8 8 9 9 .: 10 10 dependencies: 11 - '@atproto-labs/did-resolver': 12 - specifier: ^0.2.5 13 - version: 0.2.5 11 + '@atcute/car': 12 + specifier: ^5.0.0 13 + version: 5.0.0 14 + '@atcute/cbor': 15 + specifier: ^2.2.8 16 + version: 2.2.8 14 17 '@atproto-labs/handle-resolver': 15 18 specifier: ^0.3.5 16 19 version: 0.3.5 ··· 44 47 version: 7.3.1(@types/node@24.10.4) 45 48 46 49 packages: 50 + 51 + '@atcute/car@5.0.0': 52 + resolution: {integrity: sha512-OIY2xTXv8lSpZsDSn/UYQtJSMvDw5Hi4Q+uyvmiqSM+fht08QRAEq/nxa5YFciPZ3nfDFnZ3//EgJw7QhkSXLQ==} 53 + 54 + '@atcute/cbor@2.2.8': 55 + resolution: {integrity: sha512-UzOAN9BuN6JCXgn0ryV8qZuRJUDrNqrbLd6EFM8jc6RYssjRyGRxNy6RZ1NU/07Hd8Tq/0pz8+nQiMu5Zai5uw==} 56 + 57 + '@atcute/cid@2.3.0': 58 + resolution: {integrity: sha512-1SRdkTuMs/l5arQ+7Ag0F7JAueZqtzYE0d2gmbkuzi8EPweNU1kYlQs0CE4dSd81YF8PMDTOQty0K2ATq9CW9g==} 59 + 60 + '@atcute/multibase@1.1.6': 61 + resolution: {integrity: sha512-HBxuCgYLKPPxETV0Rot4VP9e24vKl8JdzGCZOVsDaOXJgbRZoRIF67Lp0H/OgnJeH/Xpva8Z5ReoTNJE5dn3kg==} 62 + 63 + '@atcute/uint8array@1.0.6': 64 + resolution: {integrity: sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A==} 65 + 66 + '@atcute/varint@1.0.3': 67 + resolution: {integrity: sha512-fdvMPyBB+McDT+Ai5e9RwEbwYV4yjZ60S2Dn5PTjGqUyxvoCH1z42viuheDZRUDkmfQehXJTZ5az7dSozVNtog==} 47 68 48 69 '@atproto-labs/did-resolver@0.2.5': 49 70 resolution: {integrity: sha512-he7EC6OMSifNs01a4RT9mta/yYitoKDzlK9ty2TFV5Uj/+HpB4vYMRdIDFrRW0Hcsehy90E2t/dw0t7361MEKQ==} ··· 840 861 resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} 841 862 842 863 snapshots: 864 + 865 + '@atcute/car@5.0.0': 866 + dependencies: 867 + '@atcute/cbor': 2.2.8 868 + '@atcute/cid': 2.3.0 869 + '@atcute/uint8array': 1.0.6 870 + '@atcute/varint': 1.0.3 871 + 872 + '@atcute/cbor@2.2.8': 873 + dependencies: 874 + '@atcute/cid': 2.3.0 875 + '@atcute/multibase': 1.1.6 876 + '@atcute/uint8array': 1.0.6 877 + 878 + '@atcute/cid@2.3.0': 879 + dependencies: 880 + '@atcute/multibase': 1.1.6 881 + '@atcute/uint8array': 1.0.6 882 + 883 + '@atcute/multibase@1.1.6': 884 + dependencies: 885 + '@atcute/uint8array': 1.0.6 886 + 887 + '@atcute/uint8array@1.0.6': {} 888 + 889 + '@atcute/varint@1.0.3': {} 843 890 844 891 '@atproto-labs/did-resolver@0.2.5': 845 892 dependencies:
-5
src/lexicons/com.ts
··· 1 - /* 2 - * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 - */ 4 - 5 - export * as atproto from './com/atproto.js'
-5
src/lexicons/com/atproto.ts
··· 1 - /* 2 - * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 - */ 4 - 5 - export * as sync from './atproto/sync.js'
-5
src/lexicons/com/atproto/sync.ts
··· 1 - /* 2 - * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 - */ 4 - 5 - export * as getRepo from './sync/getRepo.js'
-29
src/lexicons/com/atproto/sync/getRepo.defs.ts
··· 1 - /* 2 - * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 - */ 4 - 5 - import { l } from '@atproto/lex' 6 - 7 - const $nsid = 'com.atproto.sync.getRepo' 8 - 9 - export { $nsid } 10 - 11 - /** Download a repository export as CAR file. Optionally only a 'diff' since a previous revision. Does not require auth; implemented by PDS. */ 12 - const main = l.query( 13 - $nsid, 14 - l.params({ 15 - did: l.string({ format: 'did' }), 16 - since: l.optional(l.string({ format: 'tid' })), 17 - }), 18 - l.payload('application/vnd.ipld.car'), 19 - ['RepoNotFound', 'RepoTakendown', 'RepoSuspended', 'RepoDeactivated'], 20 - ) 21 - export { main } 22 - 23 - export type Params = l.InferMethodParams<typeof main> 24 - export type Output = l.InferMethodOutput<typeof main> 25 - export type OutputBody = l.InferMethodOutputBody<typeof main> 26 - 27 - export const $lxm = main.nsid, 28 - $params = main.parameters, 29 - $output = main.output
-6
src/lexicons/com/atproto/sync/getRepo.ts
··· 1 - /* 2 - * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 - */ 4 - 5 - export * from './getRepo.defs.js' 6 - export * as $defs from './getRepo.defs.js'
+48 -12
src/lib/RepoStats.svelte
··· 1 1 <script lang="ts"> 2 + import {fromStream, fromUint8Array} from '@atcute/car'; 3 + import * as CBOR from '@atcute/cbor'; 4 + 5 + interface CountedCollection { 6 + collection: string; 7 + count: number; 8 + } 9 + 10 + 2 11 const { did, pdsUrl } = $props(); 3 12 let loading = $state(true) 4 13 let downloadedBytes = $state(0) 5 14 let downloadedMB = $derived((downloadedBytes / (1024 * 1024)).toFixed(2)) 6 15 let error: string | null = $state(null) 7 - 16 + let collections = $state(new Array<CountedCollection>()); 17 + let collectionsOrdered: Array<CountedCollection> = $derived([...collections].sort((a, b) => b.count - a.count)) 18 + let totalRecords = $state(0) 8 19 9 20 const getRepoStats = async () => { 10 21 const endPoint = `${pdsUrl}/xrpc/com.atproto.sync.getRepo?did=${did}` ··· 13 24 const response = await fetch(endPoint) 14 25 if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`) 15 26 16 - const reader = response.body?.getReader() 17 - if (!reader) throw new Error('ReadableStream not supported') 27 + const car = fromStream(response.body); 28 + 29 + try { 30 + for await (const entry of car) { 31 + const data = CBOR.decode(entry.bytes); 32 + if (!data.$type) { 33 + continue; 34 + } 18 35 19 - while (true) { 20 - const { done, value } = await reader.read() 21 - if (done) break 22 - downloadedBytes += value.length 36 + let checkForCollection = collections.find(c => c.collection === data.$type); 37 + if (!checkForCollection) { 38 + collections.push({collection: data.$type, count: 1}); 39 + }else{ 40 + checkForCollection.count++; 41 + } 42 + downloadedBytes = entry.entryEnd; 43 + totalRecords++; 23 44 } 24 - loading = false 45 + }finally{ 46 + await car.dispose() 47 + } 48 + 49 + loading = false; 25 50 } catch (err) { 26 - console.error('Error fetching repo stats:', error) 51 + console.error('Error fetching repo stats:', err) 27 52 error = err.message 28 53 loading = false 29 54 } ··· 41 66 {/if} 42 67 {#if loading} 43 68 Loading... ({downloadedMB} MB downloaded) 44 - {:else } 45 - <ol> 46 - <li>Total downloaded: {downloadedMB} MB</li> 69 + {:else} 70 + <span>Repo size {downloadedMB} MB</span> 71 + {/if} 72 + {#if collectionsOrdered.length > 0} 73 + 74 + <br> 75 + <span>Total Records: {totalRecords.toLocaleString()}</span> 76 + <br> 77 + <span>Different Collections: {collectionsOrdered.length}</span> 78 + <br> 79 + <ol style="text-align: left;"> 80 + {#each collectionsOrdered as collection (collection.collection)} 81 + <li>{collection.collection} ({collection.count.toLocaleString()} records)</li> 82 + {/each} 47 83 </ol> 48 84 {/if} 49 85 </div>