Source: utils/apiEnvelope.js

// @ts-check
import { randomUUID } from 'node:crypto';

/**
 * Common HTTP envelope for the new Members service endpoints (Projects, Shares,
 * Snapshots, Event replay). See 080-Workspaces/018-Cross-Service-Contracts.mdx.
 *
 * Success:  { success: true, result, request_id, schema_version }
 * Error:    { success: false, error: { code, message, details }, request_id, schema_version }
 *
 * Legacy routes keep their own envelope; only the new endpoints use this one.
 */

export const SCHEMA_VERSION = 1;

/** @param {import('express').Request} req */
const requestId = (req) => req?.headers?.['x-request-id'] || `req-${randomUUID()}`;

/**
 * Send a successful response using the contract envelope.
 * @param {import('express').Response} res
 * @param {any} result
 */
export const sendOk = (res, result) => {
    res.json({
        success: true,
        result,
        request_id: requestId(res.req),
        schema_version: SCHEMA_VERSION,
    });
};

/**
 * Send an error response using the contract envelope.
 * @param {import('express').Response} res
 * @param {number} status
 * @param {string} code
 * @param {string} message
 * @param {any} [details]
 */
export const sendError = (res, status, code, message, details = undefined) => {
    res.status(status).json({
        success: false,
        error: { code, message, details },
        request_id: requestId(res.req),
        schema_version: SCHEMA_VERSION,
    });
};

/**
 * Normalize a thrown error into a contract error response. Errors that already
 * carry an ApiError shape (code/status) are honoured; everything else becomes 500.
 * @param {import('express').Response} res
 * @param {any} error
 */
export const sendErrorFrom = (res, error) => {
    if (error && error.status && error.code) {
        sendError(res, error.status, error.code, error.message, error.details);
        return;
    }
    sendError(res, 500, 'INTERNAL_ERROR', error?.message || 'Internal server error');
};

/**
 * Create an ApiError carrying an HTTP status and a machine-readable code.
 * @param {number} status
 * @param {string} code
 * @param {string} message
 * @param {any} [details]
 */
export const apiError = (status, code, message, details = undefined) => {
    const err = new Error(message);
    err.status = status;
    err.code = code;
    err.details = details;
    return err;
};