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

Configure Feed

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

refactor(lexicons): faster isResourceUri check

Mary 6e63aabf d7a8f603

+124 -24
+5
.changeset/grumpy-masks-add.md
··· 1 + --- 2 + '@atcute/lexicons': patch 3 + --- 4 + 5 + faster isResourceUri check
+93 -4
packages/lexicons/lexicons/lib/syntax/at-uri.ts
··· 4 4 import { isDid, type Did } from './did.ts'; 5 5 import { isNsid, type Nsid } from './nsid.ts'; 6 6 import { isRecordKey, type RecordKey } from './record-key.ts'; 7 + import { isAsciiAlphaNum } from './utils/ascii.ts'; 7 8 8 9 /** 9 10 * represents a general AT Protocol URI, representing either an entire ··· 36 37 const ATURI_RE = 37 38 /^at:\/\/([a-zA-Z0-9._:%-]+)(?:\/([a-zA-Z0-9-.]+)(?:\/([a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/; 38 39 40 + const isFragmentChar = (c: number): boolean => { 41 + return ( 42 + isAsciiAlphaNum(c) || 43 + c === 0x2e || // . 44 + c === 0x5f || // _ 45 + c === 0x7e || // ~ 46 + c === 0x3a || // : 47 + c === 0x40 || // @ 48 + c === 0x21 || // ! 49 + c === 0x24 || // $ 50 + c === 0x26 || // & 51 + c === 0x25 || // % 52 + c === 0x27 || // ' 53 + c === 0x29 || // ) 54 + c === 0x28 || // ( 55 + c === 0x2a || // * 56 + c === 0x2b || // + 57 + c === 0x2c || // , 58 + c === 0x3b || // ; 59 + c === 0x3d || // = 60 + c === 0x2d || // - 61 + c === 0x5b || // [ 62 + c === 0x5d || // ] 63 + c === 0x2f || // / 64 + c === 0x5c // \ 65 + ); 66 + }; 67 + 39 68 // #__NO_SIDE_EFFECTS__ 40 69 export const isResourceUri = (input: unknown): input is ResourceUri => { 41 70 if (typeof input !== 'string') { ··· 47 76 return false; 48 77 } 49 78 50 - const match = ATURI_RE.exec(input); 51 - if (match === null) { 79 + if ( 80 + input.charCodeAt(0) !== 0x61 || 81 + input.charCodeAt(1) !== 0x74 || 82 + input.charCodeAt(2) !== 0x3a || 83 + input.charCodeAt(3) !== 0x2f || 84 + input.charCodeAt(4) !== 0x2f 85 + ) { 86 + return false; 87 + } 88 + 89 + const hash = input.indexOf('#', 5); 90 + const stop = hash === -1 ? len : hash; 91 + 92 + if (hash !== -1) { 93 + const fragmentStart = hash + 1; 94 + if (fragmentStart >= len || input.charCodeAt(fragmentStart) !== 0x2f) { 95 + return false; 96 + } 97 + 98 + for (let idx = fragmentStart; idx < len; idx++) { 99 + if (!isFragmentChar(input.charCodeAt(idx))) { 100 + return false; 101 + } 102 + } 103 + } 104 + 105 + const firstSlash = input.indexOf('/', 5); 106 + let repoEnd = stop; 107 + let collection: string | undefined; 108 + let rkey: string | undefined; 109 + 110 + if (firstSlash !== -1 && firstSlash < stop) { 111 + repoEnd = firstSlash; 112 + 113 + const collectionStart = firstSlash + 1; 114 + if (collectionStart >= stop) { 115 + return false; 116 + } 117 + 118 + const secondSlash = input.indexOf('/', collectionStart); 119 + if (secondSlash !== -1 && secondSlash < stop) { 120 + if (secondSlash === collectionStart || secondSlash + 1 >= stop) { 121 + return false; 122 + } 123 + 124 + const thirdSlash = input.indexOf('/', secondSlash + 1); 125 + if (thirdSlash !== -1 && thirdSlash < stop) { 126 + return false; 127 + } 128 + 129 + collection = input.substring(collectionStart, secondSlash); 130 + rkey = input.substring(secondSlash + 1, stop); 131 + } else { 132 + collection = input.substring(collectionStart, stop); 133 + } 134 + } 135 + 136 + if (repoEnd <= 5) { 52 137 return false; 53 138 } 54 139 55 - const [, r, c, k] = match; 140 + const repo = input.substring(5, repoEnd); 56 141 57 - return isActorIdentifier(r) && (c === undefined || isNsid(c)) && (k === undefined || isRecordKey(k)); 142 + return ( 143 + isActorIdentifier(repo) && 144 + (collection === undefined || isNsid(collection)) && 145 + (rkey === undefined || isRecordKey(rkey)) 146 + ); 58 147 }; 59 148 60 149 // #__NO_SIDE_EFFECTS__
+6 -7
packages/lexicons/lexicons/lib/syntax/handle.ts
··· 1 + import { isAsciiAlpha, isAsciiAlphaNum } from './utils/ascii.ts'; 2 + 1 3 /** 2 4 * represents an account's handle, using domains as a human-friendly 3 5 * identifier. 4 6 */ 5 7 export type Handle = `${string}.${string}`; 6 - 7 - const isAlpha = (c: number) => (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a); 8 - const isAlphaNum = (c: number) => isAlpha(c) || (c >= 0x30 && c <= 0x39); 9 8 10 9 // validates a domain label: starts/ends with alphanumeric, middle allows hyphens, max 63 chars 11 10 const isValidLabel = (input: string, start: number, end: number): boolean => { ··· 15 14 } 16 15 17 16 const first = input.charCodeAt(start); 18 - if (!isAlphaNum(first)) { 17 + if (!isAsciiAlphaNum(first)) { 19 18 return false; 20 19 } 21 20 22 21 if (len > 1) { 23 - if (!isAlphaNum(input.charCodeAt(end - 1))) return false; 22 + if (!isAsciiAlphaNum(input.charCodeAt(end - 1))) return false; 24 23 for (let j = start + 1; j < end - 1; j++) { 25 24 const c = input.charCodeAt(j); 26 - if (!isAlphaNum(c) && c !== 0x2d) { 25 + if (!isAsciiAlphaNum(c) && c !== 0x2d) { 27 26 return false; 28 27 } 29 28 } ··· 64 63 } 65 64 66 65 // TLD must start with a letter 67 - return isAlpha(input.charCodeAt(lastLabelStart)); 66 + return isAsciiAlpha(input.charCodeAt(lastLabelStart)); 68 67 };
+8 -9
packages/lexicons/lexicons/lib/syntax/nsid.ts
··· 1 + import { isAsciiAlpha, isAsciiAlphaNum } from './utils/ascii.ts'; 2 + 1 3 /** 2 4 * represents a namespace identifier (NSID) 3 5 */ 4 6 export type Nsid = `${string}.${string}.${string}`; 5 - 6 - const isAlpha = (c: number) => (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a); 7 - const isAlphaNum = (c: number) => isAlpha(c) || (c >= 0x30 && c <= 0x39); 8 7 9 8 // #__NO_SIDE_EFFECTS__ 10 9 export const isNsid = (input: unknown): input is Nsid => { ··· 42 41 const first = input.charCodeAt(segStart); 43 42 if (segIdx === 0) { 44 43 // first domain label must start with a letter 45 - if (!isAlpha(first)) { 44 + if (!isAsciiAlpha(first)) { 46 45 return false; 47 46 } 48 47 } else { 49 48 // subsequent domain labels start with alphanumeric 50 - if (!isAlphaNum(first)) { 49 + if (!isAsciiAlphaNum(first)) { 51 50 return false; 52 51 } 53 52 } 54 53 55 54 if (segLen > 1) { 56 - if (!isAlphaNum(input.charCodeAt(i - 1))) { 55 + if (!isAsciiAlphaNum(input.charCodeAt(i - 1))) { 57 56 return false; 58 57 } 59 58 for (let j = segStart + 1; j < i - 1; j++) { 60 59 const c = input.charCodeAt(j); 61 - if (!isAlphaNum(c) && c !== 0x2d) { 60 + if (!isAsciiAlphaNum(c) && c !== 0x2d) { 62 61 return false; 63 62 } 64 63 } ··· 81 80 return false; 82 81 } 83 82 84 - if (!isAlpha(input.charCodeAt(nameStart))) { 83 + if (!isAsciiAlpha(input.charCodeAt(nameStart))) { 85 84 return false; 86 85 } 87 86 for (let j = nameStart + 1; j < len; j++) { 88 - if (!isAlphaNum(input.charCodeAt(j))) { 87 + if (!isAsciiAlphaNum(input.charCodeAt(j))) { 89 88 return false; 90 89 } 91 90 }
+3 -4
packages/lexicons/lexicons/lib/syntax/record-key.ts
··· 1 + import { isAsciiAlphaNum } from './utils/ascii.ts'; 2 + 1 3 /** 2 4 * represents a record key 3 5 */ 4 6 export type RecordKey = string; 5 - 6 - const isAlpha = (c: number) => (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a); 7 - const isAlphaNum = (c: number) => isAlpha(c) || (c >= 0x30 && c <= 0x39); 8 7 9 8 // #__NO_SIDE_EFFECTS__ 10 9 export const isRecordKey = (input: unknown): input is RecordKey => { ··· 26 25 const c = input.charCodeAt(i); 27 26 // [a-zA-Z0-9_~.:-] 28 27 if ( 29 - !isAlphaNum(c) && 28 + !isAsciiAlphaNum(c) && 30 29 c !== 0x5f && // _ 31 30 c !== 0x7e && // ~ 32 31 c !== 0x2e && // .
+9
packages/lexicons/lexicons/lib/syntax/utils/ascii.ts
··· 1 + // #__NO_SIDE_EFFECTS__ 2 + export const isAsciiAlpha = (c: number): boolean => { 3 + return (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a); 4 + }; 5 + 6 + // #__NO_SIDE_EFFECTS__ 7 + export const isAsciiAlphaNum = (c: number): boolean => { 8 + return isAsciiAlpha(c) || (c >= 0x30 && c <= 0x39); 9 + };