Skip to content

Instantly share code, notes, and snippets.

@nicolas-zozol
Created March 21, 2025 09:06
Show Gist options
  • Select an option

  • Save nicolas-zozol/9f3c21f91110649505916f0b1d0b3149 to your computer and use it in GitHub Desktop.

Select an option

Save nicolas-zozol/9f3c21f91110649505916f0b1d0b3149 to your computer and use it in GitHub Desktop.
// Define the shape of your safe action result.
export interface ActionResult<T> {
// When execution is successful, data contains the result.
data?: T;
// Optional error information when input validation fails.
validationErrors?: Record<string, any>;
// Optional error information when bound arguments validation fails.
bindArgsValidationErrors?: Record<string, any>;
// Optional error information when the server code throws.
serverError?: any;
}
// The Try monad encapsulates either a success value or an error.
export class Try<T> {
private readonly value?: T;
private readonly error?: unknown;
constructor(value?: T, error?: unknown) {
this.value = value;
this.error = error;
}
/**
* Factory method to build a Try<T> from an ActionResult<T>.
* If the result is undefined or its data is undefined, it's treated as an error.
*/
static fromActionResult<T>(result: ActionResult<T>): Try<T> {
if (!result || result.data === undefined) {
// Create a new error indicating the server returned undefined.
const error = new Error('Server returned undefined or no data.');
return new Try<T>(undefined, error);
}
return new Try(result.data);
}
/**
* Optionally transform the successful value.
* If there was an error, the error is passed along.
*
* @param fn Transformation function applied on the successful value.
*/
with<U>(fn: (value: T) => U): Try<U> {
if (this.error !== undefined || this.value === undefined) {
// Propagate the error if it exists.
return new Try<U>(undefined, this.error);
}
try {
// Apply transformation if no error.
return new Try<U>(fn(this.value));
} catch (err) {
// In case the transformation itself throws, capture the error.
return new Try<U>(undefined, err);
}
}
/**
* Terminal operation that returns the value if successful,
* or the provided default value if there was an error.
*
* @param defaultValue A fallback value to use when an error is present.
*/
fallback(defaultValue: T): T {
return this.error !== undefined || this.value === undefined
? defaultValue
: this.value;
}
/**
* Terminal operation that handles the error.
* The provided handler is called only if there was an error.
*
* @param fn Error handler that returns a value.
*/
catch(fn: (error: unknown) => T): T {
return this.error !== undefined || this.value === undefined
? fn(this.error)
: this.value;
}
}
/**
* Helper function to wrap a safe action result in the Try monad.
* It now accepts `undefined` and treats it as a server error.
*
* @param result The ActionResult<T> from a safe action, or undefined.
*/
export function tryOf<T>(result: ActionResult<T> | undefined): Try<T> {
if (!result) {
return new Try<T>(undefined, new Error('Server returned undefined.'));
}
return Try.fromActionResult(result);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment