Encrypted, ephemeral, private memos on atproto

feat(consumer): subscribe to jetstream

graham.systems 6a7981ea 46073a57

verified
Changed files
+74 -5
packages
consumer
+38
deno.lock
··· 13 "jsr:@std/internal@^1.0.10": "1.0.10", 14 "npm:@atcute/atproto@^3.1.9": "3.1.9", 15 "npm:@atcute/client@^4.0.5": "4.0.5", 16 "npm:@atcute/lex-cli@^2.3.1": "2.3.1", 17 "npm:@atcute/lexicons@^1.2.2": "1.2.2", 18 "npm:@atproto/lexicon@~0.5.1": "0.5.1" ··· 78 "@badrap/valita" 79 ] 80 }, 81 "@atcute/lex-cli@2.3.1": { 82 "integrity": "sha512-HrHD91CFSFd/p0UFe3akFA1HXiboQwd5LbYiU0srKdLxGX+NLTX/EdCdhbLV6M7LsXdmxk7PB6BMcprsX4rbvg==", 83 "dependencies": [ ··· 128 "@badrap/valita@0.4.6": { 129 "integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==" 130 }, 131 "@optique/core@0.6.2": { 132 "integrity": "sha512-HTxIHJ8xLOSZotiU6Zc5BCJv+SJ8DMYmuiQM+7tjF7RolJn/pdZNe7M78G3+DgXL9lIf82l8aGcilmgVYRQnGQ==" 133 }, ··· 143 "esm-env@1.2.2": { 144 "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" 145 }, 146 "graphemer@1.4.0": { 147 "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" 148 }, ··· 152 "multiformats@9.9.0": { 153 "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" 154 }, 155 "picocolors@1.1.1": { 156 "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" 157 }, 158 "prettier@3.6.2": { 159 "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", 160 "bin": true 161 }, 162 "uint8arrays@3.0.0": { 163 "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", ··· 165 "multiformats" 166 ] 167 }, 168 "zod@3.25.76": { 169 "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" 170 } ··· 176 "jsr:@puregarlic/randimal@^1.0.1", 177 "npm:@atcute/atproto@^3.1.9", 178 "npm:@atcute/client@^4.0.5", 179 "npm:@atcute/lexicons@^1.2.2" 180 ] 181 },
··· 13 "jsr:@std/internal@^1.0.10": "1.0.10", 14 "npm:@atcute/atproto@^3.1.9": "3.1.9", 15 "npm:@atcute/client@^4.0.5": "4.0.5", 16 + "npm:@atcute/jetstream@^1.1.2": "1.1.2", 17 "npm:@atcute/lex-cli@^2.3.1": "2.3.1", 18 "npm:@atcute/lexicons@^1.2.2": "1.2.2", 19 "npm:@atproto/lexicon@~0.5.1": "0.5.1" ··· 79 "@badrap/valita" 80 ] 81 }, 82 + "@atcute/jetstream@1.1.2": { 83 + "integrity": "sha512-u6p/h2xppp7LE6W/9xErAJ6frfN60s8adZuCKtfAaaBBiiYbb1CfpzN8Uc+2qtJZNorqGvuuDb5572Jmh7yHBQ==", 84 + "dependencies": [ 85 + "@atcute/lexicons", 86 + "@badrap/valita", 87 + "@mary-ext/event-iterator", 88 + "@mary-ext/simple-event-emitter", 89 + "partysocket", 90 + "type-fest", 91 + "yocto-queue" 92 + ] 93 + }, 94 "@atcute/lex-cli@2.3.1": { 95 "integrity": "sha512-HrHD91CFSFd/p0UFe3akFA1HXiboQwd5LbYiU0srKdLxGX+NLTX/EdCdhbLV6M7LsXdmxk7PB6BMcprsX4rbvg==", 96 "dependencies": [ ··· 141 "@badrap/valita@0.4.6": { 142 "integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==" 143 }, 144 + "@mary-ext/event-iterator@1.0.0": { 145 + "integrity": "sha512-l6gCPsWJ8aRCe/s7/oCmero70kDHgIK5m4uJvYgwEYTqVxoBOIXbKr5tnkLqUHEg6mNduB4IWvms3h70Hp9ADQ==", 146 + "dependencies": [ 147 + "yocto-queue" 148 + ] 149 + }, 150 + "@mary-ext/simple-event-emitter@1.0.0": { 151 + "integrity": "sha512-meA/zJZKIN1RVBNEYIbjufkUrW7/tRjHH60FjolpG1ixJKo76TB208qefQLNdOVDA7uIG0CGEDuhmMirtHKLAg==" 152 + }, 153 "@optique/core@0.6.2": { 154 "integrity": "sha512-HTxIHJ8xLOSZotiU6Zc5BCJv+SJ8DMYmuiQM+7tjF7RolJn/pdZNe7M78G3+DgXL9lIf82l8aGcilmgVYRQnGQ==" 155 }, ··· 165 "esm-env@1.2.2": { 166 "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" 167 }, 168 + "event-target-polyfill@0.0.4": { 169 + "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==" 170 + }, 171 "graphemer@1.4.0": { 172 "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" 173 }, ··· 177 "multiformats@9.9.0": { 178 "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" 179 }, 180 + "partysocket@1.1.6": { 181 + "integrity": "sha512-LkEk8N9hMDDsDT0iDK0zuwUDFVrVMUXFXCeN3850Ng8wtjPqPBeJlwdeY6ROlJSEh3tPoTTasXoSBYH76y118w==", 182 + "dependencies": [ 183 + "event-target-polyfill" 184 + ] 185 + }, 186 "picocolors@1.1.1": { 187 "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" 188 }, 189 "prettier@3.6.2": { 190 "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", 191 "bin": true 192 + }, 193 + "type-fest@4.41.0": { 194 + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==" 195 }, 196 "uint8arrays@3.0.0": { 197 "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", ··· 199 "multiformats" 200 ] 201 }, 202 + "yocto-queue@1.2.1": { 203 + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==" 204 + }, 205 "zod@3.25.76": { 206 "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" 207 } ··· 213 "jsr:@puregarlic/randimal@^1.0.1", 214 "npm:@atcute/atproto@^3.1.9", 215 "npm:@atcute/client@^4.0.5", 216 + "npm:@atcute/jetstream@^1.1.2", 217 "npm:@atcute/lexicons@^1.2.2" 218 ] 219 },
+1
packages/consumer/deno.jsonc
··· 6 "imports": { 7 "@atcute/atproto": "npm:@atcute/atproto@^3.1.9", 8 "@atcute/client": "npm:@atcute/client@^4.0.5", 9 "@atcute/lexicons": "npm:@atcute/lexicons@^1.2.2", 10 "@puregarlic/randimal": "jsr:@puregarlic/randimal@^1.0.1" 11 }
··· 6 "imports": { 7 "@atcute/atproto": "npm:@atcute/atproto@^3.1.9", 8 "@atcute/client": "npm:@atcute/client@^4.0.5", 9 + "@atcute/jetstream": "npm:@atcute/jetstream@^1.1.2", 10 "@atcute/lexicons": "npm:@atcute/lexicons@^1.2.2", 11 "@puregarlic/randimal": "jsr:@puregarlic/randimal@^1.0.1" 12 }
+35 -5
packages/consumer/mod.ts
··· 1 import { produceRequirements } from "@cistern/shared"; 2 import { generateKeys } from "@cistern/crypto"; 3 import { generateRandomName } from "@puregarlic/randimal"; 4 - import { parse } from "@atcute/lexicons"; 5 import type { Did } from "@atcute/lexicons/syntax"; 6 import type { Client, CredentialManager } from "@atcute/client"; 7 import { ··· 119 } 120 121 /** 122 - * Subscribes to the Jetstreams for the user's items. 123 - * @todo Add `@atcute/jetstream` dependency 124 - * @todo Return an async iterator 125 */ 126 - async getItemSubscription() {} 127 128 /** 129 * Deletes an item from the user's PDS by record key.
··· 1 import { produceRequirements } from "@cistern/shared"; 2 import { generateKeys } from "@cistern/crypto"; 3 import { generateRandomName } from "@puregarlic/randimal"; 4 + import { is, parse } from "@atcute/lexicons"; 5 + import { JetstreamSubscription } from "@atcute/jetstream"; 6 import type { Did } from "@atcute/lexicons/syntax"; 7 import type { Client, CredentialManager } from "@atcute/client"; 8 import { ··· 120 } 121 122 /** 123 + * Subscribes to the Jetstreams for the user's items. Pass `"stop"` into `subscription.next(...)` to cancel 124 + * @todo Allow specifying Jetstream endpoint 125 */ 126 + async *subscribeToItems(): AsyncIterator< 127 + AppCisternLexiconItem.Main, 128 + void, 129 + "stop" | undefined 130 + > { 131 + if (!this.keypair) { 132 + throw new Error("no key pair set; generate a key before subscribing"); 133 + } 134 + 135 + const subscription = new JetstreamSubscription({ 136 + url: "wss://jetstream2.us-east.bsky.network", 137 + wantedCollections: ["app.cistern.lexicon.item"], 138 + wantedDids: [this.did], 139 + }); 140 + 141 + for await (const event of subscription) { 142 + if (event.kind === "commit" && event.commit.operation === "create") { 143 + const record = event.commit.record; 144 + 145 + if (!is(AppCisternLexiconItem.mainSchema, record)) { 146 + continue; 147 + } 148 + 149 + if (record.pubkey !== this.keypair.publicKey) { 150 + continue; 151 + } 152 + 153 + if ((yield record) === "stop") return; 154 + } 155 + } 156 + } 157 158 /** 159 * Deletes an item from the user's PDS by record key.