UNPKG

@kellanjs/actioncraft

Version:

Fluent, type-safe builder for Next.js server actions.

129 lines 4.8 kB
import { Executor } from "./executor/executor.js"; import { INTERNAL } from "./internal.js"; // ============================================================================ // CRAFT BUILDER CLASS - Configure and define your action // ============================================================================ export class CraftBuilder { _config; _schemas; _errors; _callbacks; _handler; constructor(config, schemas, errors, callbacks, handler) { this._config = config; this._schemas = schemas; this._errors = errors; this._callbacks = callbacks; this._handler = handler; } // -------------------------------------------------------------------------- // FLUENT API METHODS // -------------------------------------------------------------------------- /** * Defines configuration options for the action. * Resets previously defined handler and callbacks. */ config(config) { return new CraftBuilder(config, this._schemas, this._errors, {}, undefined); } /** * Defines validation schemas for input, output, and bind arguments. * Resets previously defined handler and callbacks. */ schemas(schemas) { return new CraftBuilder(this._config, schemas, this._errors, {}, undefined); } /** * Defines error functions for returning typed errors from the handler. * Resets previously defined handler and callbacks. */ errors(errors) { return new CraftBuilder(this._config, this._schemas, errors, {}, undefined); } /** * Defines the handler function containing the server action's business logic. * Resets previously defined callbacks. */ handler(fn) { return new CraftBuilder(this._config, this._schemas, this._errors, {}, fn); } /** * Defines lifecycle callbacks to be triggered during the exection of an action. * Must be called after handler() for correct type inference. */ callbacks(callbacks) { return new CraftBuilder(this._config, this._schemas, this._errors, callbacks, this._handler); } /** * @returns Internal properties of the CraftBuilder instance */ [INTERNAL]() { return { config: this._config, schemas: this._schemas, errors: this._errors, callbacks: this._callbacks, handler: this._handler, }; } } /** * One of two entry points to the Actioncraft system. * It provides you with an empty CraftBuilder instance on which you can call any of the fluent * CraftBuilder methods to configure and define your action. * * Example Usage: * ```ts * const myAction = craft(async (action) => { * return action * .config(...) * .schemas(...) * .errors(...) * .handler(...) * .callbacks(...) * }); * ``` * * @param craftFn - The function that the user passes to `craft()` in order to build an action. * @returns The fully-typed server action function that can be used in your app. */ export function craft(craftFn) { const builder = craftFn(new CraftBuilder({}, {}, {}, {}, undefined)); // Handle async builder functions if (builder instanceof Promise) { return _craftAsync(builder); } // Handle sync builder functions const executor = new Executor(builder); const craftedAction = executor.craft(); return craftedAction; } /** * Internal helper function to handle async craft functions. * Encapsulates the logic for creating async actions and preserving metadata. */ function _craftAsync(builderPromise) { // Resolve the builder once and cache the resulting action to ensure consistent IDs const actionPromise = builderPromise.then((resolvedBuilder) => { const executor = new Executor(resolvedBuilder); return executor.craft(); }); // For async craft functions, we need to return an async action // eslint-disable-next-line @typescript-eslint/no-explicit-any const asyncAction = (async (...args) => { // Wait for the cached action to be ready const craftedAction = await actionPromise; // Call the action with the user's arguments return craftedAction(...args); }); // We need to preserve the config and ID for the initial() function to work // We'll use the same cached action to ensure consistent metadata actionPromise.then((craftedAction) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any asyncAction.__ac_config = craftedAction.__ac_config; // eslint-disable-next-line @typescript-eslint/no-explicit-any asyncAction.__ac_id = craftedAction.__ac_id; }); return asyncAction; } //# sourceMappingURL=craft-builder.js.map