npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@inh-lib/common

v1.11.13

Published

this lib for sharing type and utility function for service and core layer.

Readme

@inh-lib/common

this lib for sharing type and utility function for service and core layer.

Content

ResultV2 Documentation

📋 สารบัญ

  1. ภาพรวม
  2. การติดตั้งและ Import
  3. Basic Usage
  4. API Reference
  5. ตัวอย่างการใช้งาน
  6. Best Practices
  7. FAQ

🎯 ภาพรวม

ResultV2 เป็น class ที่ใช้สำหรับจัดการ error handling แบบ functional programming โดยช่วยให้คุณสามารถจัดการกับการดำเนินการที่อาจสำเร็จหรือล้มเหลวได้อย่างปลอดภัย โดยไม่ต้องใช้ try-catch หรือ throw exceptions

✨ จุดเด่น

  • 🔒 Type Safe - ใช้ TypeScript generics เพื่อความปลอดภัยในการใช้งาน
  • ⛓️ Method Chaining - รองรับการต่อ operations แบบ fluent interface
  • 🌊 Async Support - รองรับการทำงานแบบ asynchronous
  • 🔄 Immutable - ไม่สามารถแก้ไขได้หลังจากสร้างแล้ว
  • 🎭 Pattern Matching - รองรับการจัดการ success/failure แบบ functional

🧬 Type Signature

class Result<T, F = unknown> {
  // T = Type ของข้อมูลเมื่อสำเร็จ
  // F = Type ของ error เมื่อล้มเหลว (default: unknown)
}

📦 การติดตั้งและ Import

// Import จาก package
import { Result, isSuccess, isFailure, sequence } from '@your-package/common';

// หรือ import แบบ destructuring
import { 
  Result, 
  isSuccess, 
  isFailure, 
  sequence 
} from '@your-package/common';

🚀 Basic Usage

การสร้าง Result

// สร้าง successful Result
const successResult = Result.ok<string>('Hello World');
const numberResult = Result.ok<number>(42);
const voidResult = Result.ok<void>(); // ไม่มีค่า return

// สร้าง failed Result
const errorResult = Result.fail<string>('Something went wrong');
const customErrorResult = Result.fail<string, { code: number; message: string }>({
  code: 404,
  message: 'Not found'
});

การเข้าถึงข้อมูล

const result = Result.ok<string>('Hello');

// การเข้าถึงค่า
if (result.isSuccess) {
  console.log(result.getValue()); // 'Hello'
}

// การเข้าถึงแบบปลอดภัย
const value = result.getValueOrDefault('Default'); // 'Hello'
const maybeValue = result.getValueOrUndefined(); // 'Hello' | undefined

// การเข้าถึง error
const errorResult = Result.fail<string>('Error occurred');
if (errorResult.isFailure) {
  console.log(errorResult.errorValue()); // 'Error occurred'
}

📚 API Reference

Instance Methods

getValue(): T

ดึงค่าจาก successful Result หากเป็น failed Result จะ throw error

const result = Result.ok<string>('Hello');
const value = result.getValue(); // 'Hello'

const failed = Result.fail<string>('Error');
// failed.getValue(); // ❌ จะ throw error

getValueOrDefault(defaultValue: T): T

ดึงค่าจาก Result หากไม่สำเร็จจะคืนค่า default

const success = Result.ok<string>('Hello');
const failed = Result.fail<string>('Error');

console.log(success.getValueOrDefault('Default')); // 'Hello'
console.log(failed.getValueOrDefault('Default'));  // 'Default'

getValueOrUndefined(): T | undefined

ดึงค่าจาก Result หากไม่สำเร็จจะคืน undefined

const success = Result.ok<string>('Hello');
const failed = Result.fail<string>('Error');

console.log(success.getValueOrUndefined()); // 'Hello'
console.log(failed.getValueOrUndefined());  // undefined

errorValue(): F

ดึง error จาก failed Result หากเป็น successful Result จะ throw error

const failed = Result.fail<string>('Something went wrong');
const error = failed.errorValue(); // 'Something went wrong'

chain(fn: (value: T) => Result<U, F>): Result<U, F>

ต่อ operations แบบ sequential หากขั้นตอนใดล้มเหลวจะหยุดทันที

const result = Result.ok<number>(5)
  .chain(x => Result.ok<string>(`Value: ${x}`))
  .chain(text => Result.ok<string>(text.toUpperCase()));

console.log(result.getValue()); // 'VALUE: 5'

// กรณีมี error
const failedChain = Result.ok<number>(5)
  .chain(x => Result.fail<string>('Conversion failed'))
  .chain(text => Result.ok<string>(text.toUpperCase())); // ไม่ทำงาน

console.log(failedChain.errorValue()); // 'Conversion failed'

map(fn: (value: T) => U): Result<U, F>

แปลงค่าภายใน Result โดยอัตโนมัติจับ exceptions

const result = Result.ok<number>(42)
  .map(x => x * 2)
  .map(x => `Result: ${x}`);

console.log(result.getValue()); // 'Result: 84'

// จับ exception อัตโนมัติ
const errorMap = Result.ok<string>('hello')
  .map(x => {
    throw new Error('Something went wrong');
  });

console.log(errorMap.isFailure); // true

mapError(fn: (error: F) => G): Result<T, G>

แปลง error type ใน failed Result

const result = Result.fail<string>('404')
  .mapError(code => ({
    status: parseInt(code),
    message: 'Not Found'
  }));

console.log(result.errorValue()); // { status: 404, message: 'Not Found' }

chainAsync(fn: (value: T) => Promise<Result<U, F>>): Promise<Result<U, F>>

ต่อ async operations

const fetchUser = async (id: string): Promise<Result<User, string>> => {
  // simulate API call
  return Result.ok({ id, name: 'John', email: '[email protected]' });
};

const result = await Result.ok<string>('123')
  .chainAsync(fetchUser);

console.log(result.getValue()); // { id: '123', name: 'John', email: '[email protected]' }

mapAsync(fn: (value: T) => Promise): Promise<Result<U, F>>

แปลงค่าด้วย async function

const processData = async (data: string): Promise<string> => {
  // simulate async processing
  return `Processed: ${data}`;
};

const result = await Result.ok<string>('input')
  .mapAsync(processData);

console.log(result.getValue()); // 'Processed: input'

tap(fn: (value: T) => void): this

ทำ side effects โดยไม่เปลี่ยนค่า (สำหรับ successful Result)

const result = Result.ok<string>('Hello')
  .tap(value => console.log(`Success: ${value}`)) // จะ log
  .map(value => value.toUpperCase());

console.log(result.getValue()); // 'HELLO'

🆚 Side Effect vs Transform

🔄 Side Effect คืออะไร?

Side Effect หมายถึงการดำเนินการที่มีผลกระทบต่อสิ่งที่อยู่นอก function หรือมีการเปลี่ยนแปลงสถานะที่สังเกตได้จากภายนอก แต่ไม่เปลี่ยนแปลงค่าที่ return

🔧 Transform คืออะไร?

Transform หมายถึงการแปลงข้อมูลจากรูปแบบหนึ่งไปเป็นอีกรูปแบบหนึ่ง โดยเปลี่ยนแปลงค่าที่ return แต่ไม่มีผลกระทบข้างเคียง

📊 เปรียบเทียบ Side Effect vs Transform

| Aspect | Side Effect | Transform | |--------|----------------|---------------| | Return Value | ไม่เปลี่ยนค่าที่ return | เปลี่ยนค่าที่ return | | External Impact | มีผลกระทบต่อภายนอก | ไม่มีผลกระทบต่อภายนอก | | Method ใน Result | .tap(), .tapError() | .map(), .chain() | | Purity | Impure function | Pure function | | Testability | ยากต่อการ test | ง่ายต่อการ test |

🔍 ตัวอย่างการใช้งาน

interface User {
  id: string;
  name: string;
  email: string;
}

const processUser = (user: User): Result<User, string> => {
  return Result.ok(user)
    // Side Effects - ไม่เปลี่ยนค่า user
    .tap(u => console.log('🔄 Processing user:', u.name))     // Logging
    .tap(u => metrics.increment('user.processed'))           // Metrics
    .tap(u => auditLog.record('USER_ACCESSED', u.id))       // Audit logging
    .tap(u => cache.set(u.id, u))                           // Caching
    
    // Transforms - เปลี่ยนแปลงข้อมูล
    .map(u => ({ ...u, name: u.name.toUpperCase() }))       // Transform name
    .map(u => ({ ...u, email: u.email.toLowerCase() }))     // Transform email
    .map(u => ({ ...u, processedAt: new Date() }))          // Add timestamp
    
    // Side Effects หลัง transform
    .tap(processedUser => {
      console.log('✅ User processed:', processedUser.name);
      sendNotification(processedUser.email, 'Account updated');
    });
};

// ค่าใน Result เปลี่ยนแปลงตาม transforms แต่ side effects ไม่เปลี่ยนค่า

🎯 ตัวอย่าง Side Effects

// ✅ ตัวอย่าง Side Effects ที่ถูกต้อง
Result.ok(data)
  .tap(d => console.log('Processing:', d))          // Logging
  .tap(d => saveToDatabase(d))                      // Database save
  .tap(d => sendEmail(d.email, 'Welcome'))          // Email notification
  .tap(d => metrics.increment('user.created'))      // Metrics
  .tap(d => cache.invalidate(d.category))           // Cache management
  .map(d => d.name); // ค่าสุดท้ายยังคงเป็น original data

🔄 ตัวอย่าง Transforms

// ✅ ตัวอย่าง Transforms ที่ถูกต้อง
Result.ok('john doe')
  .map(name => name.trim())                         // 'john doe'
  .map(name => name.toUpperCase())                  // 'JOHN DOE'
  .map(name => name.split(' '))                     // ['JOHN', 'DOE']
  .map(parts => ({                                  // { firstName: 'JOHN', lastName: 'DOE' }
    firstName: parts[0],
    lastName: parts[1]
  }));

⚠️ Common Mistakes

// ❌ ผิด - ทำ side effects ใน map
Result.ok(user)
  .map(u => {
    console.log('Processing:', u.name);  // ❌ Side effect ใน map
    saveToDatabase(u);                   // ❌ Side effect ใน map
    return u.name.toUpperCase();         // Transform
  });

// ✅ ถูก - แยก side effects และ transforms
Result.ok(user)
  .tap(u => console.log('Processing:', u.name))     // ✅ Side effect
  .tap(u => saveToDatabase(u))                     // ✅ Side effect
  .map(u => u.name.toUpperCase());                 // ✅ Transform

tapError(fn: (error: F) => void): this

ทำ side effects โดยไม่เปลี่ยนค่า (สำหรับ failed Result)

const result = Result.fail<string>('Error occurred')
  .tapError(error => console.log(`Error: ${error}`)) // จะ log
  .mapError(error => `Handled: ${error}`);

console.log(result.errorValue()); // 'Handled: Error occurred'

match(onSuccess: (value: T) => U, onError: (error: F) => U): U

Pattern matching สำหรับจัดการทั้ง success และ failure

const result = Result.ok<number>(42);

const message = result.match(
  value => `Success: ${value}`,
  error => `Error: ${error}`
);

console.log(message); // 'Success: 42'

toHttpResponse(res: ResponseObject)

แปลงเป็น HTTP response สำหรับ web APIs

// Express.js example
app.get('/users/:id', (req, res) => {
  return getUserById(req.params.id).toHttpResponse(res);
});

// Success response: { success: true, data: { ... } }
// Error response: { success: false, error: "..." } with status 400

Static Methods

Result.ok<T, F>(value?: T): Result<T, F>

สร้าง successful Result

const stringResult = Result.ok<string>('Hello');
const numberResult = Result.ok<number>(42);
const voidResult = Result.ok<void>();

Result.fail<T, F>(error: F): Result<T, F>

สร้าง failed Result

const errorResult = Result.fail<string>('Something went wrong');
const customError = Result.fail<User, ApiError>({
  code: 'USER_NOT_FOUND',
  message: 'User does not exist'
});

Result.from<T, F>(fn: () => T, errorMapper?: (error: unknown) => F): Result<T, F>

สร้าง Result จาก function โดยจับ exceptions อัตโนมัติ

const result = Result.from(
  () => JSON.parse('{"name": "John"}'),
  error => `Parse error: ${error.message}`
);

const safeParseInt = (str: string) => Result.from(
  () => {
    const num = parseInt(str);
    if (isNaN(num)) throw new Error('Not a number');
    return num;
  }
);

Result.fromAsync<T, F>(fn: () => Promise, errorMapper?: (error: unknown) => F): Promise<Result<T, F>>

สร้าง Result จาก async function

const fetchData = async (url: string) => {
  return Result.fromAsync(
    () => fetch(url).then(res => res.json()),
    error => `Fetch error: ${error.message}`
  );
};

Result.combine(results: T): Result<...>

รวม Results หลายตัว หากมีตัวใดล้มเหลวก็จะล้มเหลวทั้งหมด

const results = [
  Result.ok<string>('Hello'),
  Result.ok<number>(42),
  Result.ok<boolean>(true)
] as const;

const combined = Result.combine(results);
if (combined.isSuccess) {
  const [str, num, bool] = combined.getValue();
  console.log(str, num, bool); // 'Hello', 42, true
}

Result.combineWith<T, R, F>(results: Result<T, F>[], combiner: (values: T[]) => R): Result<R, F>

รวม Results พร้อมใช้ combiner function

const numbers = [
  Result.ok<number>(1),
  Result.ok<number>(2),
  Result.ok<number>(3)
];

const sum = Result.combineWith(
  numbers,
  values => values.reduce((a, b) => a + b, 0)
);

console.log(sum.getValue()); // 6

Result.firstSuccess<T, F>(results: Result<T, F>[]): Result<T, F[]>

คืน Result แรกที่สำเร็จ หากไม่มีจะคืน array ของ errors

const attempts = [
  Result.fail<string, string>('Service 1 down'),
  Result.fail<string, string>('Service 2 down'),
  Result.ok<string, string>('Service 3 success')
];

const result = Result.firstSuccess(attempts);
console.log(result.getValue()); // 'Service 3 success'

Utility Functions

isSuccess<T, F>(result: Result<T, F>): boolean

Type guard สำหรับตรวจสอบว่าเป็น successful Result

const result = Result.ok<string>('Hello');

if (isSuccess(result)) {
  // TypeScript รู้ว่า result เป็น successful Result
  console.log(result.getValue()); // Type safe
}

isFailure<T, F>(result: Result<T, F>): boolean

Type guard สำหรับตรวจสอบว่าเป็น failed Result

const result = Result.fail<string>('Error');

if (isFailure(result)) {
  // TypeScript รู้ว่า result เป็น failed Result
  console.log(result.errorValue()); // Type safe
}

sequence<T, F>(results: Result<T, F>[]): Result<T[], F>

แปลง array ของ Results เป็น Result ของ array

const results = [
  Result.ok<string>('first'),
  Result.ok<string>('second'),
  Result.ok<string>('third')
];

const sequenced = sequence(results);
console.log(sequenced.getValue()); // ['first', 'second', 'third']

💡 ตัวอย่างการใช้งาน

1. 🔍 User Validation Pipeline

interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}

interface ValidationError {
  field: string;
  message: string;
}

const validateUserId = (id: string): Result<string, ValidationError> => {
  if (!id || id.length === 0) {
    return Result.fail({ field: 'id', message: 'ID is required' });
  }
  if (!/^[0-9]+$/.test(id)) {
    return Result.fail({ field: 'id', message: 'ID must be numeric' });
  }
  return Result.ok(id);
};

const fetchUser = async (id: string): Promise<Result<User, ValidationError>> => {
  // Simulate database call
  const users: User[] = [
    { id: '1', name: 'John Doe', email: '[email protected]', age: 25 }
  ];
  
  const user = users.find(u => u.id === id);
  if (!user) {
    return Result.fail({ field: 'user', message: 'User not found' });
  }
  
  return Result.ok(user);
};

const validateAge = (user: User): Result<User, ValidationError> => {
  if (user.age < 18) {
    return Result.fail({ field: 'age', message: 'User must be 18 or older' });
  }
  return Result.ok(user);
};

