a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm

Compare changes

Choose any two refs to compare.

Changed files
+37 -37
.changeset
packages
lexicons
lexicons
+5
.changeset/lucky-numbers-work.md
··· 1 + --- 2 + '@atcute/lexicons': major 3 + --- 4 + 5 + make at-uri parsing throw
+20 -21
packages/lexicons/lexicons/lib/syntax/at-uri.test.ts
··· 1 - import { assert, describe, expect, it } from 'vitest'; 1 + import { describe, expect, it } from 'vitest'; 2 2 3 3 import { 4 - isResourceUri, 5 - parseResourceUri, 6 4 isCanonicalResourceUri, 5 + isResourceUri, 7 6 parseCanonicalResourceUri, 7 + parseResourceUri, 8 8 } from './at-uri.js'; 9 9 10 10 describe('resourceUri validation', () => { ··· 47 47 ]; 48 48 for (const str of validCases) { 49 49 expect(isResourceUri(str), str).toBe(true); 50 - 51 - expect(parseResourceUri(str).ok, str).toBe(true); 50 + expect(() => parseResourceUri(str), str).not.toThrow(); 52 51 } 53 52 54 53 const invalidCases = [ ··· 106 105 ]; 107 106 for (const str of invalidCases) { 108 107 expect(isResourceUri(str), str).toBe(false); 109 - 110 - expect(parseResourceUri(str).ok, str).toBe(false); 108 + expect(() => parseResourceUri(str), str).toThrow(); 111 109 } 112 110 113 111 expect(isResourceUri(null)).toBe(false); ··· 116 114 it('parses valid at-uris', () => { 117 115 const result = parseResourceUri('at://did:plc:asdf123/com.atproto.feed.post/record'); 118 116 119 - assert(result.ok); 120 - expect(result.value).toEqual({ 117 + expect(result).toEqual({ 121 118 repo: 'did:plc:asdf123', 122 119 collection: 'com.atproto.feed.post', 123 120 rkey: 'record', ··· 127 124 128 125 it('parses at-uri with fragment', () => { 129 126 const result = parseResourceUri('at://did:plc:asdf123/com.atproto.feed.post/record#/fragment'); 130 - assert(result.ok); 131 - expect(result.value).toEqual({ 127 + 128 + expect(result).toEqual({ 132 129 repo: 'did:plc:asdf123', 133 130 collection: 'com.atproto.feed.post', 134 131 rkey: 'record', ··· 137 134 }); 138 135 139 136 it('returns error for invalid at-uri', () => { 140 - const result = parseResourceUri('invalid-uri'); 141 - assert(!result.ok); 142 - expect(result.error).toContain('invalid at-uri'); 137 + expect(() => parseResourceUri('invalid-uri')).toThrowErrorMatchingInlineSnapshot( 138 + `[SyntaxError: invalid at-uri: invalid-uri]`, 139 + ); 143 140 }); 144 141 }); 145 142 ··· 152 149 for (const str of validCases) { 153 150 expect(isCanonicalResourceUri(str), str).toBe(true); 154 151 155 - expect(parseCanonicalResourceUri(str).ok, str).toBe(true); 152 + expect(() => parseCanonicalResourceUri(str), str).not.toThrow(); 156 153 } 157 154 158 155 const invalidCases = [ ··· 164 161 for (const str of invalidCases) { 165 162 expect(isCanonicalResourceUri(str), str).toBe(false); 166 163 167 - expect(parseCanonicalResourceUri(str).ok, str).toBe(false); 164 + expect(() => parseCanonicalResourceUri(str), str).toThrow(); 168 165 } 169 166 170 167 expect(isCanonicalResourceUri(null)).toBe(false); ··· 172 169 173 170 it('parses valid canonical at-uris', () => { 174 171 const result = parseCanonicalResourceUri('at://did:plc:asdf123/com.atproto.feed.post/record'); 175 - assert(result.ok); 176 - expect(result.value).toEqual({ 172 + 173 + expect(result).toEqual({ 177 174 repo: 'did:plc:asdf123', 178 175 collection: 'com.atproto.feed.post', 179 176 rkey: 'record', ··· 182 179 }); 183 180 184 181 it('returns error for invalid canonical at-uri', () => { 185 - const result = parseCanonicalResourceUri('at://user.bsky.social/com.atproto.feed.post/record'); 186 - assert(!result.ok); 187 - expect(result.error).toContain('invalid repo in canonical-at-uri'); 182 + expect(() => { 183 + return parseCanonicalResourceUri('at://user.bsky.social/com.atproto.feed.post/record'); 184 + }).toThrowErrorMatchingInlineSnapshot( 185 + `[SyntaxError: invalid repo in canonical-at-uri: user.bsky.social]`, 186 + ); 188 187 }); 189 188 });
+12 -14
packages/lexicons/lexicons/lib/syntax/at-uri.ts
··· 3 3 import { isNsid, type Nsid } from './nsid.js'; 4 4 import { isRecordKey, type RecordKey } from './record-key.js'; 5 5 6 - import { type Result } from '../utils.js'; 7 - 8 6 /** 9 7 * represents a general AT Protocol URI, representing either an entire 10 8 * repository, a specific collection within a repository, or a record. ··· 41 39 }; 42 40 43 41 // #__NO_SIDE_EFFECTS__ 44 - export const parseResourceUri = (input: string): Result<ParsedResourceUri, string> => { 42 + export const parseResourceUri = (input: string): ParsedResourceUri => { 45 43 const match = ATURI_RE.exec(input); 46 44 if (match === null) { 47 - return { ok: false, error: `invalid at-uri: ${input}` }; 45 + throw new SyntaxError(`invalid at-uri: ${input}`); 48 46 } 49 47 50 48 const [, r, c, k, f] = match; 51 49 52 50 if (!isActorIdentifier(r)) { 53 - return { ok: false, error: `invalid repo in at-uri: ${r}` }; 51 + throw new SyntaxError(`invalid repo in at-uri: ${r}`); 54 52 } 55 53 56 54 if (c !== undefined && !isNsid(c)) { 57 - return { ok: false, error: `invalid collection in at-uri: ${c}` }; 55 + throw new SyntaxError(`invalid collection in at-uri: ${c}`); 58 56 } 59 57 60 58 if (k !== undefined && !isRecordKey(k)) { 61 - return { ok: false, error: `invalid rkey in at-uri: ${k}` }; 59 + throw new SyntaxError(`invalid rkey in at-uri: ${k}`); 62 60 } 63 61 64 - return { ok: true, value: { repo: r, collection: c, rkey: k, fragment: f } }; 62 + return { repo: r, collection: c, rkey: k, fragment: f }; 65 63 }; 66 64 67 65 /** ··· 97 95 }; 98 96 99 97 // #__NO_SIDE_EFFECTS__ 100 - export const parseCanonicalResourceUri = (input: string): Result<ParsedCanonicalResourceUri, string> => { 98 + export const parseCanonicalResourceUri = (input: string): ParsedCanonicalResourceUri => { 101 99 const match = ATURI_RE.exec(input); 102 100 if (match === null) { 103 - return { ok: false, error: `invalid canonical-at-uri: ${input}` }; 101 + throw new SyntaxError(`invalid canonical-at-uri: ${input}`); 104 102 } 105 103 106 104 const [, r, c, k, f] = match; 107 105 108 106 if (!isDid(r)) { 109 - return { ok: false, error: `invalid repo in canonical-at-uri: ${r}` }; 107 + throw new SyntaxError(`invalid repo in canonical-at-uri: ${r}`); 110 108 } 111 109 112 110 if (!isNsid(c)) { 113 - return { ok: false, error: `invalid collection in canonical-at-uri: ${c}` }; 111 + throw new SyntaxError(`invalid collection in canonical-at-uri: ${c}`); 114 112 } 115 113 116 114 if (!isRecordKey(k)) { 117 - return { ok: false, error: `invalid rkey in canonical-at-uri: ${k}` }; 115 + throw new SyntaxError(`invalid rkey in canonical-at-uri: ${k}`); 118 116 } 119 117 120 - return { ok: true, value: { repo: r, collection: c, rkey: k, fragment: f } }; 118 + return { repo: r, collection: c, rkey: k, fragment: f }; 121 119 };
-2
packages/lexicons/lexicons/lib/utils.ts
··· 1 1 import { DEV } from 'esm-env'; 2 2 3 - export type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }; 4 - 5 3 export const assert: { (condition: any, message?: string): asserts condition } = (condition, message) => { 6 4 if (!condition) { 7 5 if (DEV) {