
Security News
Lovable’s OJ Rewrites Vite’s Dev Server in Rust as AI Lowers the Cost of Forking Open Source
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.
@jesscss/awaitable-pipe
Advanced tools
A tiny, strongly-typed pipe that stays sync when possible and becomes a Promise when needed. With an optional single-point error handler.
A tiny, strongly-typed pipe that stays synchronous until a step returns a Promise — with one optional error handler.
It’s a small utility from the Jess project, where hot paths call functions that are usually — but not always — synchronous. It’s zero-dependency and usable on its own: it stays sync when everything is sync, and turns into a Promise only when something is async. No wrappers, no ceremony.
safePipe gives you a single onError + optional fallbacknpm install @jesscss/awaitable-pipe
Published to npm under both the latest and alpha dist-tags.
import { pipe, safePipe } from '@jesscss/awaitable-pipe';
// Sync stays sync
const upper = (s: string) => s.toUpperCase();
const exclaim = (s: string) => s + '!';
const out = pipe(() => 'ok', upper, exclaim); // 'OK!'
// Mixed becomes Promise
const load = async (s: string) => s + '!';
const outP = pipe(() => 'ok', upper, load); // Promise<string>
const result = await outP; // 'OK!'
// Start without an initial value
const s2 = pipe((x?: number) => (x ?? 2) * 3); // 6
// Single-point error handling (never throws)
const boom = () => { throw new Error('nope'); };
const safe = safePipe({ onError: console.error, fallback: 'X' }, () => 'ok', boom, upper);
// 'X'
JavaScript Promises are great, but they aren’t free. Every async hop schedules work, allocates objects, and pushes errors across an async boundary. In hot paths, that overhead can add up.
That said, consider this a micro-optimization! This is really only a faster approach than forced async / await when a very small percentage of unknown function calls result in Promises (< 10% of steps returning Promises in performance testing). If a greater percentage of steps would normally return promises, then using an async / await pattern on all unknown results (regardless of whether or not those results are a Promise, which JavaScript is fine with) will generally be faster than using this library.
await, no extra Promise allocations.pipe, or centralize it once with safePipe without wrapping results.undefined)// compose sync functions → string
const a = pipe(() => 'hi', (s) => s.trim(), (s) => s.toUpperCase());
// mix in async → Promise<string>
const b = pipe(() => 'hi', async (s) => s + '!', (s) => s + '?');
// no initial value
const c = pipe((x?: number) => (x ?? 1) + 1, (n) => n * 10); // 20
If you prefer not to throw or reject, safePipe centralizes error handling. You get an optional onError callback and a fallback value (or thunk). On error, the pipeline returns the fallback (or undefined if you didn’t provide one).
onError// Sync-only path
const r1 = safePipe({ onError: console.warn, fallback: 'X' }, () => 'ok', (s: string) => s.toUpperCase()); // 'OK'
// Sync error → fallback
const r2 = safePipe({ onError: console.warn, fallback: 'X' },
() => { throw new Error('boom'); },
(s: string) => s.toUpperCase()
); // 'X'
// Async path → Promise<string>
const r3 = await safePipe({ onError: console.warn, fallback: 'X' },
() => 'ok',
async (s: string) => s + '!',
(s: string) => s + '?'
); // 'ok!?'
// No fallback provided. On error, returns undefined (never throws).
const r5 = safePipe({ onError: console.warn },
() => { throw new Error('boom'); },
(s: string) => s.toUpperCase()
); // undefined
You can feed the output of one pipe (value or Promise) into another. Types keep up with you.
const p1 = pipe(() => 'hi', (s: string) => s.toUpperCase()); // string
const p2 = pipe(() => p1, (s) => s + '!'); // 'HI!'
const p3 = pipe(() => 'hi', async s => s + '!'); // Promise<string>
const p4 = pipe(() => p3, (s) => s + '?'); // Promise<string>
const fin = await p4; // 'hi!?'
Sometimes you want to guard or handle errors at a specific step without switching the whole pipeline to safe mode. Use these helpers as steps inside pipe or safePipe:
import { pipe, tryStep, guard, serialForEach, serialReduce } from '@jesscss/awaitable-pipe';
// tryStep: catch at this step only, with optional onError and fallback
const step = tryStep((n: number) => {
if (n < 0) throw new Error('no negatives');
return n * 2;
}, {
onError: (err, n) => console.warn('bad number:', n, err),
fallback: 0 // could also be (err, n) => 0
});
const out = pipe(() => 5, step); // 10
const out2 = pipe(() => -1, step); // 0
// onError can throw to rethrow the error (or a transformed error) for upstream handling
const stepWithRethrow = tryStep((input: string) => {
if (input === 'bad') throw new ReferenceError('not found');
return input.toUpperCase();
}, {
onError: (error, input) => {
// Conditionally rethrow based on error type
if (error instanceof ReferenceError) {
throw error; // Re-throw for upstream handling
}
// Otherwise, just log - fallback will be used
console.log('Handled error:', error);
},
fallback: 'default'
});
const result1 = pipe(() => 'good', stepWithRethrow); // 'GOOD'
const result2 = pipe(() => 'bad', stepWithRethrow); // Throws ReferenceError
const result3 = pipe(() => 'other', stepWithRethrow); // 'default' (if step throws non-ReferenceError)
// Or use rethrow: true to always rethrow after onError
const alwaysRethrow = tryStep((n: number) => {
if (n < 0) throw new Error('no negatives');
return n * 2;
}, {
onError: (err, n) => console.warn('Error:', err),
rethrow: true // Always rethrow the original error after onError
});
// guard: ensure a condition holds at this step (sync or async)
const positive = guard((n: number) => n > 0, (n) => new Error(`not positive: ${n}`));
const ok = pipe(() => 3, positive); // 3
// pipe(() => -2, positive) would throw: Error('not positive: -2')
// serialForEach: sync-first loop that promotes to async if a step returns a Promise
const items = [1, 2, 3];
await serialForEach(items, async (n, i) => {
if (i === 1) await new Promise(r => setTimeout(r, 10));
});
// serialReduce: sync-first reduce that promotes to async on demand
const sum = await serialReduce(items, 0, async (acc, n, i) => {
if (i === 2) await Promise.resolve();
return acc + n;
});
// sum === 6
Alpha, as part of the Jess monorepo. Please report issues.
FAQs
A tiny, strongly-typed pipe that stays sync when possible and becomes a Promise when needed. With an optional single-point error handler.
We found that @jesscss/awaitable-pipe demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.