// การใช้งาน
const processUser = async (userId: string) => {
  const result = await Result.ok(userId)
    .chain(validateUserId)
    .chainAsync(fetchUser)
    .then(result => result.chain(validateAge))
    .then(result => result.tap(user => 
      console.log(`✅ User ${user.name} processed successfully`)
    ))
    .then(result => result.tapError(error => 
      console.error(`❌ Validation failed: ${error.field} - ${error.message}`)
    ));

  return result;
};

// การใช้งาน
processUser('1'); // ✅ Success
processUser('17-year-old-id'); // ❌ Age validation fails

2. 🌐 HTTP API Integration

interface ApiResponse<T> {
  data: T;
  status: number;
}

interface ApiError {
  code: string;
  message: string;
  status: number;
}

const apiCall = async <T>(url: string): Promise<Result<T, ApiError>> => {
  return Result.fromAsync(
    async () => {
      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      return response.json() as T;
    },
    error => ({
      code: 'API_ERROR',
      message: error instanceof Error ? error.message : 'Unknown error',
      status: 500
    })
  );
};

// Express.js Controller
const getUserController = async (req: Request, res: Response) => {
  const result = await Result.ok(req.params.id)
    .chain(validateUserId)
    .chainAsync(id => apiCall<User>(`/api/users/${id}`))
    .then(result => result.map(user => ({
      ...user,
      isAdult: user.age >= 18
    })));

  return result.toHttpResponse(res);
};

3. 🔄 Batch Processing

const processItems = async (items: string[]): Promise<Result<string[], string>> => {
  const processItem = async (item: string): Promise<Result<string, string>> => {
    if (item === 'invalid') {
      return Result.fail(`Cannot process item: ${item}`);
    }
    
    // Simulate processing
    await new Promise(resolve => setTimeout(resolve, 100));
    return Result.ok(`Processed: ${item}`);
  };

  // Process all items
  const results = await Promise.all(
    items.map(item => processItem(item))
  );

  // Use sequence to convert Result[] to Result<[]>
  return sequence(results);
};

// การใช้งาน
const items = ['item1', 'item2', 'item3'];
const result = await processItems(items);

result.match(
  processedItems => console.log('All processed:', processedItems),
  error => console.error('Processing failed:', error)
);

4. 🎯 Error Recovery Pattern

const tryMultipleServices = async (data: string): Promise<Result<string, string[]>> => {
  const service1 = (): Promise<Result<string, string>> => 
    Result.fromAsync(() => Promise.reject('Service 1 unavailable'));
  
  const service2 = (): Promise<Result<string, string>> => 
    Result.fromAsync(() => Promise.reject('Service 2 unavailable'));
  
  const service3 = (): Promise<Result<string, string>> => 
    Result.fromAsync(() => Promise.resolve(`Service 3: ${data}`));

  const results = await Promise.all([
    service1(),
    service2(), 
    service3()
  ]);

  return Result.firstSuccess(results);
};

// การใช้งาน
const result = await tryMultipleServices('test data');
result.match(
  data => console.log('Success:', data),
  errors => console.log('All services failed:', errors)
);

5. 📊 Configuration Loading

interface Config {
  apiUrl: string;
  timeout: number;
  retries: number;
}

interface ConfigError {
  field: string;
  issue: string;
}

const loadConfigFile = (): Result<unknown, ConfigError> => {
  return Result.from(
    () => {
      // Simulate reading config file
      const configText = '{"apiUrl": "https://api.example.com", "timeout": 5000}';
      return JSON.parse(configText);
    },
    () => ({ field: 'file', issue: 'Cannot read config file' })
  );
};

const validateConfig = (raw: unknown): Result<Config, ConfigError> => {
  const config = raw as Partial<Config>;
  
  if (!config.apiUrl) {
    return Result.fail({ field: 'apiUrl', issue: 'Missing API URL' });
  }
  
  if (!config.timeout || config.timeout <= 0) {
    return Result.fail({ field: 'timeout', issue: 'Invalid timeout value' });
  }

  return Result.ok({
    apiUrl: config.apiUrl,
    timeout: config.timeout,
    retries: config.retries || 3 // default value
  });
};

// การใช้งาน
const configResult = loadConfigFile()
  .chain(validateConfig)
  .tap(config => console.log('✅ Config loaded:', config.apiUrl))
  .tapError(error => console.error(`❌ Config error in ${error.field}: ${error.issue}`));

if (configResult.isSuccess) {
  const config = configResult.getValue();
  // Use config...
}

🎯 Best Practices

1. ✅ Type Safety

// ✅ ระบุ types ให้ชัดเจน
const parseUser = (data: string): Result<User, string> => {
  return Result.from(() => JSON.parse(data) as User);
};

// ❌ ไม่ระบุ type
const parseData = (data: string) => {
  return Result.from(() => JSON.parse(data));
};

2. ✅ Error Types

// ✅ ใช้ custom error types
interface ValidationError {
  field: string;
  message: string;
  code: string;
}

const validate = (input: string): Result<string, ValidationError> => {
  if (!input) {
    return Result.fail({
      field: 'input',
      message: 'Input is required',
      code: 'REQUIRED'
    });
  }
  return Result.ok(input);
};

// ❌ ใช้ string error แบบธรรมดา
const validateBad = (input: string): Result<string, string> => {
  return input ? Result.ok(input) : Result.fail('Input required');
};

3. ✅ Method Chaining

// ✅ ใช้ method chaining อย่างมีเหตุผล
const processData = (input: string) => {
  return Result.ok(input)
    .map(data => data.trim())
    .chain(validateInput)
    .chainAsync(processAsync)
    .then(result => result.tap(data => logSuccess(data)))
    .then(result => result.tapError(error => logError(error)));
};

// ❌ nested callbacks
const processDataBad = async (input: string) => {
  const trimmed = Result.ok(input.trim());
  if (trimmed.isSuccess) {
    const validated = validateInput(trimmed.getValue());
    if (validated.isSuccess) {
      const processed = await processAsync(validated.getValue());
      // ... more nesting
    }
  }
};

4. ✅ Error Handling

// ✅ ใช้ match สำหรับ final result handling
const handleResult = (result: Result<User, ApiError>) => {
  return result.match(
    user => ({ success: true, data: user }),
    error => ({ success: false, error: error.message })
  );
};

// ✅ ใช้ tap/tapError สำหรับ side effects
const processWithLogging = (data: string) => {
  return Result.ok(data)
    .tap(data => console.log(`Processing: ${data}`))
    .chain(validate)
    .tapError(error => console.error(`Validation failed: ${error}`))
    .chain(process);
};

5. ✅ Async Operations

// ✅ ใช้ chainAsync สำหรับ Result-returning async functions
const processAsync = async (id: string) => {
  return await Result.ok(id)
    .chainAsync(fetchUser)
    .then(result => result.chainAsync(enrichUser))
    .then(result => result.chainAsync(saveUser));
};

// ✅ ใช้ mapAsync สำหรับ value-returning async functions
const transformAsync = async (data: string) => {
  return await Result.ok(data)
    .mapAsync(async data => {
      const processed = await externalProcess(data);
      return processed.toUpperCase();
    });
};

6. ✅ Side Effects vs Transforms

// ✅ ใช้ tap สำหรับ side effects (ไม่เปลี่ยนค่า)
const withSideEffects = (user: User) => {
  return Result.ok(user)
    .tap(u => console.log(`Processing user: ${u.name}`))     // Logging
    .tap(u => auditLog.record('USER_PROCESSED', u.id))      // Audit
    .tap(u => metrics.increment('user.processing.count'))   // Metrics
    .tap(u => cache.set(`user:${u.id}`, u))                // Caching
    .map(u => ({ ...u, lastProcessed: new Date() }));       // Transform
};

// ✅ ใช้ map สำหรับ transforms (เปลี่ยนค่า)
const withTransforms = (input: string) => {
  return Result.ok(input)
    .map(s => s.trim())                    // Transform: remove whitespace
    .map(s => s.toLowerCase())             // Transform: to lowercase
    .map(s => s.split(' '))                // Transform: to array
    .map(words => words.filter(w => w));   // Transform: remove empty
};

