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.

feat(lexicons): jit validation

Mary 5383f0c2 c061b2af

+114 -2
+11
.changeset/spotty-foxes-write.md
··· 1 + --- 2 + '@atcute/lexicons': minor 3 + --- 4 + 5 + JIT-compiled object validation 6 + 7 + this doesn't eek out as much performance as I hoped, but the added code was small enough that it 8 + seemed okay to add. 9 + 10 + this optimization requires the runtime environment to allow the use of `eval()`/`new Function()`, 11 + and generates an unrolled validation loop.
+3 -1
packages/lexicons/lexicons/lib/validations/index.test.ts
··· 16 16 array: v.array(v.string()), 17 17 boolean: v.boolean(), 18 18 integer: v.integer(), 19 + integerWithDefault: v.optional(v.integer(), 42), 20 + integerWithDefaultFn: v.optional(v.integer(), () => 421), 19 21 string: v.string(), 20 22 }); 21 23 ··· 69 71 v.parse(recordSchema, res); 70 72 }); 71 73 72 - it.only('sets optional defaults', () => { 74 + it('sets optional defaults', () => { 73 75 const objectSchema = v.object({ 74 76 foo: v.optional(v.integer(), 123), 75 77 });
+86 -1
packages/lexicons/lexicons/lib/validations/index.ts
··· 7 7 8 8 import { assert } from '../utils.js'; 9 9 10 - import { getGraphemeLength, getUtf8Length, isArray, isObject, lazy, lazyProperty } from './utils.js'; 10 + import { 11 + allowsEval, 12 + getGraphemeLength, 13 + getUtf8Length, 14 + isArray, 15 + isObject, 16 + lazy, 17 + lazyProperty, 18 + } from './utils.js'; 11 19 12 20 type Identity<T> = T; 13 21 type Flatten<T> = Identity<{ [K in keyof T]: T[K] }>; ··· 1314 1322 get '~run'() { 1315 1323 const shape = resolvedEntries.value; 1316 1324 const len = shape.length; 1325 + 1326 + const generateFastpass = (): Matcher => { 1327 + const fields: [string, any][] = [ 1328 + ['$joinIssues', joinIssues], 1329 + ['$prependPath', prependPath], 1330 + ]; 1331 + 1332 + let doc = `let $iss,$out;`; 1333 + 1334 + for (let idx = 0; idx < len; idx++) { 1335 + const entry = shape[idx]; 1336 + 1337 + const key = entry.key; 1338 + const esckey = JSON.stringify(key); 1339 + 1340 + const id = `_${idx}`; 1341 + 1342 + doc += `{const $val=$in[${esckey}];`; 1343 + 1344 + if (entry.optional) { 1345 + doc += `if($val!==undefined){`; 1346 + } else { 1347 + doc += `if($val!==undefined||${esckey} in $in){`; 1348 + } 1349 + 1350 + doc += `const $res=${id}$schema["~run"]($val,$flags);if($res!==undefined)if($res.ok)${key !== '__proto__' ? `($out??={...$in})[${esckey}]=$res.value` : `Object.defineProperty($out??={...$in},${esckey},{value:$res.value})`};else if((($iss=$joinIssues($iss,$prependPath(${esckey},$res))),$flags&FLAG_ABORT_EARLY))return $iss;}`; 1351 + 1352 + if (entry.optional) { 1353 + const schema = entry.schema as OptionalSchema; 1354 + const innerSchema = schema.wrapped; 1355 + const defaultValue = schema.default; 1356 + 1357 + fields.push([`${id}$schema`, innerSchema]); 1358 + 1359 + if (defaultValue !== undefined) { 1360 + const calls = typeof defaultValue === 'function' ? `${id}$default()` : `${id}$default`; 1361 + 1362 + fields.push([`${id}$default`, defaultValue]); 1363 + 1364 + doc += 1365 + key !== '__proto__' 1366 + ? `else($out??={...$in})[${esckey}]=${calls};` 1367 + : `else Object.defineProperty($out??={...$in},${esckey},{value:${calls}});`; 1368 + } 1369 + } else { 1370 + fields.push([`${id}$schema`, entry.schema]); 1371 + fields.push([`${id}$missing`, entry.missing]); 1372 + 1373 + doc += `else if((($iss=$joinIssues($iss,${id}$missing)),$flags&${FLAG_ABORT_EARLY}))return $iss;`; 1374 + } 1375 + 1376 + doc += `}`; 1377 + } 1378 + 1379 + doc += `if($iss!==undefined)return $iss;if($out!==undefined)return{ok:true,value:$out};`; 1380 + 1381 + const fn = new Function( 1382 + `[${fields.map(([id]) => id).join(',')}]`, 1383 + `return function matcher($in,$flags){${doc}}`, 1384 + ); 1385 + 1386 + return fn(fields.map(([, field]) => field)); 1387 + }; 1388 + 1389 + if (allowsEval.value) { 1390 + const fastpass = generateFastpass(); 1391 + 1392 + const matcher: Matcher = (input, flags) => { 1393 + if (!isObject(input)) { 1394 + return ISSUE_TYPE_OBJECT; 1395 + } 1396 + 1397 + return fastpass(input, flags); 1398 + }; 1399 + 1400 + return lazyProperty(this, '~run', matcher); 1401 + } 1317 1402 1318 1403 const matcher: Matcher = (input, flags) => { 1319 1404 if (!isObject(input)) {
+14
packages/lexicons/lexicons/lib/validations/utils.ts
··· 92 92 export const isObject = (input: unknown): input is Record<string, unknown> => { 93 93 return typeof input === 'object' && input !== null && !isArray(input); 94 94 }; 95 + 96 + export const allowsEval = /*#__PURE__*/ lazy((): boolean => { 97 + if (typeof navigator !== 'undefined' && navigator?.userAgent?.includes('Cloudflare')) { 98 + return false; 99 + } 100 + 101 + try { 102 + const F = Function; 103 + new F(''); 104 + return true; 105 + } catch (_) { 106 + return false; 107 + } 108 + });