JavaScript-optional public web frontend for Bluesky anartia.kelinci.net
sveltekit atcute bluesky typescript svelte
7
fork

Configure Feed

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

refactor: json tuple for multipath cursor

Mary 1e4bdff3 cd95e240

+54 -12
+2
package.json
··· 33 33 "@atcute/bluesky-richtext-segmenter": "^2.0.3", 34 34 "@atcute/client": "^4.0.3", 35 35 "@atcute/lexicons": "^1.1.0", 36 + "@atcute/multibase": "^1.1.4", 37 + "@atcute/uint8array": "^1.0.3", 36 38 "@badrap/valita": "^0.4.5", 37 39 "@mary/array-fns": "jsr:^0.1.4", 38 40 "@mary/date-fns": "jsr:^0.1.3",
+18
pnpm-lock.yaml
··· 26 26 '@atcute/lexicons': 27 27 specifier: ^1.1.0 28 28 version: 1.1.0 29 + '@atcute/multibase': 30 + specifier: ^1.1.4 31 + version: 1.1.4 32 + '@atcute/uint8array': 33 + specifier: ^1.0.3 34 + version: 1.0.3 29 35 '@badrap/valita': 30 36 specifier: ^0.4.5 31 37 version: 0.4.5 ··· 102 108 103 109 '@atcute/lexicons@1.1.0': 104 110 resolution: {integrity: sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q==} 111 + 112 + '@atcute/multibase@1.1.4': 113 + resolution: {integrity: sha512-NUf5AeeSOmuZHGU+4GAaMtISJoG+ZHtW/vUVA4lK/YDt/7LODAW0Fd0NNIIUPVUoW0xJS6zSEIWvwLLuxmEHhA==} 114 + 115 + '@atcute/uint8array@1.0.3': 116 + resolution: {integrity: sha512-M/K+ihiVW8Pl2PFLzaC4E3l4JaZ1IH05Q0AbPWUC4cVHnd/gZ/1kAF5ngdtGvJeDMirHZ2VAy7OmAsPwR/2nlA==} 105 117 106 118 '@badrap/valita@0.4.5': 107 119 resolution: {integrity: sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ==} ··· 1186 1198 '@atcute/lexicons@1.1.0': 1187 1199 dependencies: 1188 1200 esm-env: 1.2.2 1201 + 1202 + '@atcute/multibase@1.1.4': 1203 + dependencies: 1204 + '@atcute/uint8array': 1.0.3 1205 + 1206 + '@atcute/uint8array@1.0.3': {} 1189 1207 1190 1208 '@badrap/valita@0.4.5': {} 1191 1209
+34 -12
src/lib/queries/constellation.ts
··· 2 2 3 3 import type { Did } from '@atcute/lexicons'; 4 4 import type { Records } from '@atcute/lexicons/ambient'; 5 + import { fromBase64Url, toBase64Url } from '@atcute/multibase'; 6 + import { decodeUtf8From, encodeUtf8 } from '@atcute/uint8array'; 5 7 6 8 import * as v from '@badrap/valita'; 7 9 ··· 68 70 return json as LinkResponse<K>; 69 71 }; 70 72 73 + const multiPathCursor = v.tuple([v.string(), v.string().nullable()]); 74 + 75 + /** 76 + * generate multi path cursor 77 + * @param path current path 78 + * @param subcursor sub cursor 79 + * @returns compressed cursor 80 + */ 81 + const generateMultiPathCursor = (path: string, subcursor: string | null): string => { 82 + const mp: v.Infer<typeof multiPathCursor> = [path, subcursor]; 83 + const json = JSON.stringify(mp); 84 + 85 + return toBase64Url(encodeUtf8(json)); 86 + }; 87 + 71 88 // due to the way Bluesky has designed its embeds, quotes can be in two 72 89 // different paths, `.embed.record.uri` and `.embed.record.record.uri`. 73 - // Since Constellation can only support one path at a time, here's a function 90 + // 91 + // since Constellation can only support one path at a time, here's a function 74 92 // that will make this happen really nicely 75 - const MP_CURSOR_RE = /^mp:(\d+)(?::(.+))?$/; 76 - 77 93 export const getLinksMultiPath = async <K extends keyof Records>({ 78 94 uri, 79 95 collection, ··· 99 115 }; 100 116 101 117 if (cursor !== null) { 102 - const match = MP_CURSOR_RE.exec(cursor); 103 - if (match === null) { 104 - return result; 105 - } 118 + try { 119 + const raw = decodeUtf8From(fromBase64Url(cursor)); 120 + const json = JSON.parse(raw); 106 121 107 - index = parseInt(match[1], 10); 108 - curs = match[2] ?? null; 122 + const [currentPath, subCursor] = multiPathCursor.parse(json); 123 + const foundIndex = paths.indexOf(currentPath); 124 + 125 + if (foundIndex === -1) { 126 + return result; 127 + } 109 128 110 - if (index >= paths.length) { 129 + index = foundIndex; 130 + curs = subCursor; 131 + } catch (e) { 132 + // If decompression or parsing fails, treat as invalid cursor 111 133 return result; 112 134 } 113 135 } ··· 125 147 126 148 // response returned a cursor, so we're breaking early 127 149 if (data.cursor !== null) { 128 - result.cursor = `mp:${index}:${data.cursor}`; 150 + result.cursor = generateMultiPathCursor(paths[index], data.cursor); 129 151 break; 130 152 } 131 153 ··· 137 159 // move to the next path 138 160 index++; 139 161 curs = null; 140 - result.cursor = index < paths.length ? `mp:${index}` : null; 162 + result.cursor = index < paths.length ? generateMultiPathCursor(paths[index], null) : null; 141 163 } 142 164 143 165 return result;