// ❌ ไม่ควรทำ side effects ใน map
const badPractice = (user: User) => {
  return Result.ok(user)
    .map(u => {
      console.log('Processing:', u.name);  // ❌ Side effect ใน map
      saveToDatabase(u);                   // ❌ Side effect ใน map
      return u.name.toUpperCase();         // Transform
    });
};

// ✅ แยก side effects และ transforms ให้ชัดเจน
const goodPractice = (user: User) => {
  return Result.ok(user)
    .tap(u => console.log('Processing:', u.name))    // ✅ Side effect
    .tap(u => saveToDatabase(u))                     // ✅ Side effect
    .map(u => u.name.toUpperCase());                 // ✅ Transform
};

❓ FAQ

Q: เมื่อไหร่ควรใช้ Result แทน try-catch?

A: ใช้ Result เมื่อ:

  • ต้องการ type safety สำหรับ errors
  • มี operations หลายขั้นตอนที่อาจล้มเหลว
  • ต้องการ functional programming style
  • ต้องการ method chaining

Q: Result vs Optional/Maybe?

A: Result เหมาะสำหรับกรณีที่ต้องการข้อมูล error, Optional/Maybe เหมาะสำหรับกรณีที่อาจมีหรือไม่มีค่า

Q: จะจัดการ nested Results ยังไง?

A: ใช้ chain() แทน map() เพื่อหลีกเลี่ยง Result<Result<T>>

// ❌ nested Results
const nested = result.map(value => anotherResult(value)); // Result<Result<U>>

// ✅ flattened
const flattened = result.chain(value => anotherResult(value)); // Result<U>

Q: จะรวม Results หลายตัวยังไง?

A: ใช้ Result.combine() หรือ sequence():

// สำหรับ Results ที่ต่างกัน
const combined = Result.combine([userResult, configResult, dataResult]);

// สำหรับ Results เหมือนกัน
const sequenced = sequence([result1, result2, result3]);

Q: จะแปลง Promise เป็น Result ยังไง?

A: ใช้ Result.fromAsync():

const result = await Result.fromAsync(
  () => fetch('/api/data').then(res => res.json()),
  error => `API Error: ${error.message}`
);

Q: Side Effect และ Transform ต่างกันยังไง?

A:

  • Side Effect (ใช้ .tap()): ทำกิจกรรมข้างเคียงโดยไม่เปลี่ยนค่า เช่น logging, database save, notifications
  • Transform (ใช้ .map()): แปลงข้อมูลจากรูปแบบหนึ่งไปอีกรูปแบบหนึ่ง
// Side Effect - ไม่เปลี่ยนค่า
Result.ok(user).tap(u => console.log(u.name)); // ค่ายังคงเป็น user

// Transform - เปลี่ยนค่า
Result.ok(user).map(u => u.name); // ค่าเปลี่ยนเป็น string

Q: เมื่อไหร่ควรใช้ tap และเมื่อไหร่ควรใช้ map?

A:

  • ใช้ .tap() เมื่อต้องการ: logging, metrics, caching, notifications, audit trails
  • ใช้ .map() เมื่อต้องการ: แปลงข้อมูล, คำนวณค่าใหม่, format ข้อมูล, extract properties
// ตัวอย่างการผสมใช้
Result.ok(rawData)
  .tap(data => logger.info('Processing started'))  // Side effect
  .map(data => data.trim())                        // Transform
  .map(data => JSON.parse(data))                   // Transform
  .tap(parsed => cache.set('data', parsed))        // Side effect
  .map(parsed => parsed.result);                   // Transform

🎉 ขอให้สนุกกับการใช้ ResultV2!

สำหรับคำถามเพิ่มเติมหรือการพัฒนาฟีเจอร์ใหม่ กรุณาติดต่อทีมพัฒนา


ResponseBuilder Usage Examples

📋 สารบัญ

  1. ภาพรวม
  2. การใช้งานพื้นฐาน
  3. การใช้งานกับ Result
  4. Express.js Controllers
  5. Error Handling Patterns
  6. Batch Processing
  7. Async Chain Operations
  8. Best Practices

🎯 ภาพรวม

ResponseBuilder เป็น utility class ที่ช่วยในการสร้าง standardized API responses โดยรองรับ:

  • ✅ Success responses (200, 201)
  • ❌ Error responses จาก BaseFailure
  • 🔄 แปลง Result เป็น DataResponse
  • 🏷️ TraceId สำหรับ tracking

🚀 การใช้งานพื้นฐาน

1. Success Responses

import { ResponseBuilder } from './ResponseBuilder';

interface User {
  id: string;
  name: string;
  email: string;
}

const user: User = {
  id: '1',
  name: 'John Doe',
  email: '[email protected]'
};

// ✅ Success Response (200)
const successResponse = ResponseBuilder.success(
  user, 
  'User retrieved successfully',
  'trace-001'
);

console.log(successResponse);
/*
Output:
{
  statusCode: 200,
  isSuccess: true,
  traceId: 'trace-001',
  codeResult: 'SUCCESS',
  message: 'User retrieved successfully',
  dataResult: {
    id: '1',
    name: 'John Doe',
    email: '[email protected]'
  }
}
*/

// ✅ Created Response (201)
const createdResponse = ResponseBuilder.created(
  user,
  'User created successfully',
  'trace-002'
);

console.log(createdResponse);
/*
Output:
{
  statusCode: 201,
  isSuccess: true,
  traceId: 'trace-002',
  codeResult: 'SUCCESS',
  message: 'User created successfully',
  dataResult: { ... }
}
*/

2. Error Responses

import { CommonFailures } from './Failure';

// ✅ Error จาก BaseFailure
const validationError = new CommonFailures.ValidationFail(
  'Email is required',
  { field: 'email' }
);

const errorResponse = ResponseBuilder.error(validationError);

console.log(errorResponse);
/*
Output:
{
  statusCode: 400,
  isSuccess: false,
  codeResult: 'VALIDATION_FAIL',
  message: 'Email is required',
  dataResult: null,
  errorDetail: { field: 'email' }
}
*/

// ✅ Error จาก unknown error
const unknownError = new Error('Database connection failed');
const errorFromUnknown = ResponseBuilder.fromError(unknownError, 'trace-003');

console.log(errorFromUnknown);
/*
Output:
{
  statusCode: 500,
  isSuccess: false,
  traceId: 'trace-003',
  codeResult: 'INTERNAL_FAIL',
  message: 'Database connection failed',
  dataResult: null,
  errorDetail: { originalError: 'Error' }
}
*/

🔗 การใช้งานกับ Result

1. Basic Result to Response

import { Result } from '../ResultV2';
import { resultToResponse } from './ResponseBuilder';

interface UserData {
  id: string;
  name: string;
  email: string;
}

// Mock validation function
const validateUser = (data: unknown): Result<UserData, BaseFailure> => {
  if (!data || typeof data !== 'object') {
    return Result.fail(new CommonFailures.ValidationFail('Invalid user data'));
  }

  const user = data as Partial<UserData>;
  if (!user.name || !user.email) {
    return Result.fail(new CommonFailures.ValidationFail('Name and email are required'));
  }

  return Result.ok(user as UserData);
};

// การใช้งาน
const processUserData = (inputData: unknown, traceId?: string) => {
  const result = validateUser(inputData);
  
  return resultToResponse(result, {
    successMessage: 'User data validated successfully',
    traceId
  });
};

// ✅ Success case
const validData = { id: '1', name: 'John', email: '[email protected]' };
const successCase = processUserData(validData, 'trace-success-001');

// ❌ Error case  
const invalidData = { name: 'John' }; // missing email
const errorCase = processUserData(invalidData, 'trace-error-001');

2. Complex Result Chain

const processUserWithChain = (userData: unknown, traceId?: string) => {
  const result = Result.ok(userData)
    .chain(data => validateUser(data))
    .chain(user => {
      // Additional validation
      if (user.email.includes('@')) {
        return Result.ok(user);
      }
      return Result.fail(new CommonFailures.ValidationFail('Invalid email format'));
    })
    .map(user => ({
      ...user,
      id: `user_${Date.now()}`,
      createdAt: new Date()
    }));

  return resultToResponse(result, {
    successMessage: 'User processed and created successfully',
    errorDataResult: userData,
    traceId
  });
};

