/**
* Feature-First Auto-Orchestration Types
*
* Type definitions for Feature-First architecture, the core differentiating feature of Numflow framework
*/
///
import { IncomingMessage, ServerResponse } from 'http';
import { RequestHandler } from '../types/index';
import type { RetrySignal, RETRY } from './retry';
/**
* HTTP Method type
*/
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
/**
* Context object
* Pure business data storage shared by all steps and async tasks
*
* @remarks
* Context is the central storage for sharing business data during Feature execution.
* **Completely separated from HTTP layer (req, res)**.
*
* Steps receive context, req, res as separate parameters:
* ```javascript
* module.exports = async (ctx, req, res) => {
* const userId = req.user?.id
* res.json({ success: true })
* }
* ```
*
* ## Structure
*
* Context contains only pure business data:
* - Added from contextInitializer: `userId`, `sessionId` etc.
* - Added directly in Step: `ctx.validated = true`
* - Any field name can be used (no restrictions)
*
* ## Naming Conventions
*
* Recommendations for custom field naming:
* - ✅ Use camelCase: `userId`, `orderData`, `sessionInfo`
* - ✅ Meaningful names: `validatedInput`, `dbConnection`
* - ✅ Use prefixes: `input*`, `output*`, `db*`
*
* ## Data Storage Method
*
* **How to store data in Step**:
* ```javascript
* // Store directly in ctx from Step function
* module.exports = async (ctx, req, res) => {
* // Explicitly add field to ctx
* ctx.validated = true
* ctx.userData = { name: 'John', email: 'john@example.com' }
*
* // Use only true/false/void for return (flow control)
* return true // success
* }
* ```
*
* **Read data from other Steps**:
* ```javascript
* module.exports = async (ctx, req, res) => {
* // Access data stored in previous Step
* const validated = ctx.validated
* const userData = ctx.userData
*
* // Store current Step data
* ctx.processedData = { ... }
* }
* ```
*
* @example
* ```javascript
* // ✅ Correct usage: Add initial data in contextInitializer
* module.exports = numflow.feature({
* contextInitializer: (req, res) => ({
* userId: req.user?.id, // ctx.userId
* orderData: req.body, // ctx.orderData
* sessionId: req.session?.id, // ctx.sessionId
* }),
* })
* ```
*
* @example
* ```javascript
* // ✅ Correct usage: Explicitly store data in Step
* // 100-validate.js
* module.exports = async (ctx, req, res) => {
* const userId = ctx.userId // Added from contextInitializer
* const orderData = ctx.orderData
*
* // Validation logic...
* const isValid = orderData && orderData.productId
*
* if (!isValid) {
* res.status(400).json({ error: 'Invalid order data' })
* return // void - Send error response
* }
*
* // Explicitly store in ctx
* ctx.validated = true
* ctx.validatedData = {
* productId: orderData.productId,
* quantity: orderData.quantity || 1
* }
*
* return true // success
* }
*
* // 200-process.js
* module.exports = async (ctx, req, res) => {
* // Access data stored in previous Step
* const validated = ctx.validated // true
* const validatedData = ctx.validatedData // { productId, quantity }
*
* // Process logic...
* const result = await processOrder(validatedData)
*
* // Store current Step result
* ctx.orderResult = result
*
* return true
* }
* ```
*
* @example
* ```javascript
* // ✅ JavaScript flexibility: Declare only needed parameters
* // When only context is needed
* module.exports = async (ctx) => {
* ctx.total = ctx.items.reduce((sum, item) => sum + item.price, 0)
* return true
* }
*
* // When req is also needed
* module.exports = async (ctx, req) => {
* const userId = req.user?.id
* ctx.userId = userId
* return true
* }
*
* // When all are needed
* module.exports = async (ctx, req, res) => {
* res.json({ success: true, data: ctx })
* }
* ```
*
* @example
* ```javascript
* // ❌ Incorrect usage: returning object (no longer auto-merged)
* module.exports = async (ctx, req, res) => {
* return { validated: true } // ❌ Now ignored! Not stored in ctx
* }
*
* // ✅ Correct usage: Explicitly store in ctx
* module.exports = async (ctx, req, res) => {
* ctx.validated = true // ✅ Explicit storage
* return true // Success indicator
* }
* ```
*/
export interface Context {
/**
* Custom fields (added by developer)
*
* @remarks
* All fields from object returned by contextInitializer or
* Fields added directly in Step are stored here.
*
* Example: userId, sessionId, orderData, validated, processedData etc.
*
* @example
* ```javascript
* // Add directly in Step
* ctx.validated = true
* ctx.userData = { name: 'John' }
*
* // Access from other Step
* const validated = ctx.validated
* const userData = ctx.userData
* ```
*/
[key: string]: any;
}
/**
* Step function type
* Each step receives context, req, res for processing.
*
* @remarks
* Step function stores data by directly modifying context.
* Flow control follows JavaScript default behavior.
*
* ## Flow control
*
* 1. **Function runs to completion** → Automatically proceeds to next Step
* 2. **throw Error** → Immediately to onError handler
* 3. **return** (Function exit) → After Step ends:
* - `res.headersSent === true` → Skip next Step (Early response)
* - `res.headersSent === false` → Proceed to next Step
*
* **Return value is completely ignored.**
*
* ## Parameters
*
* - `context`: Pure business data storage
* - `req`: HTTP Request object
* - `res`: HTTP Response object
*
* @example
* ```javascript
* // ✅ Normal usage (99%)
* module.exports = async (ctx, req, res) => {
* const user = await db.users.findById(req.params.id)
* ctx.user = user
* ctx.validated = true
* // Done! Next Step automatically
* }
* ```
*
* @example
* ```javascript
* // ✅ Error occurs (Immediate stop)
* module.exports = async (ctx, req, res) => {
* if (!ctx.user) {
* throw new Error('User not found') // To onError handler
* }
* ctx.validated = true
* // Done! Next Step
* }
* ```
*
* @example
* ```javascript
* // ✅ Early response (return required!)
* module.exports = async (ctx, req, res) => {
* const cached = cache.get(req.url)
* if (cached) {
* return res.json(cached) // Response + immediate exit
* }
* ctx.fresh = await fetchData()
* // Done! Next Step
* }
* ```
*
* @example
* ```javascript
* // ✅ JavaScript flexibility: Declare only needed parameters
* module.exports = async (ctx) => {
* ctx.total = 100
* }
*
* module.exports = async (ctx, req) => {
* ctx.userId = req.params.id
* }
* ```
*/
export type StepFunction = (context: Context, req: IncomingMessage, res: ServerResponse) => Promise | void;
/**
* Async Task function type
* Task executed asynchronously after transaction completion
*/
export type AsyncTaskFunction = (context: Context) => Promise | void;
/**
* Step information
*/
export interface StepInfo {
/** Step number */
number: number;
/** Filename */
name: string;
/** Step function */
fn: StepFunction;
/** File path */
path: string;
}
/**
* Async Task information
*/
export interface AsyncTaskInfo {
/** Task name */
name: string;
/** Task function */
fn: AsyncTaskFunction;
/** File path */
path: string;
}
/**
* Feature Error Handler
* Function that handles errors that occur during Feature execution
*
* @returns void | typeof RETRY | RetrySignal
* - void: When error response is sent
* - typeof RETRY: Immediate retry
* - RetrySignal: Retry with options (delay, maxAttempts)
*/
export type FeatureErrorHandler = (error: Error, context: Context, req: IncomingMessage, res: ServerResponse) => Promise | void | typeof RETRY | RetrySignal;
/**
* Feature configuration
*
* Convention over Configuration:
* method, path, steps, asyncTasks are auto-inferred from folder structure.
* Explicit specification overrides auto-inference.
*/
export interface FeatureConfig {
/**
* HTTP Method (optional)
* Auto-inferred from folder name if not specified (get/post/put/delete/patch)
*/
method?: HttpMethod;
/**
* Route path (optional)
* Auto-inferred from folder structure if not specified
* Example: /features/api/v1/orders/post -> /api/v1/orders
*/
path?: string;
/**
* Steps (optional)
* - String: folder path (Example: './steps')
* - Auto-recognized as ./steps if not specified
*
* Convention over Configuration:
* Create steps/ folder in Feature directory and create files in 100-xxx.js, 200-xxx.js format
*/
steps?: string;
/**
* Async tasks (optional)
* - String: folder path (Example: './async-tasks')
* - Auto-recognized as ./async-tasks if not specified
*
* Convention over Configuration:
* Create async-tasks/ folder in Feature directory and create task files
*/
asyncTasks?: string;
/** Feature-level middlewares (optional)
* Executed before contextInitializer
* Execution order: Global middlewares → Feature middlewares → contextInitializer → Steps
*/
middlewares?: RequestHandler[];
/** Context initializer function (optional)
* Receives empty context object and initializes it.
*
* @example
* ```javascript
* contextInitializer: (ctx, req, res) => {
* ctx.userId = req.user?.id
* ctx.transaction = { id: generateId(), startedAt: Date.now() }
* }
* ```
*/
contextInitializer?: (context: Context, req: IncomingMessage, res: ServerResponse) => Promise | void;
/** Error handler (optional)
* Called when error occurs during Step execution
* Users can handle transaction rollback, logging, response sending, etc. directly
*/
onError?: FeatureErrorHandler;
}
/**
* Feature Handler
* Function that handles actual HTTP requests
*/
export type FeatureHandler = (req: IncomingMessage, res: ServerResponse) => Promise;
/**
* Feature execution result
*/
export interface FeatureResult {
/** Success status */
success: boolean;
/** Final Context */
context: Context;
/** Error (on failure) */
error?: Error;
/** Execution time (ms) */
executionTime: number;
}
/**
* Auto-Discovery options
*/
export interface AutoDiscoveryOptions {
/** Directory path */
directory: string;
/** File pattern */
pattern: RegExp;
/** Allow duplicate numbers */
allowDuplicates: boolean;
}
/**
* Auto-Execution options
*/
export interface AutoExecutionOptions {
/** Step list */
steps: StepInfo[];
/** Context */
context: Context;
/** HTTP Request */
req: IncomingMessage;
/** HTTP Response */
res: ServerResponse;
}
/**
* Async Task Scheduler options
*/
export interface AsyncTaskSchedulerOptions {
/** Async task list */
tasks: AsyncTaskInfo[];
/** Context */
context: Context;
}
/**
* Feature Error
* Error that occurs during Feature execution
*
* @remarks
* FeatureError wraps the original error to preserve all custom properties
* (e.g., code, validationErrors, transactionId, etc.) via originalError reference.
*
* This allows:
* - onError handler to access original error properties for retry logic
* - Global error handler to extract all custom properties automatically
* - No loss of custom error information during error wrapping
*
* @example
* ```typescript
* // Step throws BusinessError
* throw new BusinessError('Out of stock', 'OUT_OF_STOCK')
*
* // AutoExecutor wraps it
* const featureError = new FeatureError(
* err.message,
* err, // originalError preserved!
* step,
* context,
* 400
* )
*
* // onError can access code via originalError
* if (error.originalError instanceof BusinessError) {
* const code = error.originalError.code // 'OUT_OF_STOCK'
* }
* ```
*/
export declare class FeatureError extends Error {
readonly originalError?: Error;
readonly step?: StepInfo;
readonly context?: Context;
readonly statusCode: number;
constructor(message: string, originalError?: Error, step?: StepInfo, context?: Context, statusCode?: number);
}
//# sourceMappingURL=types.d.ts.map