Sometimes the old ways are the best ways.
PHP has a pattern that TypeScript developers secretly envy:
$config = getenv('API_KEY') or die('Missing API_KEY');
One line. Get the value or crash with a message. No if statements. No try-catch. Just inline error handling.
I brought this to TypeScript.
import { DIE } from "phpdie";
const apiKey = process.env.API_KEY ?? DIE("Missing API_KEY");
// TypeScript knows apiKey is string, not string | undefined
That's it. If API_KEY is undefined, the program crashes with "Missing API_KEY". If it exists, you get the value with proper type narrowing.
The magic is the never return type:
export function DIE(reason?: string | Error): never {
if (typeof reason === "string") {
const err = new Error(reason);
throw err.stack;
}
throw reason;
}
never tells TypeScript: "This function never returns normally." The compiler uses this for type narrowing.
When you write:
const value = possiblyUndefined ?? DIE("error");
TypeScript thinks:
possiblyUndefined is defined → return itpossiblyUndefined is undefined → DIE() runs, which never returnsvalue must be the defined typeNo more string | undefined. Just string.
const config = {
apiKey: process.env.API_KEY ?? DIE("Missing API_KEY"),
dbUrl: process.env.DATABASE_URL ?? DIE("Missing DATABASE_URL"),
port: parseInt(process.env.PORT ?? DIE("Missing PORT")),
};
// All fields are guaranteed non-undefined
const response = await fetch(url) ?? DIE("Fetch failed");
const data = await response.json() ?? DIE("Invalid JSON");
const user = data.users[0] ?? DIE("No users found");
// Each step is validated inline
const settings = config.settings ?? DIE("Missing settings");
const theme = settings.theme ?? DIE("Missing theme");
// No nested if-statements
Want to show a UI alert before dying?
import { DIES } from "phpdie";
const value = getValue() ?? DIES(alert, "Something went wrong!");
// Shows alert, then throws
DIES calls your function first, then throws:
export function DIES(alertFn: Function, ...args: any[]): never {
alertFn(...args);
throw new Error("DIES", { cause: args });
}
Works with any alert function:
alert() for browserconsole.error() for loggingtoast.error() for UI librariesSentry.captureException() for monitoringconst apiKey = process.env.API_KEY;
if (!apiKey) {
throw new Error("Missing API_KEY");
}
// Now apiKey is string
const dbUrl = process.env.DATABASE_URL;
if (!dbUrl) {
throw new Error("Missing DATABASE_URL");
}
// Now dbUrl is string
8 lines for 2 variables.
const apiKey = process.env.API_KEY ?? DIE("Missing API_KEY");
const dbUrl = process.env.DATABASE_URL ?? DIE("Missing DATABASE_URL");
2 lines. Same type safety.
npm install phpdie
import { DIE, DIES } from "phpdie";
Good for:
Not for:
Sometimes the simplest solution is the best.
PHP's or die() pattern has been around for decades. It's not sophisticated. It's not "clean architecture." But it works.
TypeScript's type system is powerful enough to make this pattern type-safe. never is the key - it tells the compiler "this branch terminates here."
Embrace the simple patterns. Make them type-safe. Ship code.
Install: npm install phpdie
GitHub: github.com/snomiao/phpdie
const answer = getAnswer() ?? DIE("No answer found");
Snowstar Miao occasionally appreciates PHP.