// การทดสอบ
const testData = { name: 'Jane', email: '[email protected]' };
const chainResult = processUserWithChain(testData, 'chain-001');

🌐 Express.js Controllers

1. GET Controller

import { Request, Response } from 'express';

interface GetUserRequest extends Request {
  params: { id: string };
}

// ✅ GET User Controller
export const getUserController = async (req: GetUserRequest, res: Response) => {
  const userId = req.params.id;
  const traceId = `get_user_${Date.now()}`;

  const result = await Result.fromAsync(
    async () => {
      // Simulate database call
      if (!userId) {
        throw new Error('User ID is required');
      }
      
      if (userId === 'notfound') {
        throw new Error('User not found');
      }

      // Mock user data
      return {
        id: userId,
        name: 'John Doe',
        email: '[email protected]',
        createdAt: new Date()
      };
    },
    (error: unknown) => {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      if (errorMessage.includes('not found')) {
        return new CommonFailures.NotFoundFail(errorMessage);
      }
      return new CommonFailures.GetFail(errorMessage);
    }
  );

  const response = resultToResponse(result, {
    successMessage: 'User retrieved successfully',
    traceId
  });

  return res.status(response.statusCode).json(response);
};

// การใช้งาน
// GET /users/123 -> Success
// GET /users/notfound -> 404 Error
// GET /users/ -> 400 Error

2. POST Controller

interface CreateUserRequest extends Request {
  body: {
    name?: string;
    email?: string;
    age?: number;
  };
}

// ✅ POST User Controller
export const createUserController = async (req: CreateUserRequest, res: Response) => {
  const userData = req.body;
  const traceId = `create_user_${Date.now()}`;

  const result = Result.from(
    () => {
      // Validation
      if (!userData.name || !userData.email) {
        throw new Error('Name and email are required');
      }

      if (userData.age && userData.age < 0) {
        throw new Error('Age must be positive');
      }

      // Create user
      return {
        id: `user_${Date.now()}`,
        name: userData.name,
        email: userData.email,
        age: userData.age || null,
        createdAt: new Date()
      };
    },
    (error: unknown) => {
      const errorMessage = error instanceof Error ? error.message : 'Validation failed';
      return new CommonFailures.ValidationFail(errorMessage);
    }
  );

  const response = resultToResponse(result, {
    successMessage: 'User created successfully',
    traceId
  });

  return res.status(response.statusCode).json(response);
};

3. PUT Controller

interface UpdateUserRequest extends Request {
  params: { id: string };
  body: Partial<{
    name: string;
    email: string;
    age: number;
  }>;
}

// ✅ PUT User Controller
export const updateUserController = async (req: UpdateUserRequest, res: Response) => {
  const userId = req.params.id;
  const updateData = req.body;
  const traceId = `update_user_${Date.now()}`;

  const result = await Result.ok({ userId, updateData })
    .chain(async ({ userId, updateData }) => {
      // Fetch existing user
      return Result.fromAsync(
        async () => {
          if (userId === 'notfound') {
            throw new Error('User not found');
          }
          return {
            id: userId,
            name: 'John Doe',
            email: '[email protected]',
            age: 25
          };
        },
        (error: unknown) => new CommonFailures.NotFoundFail(
          error instanceof Error ? error.message : 'User not found'
        )
      );
    })
    .then(result => result.map(existingUser => {
      // Apply updates
      return {
        ...existingUser,
        ...updateData,
        updatedAt: new Date()
      };
    }));

  const response = resultToResponse(result, {
    successMessage: 'User updated successfully',
    traceId
  });

  return res.status(response.statusCode).json(response);
};

⚠️ Error Handling Patterns

1. Different Error Types

const handleDifferentErrors = (errorType: string, traceId?: string) => {
  try {
    switch (errorType) {
      case 'validation':
        return Result.fail(new CommonFailures.ValidationFail('Invalid input data'));
      
      case 'notfound':
        return Result.fail(new CommonFailures.NotFoundFail('Resource not found'));
      
      case 'unauthorized':
        return Result.fail(new CommonFailures.UnauthorizedFail('Access denied'));
      
      case 'internal':
        throw new Error('Internal server error');
      
      default:
        return Result.ok('Success');
    }
  } catch (error) {
    return Result.fail(
      new CommonFailures.InternalFail(
        error instanceof Error ? error.message : 'Unknown error'
      )
    );
  }
};

// การทดสอบ error types ต่างๆ
const testErrorHandling = () => {
  const errors = ['validation', 'notfound', 'unauthorized', 'internal', 'success'];
  
  return errors.map(errorType => {
    const result = handleDifferentErrors(errorType, `trace-${errorType}`);
    return resultToResponse(result, {
      successMessage: `Handled ${errorType} successfully`,
      traceId: `trace-${errorType}`
    });
  });
};

2. Error Recovery Patterns

const withErrorRecovery = async (operation: () => Promise<unknown>) => {
  const traceId = `recovery_${Date.now()}`;

  const result = await Result.fromAsync(
    operation,
    (error: unknown) => {
      if (error instanceof Error) {
        // Try to categorize the error
        if (error.message.includes('network')) {
          return new CommonFailures.GetFail('Network error occurred');
        }
        if (error.message.includes('timeout')) {
          return new CommonFailures.GetFail('Request timeout');
        }
        if (error.message.includes('not found')) {
          return new CommonFailures.NotFoundFail('Resource not found');
        }
      }
      
      return new CommonFailures.InternalFail('Unexpected error occurred');
    }
  );

  return resultToResponse(result, {
    successMessage: 'Operation completed successfully',
    errorDataResult: { attempted: true, timestamp: new Date() },
    traceId
  });
};

// การใช้งาน
const testRecovery = async () => {
  // Simulate different types of errors
  const networkError = () => Promise.reject(new Error('network connection failed'));
  const timeoutError = () => Promise.reject(new Error('timeout exceeded'));
  const notFoundError = () => Promise.reject(new Error('resource not found'));
  const success = () => Promise.resolve({ data: 'success' });

  const results = await Promise.all([
    withErrorRecovery(networkError),
    withErrorRecovery(timeoutError),
    withErrorRecovery(notFoundError),
    withErrorRecovery(success)
  ]);

  return results;
};

📊 Batch Processing

1. Processing Multiple Items

interface BatchItem {
  id: string;
  data: string;
}

interface BatchResult {
  processed: BatchItem[];
  failed: string[];
  summary: {
    total: number;
    success: number;
    failed: number;
  };
}

const processItem = async (item: BatchItem): Promise<Result<BatchItem, BaseFailure>> => {
  return Result.fromAsync(
    async () => {
      if (!item.id) {
        throw new Error('ID is required');
      }
      
      if (item.data === 'error') {
        throw new Error('Processing failed for this item');
      }
      
      // Simulate processing
      await new Promise(resolve => setTimeout(resolve, 10));
      
      return {
        ...item,
        data: `processed_${item.data}`,
        processedAt: new Date()
      };
    },
    (error: unknown) => new CommonFailures.ValidationFail(
      error instanceof Error ? error.message : 'Processing failed',
      { itemId: item.id }
    )
  );
};

const processBatch = async (items: BatchItem[], traceId?: string) => {
  const results = await Promise.all(
    items.map(item => processItem(item))
  );

  const processed: BatchItem[] = [];
  const failed: string[] = [];

  results.forEach((result, index) => {
    if (result.isSuccess) {
      processed.push(result.getValue());
    } else {
      failed.push(items[index].id);
    }
  });

  const batchResult: BatchResult = {
    processed,
    failed,
    summary: {
      total: items.length,
      success: processed.length,
      failed: failed.length
    }
  };

  // ถ้ามี item ที่ล้มเหลว ให้ return error
  if (failed.length > 0) {
    const errorResult = Result.fail(
      new CommonFailures.ValidationFail(
        `${failed.length} out of ${items.length} items failed to process`,
        { failedItems: failed, batchResult }
      )
    );

    return resultToResponse(errorResult, {
      errorDataResult: batchResult,
      traceId
    });
  }

  // ทุก item สำเร็จ
  const successResult = Result.ok(batchResult);
  return resultToResponse(successResult, {
    successMessage: `Successfully processed ${items.length} items`,
    traceId
  });
};

