feat: add zero-configuration Brainy service template with augmentation-first architecture
- Implement WebSocket augmentation for real-time communication - Implement WebRTC augmentation for peer-to-peer connections - Implement HTTP augmentation as minimal REST fallback - Add auto-discovery augmentation for data pattern analysis - Add adaptive storage augmentation for intelligent resource management - Add environment adapter augmentation for universal compatibility - Template auto-detects environment (browser, Node.js, serverless, containers) - Intelligent transport selection (WebRTC → WebSocket → HTTP) - Automatic storage optimization (memory → filesystem → S3) - Zero configuration required - just npm start - Includes intelligent verb scoring by default - Works in any environment without configuration - Full documentation and examples included
This commit is contained in:
parent
880b8f74e3
commit
ee003a9473
30 changed files with 3548 additions and 0 deletions
136
examples/brainy-service-template/src/middleware/errorHandler.js
Normal file
136
examples/brainy-service-template/src/middleware/errorHandler.js
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import { logger } from '../utils/logger.js'
|
||||
import { ApiError } from '../utils/errors.js'
|
||||
|
||||
export const errorHandler = (error, req, res, next) => {
|
||||
// Default error response
|
||||
let statusCode = 500
|
||||
let message = 'Internal Server Error'
|
||||
let details = null
|
||||
|
||||
// Handle known API errors
|
||||
if (error instanceof ApiError) {
|
||||
statusCode = error.statusCode
|
||||
message = error.message
|
||||
|
||||
// Add additional details for validation errors
|
||||
if (error.field) {
|
||||
details = { field: error.field }
|
||||
}
|
||||
|
||||
if (error.resource) {
|
||||
details = {
|
||||
resource: error.resource,
|
||||
resourceId: error.resourceId
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle Brainy-specific errors
|
||||
else if (error.name === 'BrainyError') {
|
||||
statusCode = 400
|
||||
message = error.message
|
||||
details = { type: 'brainy_error' }
|
||||
}
|
||||
// Handle validation errors from other sources
|
||||
else if (error.name === 'ValidationError' || error.name === 'CastError') {
|
||||
statusCode = 400
|
||||
message = error.message
|
||||
details = { type: 'validation_error' }
|
||||
}
|
||||
// Handle JSON parsing errors
|
||||
else if (error instanceof SyntaxError && error.status === 400 && 'body' in error) {
|
||||
statusCode = 400
|
||||
message = 'Invalid JSON format'
|
||||
details = { type: 'json_error' }
|
||||
}
|
||||
// Handle timeout errors
|
||||
else if (error.code === 'ETIMEDOUT' || error.message.includes('timeout')) {
|
||||
statusCode = 504
|
||||
message = 'Request timeout'
|
||||
details = { type: 'timeout_error' }
|
||||
}
|
||||
// Handle rate limiting
|
||||
else if (error.status === 429) {
|
||||
statusCode = 429
|
||||
message = 'Too Many Requests'
|
||||
details = { type: 'rate_limit_error' }
|
||||
}
|
||||
|
||||
// Log the error
|
||||
const logLevel = statusCode >= 500 ? 'error' : 'warn'
|
||||
const logMessage = `${req.method} ${req.originalUrl} - ${statusCode} ${message}`
|
||||
const logMeta = {
|
||||
statusCode,
|
||||
method: req.method,
|
||||
url: req.originalUrl,
|
||||
userAgent: req.get('User-Agent'),
|
||||
ip: req.ip,
|
||||
stack: error.stack
|
||||
}
|
||||
|
||||
logger[logLevel](logMessage, logMeta)
|
||||
|
||||
// Prepare error response
|
||||
const errorResponse = {
|
||||
success: false,
|
||||
error: {
|
||||
message,
|
||||
statusCode,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: req.originalUrl,
|
||||
method: req.method
|
||||
}
|
||||
}
|
||||
|
||||
// Add details if available
|
||||
if (details) {
|
||||
errorResponse.error.details = details
|
||||
}
|
||||
|
||||
// Add stack trace in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
errorResponse.error.stack = error.stack
|
||||
}
|
||||
|
||||
// Add request ID if available
|
||||
if (req.id) {
|
||||
errorResponse.error.requestId = req.id
|
||||
}
|
||||
|
||||
res.status(statusCode).json(errorResponse)
|
||||
}
|
||||
|
||||
// 404 handler for unmatched routes
|
||||
export const notFoundHandler = (req, res) => {
|
||||
const message = `Route ${req.method} ${req.originalUrl} not found`
|
||||
|
||||
logger.warn('Route not found', {
|
||||
method: req.method,
|
||||
url: req.originalUrl,
|
||||
ip: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
})
|
||||
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: {
|
||||
message,
|
||||
statusCode: 404,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: req.originalUrl,
|
||||
method: req.method
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Async error wrapper
|
||||
export const asyncHandler = (fn) => {
|
||||
return (req, res, next) => {
|
||||
Promise.resolve(fn(req, res, next)).catch(next)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
errorHandler,
|
||||
notFoundHandler,
|
||||
asyncHandler
|
||||
}
|
||||
177
examples/brainy-service-template/src/middleware/validation.js
Normal file
177
examples/brainy-service-template/src/middleware/validation.js
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import { ValidationError } from '../utils/errors.js'
|
||||
|
||||
// Validate required fields
|
||||
export const validateRequired = (fields) => {
|
||||
return (req, res, next) => {
|
||||
const missingFields = []
|
||||
|
||||
fields.forEach(field => {
|
||||
if (req.body[field] === undefined || req.body[field] === null || req.body[field] === '') {
|
||||
missingFields.push(field)
|
||||
}
|
||||
})
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
throw new ValidationError(`Missing required fields: ${missingFields.join(', ')}`)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
// Validate entity creation
|
||||
export const validateEntity = (req, res, next) => {
|
||||
const { data } = req.body
|
||||
|
||||
if (!data) {
|
||||
throw new ValidationError('Entity data is required')
|
||||
}
|
||||
|
||||
if (typeof data !== 'object' && typeof data !== 'string') {
|
||||
throw new ValidationError('Entity data must be an object or string')
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
// Validate relationship creation
|
||||
export const validateRelationship = (req, res, next) => {
|
||||
const { sourceId, targetId, type, weight } = req.body
|
||||
|
||||
if (!sourceId || !targetId || !type) {
|
||||
throw new ValidationError('sourceId, targetId, and type are required')
|
||||
}
|
||||
|
||||
if (typeof sourceId !== 'string' || typeof targetId !== 'string' || typeof type !== 'string') {
|
||||
throw new ValidationError('sourceId, targetId, and type must be strings')
|
||||
}
|
||||
|
||||
if (sourceId === targetId) {
|
||||
throw new ValidationError('sourceId and targetId cannot be the same')
|
||||
}
|
||||
|
||||
if (weight !== undefined) {
|
||||
if (typeof weight !== 'number' || weight < 0 || weight > 1) {
|
||||
throw new ValidationError('Weight must be a number between 0 and 1')
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
// Validate search query
|
||||
export const validateSearch = (req, res, next) => {
|
||||
const { query, limit, threshold } = req.body
|
||||
|
||||
if (!query) {
|
||||
throw new ValidationError('Search query is required')
|
||||
}
|
||||
|
||||
if (typeof query !== 'string' || query.trim().length === 0) {
|
||||
throw new ValidationError('Search query must be a non-empty string')
|
||||
}
|
||||
|
||||
if (limit !== undefined) {
|
||||
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
|
||||
throw new ValidationError('Limit must be an integer between 1 and 100')
|
||||
}
|
||||
}
|
||||
|
||||
if (threshold !== undefined) {
|
||||
if (typeof threshold !== 'number' || threshold < 0 || threshold > 1) {
|
||||
throw new ValidationError('Threshold must be a number between 0 and 1')
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
// Validate feedback data
|
||||
export const validateFeedback = (req, res, next) => {
|
||||
const { weight, confidence, type } = req.body
|
||||
|
||||
if (weight === undefined) {
|
||||
throw new ValidationError('Weight is required for feedback')
|
||||
}
|
||||
|
||||
if (typeof weight !== 'number' || weight < 0 || weight > 1) {
|
||||
throw new ValidationError('Weight must be a number between 0 and 1')
|
||||
}
|
||||
|
||||
if (confidence !== undefined) {
|
||||
if (typeof confidence !== 'number' || confidence < 0 || confidence > 1) {
|
||||
throw new ValidationError('Confidence must be a number between 0 and 1')
|
||||
}
|
||||
}
|
||||
|
||||
if (type !== undefined) {
|
||||
const validTypes = ['correction', 'reinforcement', 'adjustment']
|
||||
if (!validTypes.includes(type)) {
|
||||
throw new ValidationError(`Type must be one of: ${validTypes.join(', ')}`)
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
// Validate pagination parameters
|
||||
export const validatePagination = (req, res, next) => {
|
||||
const { page = 1, limit = 50 } = req.query
|
||||
|
||||
const pageNum = parseInt(page)
|
||||
const limitNum = parseInt(limit)
|
||||
|
||||
if (!Number.isInteger(pageNum) || pageNum < 1) {
|
||||
throw new ValidationError('Page must be a positive integer')
|
||||
}
|
||||
|
||||
if (!Number.isInteger(limitNum) || limitNum < 1 || limitNum > 100) {
|
||||
throw new ValidationError('Limit must be an integer between 1 and 100')
|
||||
}
|
||||
|
||||
// Normalize values
|
||||
req.query.page = pageNum
|
||||
req.query.limit = limitNum
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
// Validate UUID format (basic check)
|
||||
export const validateId = (paramName = 'id') => {
|
||||
return (req, res, next) => {
|
||||
const id = req.params[paramName]
|
||||
|
||||
if (!id) {
|
||||
throw new ValidationError(`${paramName} is required`)
|
||||
}
|
||||
|
||||
// Basic string validation - Brainy uses various ID formats
|
||||
if (typeof id !== 'string' || id.trim().length === 0) {
|
||||
throw new ValidationError(`${paramName} must be a valid string`)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
// Generic validation wrapper
|
||||
export const validate = (validationFn) => {
|
||||
return (req, res, next) => {
|
||||
try {
|
||||
validationFn(req, res, next)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
validateRequired,
|
||||
validateEntity,
|
||||
validateRelationship,
|
||||
validateSearch,
|
||||
validateFeedback,
|
||||
validatePagination,
|
||||
validateId,
|
||||
validate
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue