@kellanjs/actioncraft
Version:
Fluent, type-safe builder for Next.js server actions.
129 lines • 4.8 kB
JavaScript
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