// การทดสอบ batch processing
const testBatchProcessing = async () => {
  const successItems: BatchItem[] = [
    { id: '1', data: 'item1' },
    { id: '2', data: 'item2' },
    { id: '3', data: 'item3' }
  ];

  const mixedItems: BatchItem[] = [
    { id: '1', data: 'item1' },
    { id: '2', data: 'error' }, // This will fail
    { id: '3', data: 'item3' }
  ];

  const successBatch = await processBatch(successItems, 'batch-success');
  const mixedBatch = await processBatch(mixedItems, 'batch-mixed');

  return { successBatch, mixedBatch };
};

🔄 Async Chain Operations

1. User Profile Processing

interface UserProfile {
  id: string;
  name: string;
  email: string;
  preferences?: Record<string, unknown>;
  permissions?: string[];
  lastLogin?: Date;
}

// Mock async functions
const fetchUser = async (id: string): Promise<Result<UserProfile, BaseFailure>> => {
  return Result.fromAsync(
    async () => {
      if (id === 'invalid') {
        throw new Error('User not found');
      }
      
      // Simulate database call
      await new Promise(resolve => setTimeout(resolve, 50));
      
      return {
        id,
        name: 'John Doe',
        email: '[email protected]'
      };
    },
    (error: unknown) => new CommonFailures.NotFoundFail(
      error instanceof Error ? error.message : 'User not found'
    )
  );
};

const enrichWithPreferences = async (user: UserProfile): Promise<Result<UserProfile, BaseFailure>> => {
  return Result.fromAsync(
    async () => {
      // Simulate preferences fetch
      await new Promise(resolve => setTimeout(resolve, 30));
      
      const preferences = {
        theme: 'dark',
        language: 'en',
        notifications: true
      };
      
      return { ...user, preferences };
    },
    () => new CommonFailures.GetFail('Failed to fetch user preferences')
  );
};

const enrichWithPermissions = async (user: UserProfile): Promise<Result<UserProfile, BaseFailure>> => {
  return Result.fromAsync(
    async () => {
      // Simulate permissions fetch
      await new Promise(resolve => setTimeout(resolve, 20));
      
      const permissions = ['read', 'write', 'delete'];
      return { ...user, permissions };
    },
    () => new CommonFailures.GetFail('Failed to fetch user permissions')
  );
};

const updateLastLogin = async (user: UserProfile): Promise<Result<UserProfile, BaseFailure>> => {
  return Result.fromAsync(
    async () => {
      // Simulate database update
      await new Promise(resolve => setTimeout(resolve, 40));
      
      return { ...user, lastLogin: new Date() };
    },
    () => new CommonFailures.UpdateFail('Failed to update last login')
  );
};

// ✅ Complete async chain
const getFullUserProfile = async (userId: string, traceId?: string) => {
  let result = await fetchUser(userId);
  result = await result.chainAsync(enrichWithPreferences);
  result = await result.chainAsync(enrichWithPermissions);
  result = await result.chainAsync(updateLastLogin);

  return resultToResponse(result, {
    successMessage: 'User profile retrieved and updated successfully',
    traceId
  });
};

// การทดสอบ async chain
const testAsyncChain = async () => {
  const successProfile = await getFullUserProfile('user123', 'profile-success');
  const errorProfile = await getFullUserProfile('invalid', 'profile-error');
  
  return { successProfile, errorProfile };
};

2. Multi-step Data Processing

interface ProcessingSteps {
  validate: (data: unknown) => Result<ProcessingData, BaseFailure>;
  transform: (data: ProcessingData) => Promise<Result<ProcessingData, BaseFailure>>;
  enrich: (data: ProcessingData) => Promise<Result<ProcessingData, BaseFailure>>;
  save: (data: ProcessingData) => Promise<Result<ProcessingData, BaseFailure>>;
}

interface ProcessingData {
  id: string;
  originalData: unknown;
  validatedData?: unknown;
  transformedData?: unknown;
  enrichedData?: unknown;
  savedAt?: Date;
}

const createProcessingPipeline = (): ProcessingSteps => ({
  validate: (data: unknown) => {
    if (!data || typeof data !== 'object') {
      return Result.fail(new CommonFailures.ValidationFail('Invalid input data'));
    }
    
    return Result.ok({
      id: `process_${Date.now()}`,
      originalData: data,
      validatedData: data
    });
  },

  transform: async (data: ProcessingData) => {
    return Result.fromAsync(
      async () => {
        await new Promise(resolve => setTimeout(resolve, 30));
        
        return {
          ...data,
          transformedData: {
            ...data.validatedData,
            transformed: true,
            transformedAt: new Date()
          }
        };
      },
      () => new CommonFailures.ValidationFail('Data transformation failed')
    );
  },

  enrich: async (data: ProcessingData) => {
    return Result.fromAsync(
      async () => {
        await new Promise(resolve => setTimeout(resolve, 40));
        
        return {
          ...data,
          enrichedData: {
            ...data.transformedData,
            metadata: {
              processedBy: 'system',
              version: '1.0.0',
              enrichedAt: new Date()
            }
          }
        };
      },
      () => new CommonFailures.ValidationFail('Data enrichment failed')
    );
  },

  save: async (data: ProcessingData) => {
    return Result.fromAsync(
      async () => {
        await new Promise(resolve => setTimeout(resolve, 50));
        
        return {
          ...data,
          savedAt: new Date()
        };
      },
      () => new CommonFailures.CreateFail('Failed to save processed data')
    );
  }
});

const processDataPipeline = async (inputData: unknown, traceId?: string) => {
  const pipeline = createProcessingPipeline();
  
  let result = pipeline.validate(inputData);
  result = await result.chainAsync(pipeline.transform);
  result = await result.chainAsync(pipeline.enrich);
  result = await result.chainAsync(pipeline.save);

  return resultToResponse(result, {
    successMessage: 'Data processing pipeline completed successfully',
    errorDataResult: { inputData, pipeline: 'failed' },
    traceId
  });
};

// การทดสอบ pipeline
const testDataPipeline = async () => {
  const validData = { name: 'test', value: 123 };
  const invalidData = null;
  
  const successPipeline = await processDataPipeline(validData, 'pipeline-success');
  const errorPipeline = await processDataPipeline(invalidData, 'pipeline-error');
  
  return { successPipeline, errorPipeline };
};

🎯 Best Practices

1. ✅ Consistent Error Handling

// ✅ Good: Consistent error types
const handleUserOperation = (operation: string, data: unknown, traceId?: string) => {
  const result = Result.from(
    () => {
      switch (operation) {
        case 'create':
          if (!data) throw new Error('Data is required for creation');
          return { action: 'created', data };
        
        case 'update':
          if (!data) throw new Error('Data is required for update');
          return { action: 'updated', data };
        
        default:
          throw new Error(`Unknown operation: ${operation}`);
      }
    },
    (error: unknown) => {
      const message = error instanceof Error ? error.message : 'Operation failed';
      
      if (message.includes('required')) {
        return new CommonFailures.ValidationFail(message);
      }
      
      if (message.includes('Unknown')) {
        return new CommonFailures.BadRequestFail(message);
      }
      
      return new CommonFailures.InternalFail(message);
    }
  );

  return resultToResponse(result, {
    successMessage: `${operation} operation completed successfully`,
    traceId
  });
};

2. ✅ Structured Logging with TraceId

// ✅ Good: Structured logging with traceId
const createUserWithLogging = async (userData: unknown, traceId?: string) => {
  const startTime = Date.now();
  
  const result = await Result.ok(userData)
    .tap(() => console.log(`[${traceId}] Starting user creation`))
    .chain(data => {
      console.log(`[${traceId}] Validating user data`);
      return validateUser(data);
    })
    .then(result => result.chainAsync(async (user) => {
      console.log(`[${traceId}] Saving user to database`);
      return saveUserToDatabase(user);
    }))
    .then(result => result.tap(user => {
      const duration = Date.now() - startTime;
      console.log(`[${traceId}] User created successfully in ${duration}ms`, { userId: user.id });
    }))
    .then(result => result.tapError(error => {
      const duration = Date.now() - startTime;
      console.error(`[${traceId}] User creation failed after ${duration}ms`, { error: error.message });
    }));

  return resultToResponse(result, {
    successMessage: 'User created successfully',
    traceId
  });
};

