import { safeFetch, jsonParse, wrapError, assertNever, safeRegex, safeDate, safeNumber, safeStructuredClone, safeBigInt } from 'shotscript/std' import type { Result, PromiseResult } from 'shotscript/std'The canonical return type for synchronous fallible functions. A discriminated tuple — either [T, null] on success or [null, E] on failure. E defaults to Error.
type Result<T, E = Error> = [T, null] | [null, E] function parseConfig(src: string): Result<Config> { return jsonParse<Config>(src) }
The canonical return type for async fallible functions. Shorthand for Promise<Result<T, E>>.
type PromiseResult<T, E = Error> = Promise<Result<T, E>> async function queryUser(id: number): PromiseResult<User> { const [res, err] = await safeFetch(`/users/${${id}`) if (err !== null) { return [null, err] } return jsonParse<User>(await res.text()) }
Wraps globalThis.fetch. Returns [Response, null] on success, [null, Error] on network failure. HTTP error status codes are not treated as errors — check res.ok yourself.
function safeFetch( input: string | URL, init: RequestInit | null, ): PromiseResult<Response> const [res, err] = await safeFetch(`https://api.example.com/users/${${id}`, null) if (err !== null) { return [null, err] } if (!res.ok) { return [null, new Error(`HTTP ${${res.status}`)] }
Wraps JSON.parse. Returns [T, null] or [null, Error] if parsing fails.
function jsonParse<T>(str: string): Result<T> const [user, err] = jsonParse<User>(text)
Wraps JSON.stringify. Returns [string, null] or [null, Error] if serialization fails.
function jsonStringify( value: unknown, indent: number | null, ): Result<string>
Adds context to a propagated error — the ShotScript equivalent of Go's fmt.Errorf("context: %w", err). Sets err.cause so the original error is inspectable.
function wrapError(message: string, cause: Error): Error const [res, err] = await safeFetch(url, null) if (err !== null) { return [null, wrapError(`fetchUser: ${${url}`, err)] }
Wraps any synchronous dependency call that might throw — any library that doesn't return tuples.
function toResult<T>(fn: () => T): Result<T> const [parsed, err] = toResult(function parse(): ParsedData { return someLib.parseSync(input) })
Wraps any async dependency call that might reject — any library that returns plain Promises.
function toPromiseResult<T>( fn: () => Promise<T>, ): PromiseResult<T> const [rows, err] = await toPromiseResult(function query(): Promise<Row[]> { return db.query(sql) })
Safe URL constructor — replaces the banned new URL(str) which throws on malformed input. Returns [URL, null] on success, [null, Error] on invalid URL string.
function safeURL(url: string, base: string | null): Result<URL> const [url, err] = safeURL(rawInput, null) if (err !== null) { return [null, err] }
Safe decodeURIComponent — replaces the banned global which throws on malformed sequences. Returns [decoded, null] on success, [null, Error] on invalid percent-encoding.
function safeDecodeURIComponent(str: string): Result<string> const [decoded, err] = safeDecodeURIComponent(rawStr)
Safe decodeURI — replaces the banned global which throws on malformed sequences. Returns [decoded, null] on success, [null, Error] on invalid percent-encoding.
function safeDecodeURI(str: string): Result<string> const [decoded, err] = safeDecodeURI(rawStr)
Safe atob — replaces the banned global which throws on invalid base64 input. Returns [decoded, null] on success, [null, Error] on invalid input.
function safeAtob(data: string): Result<string> const [bin, err] = safeAtob(base64Str)
Safe btoa — replaces the banned global which throws on non-Latin1 characters. Returns [encoded, null] on success, [null, Error] on invalid input.
function safeBtoa(data: string): Result<string> const [b64, err] = safeBtoa(binaryStr)
Exhaustive-switch helper — call in the default branch to assert a value is never. The type system guarantees this is only reachable if an exhaustiveness check fails at runtime due to a bad cast.
function assertNever(x: never): never switch (direction) { case 'left': ...; break default: assertNever(direction) }
Safe new RegExp() — replaces the banned constructor which throws on invalid patterns. Returns [RegExp, null] on success, [null, Error] on invalid pattern.
function safeRegex(pattern: string, flags: string | null): Result<RegExp> const [re, err] = safeRegex('^foo.*', 'i')
Safe new Date() — replaces the banned constructor which silently produces an invalid Date. Returns [Date, null] on success, [null, Error] if the input produces an invalid date.
function safeDate(input: string | number): Result<Date> const [d, err] = safeDate('2024-01-15')
Safe number parser — fails on NaN instead of returning it silently. Returns [number, null] on success, [null, Error] if the result is NaN.
function safeNumber(str: string): Result<number> const [n, err] = safeNumber('42')
Safe structuredClone — replaces the banned global which throws on non-cloneable values. Returns [cloned, null] on success, [null, Error] if the value cannot be cloned.
function safeStructuredClone<T>(value: T): Result<T> const [copy, err] = safeStructuredClone(value)
Safe BigInt() — replaces the banned global which throws on invalid input. Returns [bigint, null] on success, [null, Error] on invalid input.
function safeBigInt(str: string): Result<bigint> const [n, err] = safeBigInt('12345678901234567890')
When a library used in multiple places doesn't return Result/PromiseResult tuples, create a dedicated facade module. Wrap all its calls there, export Result/PromiseResult-returning functions, and have consumers import only from the facade. toResult/toPromiseResult stays confined to that one file.
// lib/db.ts — facade for the db library import { db } from 'some-db' import { toPromiseResult } from 'shotscript/std' import type { PromiseResult } from 'shotscript/std' export function dbFind(id: string): PromiseResult<Row> { return toPromiseResult(function find(): Promise<Row> { return db.find(id) }) } export function dbInsert(row: Row): PromiseResult<void> { return toPromiseResult(function insert(): Promise<void> { return db.insert(row) }) } // consumers import from the facade — never from the library directly import { dbFind } from './lib/db' const [row, err] = await dbFind(userId)