@kellanjs/actioncraft
Version:
Fluent, type-safe builder for Next.js server actions.
104 lines • 3.92 kB
JavaScript
import { ActioncraftError } from "./classes/error.js";
import { EXTERNAL_ERROR_TYPES } from "./types/errors.js";
import { err } from "./types/result.js";
export function unwrap(resultOrPromise) {
// Handle Promise case
if (resultOrPromise instanceof Promise) {
return resultOrPromise.then((result) => _unwrapSync(result));
}
// Handle direct result case
return _unwrapSync(resultOrPromise);
}
/**
* Synchronously unwraps a result, throwing on error with embedded action ID.
*/
function _unwrapSync(result) {
// Extract action ID from result if present
const actionId = "__ac_id" in result ? result.__ac_id : undefined;
// Handle api-style results ({ success: true/false }) - includes both ApiResult and StatefulApiResult
if (typeof result === "object" && result !== null && "success" in result) {
const apiResult = result;
if (apiResult.success) {
return apiResult.data;
}
throw new ActioncraftError(apiResult.error, actionId);
}
// Handle functional-style results ({ type: "ok"/"err" })
if (typeof result === "object" && result !== null && "type" in result) {
const functionalResult = result;
if (functionalResult.type === "ok") {
return functionalResult.value;
}
throw new ActioncraftError(functionalResult.error, actionId);
}
throw new Error("Invalid result format from Actioncraft action");
}
/**
* Creates a throwable version of an Actioncraft action.
* The returned function throws ActioncraftErrors with automatic action ID verification support.
* Errors thrown by this function can be verified with isActioncraftError(error, action).
*/
export function throwable(action) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (async (...args) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = await action(...args);
return unwrap(result);
});
}
/**
* Creates an appropriate initial state for any action based on its configuration.
* The initial state uses the action's real ID for consistency with actual results.
*
* For useActionState actions: returns StatefulApiResult with error and values
* For functional format actions: returns Result.err() with error
* For regular actions: returns ApiResult with error
*
* Usage:
* - useActionState: const [state, action] = useActionState(myAction, initial(myAction))
* - useState: const [state, setState] = useState(initial(myAction))
*/
export function initial(action) {
const error = {
type: EXTERNAL_ERROR_TYPES.INITIAL_STATE,
message: "Action has not been executed yet",
};
// Attempt to read the action ID created during craft()
const actionId =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
action?.__ac_id ?? "unknown";
// Attempt to read the Actioncraft config attached during craft()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cfg = action?.__ac_config;
// Functional format -> Result<_, _>
if (cfg?.resultFormat === "functional") {
return err(error, actionId);
}
// useActionState enabled -> StatefulApiResult
if (cfg?.useActionState) {
return {
success: false,
error,
values: undefined,
__ac_id: actionId,
};
}
// Default ApiResult shape
return {
success: false,
error,
__ac_id: actionId,
};
}
/**
* Utility to extract the action ID from a crafted action.
* Useful for debugging and logging purposes.
*
* @param action - The crafted action
* @returns The action ID if available, undefined otherwise
*/
export function getActionId(action) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return action.__ac_id;
}
//# sourceMappingURL=utils.js.map