3. ✅ Type-safe Response Handling

// ✅ Good: Type-safe response definitions
interface ApiResponse<T> {
  data?: T;
  error?: string;
  traceId?: string;
}

const handleApiResponse = <T>(
  result: Result<T, BaseFailure>,
  traceId?: string
): ApiResponse<T> => {
  const response = resultToResponse(result, { traceId });
  
  return {
    data: response.isSuccess ? response.dataResult : undefined,
    error: response.isSuccess ? undefined : response.message,
    traceId: response.traceId
  };
};

// การใช้งาน
const processApiRequest = async <T>(
  operation: () => Promise<Result<T, BaseFailure>>,
  traceId?: string
): Promise<ApiResponse<T>> => {
  try {
    const result = await operation();
    return handleApiResponse(result, traceId);
  } catch (error) {
    const errorResult = Result.fail(
      new CommonFailures.InternalFail(
        error instanceof Error ? error.message : 'Unexpected error'
      )
    );
    return handleApiResponse(errorResult, traceId);
  }
};

4. ✅ Standardized Controller Pattern

// ✅ Good: Standardized controller pattern
interface ControllerContext {
  traceId: string;
  userId?: string;
  requestId: string;
}

type ControllerHandler<TRequest, TResponse> = (
  request: TRequest,
  context: ControllerContext
) => Promise<DataResponse<TResponse>>;

const createController = <TRequest, TResponse>(
  handler: (request: TRequest, context: ControllerContext) => Promise<Result<TResponse, BaseFailure>>
): ControllerHandler<TRequest, TResponse> => {
  return async (request: TRequest, context: ControllerContext) => {
    try {
      const result = await handler(request, context);
      return resultToResponse(result, {
        successMessage: 'Request processed successfully',
        traceId: context.traceId
      });
    } catch (error) {
      const errorResult = Result.fail(
        new CommonFailures.InternalFail(
          error instanceof Error ? error.message : 'Controller error'
        )
      );
      
      return resultToResponse(errorResult, {
        traceId: context.traceId
      });
    }
  };
};

// การใช้งาน
const getUserHandler = createController(
  async (request: { userId: string }, context: ControllerContext) => {
    return await fetchUser(request.userId)
      .tap(user => console.log(`[${context.traceId}] Retrieved user: ${user.id}`));
  }
);

🎉 สรุป

ResponseBuilder ช่วยให้การสร้าง API responses เป็นมาตรฐานและง่ายต่อการจัดการ:

✅ ข้อดี

  • Standardized Format - รูปแบบ response ที่สม่ำเสมอ
  • Type Safety - TypeScript support เต็มรูปแบบ
  • Error Handling - จัดการ error แบบมีโครงสร้าง
  • Tracing Support - รองรับ traceId สำหรับ debugging
  • Result Integration - ทำงานร่วมกับ Result monad ได้ดี

🎯 Best Practices

  1. ใช้ traceId สำหรับทุก request
  2. สร้าง error types ที่ชัดเจน
  3. ใช้ Result chain สำหรับ complex operations
  4. Log การทำงานด้วย structured logging
  5. Handle errors อย่างสม่ำเสมอ

ResponseBuilder ช่วยให้ API development มีประสิทธิภาพและเชื่อถือได้มากขึ้น! 🚀

🔧 Helper Functions Usage Examples

1. failureToResult Helper Function

Helper function นี้ช่วยแปลง BaseFailure เป็น Result<T, BaseFailure> เพื่อใช้ใน Result chains

import { failureToResult, CommonFailures } from './ResponseBuilder';

// ✅ ตัวอย่างการใช้งาน failureToResult
const validateEmail = (email: string): Result<string, BaseFailure> => {
  if (!email?.includes('@')) {
    const failure = new CommonFailures.ValidationFail('Invalid email format', {
      field: 'email',
      value: email
    });
    return failureToResult<string>(failure);
  }
  return Result.ok(email);
};

const findUser = (userId: string): Result<{ id: string; name: string }, BaseFailure> => {
  if (userId === 'notfound') {
    const failure = new CommonFailures.NotFoundFail(`User with ID ${userId} not found`, {
      userId,
      resource: 'user'
    });
    return failureToResult(failure);
  }
  
  return Result.ok({ id: userId, name: 'John Doe' });
};

// ✅ การใช้ใน Result chain
const processUserRegistration = (userData: { email: string; userId: string }) => {
  return Result.ok(userData)
    .chain(data => validateEmail(data.email).map(() => data))
    .chain(data => findUser(data.userId).map(() => data))
    .map(data => ({
      ...data,
      registeredAt: new Date(),
      status: 'active'
    }));
};

// การทดสอบ
const successCase = processUserRegistration({ 
  email: '[email protected]', 
  userId: 'user123' 
});

const emailErrorCase = processUserRegistration({ 
  email: 'invalid-email', 
  userId: 'user123' 
});

const userNotFoundCase = processUserRegistration({ 
  email: '[email protected]', 
  userId: 'notfound' 
});

2. resultToResponse Helper Function

Helper function นี้แปลง Result<T, BaseFailure> เป็น DataResponse<T> พร้อม options ต่างๆ

import { resultToResponse, ResultToResponseOptions } from './ResponseBuilder';

// ✅ Basic usage
const basicExample = () => {
  const result = Result.ok({ id: '1', name: 'John' });
  
  return resultToResponse(result, {
    successMessage: 'User retrieved successfully',
    traceId: 'trace-001'
  });
};

// ✅ Error case with custom data
const errorWithDataExample = () => {
  const failure = new CommonFailures.ValidationFail('Validation failed', {
    fields: ['name', 'email']
  });
  const result = Result.fail<never, BaseFailure>(failure);
  
  return resultToResponse(result, {
    errorDataResult: { 
      submittedData: { name: '', email: 'invalid' },
      validationRules: { name: 'required', email: 'valid email format' }
    },
    traceId: 'validation-error-001'
  });
};

// ✅ Complex validation with multiple steps
interface UserRegistrationData {
  name: string;
  email: string;
  password: string;
  age: number;
}

const validateUserRegistration = (data: Partial<UserRegistrationData>): Result<UserRegistrationData, BaseFailure> => {
  // Name validation
  if (!data.name || data.name.trim().length === 0) {
    return failureToResult(new CommonFailures.ValidationFail('Name is required'));
  }

  // Email validation
  if (!data.email?.includes('@')) {
    return failureToResult(new CommonFailures.ValidationFail('Valid email is required'));
  }

  // Password validation
  if (!data.password || data.password.length < 8) {
    return failureToResult(new CommonFailures.ValidationFail('Password must be at least 8 characters'));
  }

  // Age validation
  if (!data.age || data.age < 18) {
    return failureToResult(new CommonFailures.ValidationFail('Age must be 18 or older'));
  }

  return Result.ok(data as UserRegistrationData);
};

const registerUser = (userData: Partial<UserRegistrationData>, traceId?: string) => {
  const result = validateUserRegistration(userData)
    .map(validData => ({
      ...validData,
      id: `user_${Date.now()}`,
      createdAt: new Date()
    }));

  return resultToResponse(result, {
    successMessage: 'User registered successfully',
    errorDataResult: userData,
    traceId
  });
};

// การทดสอบ registration
const validRegistration = registerUser({
  name: 'John Doe',
  email: '[email protected]',
  password: 'securepassword123',
  age: 25
}, 'register-success-001');

const invalidRegistration = registerUser({
  name: '',
  email: 'invalid-email',
  password: '123',
  age: 16
}, 'register-error-001');

3. Async Operations with Helper Functions

// ✅ Async validation และการใช้ helper functions
const asyncUserProcessing = {
  // Database simulation
  checkEmailExists: async (email: string): Promise<Result<boolean, BaseFailure>> => {
    return Result.fromAsync(
      async () => {
        await new Promise(resolve => setTimeout(resolve, 100));
        return email === '[email protected]';
      },
      () => new CommonFailures.GetFail('Database error while checking email')
    );
  },

  saveUser: async (userData: UserRegistrationData): Promise<Result<UserRegistrationData & { id: string }, BaseFailure>> => {
    return Result.fromAsync(
      async () => {
        await new Promise(resolve => setTimeout(resolve, 200));
        return {
          ...userData,
          id: `saved_${Date.now()}`
        };
      },
      () => new CommonFailures.CreateFail('Failed to save user to database')
    );
  },

  // ✅ Complete async registration flow
  processRegistration: async (userData: Partial<UserRegistrationData>, traceId?: string) => {
    const result = await validateUserRegistration(userData)
      .chainAsync(async (validData) => {
        const emailExists = await asyncUserProcessing.checkEmailExists(validData.email);
        if (emailExists.isSuccess && emailExists.getValue()) {
          return failureToResult(new CommonFailures.CreateFail('Email already exists'));
        }
        return Result.ok(validData);
      })
      .then(result => result.chainAsync(asyncUserProcessing.saveUser));

    return resultToResponse(result, {
      successMessage: 'User registration completed successfully',
      errorDataResult: userData,
      traceId
    });
  }
};

// การทดสอบ async registration
const testAsyncRegistration = async () => {
  const newUser = {
    name: 'Jane Doe',
    email: '[email protected]',
    password: 'newpassword123',
    age: 30
  };

  const existingUser = {
    name: 'John Doe',
    email: '[email protected]',
    password: 'password123',
    age: 25
  };

  const successResult = await asyncUserProcessing.processRegistration(newUser, 'async-success');
  const errorResult = await asyncUserProcessing.processRegistration(existingUser, 'async-error');
  
  return { successResult, errorResult };
};

4. Express.js Integration with Helper Functions

// ✅ Express controller ที่ใช้ helper functions
interface ExpressController {
  // POST /users - Create user with full validation
  createUser: (req: { body: unknown }, res: { status: (code: number) => { json: (data: unknown) => void } }) => Promise<void>;
  
  // GET /users/:id - Get user with validation
  getUser: (req: { params: { id: string } }, res: { status: (code: number) => { json: (data: unknown) => void } }) => Promise<void>;
  
  // PUT /users/:id - Update user
  updateUser: (req: { params: { id: string }; body: unknown }, res: { status: (code: number) => { json: (data: unknown) => void } }) => Promise<void>;
}

const createExpressControllers = (): ExpressController => ({
  createUser: async (req, res) => {
    const userData = req.body;
    const traceId = `create-user-${Date.now()}`;

    // ใช้ helper functions ใน complex validation chain
    const result = await Result.ok(userData)
      .chain(data => {
        if (!data || typeof data !== 'object') {
          return failureToResult(new CommonFailures.ValidationFail('Request body must be an object'));
        }
        return Result.ok(data);
      })
      .chain(data => validateUserRegistration(data as Partial<UserRegistrationData>))
      .chainAsync(async (validData) => {
        // Check if email exists
        const emailCheck = await asyncUserProcessing.checkEmailExists(validData.email);
        if (emailCheck.isSuccess && emailCheck.getValue()) {
          return failureToResult(new CommonFailures.CreateFail('Email already registered'));
        }
        return Result.ok(validData);
      })
      .then(result => result.chainAsync(asyncUserProcessing.saveUser));

    // ใช้ resultToResponse เพื่อแปลงเป็น API response
    const response = resultToResponse(result, {
      successMessage: 'User created successfully',
      errorDataResult: userData,
      traceId
    });

    res.status(response.statusCode).json(response);
  },

  getUser: async (req, res) => {
    const userId = req.params.id;
    const traceId = `get-user-${Date.now()}`;

    // Validation และ fetch user
    const result = await Result.ok(userId)
      .chain(id => {
        if (!id || id.trim().length === 0) {
          return failureToResult(new CommonFailures.ValidationFail('User ID is required'));
        }
        return Result.ok(id);
      })
      .chain(findUser)
      .chainAsync(async (user) => {
        // Enrich user data
        return Result.ok({
          ...user,
          lastAccessed: new Date(),
          status: 'active'
        });
      });

    const response = resultToResponse(result, {
      successMessage: 'User retrieved successfully',
      traceId
    });

    res.status(response.statusCode).json(response);
  },

  updateUser: async (req, res) => {
    const userId = req.params.id;
    const updateData = req.body;
    const traceId = `update-user-${Date.now()}`;

    const result = await Result.ok({ userId, updateData })
      .chain(({ userId, updateData }) => {
        // Validate user ID
        if (!userId) {
          return failureToResult(new CommonFailures.ValidationFail('User ID is required'));
        }
        
        // Validate update data
        if (!updateData || typeof updateData !== 'object') {
          return failureToResult(new CommonFailures.ValidationFail('Update data must be an object'));
        }

        return Result.ok({ userId, updateData });
      })
      .chain(({ userId }) => findUser(userId)) // Check if user exists
      .chainAsync(async (existingUser) => {
        // Apply updates
        const updatedUser = {
          ...existingUser,
          ...(updateData as object),
          updatedAt: new Date()
        };

        // Simulate save
        await new Promise(resolve => setTimeout(resolve, 100));
        return Result.ok(updatedUser);
      });

    const response = resultToResponse(result, {
      successMessage: 'User updated successfully',
      errorDataResult: { userId, updateData },
      traceId
    });

    res.status(response.statusCode).json(response);
  }
});

// การใช้งาน controllers
const controllers = createExpressControllers();

// Test requests simulation
const simulateRequests = async () => {
  console.log('=== Testing Express Controllers with Helper Functions ===');

  // Mock response object
  const createMockResponse = () => ({
    status: (code: number) => ({
      json: (data: unknown) => console.log(`Status ${code}:`, JSON.stringify(data, null, 2))
    })
  });

  // Test create user
  console.log('\n1. Create User Test:');
  await controllers.createUser(
    { body: { name: 'Test User', email: '[email protected]', password: 'password123', age: 25 } },
    createMockResponse()
  );

  // Test create user with validation error
  console.log('\n2. Create User Validation Error:');
  await controllers.createUser(
    { body: { name: '', email: 'invalid-email' } },
    createMockResponse()
  );

  // Test get user
  console.log('\n3. Get User Test:');
  await controllers.getUser(
    { params: { id: 'user123' } },
    createMockResponse()
  );

  // Test get user not found
  console.log('\n4. Get User Not Found:');
  await controllers.getUser(
    { params: { id: 'notfound' } },
    createMockResponse()
  );

  // Test update user
  console.log('\n5. Update User Test:');
  await controllers.updateUser(
    { params: { id: 'user123' }, body: { name: 'Updated Name' } },
    createMockResponse()
  );
};

5. Advanced Helper Function Patterns

// ✅ Creating custom helper functions
const createCustomHelpers = () => {
  // Helper สำหรับ validation chains
  const validateRequired = <T>(value: T, fieldName: string): Result<T, BaseFailure> => {
    if (value === null || value === undefined || value === '') {
      return failureToResult(new CommonFailures.ValidationFail(`${fieldName} is required`));
    }
    return Result.ok(value);
  };

  const validateLength = (value: string, min: number, max: number, fieldName: string): Result<string, BaseFailure> => {
    if (value.length < min || value.length > max) {
      return failureToResult(new CommonFailures.ValidationFail(
        `${fieldName} must be between ${min} and ${max} characters`
      ));
    }
    return Result.ok(value);
  };

  const validateEmail = (email: string): Result<string, BaseFailure> => {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
      return failureToResult(new CommonFailures.ValidationFail('Invalid email format'));
    }
    return Result.ok(email);
  };

  // Helper สำหรับสร้าง standardized responses
  const createSuccessResponse = <T>(data: T, message: string, traceId?: string) => {
    return resultToResponse(Result.ok(data), {
      successMessage: message,
      traceId
    });
  };

  const createErrorResponse = (error: BaseFailure, data?: unknown, traceId?: string) => {
    return resultToResponse(Result.fail(error), {
      errorDataResult: data,
      traceId
    });
  };

  // Complex validation helper
  const validateUserInput = (input: {
    name?: string;
    email?: strin