@uploadista/server 0.0.20 → 0.1.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.cjs +1 -1
- package/dist/auth/index.d.cts +80 -2
- package/dist/auth/index.d.cts.map +1 -0
- package/dist/auth/index.d.mts +80 -2
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +2 -1
- package/dist/auth/index.mjs.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -13
- package/src/core/http-handlers/flow-http-handlers.ts +1 -1
- package/src/core/plugin-types.ts +1 -2
- package/tests/core/http-handlers/flow-handlers.test.ts +83 -51
- package/tests/core/http-handlers/health-handlers.test.ts +11 -10
- package/dist/auth-Ck4gisA2.cjs +0 -1
- package/dist/auth-DG0enyjj.mjs +0 -2
- package/dist/auth-DG0enyjj.mjs.map +0 -1
- package/dist/index-BXLtlr98.d.mts +0 -80
- package/dist/index-BXLtlr98.d.mts.map +0 -1
- package/dist/index-mMP18lsw.d.cts +0 -80
- package/dist/index-mMP18lsw.d.cts.map +0 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["oldestKey: string | null","NoAuthCacheServiceLive: Layer.Layer<AuthCacheService>","details: unknown","result: {\n status: number;\n code: string;\n message: string;\n details?: unknown;\n }","uploadEngine","statusCode: number","errorCode: string","response: {\n error: string;\n code: string;\n timestamp: string;\n details?: unknown;\n }","PERMISSION_HIERARCHY: Record<string, readonly string[]>","matchHasPermission","matchHasAnyPermission","NoAuthContextServiceLive: Layer.Layer<AuthContextService>","NoUsageHookServiceLive: Layer.Layer<UsageHookService>","flowEngine","statuses: HealthStatus[]","allStatuses: HealthStatus[]","status: HealthStatus","components: HealthComponents","uploadEngine","authContext: AuthContext | null","authResult:\n | AuthContext\n | null\n | { _tag: \"TimeoutError\" }\n | { _tag: \"AuthError\"; error: unknown }","waitUntilLayers: Layer.Layer<any, never, never>[]","errorBody: Record<string, unknown>","errorResponse: StandardResponse","KNOWN_PLUGINS: Record<\n string,\n { packageName: string; variableName: string }\n>","lines: string[]","flowEngine","uploadEngine","flowEngine","uploadEngine"],"sources":["../src/cache.ts","../src/http-utils.ts","../src/layer-utils.ts","../src/error-types.ts","../src/permissions/errors.ts","../src/permissions/types.ts","../src/permissions/matcher.ts","../src/service.ts","../src/usage-hooks/types.ts","../src/usage-hooks/service.ts","../src/core/http-handlers/dlq-http-handlers.ts","../src/core/http-handlers/flow-http-handlers.ts","../src/core/health-check-service.ts","../src/core/http-handlers/health-http-handlers.ts","../src/core/http-handlers/upload-http-handlers.ts","../src/core/http-handlers/http-handlers.ts","../src/core/server.ts","../src/core/create-type-safe-server.ts","../src/core/plugin-validation.ts","../src/core/websocket-handlers/flow-websocket-handlers.ts","../src/core/websocket-handlers/upload-websocket-handlers.ts","../src/core/websocket-handlers/websocket-handlers.ts"],"sourcesContent":["import { Context, Effect, Layer } from \"effect\";\nimport type { AuthContext } from \"./types\";\n\n/**\n * Configuration options for the auth cache.\n */\nexport type AuthCacheConfig = {\n /**\n * Maximum number of entries in the cache.\n * When exceeded, oldest entries are removed (LRU eviction).\n * @default 10000\n */\n maxSize?: number;\n\n /**\n * Time-to-live for cache entries in milliseconds.\n * Entries older than this will be automatically evicted.\n * @default 3600000 (1 hour)\n */\n ttl?: number;\n};\n\n/**\n * Cache entry with auth context and timestamp.\n */\ntype CacheEntry = {\n authContext: AuthContext;\n timestamp: number;\n};\n\n/**\n * Auth Cache Service\n *\n * Provides caching of authentication contexts for upload and flow jobs.\n * This allows subsequent operations (chunk uploads, flow continuations)\n * to reuse the auth context from the initial request without re-authenticating.\n *\n * @example\n * ```typescript\n * import { Effect } from \"effect\";\n * import { AuthCacheService } from \"@uploadista/server\";\n *\n * const handler = Effect.gen(function* () {\n * const authCache = yield* AuthCacheService;\n * const authContext = { userId: \"user-123\" };\n *\n * // Cache auth for upload\n * yield* authCache.set(\"upload-abc\", authContext);\n *\n * // Retrieve cached auth later\n * const cached = yield* authCache.get(\"upload-abc\");\n * console.log(cached?.userId); // \"user-123\"\n *\n * // Clear when done\n * yield* authCache.delete(\"upload-abc\");\n * });\n * ```\n */\nexport class AuthCacheService extends Context.Tag(\"AuthCacheService\")<\n AuthCacheService,\n {\n /**\n * Store an auth context for a job ID.\n */\n readonly set: (\n jobId: string,\n authContext: AuthContext,\n ) => Effect.Effect<void>;\n\n /**\n * Retrieve a cached auth context by job ID.\n * Returns null if not found or expired.\n */\n readonly get: (jobId: string) => Effect.Effect<AuthContext | null>;\n\n /**\n * Delete a cached auth context by job ID.\n */\n readonly delete: (jobId: string) => Effect.Effect<void>;\n\n /**\n * Clear all cached auth contexts.\n */\n readonly clear: () => Effect.Effect<void>;\n\n /**\n * Get the current number of cached entries.\n */\n readonly size: () => Effect.Effect<number>;\n }\n>() {}\n\n/**\n * Creates an AuthCacheService Layer with in-memory storage.\n *\n * @param config - Optional configuration for cache behavior\n * @returns Effect Layer providing AuthCacheService\n */\nexport const AuthCacheServiceLive = (\n config: AuthCacheConfig = {},\n): Layer.Layer<AuthCacheService> => {\n const maxSize = config.maxSize ?? 10000;\n const ttl = config.ttl ?? 3600000; // 1 hour default\n\n // In-memory cache storage\n const cache = new Map<string, CacheEntry>();\n\n /**\n * Evict expired entries based on TTL.\n */\n const evictExpired = (): void => {\n const now = Date.now();\n for (const [jobId, entry] of cache.entries()) {\n if (now - entry.timestamp > ttl) {\n cache.delete(jobId);\n }\n }\n };\n\n /**\n * Enforce max size limit using LRU eviction.\n * Removes oldest entry when cache exceeds max size.\n */\n const enforceSizeLimit = (): void => {\n if (cache.size <= maxSize) return;\n\n // Find and remove oldest entry\n let oldestKey: string | null = null;\n let oldestTime = Number.POSITIVE_INFINITY;\n\n for (const [jobId, entry] of cache.entries()) {\n if (entry.timestamp < oldestTime) {\n oldestTime = entry.timestamp;\n oldestKey = jobId;\n }\n }\n\n if (oldestKey) {\n cache.delete(oldestKey);\n }\n };\n\n return Layer.succeed(AuthCacheService, {\n set: (jobId: string, authContext: AuthContext) =>\n Effect.sync(() => {\n // Evict expired entries periodically\n if (cache.size % 100 === 0) {\n evictExpired();\n }\n\n cache.set(jobId, {\n authContext,\n timestamp: Date.now(),\n });\n\n // Enforce size limit after adding\n enforceSizeLimit();\n }),\n\n get: (jobId: string) =>\n Effect.sync(() => {\n const entry = cache.get(jobId);\n if (!entry) return null;\n\n // Check if expired\n const now = Date.now();\n if (now - entry.timestamp > ttl) {\n cache.delete(jobId);\n return null;\n }\n\n return entry.authContext;\n }),\n\n delete: (jobId: string) =>\n Effect.sync(() => {\n cache.delete(jobId);\n }),\n\n clear: () =>\n Effect.sync(() => {\n cache.clear();\n }),\n\n size: () =>\n Effect.sync(() => {\n return cache.size;\n }),\n });\n};\n\n/**\n * No-op implementation of AuthCacheService.\n * Does not cache anything - all operations are no-ops.\n * Used when caching is disabled or not needed.\n */\nexport const NoAuthCacheServiceLive: Layer.Layer<AuthCacheService> =\n Layer.succeed(AuthCacheService, {\n set: () => Effect.void,\n get: () => Effect.succeed(null),\n delete: () => Effect.void,\n clear: () => Effect.void,\n size: () => Effect.succeed(0),\n });\n","/**\n * Shared HTTP utilities for server adapters\n *\n * This module provides routing and error handling utilities used across\n * Hono, Express, and Fastify adapters for request parsing and response formatting.\n */\n\n/**\n * Parses URL segments from a pathname, filtering out empty segments.\n * Useful for extracting route components from request paths.\n *\n * @param pathname - The URL pathname (e.g., \"/uploadista/api/upload/abc123\")\n * @returns Array of non-empty path segments\n *\n * @example\n * ```typescript\n * const segments = parseUrlSegments(\"/uploadista/api/upload/abc123\");\n * // => [\"uploadista\", \"api\", \"upload\", \"abc123\"]\n * ```\n */\nexport const parseUrlSegments = (pathname: string): string[] => {\n return pathname.split(\"/\").filter(Boolean);\n};\n\n/**\n * Extracts the last segment from a URL pathname.\n *\n * @param pathname - The URL pathname to parse\n * @returns The last non-empty segment, or undefined if none exists\n *\n * @example\n * ```typescript\n * const id = getLastSegment(\"/uploadista/api/upload/abc123\");\n * // => \"abc123\"\n * ```\n */\nexport const getLastSegment = (pathname: string): string | undefined => {\n const segments = parseUrlSegments(pathname);\n return segments[segments.length - 1];\n};\n\n/**\n * Checks if a pathname includes a specific base path and API prefix.\n * Used to determine if a request should be handled by the Uploadista adapter.\n *\n * @param pathname - The request pathname\n * @param basePath - The base path configured for the adapter (e.g., \"uploadista\")\n * @returns true if the path includes `{basePath}/api/`\n *\n * @example\n * ```typescript\n * const isUploadistaPath = hasBasePath(\"/uploadista/api/upload\", \"uploadista\");\n * // => true\n * ```\n */\nexport const hasBasePath = (pathname: string, basePath: string): boolean => {\n return pathname.includes(`${basePath}/api/`);\n};\n\n/**\n * Removes the base path prefix and returns clean route segments.\n * Transforms \"/uploadista/api/upload/abc123\" → [\"upload\", \"abc123\"]\n *\n * @param pathname - The full request pathname\n * @param basePath - The base path to remove (e.g., \"uploadista\")\n * @returns Array of route segments without base path prefix\n *\n * @example\n * ```typescript\n * const route = getRouteSegments(\"/uploadista/api/upload/abc123\", \"uploadista\");\n * // => [\"upload\", \"abc123\"]\n * ```\n */\nexport const getRouteSegments = (\n pathname: string,\n basePath: string,\n): string[] => {\n return pathname.replace(`${basePath}/api/`, \"\").split(\"/\").filter(Boolean);\n};\n\n/**\n * Standard error handler for flow and job operations.\n * Maps application errors to appropriate HTTP status codes and error formats.\n *\n * Supports errors with `code`, `message`, `status`, and `details` properties.\n * Maps error codes to HTTP status codes (e.g., NOT_FOUND → 404, VALIDATION_ERROR → 400).\n *\n * @param error - The error object to handle (can be any type)\n * @returns Standardized error response with status, code, message, and optional details\n *\n * @example\n * ```typescript\n * import { handleFlowError } from \"@uploadista/server\";\n *\n * const response = handleFlowError({\n * code: \"FLOW_JOB_NOT_FOUND\",\n * message: \"Job not found\",\n * });\n * // => { status: 404, code: \"FLOW_JOB_NOT_FOUND\", message: \"Job not found\" }\n * ```\n */\nexport const handleFlowError = (\n error: unknown,\n): { status: number; code: string; message: string; details?: unknown } => {\n let status = 500;\n let code = \"UNKNOWN_ERROR\";\n let message = \"Internal server error\";\n let details: unknown;\n\n if (typeof error === \"object\" && error !== null) {\n const errorObj = error as Record<string, unknown>;\n\n // Extract error code\n if (\"code\" in errorObj && typeof errorObj.code === \"string\") {\n code = errorObj.code;\n }\n\n // Extract message\n if (\"message\" in errorObj && typeof errorObj.message === \"string\") {\n message = errorObj.message;\n } else if (\"body\" in errorObj && typeof errorObj.body === \"string\") {\n // Support UploadistaError's body property\n message = errorObj.body;\n }\n\n // Extract details if present\n if (\"details\" in errorObj) {\n details = errorObj.details;\n }\n\n // Map error codes to HTTP status codes\n if (\"status\" in errorObj && typeof errorObj.status === \"number\") {\n status = errorObj.status;\n } else if (\"code\" in errorObj) {\n // Fallback: derive status from common error codes\n switch (errorObj.code) {\n case \"FILE_NOT_FOUND\":\n case \"FLOW_JOB_NOT_FOUND\":\n case \"UPLOAD_ID_NOT_FOUND\":\n status = 404;\n break;\n case \"FLOW_JOB_ERROR\":\n case \"VALIDATION_ERROR\":\n case \"INVALID_METADATA\":\n case \"INVALID_LENGTH\":\n case \"ABORTED\":\n case \"INVALID_TERMINATION\":\n status = 400;\n break;\n case \"INVALID_OFFSET\":\n status = 409;\n break;\n case \"ERR_SIZE_EXCEEDED\":\n case \"ERR_MAX_SIZE_EXCEEDED\":\n status = 413;\n break;\n case \"FILE_NO_LONGER_EXISTS\":\n status = 410;\n break;\n case \"MISSING_OFFSET\":\n case \"INVALID_CONTENT_TYPE\":\n status = 403;\n break;\n default:\n status = 500;\n }\n }\n\n // Special handling for specific error messages\n if (\"message\" in errorObj && errorObj.message === \"Invalid JSON body\") {\n status = 400;\n code = \"VALIDATION_ERROR\";\n }\n }\n\n const result: {\n status: number;\n code: string;\n message: string;\n details?: unknown;\n } = {\n status,\n code,\n message,\n };\n\n if (details !== undefined) {\n result.details = details;\n }\n\n return result;\n};\n\n/**\n * Extracts job ID from URL segments for job status endpoint.\n * Expected URL format: `/uploadista/api/jobs/:jobId/status`\n *\n * @param urlSegments - Parsed URL segments (without base path)\n * @returns The job ID if found, or undefined\n *\n * @example\n * ```typescript\n * const jobId = extractJobIdFromStatus([\"jobs\", \"job-123\", \"status\"]);\n * // => \"job-123\"\n * ```\n */\nexport const extractJobIdFromStatus = (\n urlSegments: string[],\n): string | undefined => {\n return urlSegments[urlSegments.length - 2];\n};\n\n/**\n * Extracts job ID and node ID from URL segments for resume flow endpoint.\n * Expected URL format: `/uploadista/api/jobs/:jobId/resume/:nodeId`\n *\n * @param urlSegments - Parsed URL segments (without base path)\n * @returns Object with extracted jobId and nodeId (either can be undefined if not found)\n *\n * @example\n * ```typescript\n * const { jobId, nodeId } = extractJobAndNodeId([\n * \"jobs\",\n * \"job-123\",\n * \"resume\",\n * \"node-456\",\n * ]);\n * // => { jobId: \"job-123\", nodeId: \"node-456\" }\n * ```\n */\nexport const extractJobAndNodeId = (\n urlSegments: string[],\n): { jobId: string | undefined; nodeId: string | undefined } => {\n return {\n jobId: urlSegments[urlSegments.length - 3],\n nodeId: urlSegments[urlSegments.length - 1],\n };\n};\n\n/**\n * Extracts flow ID and storage ID from URL segments.\n * Expected URL format: `/uploadista/api/flow/:flowId/:storageId`\n *\n * Mutates the input array (removes last 2 elements).\n *\n * @param urlSegments - Parsed URL segments (will be mutated)\n * @returns Object with extracted flowId and storageId\n *\n * @example\n * ```typescript\n * const segments = [\"flow\", \"flow-123\", \"storage-456\"];\n * const { flowId, storageId } = extractFlowAndStorageId(segments);\n * // => { flowId: \"flow-123\", storageId: \"storage-456\" }\n * // segments is now [\"flow\"]\n * ```\n */\nexport const extractFlowAndStorageId = (\n urlSegments: string[],\n): { flowId: string | undefined; storageId: string | undefined } => {\n return {\n storageId: urlSegments.pop(),\n flowId: urlSegments.pop(),\n };\n};\n","import type { FlowProvider } from \"@uploadista/core/flow\";\nimport { flowEngine } from \"@uploadista/core/flow\";\nimport {\n type BaseEventEmitterService,\n type BaseKvStoreService,\n flowEventEmitter,\n flowJobKvStore,\n type UploadFileDataStore,\n type UploadFileDataStores,\n type UploadFileKVStore,\n uploadEventEmitter,\n uploadFileKvStore,\n} from \"@uploadista/core/types\";\nimport { type UploadEngine, uploadEngine } from \"@uploadista/core/upload\";\nimport type { GenerateId } from \"@uploadista/core/utils\";\nimport { Layer } from \"effect\";\n\n/**\n * Configuration for creating upload engine layers.\n * Specifies all dependencies needed by the upload engine Effect Layer.\n *\n * @property kvStore - Key-value store for upload metadata\n * @property eventEmitter - Event emitter for upload progress events\n * @property dataStore - File data storage implementation\n * @property bufferedDataStore - Optional buffered storage for performance optimization\n * @property generateId - Optional custom ID generator (uses default if omitted)\n *\n * @example\n * ```typescript\n * import { createUploadEngineLayer } from \"@uploadista/server\";\n *\n * const uploadLayerConfig: UploadEngineLayerConfig = {\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * dataStore: s3DataStore,\n * };\n * ```\n */\nexport interface UploadEngineLayerConfig {\n kvStore: Layer.Layer<BaseKvStoreService>;\n eventEmitter: Layer.Layer<BaseEventEmitterService>;\n dataStore: Layer.Layer<UploadFileDataStores, never, UploadFileKVStore>;\n bufferedDataStore?: Layer.Layer<\n UploadFileDataStore,\n never,\n UploadFileKVStore\n >;\n generateId?: Layer.Layer<GenerateId>;\n}\n\n/**\n * Configuration for creating flow server layers.\n * Specifies all dependencies needed by the flow processing server.\n *\n * @property kvStore - Key-value store for flow job metadata\n * @property eventEmitter - Event emitter for flow progress events\n * @property flowProvider - Factory function for creating flows\n * @property uploadEngine - Upload engine layer (used by flows for uploads)\n *\n * @example\n * ```typescript\n * import { createFlowServerLayer } from \"@uploadista/server\";\n *\n * const flowLayerConfig: FlowServerLayerConfig = {\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * flowProvider: createFlowsEffect,\n * uploadEngine: uploadEngineLayer,\n * };\n * ```\n */\nexport interface FlowServerLayerConfig {\n kvStore: Layer.Layer<BaseKvStoreService>;\n eventEmitter: Layer.Layer<BaseEventEmitterService>;\n flowProvider: Layer.Layer<FlowProvider>;\n uploadEngine: Layer.Layer<UploadEngine>;\n}\n\n/**\n * Creates the upload server layer with all dependencies composed.\n * This layer handles file uploads with chunked transfer, resumption, and metadata tracking.\n *\n * The created layer includes:\n * - Upload KV store (metadata tracking)\n * - Data store (file storage)\n * - Event emitter (progress notifications)\n * - Optional buffered data store (performance optimization)\n * - Optional custom ID generator\n *\n * @param config - Upload server layer configuration\n * @returns Effect Layer providing UploadEngine\n *\n * @example\n * ```typescript\n * import { createUploadEngineLayer } from \"@uploadista/server\";\n * import { Layer } from \"effect\";\n *\n * const uploadEngineLayer = createUploadEngineLayer({\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * dataStore: s3DataStore,\n * });\n *\n * // Use in application\n * const app = Layer.provide(appLogic, uploadEngineLayer);\n * ```\n */\nexport const createUploadEngineLayer = ({\n kvStore,\n eventEmitter,\n dataStore,\n bufferedDataStore,\n generateId,\n}: UploadEngineLayerConfig) => {\n // Set up upload server dependencies\n const uploadFileKVStoreLayer = Layer.provide(uploadFileKvStore, kvStore);\n const uploadDataStoreLayer = Layer.provide(dataStore, uploadFileKVStoreLayer);\n const uploadBufferedDataStoreLayer = bufferedDataStore\n ? Layer.provide(bufferedDataStore, uploadFileKVStoreLayer)\n : Layer.empty;\n const uploadEventEmitterLayer = Layer.provide(\n uploadEventEmitter,\n eventEmitter,\n );\n\n const uploadEngineLayers = Layer.mergeAll(\n uploadDataStoreLayer,\n uploadFileKVStoreLayer,\n uploadEventEmitterLayer,\n ...(generateId ? [generateId] : []),\n uploadBufferedDataStoreLayer,\n );\n\n return Layer.provide(uploadEngine, uploadEngineLayers);\n};\n\n/**\n * Creates the flow server layer with all dependencies composed.\n * This layer handles file processing workflows with multi-stage pipelines.\n *\n * The created layer includes:\n * - Flow job KV store (job metadata and state)\n * - Event emitter (progress notifications)\n * - Flow provider (flow definitions)\n * - Upload server (for uploads within flows)\n *\n * @param config - Flow server layer configuration\n * @returns Effect Layer providing FlowServer\n *\n * @example\n * ```typescript\n * import { createFlowServerLayer } from \"@uploadista/server\";\n * import { Layer } from \"effect\";\n *\n * const flowServerLayer = createFlowServerLayer({\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * flowProvider: createFlowsEffect,\n * uploadEngine: uploadEngineLayer,\n * });\n *\n * // Use in application\n * const app = Layer.provide(appLogic, flowServerLayer);\n * ```\n */\nexport const createFlowEngineLayer = ({\n kvStore,\n eventEmitter,\n flowProvider,\n uploadEngine,\n}: FlowServerLayerConfig) => {\n // Set up flow server dependencies\n const flowJobKVStoreLayer = Layer.provide(flowJobKvStore, kvStore);\n const flowEventEmitterLayer = Layer.provide(flowEventEmitter, eventEmitter);\n\n const flowEngineLayers = Layer.mergeAll(\n flowProvider,\n flowEventEmitterLayer,\n flowJobKVStoreLayer,\n uploadEngine,\n );\n\n return Layer.provide(flowEngine, flowEngineLayers);\n};\n","import type { UploadistaError } from \"@uploadista/core/errors\";\n\n/**\n * Base adapter error class for HTTP adapters.\n * All adapter-specific errors should extend this class or one of its subclasses.\n *\n * @example\n * ```typescript\n * throw new AdapterError(\"Something went wrong\", 500, \"INTERNAL_ERROR\");\n * ```\n */\nexport class AdapterError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number = 500,\n public readonly errorCode: string = \"INTERNAL_ERROR\",\n ) {\n super(message);\n this.name = \"AdapterError\";\n }\n}\n\n/**\n * Validation error - indicates invalid request data or parameters.\n * Returns HTTP 400 Bad Request status.\n *\n * @example\n * ```typescript\n * if (!isValidUploadId(id)) {\n * throw new ValidationError(\"Invalid upload ID format\");\n * }\n * ```\n */\nexport class ValidationError extends AdapterError {\n constructor(message: string) {\n super(message, 400, \"VALIDATION_ERROR\");\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * Not found error - indicates a requested resource does not exist.\n * Returns HTTP 404 Not Found status.\n *\n * @example\n * ```typescript\n * if (!upload) {\n * throw new NotFoundError(\"Upload\");\n * }\n * ```\n */\nexport class NotFoundError extends AdapterError {\n constructor(resource: string) {\n super(`${resource} not found`, 404, \"NOT_FOUND\");\n this.name = \"NotFoundError\";\n }\n}\n\n/**\n * Bad request error - indicates a malformed request.\n * Returns HTTP 400 Bad Request status.\n * Similar to ValidationError but for request structure issues.\n *\n * @example\n * ```typescript\n * try {\n * const data = JSON.parse(body);\n * } catch {\n * throw new BadRequestError(\"Invalid JSON body\");\n * }\n * ```\n */\nexport class BadRequestError extends AdapterError {\n constructor(message: string) {\n super(message, 400, \"BAD_REQUEST\");\n this.name = \"BadRequestError\";\n }\n}\n\n/**\n * Creates a standardized error response object for AdapterError.\n * Includes error message, error code, and ISO timestamp.\n *\n * @param error - The AdapterError to format\n * @returns Standardized error response body\n *\n * @example\n * ```typescript\n * import { createErrorResponseBody } from \"@uploadista/server\";\n *\n * try {\n * // ... operation\n * } catch (err) {\n * const errorResponse = createErrorResponseBody(err);\n * res.status(err.statusCode).json(errorResponse);\n * }\n * ```\n */\nexport const createErrorResponseBody = (error: AdapterError) => ({\n error: error.message,\n code: error.errorCode,\n timestamp: new Date().toISOString(),\n});\n\n/**\n * Creates a standardized error response body from UploadistaError.\n * Formats core library errors for HTTP responses with optional details.\n *\n * @param error - The UploadistaError to format\n * @returns Standardized error response body with error, code, timestamp, and optional details\n *\n * @example\n * ```typescript\n * import { createUploadistaErrorResponseBody } from \"@uploadista/server\";\n *\n * try {\n * const result = yield* uploadEngine.handleUpload(input);\n * } catch (err) {\n * if (err instanceof UploadistaError) {\n * const errorResponse = createUploadistaErrorResponseBody(err);\n * res.status(400).json(errorResponse);\n * }\n * }\n * ```\n */\nexport const createUploadistaErrorResponseBody = (error: UploadistaError) => {\n const response: {\n error: string;\n code: string;\n timestamp: string;\n details?: unknown;\n } = {\n error: error.body,\n code: error.code,\n timestamp: new Date().toISOString(),\n };\n\n if (error.details !== undefined) {\n response.details = error.details;\n }\n\n return response;\n};\n\n/**\n * Creates a generic error response body for unknown/unexpected errors.\n * Used as a fallback when error type cannot be determined.\n *\n * @param message - Error message to include in response (defaults to \"Internal server error\")\n * @returns Standardized error response body with generic INTERNAL_ERROR code\n *\n * @example\n * ```typescript\n * import { createGenericErrorResponseBody } from \"@uploadista/server\";\n *\n * try {\n * // ... operation\n * } catch (err) {\n * const errorResponse = createGenericErrorResponseBody(\n * err instanceof Error ? err.message : \"Unknown error\"\n * );\n * res.status(500).json(errorResponse);\n * }\n * ```\n */\nexport const createGenericErrorResponseBody = (\n message = \"Internal server error\",\n) => ({\n error: message,\n code: \"INTERNAL_ERROR\",\n timestamp: new Date().toISOString(),\n});\n","/**\n * Authorization Error Types\n *\n * Error classes for permission and authorization failures.\n */\n\nimport { AdapterError } from \"../error-types\";\n\n/**\n * Authorization error - indicates the user lacks required permissions.\n * Returns HTTP 403 Forbidden status.\n *\n * @example\n * ```typescript\n * if (!hasPermission(permissions, \"engine:metrics\")) {\n * throw new AuthorizationError(\"engine:metrics\");\n * }\n * ```\n */\nexport class AuthorizationError extends AdapterError {\n /**\n * The permission that was required but not granted.\n */\n public readonly requiredPermission: string;\n\n constructor(requiredPermission: string, message?: string) {\n super(\n message ?? `Permission denied: ${requiredPermission} required`,\n 403,\n \"PERMISSION_DENIED\",\n );\n this.name = \"AuthorizationError\";\n this.requiredPermission = requiredPermission;\n }\n}\n\n/**\n * Authentication required error - indicates no authentication context.\n * Returns HTTP 401 Unauthorized status.\n *\n * @example\n * ```typescript\n * if (!authContext) {\n * throw new AuthenticationRequiredError();\n * }\n * ```\n */\nexport class AuthenticationRequiredError extends AdapterError {\n constructor(message = \"Authentication required\") {\n super(message, 401, \"AUTHENTICATION_REQUIRED\");\n this.name = \"AuthenticationRequiredError\";\n }\n}\n\n/**\n * Organization mismatch error - indicates accessing a resource from another organization.\n * Returns HTTP 403 Forbidden status.\n *\n * @example\n * ```typescript\n * if (resource.organizationId !== clientId) {\n * throw new OrganizationMismatchError();\n * }\n * ```\n */\nexport class OrganizationMismatchError extends AdapterError {\n constructor(\n message = \"Access denied: resource belongs to another organization\",\n ) {\n super(message, 403, \"ORGANIZATION_MISMATCH\");\n this.name = \"OrganizationMismatchError\";\n }\n}\n\n/**\n * Quota exceeded error - indicates usage quota has been exceeded.\n * Returns HTTP 402 Payment Required status.\n *\n * @example\n * ```typescript\n * if (usage > quota) {\n * throw new QuotaExceededError(\"Storage quota exceeded\");\n * }\n * ```\n */\nexport class QuotaExceededError extends AdapterError {\n constructor(message = \"Quota exceeded\", code = \"QUOTA_EXCEEDED\") {\n super(message, 402, code);\n this.name = \"QuotaExceededError\";\n }\n}\n\n/**\n * Creates a standardized error response body for AuthorizationError.\n * Includes the required permission in the response.\n *\n * @param error - The AuthorizationError to format\n * @returns Standardized error response body\n */\nexport const createAuthorizationErrorResponseBody = (\n error: AuthorizationError,\n) => ({\n error: error.message,\n code: error.errorCode,\n requiredPermission: error.requiredPermission,\n timestamp: new Date().toISOString(),\n});\n","/**\n * Permission Types and Constants\n *\n * Defines the permission model for fine-grained access control in the uploadista engine.\n * Permissions follow a hierarchical format: `resource:action` with support for wildcards.\n */\n\n// ============================================================================\n// Engine Permissions - Admin operations\n// ============================================================================\n\n/**\n * Engine permissions for administrative operations.\n * These control access to health, readiness, metrics, and DLQ endpoints.\n */\nexport const ENGINE_PERMISSIONS = {\n /** Full admin access to all engine operations */\n ALL: \"engine:*\",\n /** Access health endpoint */\n HEALTH: \"engine:health\",\n /** Access readiness endpoint */\n READINESS: \"engine:readiness\",\n /** Access metrics endpoint */\n METRICS: \"engine:metrics\",\n /** Full DLQ access (implies read and write) */\n DLQ: \"engine:dlq\",\n /** Read DLQ entries */\n DLQ_READ: \"engine:dlq:read\",\n /** Retry/delete DLQ entries */\n DLQ_WRITE: \"engine:dlq:write\",\n} as const;\n\n// ============================================================================\n// Flow Permissions - Flow execution operations\n// ============================================================================\n\n/**\n * Flow permissions for flow execution operations.\n */\nexport const FLOW_PERMISSIONS = {\n /** Full access to all flow operations */\n ALL: \"flow:*\",\n /** Execute flows */\n EXECUTE: \"flow:execute\",\n /** Cancel running flows */\n CANCEL: \"flow:cancel\",\n /** Check flow status */\n STATUS: \"flow:status\",\n} as const;\n\n// ============================================================================\n// Upload Permissions - File upload operations\n// ============================================================================\n\n/**\n * Upload permissions for file upload operations.\n */\nexport const UPLOAD_PERMISSIONS = {\n /** Full access to all upload operations */\n ALL: \"upload:*\",\n /** Create uploads */\n CREATE: \"upload:create\",\n /** Read upload status */\n READ: \"upload:read\",\n /** Cancel uploads */\n CANCEL: \"upload:cancel\",\n} as const;\n\n// ============================================================================\n// Combined Permissions Object\n// ============================================================================\n\n/**\n * All available permissions organized by category.\n *\n * @example\n * ```typescript\n * import { PERMISSIONS } from \"@uploadista/server\";\n *\n * const adminPermissions = [PERMISSIONS.ENGINE.ALL];\n * const userPermissions = [PERMISSIONS.FLOW.ALL, PERMISSIONS.UPLOAD.ALL];\n * ```\n */\nexport const PERMISSIONS = {\n ENGINE: ENGINE_PERMISSIONS,\n FLOW: FLOW_PERMISSIONS,\n UPLOAD: UPLOAD_PERMISSIONS,\n} as const;\n\n// ============================================================================\n// Permission Type Definitions\n// ============================================================================\n\n/** All engine permission strings */\nexport type EnginePermission =\n (typeof ENGINE_PERMISSIONS)[keyof typeof ENGINE_PERMISSIONS];\n\n/** All flow permission strings */\nexport type FlowPermission =\n (typeof FLOW_PERMISSIONS)[keyof typeof FLOW_PERMISSIONS];\n\n/** All upload permission strings */\nexport type UploadPermission =\n (typeof UPLOAD_PERMISSIONS)[keyof typeof UPLOAD_PERMISSIONS];\n\n/**\n * Union type of all valid permission strings.\n * Includes standard permissions and allows custom permissions via string.\n */\nexport type Permission =\n | EnginePermission\n | FlowPermission\n | UploadPermission\n | (string & {}); // Allow custom permissions while maintaining autocomplete\n\n/**\n * Predefined permission sets for common use cases.\n */\nexport const PERMISSION_SETS = {\n /** Full admin access - all engine, flow, and upload permissions */\n ADMIN: [ENGINE_PERMISSIONS.ALL] as const,\n\n /** Organization owner - all flow and upload permissions */\n ORGANIZATION_OWNER: [FLOW_PERMISSIONS.ALL, UPLOAD_PERMISSIONS.ALL] as const,\n\n /** Organization member - same as owner for now */\n ORGANIZATION_MEMBER: [FLOW_PERMISSIONS.ALL, UPLOAD_PERMISSIONS.ALL] as const,\n\n /** API key - limited to execute flows and create uploads */\n API_KEY: [FLOW_PERMISSIONS.EXECUTE, UPLOAD_PERMISSIONS.CREATE] as const,\n} as const;\n\n/**\n * Hierarchical permission relationships.\n * When a parent permission is granted, all child permissions are implied.\n */\nexport const PERMISSION_HIERARCHY: Record<string, readonly string[]> = {\n [ENGINE_PERMISSIONS.DLQ]: [\n ENGINE_PERMISSIONS.DLQ_READ,\n ENGINE_PERMISSIONS.DLQ_WRITE,\n ],\n} as const;\n","/**\n * Permission Matching Logic\n *\n * Implements permission matching with support for:\n * - Exact match: `engine:health` matches `engine:health`\n * - Wildcard match: `engine:*` matches `engine:health`, `engine:metrics`, etc.\n * - Hierarchical match: `engine:dlq` implies `engine:dlq:read` and `engine:dlq:write`\n */\n\nimport { PERMISSION_HIERARCHY } from \"./types\";\n\n/**\n * Checks if a granted permission matches a required permission.\n *\n * @param granted - The permission that has been granted to the user\n * @param required - The permission that is required for the operation\n * @returns true if the granted permission satisfies the required permission\n *\n * @example\n * ```typescript\n * matchesPermission(\"engine:*\", \"engine:health\") // true (wildcard)\n * matchesPermission(\"engine:health\", \"engine:health\") // true (exact)\n * matchesPermission(\"engine:dlq\", \"engine:dlq:read\") // true (hierarchical)\n * matchesPermission(\"flow:execute\", \"engine:health\") // false\n * ```\n */\nexport const matchesPermission = (\n granted: string,\n required: string,\n): boolean => {\n // Exact match\n if (granted === required) {\n return true;\n }\n\n // Wildcard match: `engine:*` matches `engine:health`\n if (granted.endsWith(\":*\")) {\n const prefix = granted.slice(0, -1); // Remove the `*`, keep the `:`\n if (required.startsWith(prefix)) {\n return true;\n }\n }\n\n // Hierarchical match: `engine:dlq` implies `engine:dlq:read`\n const impliedPermissions = PERMISSION_HIERARCHY[granted];\n if (impliedPermissions?.includes(required)) {\n return true;\n }\n\n return false;\n};\n\n/**\n * Checks if any of the granted permissions satisfy the required permission.\n *\n * @param grantedPermissions - Array of permissions granted to the user\n * @param required - The permission that is required for the operation\n * @returns true if any granted permission satisfies the required permission\n *\n * @example\n * ```typescript\n * hasPermission([\"flow:*\", \"upload:create\"], \"flow:execute\") // true\n * hasPermission([\"upload:create\"], \"flow:execute\") // false\n * ```\n */\nexport const hasPermission = (\n grantedPermissions: readonly string[],\n required: string,\n): boolean => {\n return grantedPermissions.some((granted) =>\n matchesPermission(granted, required),\n );\n};\n\n/**\n * Checks if any of the granted permissions satisfy any of the required permissions.\n *\n * @param grantedPermissions - Array of permissions granted to the user\n * @param requiredPermissions - Array of permissions, any of which would be sufficient\n * @returns true if any granted permission satisfies any required permission\n *\n * @example\n * ```typescript\n * hasAnyPermission([\"upload:create\"], [\"flow:execute\", \"upload:create\"]) // true\n * hasAnyPermission([\"upload:read\"], [\"flow:execute\", \"upload:create\"]) // false\n * ```\n */\nexport const hasAnyPermission = (\n grantedPermissions: readonly string[],\n requiredPermissions: readonly string[],\n): boolean => {\n return requiredPermissions.some((required) =>\n hasPermission(grantedPermissions, required),\n );\n};\n\n/**\n * Checks if all of the required permissions are satisfied.\n *\n * @param grantedPermissions - Array of permissions granted to the user\n * @param requiredPermissions - Array of permissions, all of which must be satisfied\n * @returns true if all required permissions are satisfied\n *\n * @example\n * ```typescript\n * hasAllPermissions([\"flow:*\", \"upload:*\"], [\"flow:execute\", \"upload:create\"]) // true\n * hasAllPermissions([\"flow:execute\"], [\"flow:execute\", \"upload:create\"]) // false\n * ```\n */\nexport const hasAllPermissions = (\n grantedPermissions: readonly string[],\n requiredPermissions: readonly string[],\n): boolean => {\n return requiredPermissions.every((required) =>\n hasPermission(grantedPermissions, required),\n );\n};\n\n/**\n * Expands a permission to include all implied permissions.\n * Useful for display or audit purposes.\n *\n * @param permission - The permission to expand\n * @returns Array of the permission and all implied permissions\n *\n * @example\n * ```typescript\n * expandPermission(\"engine:dlq\") // [\"engine:dlq\", \"engine:dlq:read\", \"engine:dlq:write\"]\n * expandPermission(\"engine:health\") // [\"engine:health\"]\n * ```\n */\nexport const expandPermission = (permission: string): string[] => {\n const result = [permission];\n const implied = PERMISSION_HIERARCHY[permission];\n if (implied) {\n result.push(...implied);\n }\n return result;\n};\n","import { Context, Effect, Layer } from \"effect\";\nimport {\n AuthenticationRequiredError,\n AuthorizationError,\n} from \"./permissions/errors\";\nimport {\n hasAnyPermission as matchHasAnyPermission,\n hasPermission as matchHasPermission,\n} from \"./permissions/matcher\";\nimport type { AuthContext } from \"./types\";\n\n/**\n * Authentication Context Service\n *\n * Provides access to the current authentication context throughout\n * the upload and flow processing pipeline. The service is provided\n * via Effect Layer and can be accessed using Effect.service().\n *\n * @example\n * ```typescript\n * import { Effect } from \"effect\";\n * import { AuthContextService } from \"@uploadista/server\";\n *\n * const uploadHandler = Effect.gen(function* () {\n * const authService = yield* AuthContextService;\n * const clientId = yield* authService.getClientId();\n * if (clientId) {\n * console.log(`Processing upload for client: ${clientId}`);\n * }\n * });\n * ```\n */\nexport class AuthContextService extends Context.Tag(\"AuthContextService\")<\n AuthContextService,\n {\n /**\n * Get the current client ID from auth context.\n * Returns null if no authentication context is available.\n */\n readonly getClientId: () => Effect.Effect<string | null>;\n\n /**\n * Get the current auth metadata.\n * Returns empty object if no authentication context or no metadata.\n */\n readonly getMetadata: () => Effect.Effect<Record<string, unknown>>;\n\n /**\n * Check if the current client has a specific permission.\n * Supports exact match, wildcard match, and hierarchical match.\n * Returns false if no authentication context or permission not found.\n *\n * @example\n * ```typescript\n * // Exact match\n * yield* authService.hasPermission(\"engine:health\")\n *\n * // Wildcard: user with \"engine:*\" will match \"engine:health\"\n * yield* authService.hasPermission(\"engine:health\")\n *\n * // Hierarchical: user with \"engine:dlq\" will match \"engine:dlq:read\"\n * yield* authService.hasPermission(\"engine:dlq:read\")\n * ```\n */\n readonly hasPermission: (permission: string) => Effect.Effect<boolean>;\n\n /**\n * Check if the current client has any of the specified permissions.\n * Returns true if at least one permission is granted.\n */\n readonly hasAnyPermission: (\n permissions: readonly string[],\n ) => Effect.Effect<boolean>;\n\n /**\n * Require a specific permission, failing with AuthorizationError if not granted.\n * Use this when you want to fail fast on missing permissions.\n *\n * @throws AuthorizationError if permission is not granted\n * @throws AuthenticationRequiredError if no auth context\n *\n * @example\n * ```typescript\n * const protectedHandler = Effect.gen(function* () {\n * const authService = yield* AuthContextService;\n * yield* authService.requirePermission(\"engine:metrics\");\n * // Only reaches here if permission is granted\n * return yield* getMetrics();\n * });\n * ```\n */\n readonly requirePermission: (\n permission: string,\n ) => Effect.Effect<void, AuthorizationError | AuthenticationRequiredError>;\n\n /**\n * Require authentication, failing with AuthenticationRequiredError if not authenticated.\n *\n * @throws AuthenticationRequiredError if no auth context\n */\n readonly requireAuthentication: () => Effect.Effect<\n AuthContext,\n AuthenticationRequiredError\n >;\n\n /**\n * Get all permissions granted to the current client.\n * Returns empty array if no authentication context or no permissions.\n */\n readonly getPermissions: () => Effect.Effect<readonly string[]>;\n\n /**\n * Get the full authentication context if available.\n * Returns null if no authentication context is available.\n */\n readonly getAuthContext: () => Effect.Effect<AuthContext | null>;\n }\n>() {}\n\n/**\n * Options for creating an AuthContextService Layer.\n */\nexport interface AuthContextServiceOptions {\n /**\n * When true, bypasses all permission checks (grants all permissions).\n * Used when no auth middleware is configured (backward compatibility).\n * @default false\n */\n bypassAuth?: boolean;\n}\n\n/**\n * Creates an AuthContextService Layer from an AuthContext.\n * This is typically called by adapters after successful authentication.\n *\n * @param authContext - The authentication context from middleware\n * @param options - Optional configuration for auth behavior\n * @returns Effect Layer providing AuthContextService\n */\nexport const AuthContextServiceLive = (\n authContext: AuthContext | null,\n options?: AuthContextServiceOptions,\n): Layer.Layer<AuthContextService> => {\n const permissions = authContext?.permissions ?? [];\n const bypassAuth = options?.bypassAuth ?? false;\n\n return Layer.succeed(AuthContextService, {\n getClientId: () => Effect.succeed(authContext?.clientId ?? null),\n\n getMetadata: () => Effect.succeed(authContext?.metadata ?? {}),\n\n hasPermission: (permission: string) =>\n bypassAuth\n ? Effect.succeed(true)\n : Effect.succeed(matchHasPermission(permissions, permission)),\n\n hasAnyPermission: (requiredPermissions: readonly string[]) =>\n bypassAuth\n ? Effect.succeed(true)\n : Effect.succeed(\n matchHasAnyPermission(permissions, requiredPermissions),\n ),\n\n requirePermission: (permission: string) =>\n Effect.gen(function* () {\n // If bypass mode is enabled, grant all permissions\n if (bypassAuth) {\n yield* Effect.logDebug(\n `[Auth] Bypass mode: permission '${permission}' auto-granted`,\n );\n return;\n }\n if (!authContext) {\n yield* Effect.logDebug(\n `[Auth] Permission check failed: authentication required for '${permission}'`,\n );\n return yield* Effect.fail(new AuthenticationRequiredError());\n }\n if (!matchHasPermission(permissions, permission)) {\n yield* Effect.logDebug(\n `[Auth] Permission denied: '${permission}' for client '${authContext.clientId}'`,\n );\n return yield* Effect.fail(new AuthorizationError(permission));\n }\n yield* Effect.logDebug(\n `[Auth] Permission granted: '${permission}' for client '${authContext.clientId}'`,\n );\n }),\n\n requireAuthentication: () =>\n authContext\n ? Effect.succeed(authContext)\n : Effect.fail(new AuthenticationRequiredError()),\n\n getPermissions: () => Effect.succeed(permissions),\n\n getAuthContext: () => Effect.succeed(authContext),\n });\n};\n\n/**\n * No-auth implementation of AuthContextService.\n * Returns null/empty values for all operations.\n * Used when no authentication middleware is configured (backward compatibility).\n */\nexport const NoAuthContextServiceLive: Layer.Layer<AuthContextService> =\n AuthContextServiceLive(null);\n","/**\n * Usage Hook Types\n *\n * Types for lifecycle hooks that fire during upload and flow processing.\n * Used for usage tracking, quota enforcement, and billing integration.\n */\n\nimport type { Effect } from \"effect\";\n\n// ============================================================================\n// Usage Hook Result Types\n// ============================================================================\n\n/**\n * Result of a usage hook that can abort processing.\n * Used by onUploadStart and onFlowStart hooks.\n */\nexport type UsageHookResult =\n | { readonly action: \"continue\" }\n | {\n readonly action: \"abort\";\n readonly reason: string;\n readonly code?: string;\n };\n\n/**\n * Helper to create a continue result.\n */\nexport const continueResult = (): UsageHookResult => ({ action: \"continue\" });\n\n/**\n * Helper to create an abort result.\n */\nexport const abortResult = (\n reason: string,\n code?: string,\n): UsageHookResult => ({\n action: \"abort\",\n reason,\n code,\n});\n\n// ============================================================================\n// Usage Context Types\n// ============================================================================\n\n/**\n * Base metadata shared across all usage contexts.\n */\nexport interface BaseUsageMetadata {\n /** File size in bytes (if known) */\n fileSize?: number;\n /** MIME type of the file */\n mimeType?: string;\n /** Original file name */\n fileName?: string;\n}\n\n/**\n * Metadata specific to upload operations.\n */\nexport interface UploadUsageMetadata extends BaseUsageMetadata {\n /** Unique upload identifier */\n uploadId?: string;\n /** Duration of the upload in milliseconds */\n duration?: number;\n}\n\n/**\n * Metadata specific to flow operations.\n */\nexport interface FlowUsageMetadata extends BaseUsageMetadata {\n /** Flow identifier being executed */\n flowId?: string;\n /** Unique job identifier */\n jobId?: string;\n /** Number of nodes in the flow */\n nodeCount?: number;\n /** Total size of input files */\n inputFileSize?: number;\n /** Total size of output files (on complete) */\n outputSize?: number;\n /** Number of nodes that were executed (on complete) */\n nodesExecuted?: number;\n /** Duration of flow execution in milliseconds (on complete) */\n duration?: number;\n /** Flow completion status (on complete) */\n status?: \"success\" | \"failed\" | \"cancelled\";\n}\n\n/**\n * Context passed to usage hooks containing client and operation information.\n */\nexport interface UsageContext<\n TMetadata extends BaseUsageMetadata = BaseUsageMetadata,\n> {\n /** Organization/client identifier */\n clientId: string;\n /** Type of operation */\n operation: \"upload\" | \"flow\";\n /** Operation-specific metadata */\n metadata: TMetadata;\n}\n\n/**\n * Upload-specific usage context.\n */\nexport type UploadUsageContext = UsageContext<UploadUsageMetadata>;\n\n/**\n * Flow-specific usage context.\n */\nexport type FlowUsageContext = UsageContext<FlowUsageMetadata>;\n\n// ============================================================================\n// Usage Hook Function Types\n// ============================================================================\n\n/**\n * Hook called before upload processing begins.\n * Can return abort to reject the upload (e.g., quota exceeded).\n */\nexport type OnUploadStartHook = (\n ctx: UploadUsageContext,\n) => Effect.Effect<UsageHookResult>;\n\n/**\n * Hook called after upload completes successfully.\n * Used for recording usage. Errors are logged but don't fail the upload.\n */\nexport type OnUploadCompleteHook = (\n ctx: UploadUsageContext,\n) => Effect.Effect<void>;\n\n/**\n * Hook called before flow execution begins.\n * Can return abort to reject the flow (e.g., subscription expired).\n */\nexport type OnFlowStartHook = (\n ctx: FlowUsageContext,\n) => Effect.Effect<UsageHookResult>;\n\n/**\n * Hook called after flow completes (success, failure, or cancellation).\n * Used for recording usage. Errors are logged but don't fail the response.\n */\nexport type OnFlowCompleteHook = (ctx: FlowUsageContext) => Effect.Effect<void>;\n\n// ============================================================================\n// Usage Hooks Configuration\n// ============================================================================\n\n/**\n * Configuration for usage tracking hooks.\n * All hooks are optional - unconfigured hooks are no-ops.\n *\n * @example\n * ```typescript\n * const usageHooks: UsageHooks = {\n * onUploadStart: (ctx) => Effect.gen(function* () {\n * const hasQuota = yield* checkQuota(ctx.clientId, ctx.metadata.fileSize);\n * if (!hasQuota) {\n * return abortResult(\"Storage quota exceeded\", \"QUOTA_EXCEEDED\");\n * }\n * return continueResult();\n * }),\n * onUploadComplete: (ctx) => Effect.gen(function* () {\n * yield* recordUsage(ctx.clientId, ctx.metadata.fileSize);\n * }),\n * };\n * ```\n */\nexport interface UsageHooks {\n /**\n * Called before upload processing begins.\n * Return abort to reject the upload.\n */\n onUploadStart?: OnUploadStartHook;\n\n /**\n * Called after upload completes successfully.\n * Errors are logged but don't fail the upload.\n */\n onUploadComplete?: OnUploadCompleteHook;\n\n /**\n * Called before flow execution begins.\n * Return abort to reject the flow.\n */\n onFlowStart?: OnFlowStartHook;\n\n /**\n * Called after flow completes (success, failure, or cancellation).\n * Errors are logged but don't fail the response.\n */\n onFlowComplete?: OnFlowCompleteHook;\n}\n\n/**\n * Configuration for the usage hook service.\n */\nexport interface UsageHookConfig {\n /**\n * The usage hooks to execute.\n */\n hooks?: UsageHooks;\n\n /**\n * Timeout for hook execution in milliseconds.\n * If a hook takes longer than this, it will be considered failed.\n * Default: 5000ms (5 seconds)\n */\n timeout?: number;\n}\n\n/**\n * Default timeout for usage hooks (5 seconds).\n */\nexport const DEFAULT_USAGE_HOOK_TIMEOUT = 5000;\n","/**\n * Usage Hook Service\n *\n * Effect service for executing usage tracking hooks with timeout handling.\n */\n\nimport { Context, Effect, Layer } from \"effect\";\nimport type {\n FlowUsageContext,\n UploadUsageContext,\n UsageHookConfig,\n UsageHookResult,\n} from \"./types\";\nimport { continueResult, DEFAULT_USAGE_HOOK_TIMEOUT } from \"./types\";\n\n/**\n * Usage Hook Service\n *\n * Provides methods to execute usage hooks during upload and flow processing.\n * Handles timeout and error recovery gracefully.\n */\nexport class UsageHookService extends Context.Tag(\"UsageHookService\")<\n UsageHookService,\n {\n /**\n * Execute onUploadStart hook if configured.\n * Returns continue result if no hook is configured or on error/timeout.\n */\n readonly onUploadStart: (\n ctx: UploadUsageContext,\n ) => Effect.Effect<UsageHookResult>;\n\n /**\n * Execute onUploadComplete hook if configured.\n * Errors are logged but swallowed (fire-and-forget).\n */\n readonly onUploadComplete: (ctx: UploadUsageContext) => Effect.Effect<void>;\n\n /**\n * Execute onFlowStart hook if configured.\n * Returns continue result if no hook is configured or on error/timeout.\n */\n readonly onFlowStart: (\n ctx: FlowUsageContext,\n ) => Effect.Effect<UsageHookResult>;\n\n /**\n * Execute onFlowComplete hook if configured.\n * Errors are logged but swallowed (fire-and-forget).\n */\n readonly onFlowComplete: (ctx: FlowUsageContext) => Effect.Effect<void>;\n }\n>() {}\n\n/**\n * Creates a UsageHookService Layer from configuration.\n *\n * @param config - Usage hook configuration with optional hooks and timeout\n * @returns Effect Layer providing UsageHookService\n */\nexport const UsageHookServiceLive = (\n config?: UsageHookConfig,\n): Layer.Layer<UsageHookService> => {\n const hooks = config?.hooks;\n const timeout = config?.timeout ?? DEFAULT_USAGE_HOOK_TIMEOUT;\n\n return Layer.succeed(UsageHookService, {\n onUploadStart: (ctx: UploadUsageContext) => {\n if (!hooks?.onUploadStart) {\n return Effect.succeed(continueResult());\n }\n\n return hooks.onUploadStart(ctx).pipe(\n // Add timeout - proceed on timeout (fail-open)\n Effect.timeout(timeout),\n Effect.map((result) => result ?? continueResult()),\n // On any error, log and continue (fail-open for availability)\n Effect.catchAll((error) =>\n Effect.gen(function* () {\n yield* Effect.logWarning(\n `onUploadStart hook failed: ${error}. Proceeding with upload.`,\n );\n return continueResult();\n }),\n ),\n );\n },\n\n onUploadComplete: (ctx: UploadUsageContext) => {\n if (!hooks?.onUploadComplete) {\n return Effect.void;\n }\n\n return hooks.onUploadComplete(ctx).pipe(\n // Add timeout\n Effect.timeout(timeout),\n Effect.asVoid,\n // On any error, just log (fire-and-forget)\n Effect.catchAll((error) =>\n Effect.logWarning(\n `onUploadComplete hook failed: ${error}. Upload already completed.`,\n ),\n ),\n );\n },\n\n onFlowStart: (ctx: FlowUsageContext) => {\n if (!hooks?.onFlowStart) {\n return Effect.succeed(continueResult());\n }\n\n return hooks.onFlowStart(ctx).pipe(\n // Add timeout - proceed on timeout (fail-open)\n Effect.timeout(timeout),\n Effect.map((result) => result ?? continueResult()),\n // On any error, log and continue (fail-open for availability)\n Effect.catchAll((error) =>\n Effect.gen(function* () {\n yield* Effect.logWarning(\n `onFlowStart hook failed: ${error}. Proceeding with flow.`,\n );\n return continueResult();\n }),\n ),\n );\n },\n\n onFlowComplete: (ctx: FlowUsageContext) => {\n if (!hooks?.onFlowComplete) {\n return Effect.void;\n }\n\n return hooks.onFlowComplete(ctx).pipe(\n // Add timeout\n Effect.timeout(timeout),\n Effect.asVoid,\n // On any error, just log (fire-and-forget)\n Effect.catchAll((error) =>\n Effect.logWarning(\n `onFlowComplete hook failed: ${error}. Flow already completed.`,\n ),\n ),\n );\n },\n });\n};\n\n/**\n * No-op implementation of UsageHookService.\n * All hooks are no-ops that return continue/void.\n * Used when no usage hooks are configured (default backward compatibility).\n */\nexport const NoUsageHookServiceLive: Layer.Layer<UsageHookService> =\n UsageHookServiceLive();\n","import { DeadLetterQueueService } from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport { PERMISSIONS } from \"../../permissions/types\";\nimport { AuthContextService } from \"../../service\";\nimport type {\n DlqCleanupRequest,\n DlqCleanupResponse,\n DlqDeleteRequest,\n DlqDeleteResponse,\n DlqGetRequest,\n DlqGetResponse,\n DlqListRequest,\n DlqListResponse,\n DlqResolveRequest,\n DlqResolveResponse,\n DlqRetryAllRequest,\n DlqRetryAllResponse,\n DlqRetryRequest,\n DlqRetryResponse,\n DlqStatsRequest,\n DlqStatsResponse,\n} from \"../routes\";\n\n/**\n * Handle GET /api/dlq - List DLQ items\n */\nexport const handleDlqList = (req: DlqListRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for reading DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_READ);\n\n const dlq = yield* DeadLetterQueueService;\n const result = yield* dlq.list(req.options);\n\n return {\n type: \"dlq-list\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: result,\n } satisfies DlqListResponse;\n });\n\n/**\n * Handle GET /api/dlq/:itemId - Get a specific DLQ item\n */\nexport const handleDlqGet = (req: DlqGetRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for reading DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_READ);\n\n const dlq = yield* DeadLetterQueueService;\n const item = yield* dlq.get(req.itemId);\n\n return {\n type: \"dlq-get\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: item,\n } satisfies DlqGetResponse;\n });\n\n/**\n * Handle POST /api/dlq/:itemId/retry - Retry a specific DLQ item\n */\nexport const handleDlqRetry = (req: DlqRetryRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for writing to DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_WRITE);\n\n const dlq = yield* DeadLetterQueueService;\n\n // Mark item as retrying\n yield* dlq.markRetrying(req.itemId);\n\n // TODO: Implement actual retry logic by re-executing the flow\n // This would require access to FlowServer and the original job context\n // For now, we just mark it as retrying and return success\n // The actual retry would be handled by a background scheduler\n\n return {\n type: \"dlq-retry\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: { success: true },\n } satisfies DlqRetryResponse;\n });\n\n/**\n * Handle POST /api/dlq/retry-all - Retry all matching DLQ items\n */\nexport const handleDlqRetryAll = (req: DlqRetryAllRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for writing to DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_WRITE);\n\n const dlq = yield* DeadLetterQueueService;\n\n // List items matching the filter\n const { items } = yield* dlq.list({\n status: req.options?.status,\n flowId: req.options?.flowId,\n });\n\n let succeeded = 0;\n let failed = 0;\n\n // Mark each item for retry\n for (const item of items) {\n const result = yield* Effect.either(dlq.markRetrying(item.id));\n if (result._tag === \"Right\") {\n succeeded++;\n } else {\n failed++;\n }\n }\n\n return {\n type: \"dlq-retry-all\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: {\n retried: items.length,\n succeeded,\n failed,\n },\n } satisfies DlqRetryAllResponse;\n });\n\n/**\n * Handle DELETE /api/dlq/:itemId - Delete a DLQ item\n */\nexport const handleDlqDelete = (req: DlqDeleteRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for writing to DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_WRITE);\n\n const dlq = yield* DeadLetterQueueService;\n yield* dlq.delete(req.itemId);\n\n return {\n type: \"dlq-delete\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: { success: true },\n } satisfies DlqDeleteResponse;\n });\n\n/**\n * Handle POST /api/dlq/:itemId/resolve - Manually resolve a DLQ item\n */\nexport const handleDlqResolve = (req: DlqResolveRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for writing to DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_WRITE);\n\n const dlq = yield* DeadLetterQueueService;\n const item = yield* dlq.markResolved(req.itemId);\n\n return {\n type: \"dlq-resolve\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: item,\n } satisfies DlqResolveResponse;\n });\n\n/**\n * Handle POST /api/dlq/cleanup - Cleanup old DLQ items\n */\nexport const handleDlqCleanup = (req: DlqCleanupRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for writing to DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_WRITE);\n\n const dlq = yield* DeadLetterQueueService;\n const result = yield* dlq.cleanup(req.options);\n\n return {\n type: \"dlq-cleanup\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: result,\n } satisfies DlqCleanupResponse;\n });\n\n/**\n * Handle GET /api/dlq/stats - Get DLQ statistics\n */\nexport const handleDlqStats = (_req: DlqStatsRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for reading DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_READ);\n\n const dlq = yield* DeadLetterQueueService;\n const stats = yield* dlq.getStats();\n\n return {\n type: \"dlq-stats\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: stats,\n } satisfies DlqStatsResponse;\n });\n","import { FlowEngine } from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport { AuthCacheService } from \"../../cache\";\nimport { QuotaExceededError } from \"../../permissions/errors\";\nimport { PERMISSIONS } from \"../../permissions/types\";\nimport { AuthContextService } from \"../../service\";\nimport { UsageHookService } from \"../../usage-hooks/service\";\nimport type {\n CancelFlowRequest,\n CancelFlowResponse,\n GetFlowRequest,\n GetFlowResponse,\n GetJobStatusRequest,\n GetJobStatusResponse,\n PauseFlowRequest,\n PauseFlowResponse,\n ResumeFlowRequest,\n ResumeFlowResponse,\n RunFlowRequest,\n RunFlowResponse,\n} from \"../routes\";\n\nexport const handleGetFlow = ({ flowId }: GetFlowRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const clientId = yield* authService.getClientId();\n\n // Check permission for reading flow status\n yield* authService.requirePermission(PERMISSIONS.FLOW.STATUS);\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Getting flow data: ${flowId}, client: ${clientId}`,\n );\n }\n\n const flowData = yield* flowEngine.getFlowData(flowId, clientId);\n\n return {\n status: 200,\n body: flowData,\n } as GetFlowResponse;\n });\n};\n\nexport const handleRunFlow = <TRequirements>({\n flowId,\n storageId,\n inputs,\n}: RunFlowRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n const usageHookService = yield* UsageHookService;\n const clientId = yield* authService.getClientId();\n\n // Check permission for executing flows\n yield* authService.requirePermission(PERMISSIONS.FLOW.EXECUTE);\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Executing flow: ${flowId}, storage: ${storageId}, client: ${clientId}`,\n );\n yield* Effect.logInfo(JSON.stringify(inputs, null, 2));\n } else {\n yield* Effect.logInfo(\n `[Flow] Executing flow: ${flowId}, storage: ${storageId}`,\n );\n yield* Effect.logInfo(\n `[Flow] Inputs: ${JSON.stringify(inputs, null, 2)}`,\n );\n }\n\n // Execute onFlowStart hook for quota checking\n if (clientId) {\n const hookResult = yield* usageHookService.onFlowStart({\n clientId,\n operation: \"flow\",\n metadata: {\n flowId,\n },\n });\n\n if (hookResult.action === \"abort\") {\n return yield* Effect.fail(\n new QuotaExceededError(\n hookResult.reason,\n hookResult.code ?? \"SUBSCRIPTION_REQUIRED\",\n ),\n );\n }\n }\n\n // Run flow returns immediately with jobId\n const startTime = Date.now();\n yield* Effect.logInfo(`[Flow] Calling flowServer.runFlow...`);\n const result = yield* flowEngine\n .runFlow<TRequirements>({\n flowId,\n storageId,\n clientId,\n inputs,\n })\n .pipe(\n Effect.tap(() =>\n Effect.logInfo(`[Flow] runFlow completed successfully`),\n ),\n Effect.tapError((error) =>\n Effect.logError(`[Flow] runFlow failed with error: ${error}`),\n ),\n );\n\n // Cache auth context for subsequent flow operations (continue, status)\n const authContext = yield* authService.getAuthContext();\n if (authContext) {\n yield* authCache.set(result.id, authContext);\n }\n\n yield* Effect.logInfo(`[Flow] Flow started with jobId: ${result.id}`);\n\n // Note: onFlowComplete hook is called when the flow actually completes,\n // which happens asynchronously. The hook should be invoked from the\n // flow execution pipeline, not here where we just start the flow.\n\n return {\n status: 200,\n body: result,\n } as RunFlowResponse;\n });\n};\n\nexport const handleJobStatus = ({ jobId }: GetJobStatusRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n const clientId = yield* authService.getClientId();\n\n // Check permission for checking flow status\n yield* authService.requirePermission(PERMISSIONS.FLOW.STATUS);\n\n if (!jobId) {\n throw new Error(\"No job id\");\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Getting job status: ${jobId}, client: ${clientId}`,\n );\n }\n\n const result = yield* flowEngine.getJobStatus(jobId);\n\n // Clear cache if flow is completed or failed\n if (result.status === \"completed\" || result.status === \"failed\") {\n yield* authCache.delete(jobId);\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Flow ${result.status}, cleared auth cache: ${jobId}`,\n );\n }\n }\n\n return {\n status: 200,\n body: result,\n } as GetJobStatusResponse;\n });\n};\n\nexport const handleResumeFlow = <TRequirements>({\n jobId,\n nodeId,\n newData,\n}: ResumeFlowRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n\n // Check permission for executing flows (resume is part of execution)\n yield* authService.requirePermission(PERMISSIONS.FLOW.EXECUTE);\n\n // Try current auth first, fallback to cached auth\n let clientId = yield* authService.getClientId();\n if (!clientId) {\n const cachedAuth = yield* authCache.get(jobId);\n clientId = cachedAuth?.clientId ?? null;\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Continuing flow: jobId=${jobId}, nodeId=${nodeId}, client: ${clientId}`,\n );\n }\n\n if (newData === undefined) {\n throw new Error(\"Missing newData\");\n }\n\n const result = yield* flowEngine.resumeFlow<TRequirements>({\n jobId,\n nodeId,\n newData,\n clientId,\n });\n\n // Clear cache if flow is completed or failed\n if (result.status === \"completed\" || result.status === \"failed\") {\n yield* authCache.delete(jobId);\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Flow ${result.status}, cleared auth cache: ${jobId}`,\n );\n }\n }\n\n return {\n status: 200,\n body: result,\n } as ResumeFlowResponse;\n });\n};\n\nexport const handlePauseFlow = ({ jobId }: PauseFlowRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n\n // Check permission for cancelling flows (pause is related to cancel)\n yield* authService.requirePermission(PERMISSIONS.FLOW.CANCEL);\n\n // Try current auth first, fallback to cached auth\n let clientId = yield* authService.getClientId();\n if (!clientId) {\n const cachedAuth = yield* authCache.get(jobId);\n clientId = cachedAuth?.clientId ?? null;\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Pausing flow: jobId=${jobId}, client: ${clientId}`,\n );\n }\n\n const result = yield* flowEngine.pauseFlow(jobId, clientId);\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Flow paused: ${jobId}, status: ${result.status}`,\n );\n }\n\n return {\n status: 200,\n body: result,\n } as PauseFlowResponse;\n });\n};\n\nexport const handleCancelFlow = ({ jobId }: CancelFlowRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n const usageHookService = yield* UsageHookService;\n\n // Check permission for cancelling flows\n yield* authService.requirePermission(PERMISSIONS.FLOW.CANCEL);\n\n if (!jobId) {\n throw new Error(\"No job id\");\n }\n\n // Try current auth first, fallback to cached auth\n let clientId = yield* authService.getClientId();\n if (!clientId) {\n const cachedAuth = yield* authCache.get(jobId);\n clientId = cachedAuth?.clientId ?? null;\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Cancelling flow: jobId=${jobId}, client: ${clientId}`,\n );\n }\n\n const result = yield* flowEngine.cancelFlow(jobId, clientId);\n\n // Clear cache since flow is cancelled\n yield* authCache.delete(jobId);\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Flow cancelled, cleared auth cache: ${jobId}`,\n );\n\n // Execute onFlowComplete hook for cancelled flow\n yield* Effect.forkDaemon(\n usageHookService.onFlowComplete({\n clientId,\n operation: \"flow\",\n metadata: {\n jobId,\n status: \"cancelled\",\n },\n }),\n );\n }\n\n return {\n status: 200,\n body: result,\n } as CancelFlowResponse;\n });\n};\n","/**\n * Health Check Service for Uploadista SDK.\n *\n * This module provides health checking functionality for:\n * - Storage backends\n * - KV stores\n * - Event broadcasters\n * - Circuit breaker state\n * - Dead letter queue state\n *\n * @module core/health-check-service\n */\n\nimport { DeadLetterQueueService } from \"@uploadista/core/flow\";\nimport type {\n CircuitBreakerHealthSummary,\n ComponentHealth,\n DlqHealthSummary,\n HealthCheckConfig,\n HealthComponents,\n HealthResponse,\n HealthStatus,\n} from \"@uploadista/core/types\";\nimport {\n CircuitBreakerStoreService,\n DEFAULT_HEALTH_CHECK_CONFIG,\n} from \"@uploadista/core/types\";\nimport { Effect, Option } from \"effect\";\n\n// Track server start time for uptime calculation\nconst serverStartTime = Date.now();\n\n/**\n * Gets the server uptime in milliseconds.\n */\nexport function getServerUptime(): number {\n return Date.now() - serverStartTime;\n}\n\n/**\n * Creates a timestamp string in ISO 8601 format.\n */\nexport function getTimestamp(): string {\n return new Date().toISOString();\n}\n\n/**\n * Creates a simple liveness health response.\n *\n * This is used for the `/health` endpoint which should return immediately\n * without checking any dependencies.\n */\nexport function createLivenessResponse(\n config?: HealthCheckConfig,\n): HealthResponse {\n return {\n status: \"healthy\",\n timestamp: getTimestamp(),\n version: config?.version,\n uptime: getServerUptime(),\n };\n}\n\n/**\n * Aggregates component health statuses into an overall status.\n *\n * @param components - The components to aggregate\n * @returns The overall health status\n */\nexport function aggregateHealthStatus(\n components: HealthComponents,\n): HealthStatus {\n const statuses: HealthStatus[] = [];\n\n // Core components (critical for readiness)\n if (components.storage) statuses.push(components.storage.status);\n if (components.kvStore) statuses.push(components.kvStore.status);\n\n // Optional components (don't fail readiness)\n // eventBroadcaster, circuitBreaker, and DLQ being degraded doesn't make system unhealthy\n\n // If any critical component is unhealthy, overall is unhealthy\n if (statuses.includes(\"unhealthy\")) {\n return \"unhealthy\";\n }\n\n // Check if any component (including optional) is degraded\n const allStatuses: HealthStatus[] = [...statuses];\n if (components.eventBroadcaster)\n allStatuses.push(components.eventBroadcaster.status);\n if (components.circuitBreaker)\n allStatuses.push(components.circuitBreaker.status);\n if (components.deadLetterQueue)\n allStatuses.push(components.deadLetterQueue.status);\n\n if (allStatuses.includes(\"degraded\")) {\n return \"degraded\";\n }\n\n return \"healthy\";\n}\n\n/**\n * Checks storage backend health by performing a simple operation.\n *\n * Currently returns healthy as we don't have direct access to storage\n * in health check context. This can be enhanced to do actual connectivity\n * checks when storage service is available.\n */\nexport function checkStorageHealth(\n _config: HealthCheckConfig,\n): Effect.Effect<ComponentHealth, never, never> {\n const startTime = Date.now();\n\n // TODO: When storage service is available in health context,\n // perform actual health check (e.g., list bucket, check credentials)\n // For now, return healthy with a note\n return Effect.succeed({\n status: \"healthy\" as HealthStatus,\n latency: Date.now() - startTime,\n message: \"Storage backend configured\",\n lastCheck: getTimestamp(),\n });\n}\n\n/**\n * Checks KV store health by performing a simple operation.\n *\n * Currently returns healthy as we don't have direct access to KV store\n * in health check context. This can be enhanced to do actual connectivity\n * checks when KV store service is available.\n */\nexport function checkKvStoreHealth(\n _config: HealthCheckConfig,\n): Effect.Effect<ComponentHealth, never, never> {\n const startTime = Date.now();\n\n // TODO: When KV store service is available in health context,\n // perform actual health check (e.g., get/set test key)\n // For now, return healthy with a note\n return Effect.succeed({\n status: \"healthy\" as HealthStatus,\n latency: Date.now() - startTime,\n message: \"KV store configured\",\n lastCheck: getTimestamp(),\n });\n}\n\n/**\n * Checks event broadcaster health.\n *\n * Currently returns healthy as we don't have direct access to event broadcaster\n * in health check context.\n */\nexport function checkEventBroadcasterHealth(\n _config: HealthCheckConfig,\n): Effect.Effect<ComponentHealth, never, never> {\n const startTime = Date.now();\n\n // TODO: When event broadcaster service is available in health context,\n // perform actual health check\n return Effect.succeed({\n status: \"healthy\" as HealthStatus,\n latency: Date.now() - startTime,\n message: \"Event broadcaster configured\",\n lastCheck: getTimestamp(),\n });\n}\n\n/**\n * Gets circuit breaker health summary from the circuit breaker store.\n *\n * Uses the optional service pattern to check if circuit breaker is available.\n */\nexport function getCircuitBreakerSummary(): Effect.Effect<\n CircuitBreakerHealthSummary | undefined,\n never,\n never\n> {\n return Effect.gen(function* () {\n const cbStoreOption = yield* Effect.serviceOption(\n CircuitBreakerStoreService,\n );\n\n if (Option.isNone(cbStoreOption)) {\n // Circuit breaker not configured\n return undefined;\n }\n\n const cbStore = cbStoreOption.value;\n const statsResult = yield* Effect.either(cbStore.getAllStats());\n\n if (statsResult._tag === \"Left\") {\n // Error getting stats - return degraded status\n return {\n status: \"degraded\" as HealthStatus,\n openCircuits: 0,\n totalCircuits: 0,\n };\n }\n\n const stats = statsResult.right;\n const circuits = Array.from(stats.values());\n const openCircuits = circuits.filter((c) => c.state === \"open\").length;\n const totalCircuits = circuits.length;\n\n // Determine status based on open circuits\n let status: HealthStatus = \"healthy\";\n if (openCircuits > 0) {\n status = \"degraded\";\n }\n\n return {\n status,\n openCircuits,\n totalCircuits,\n circuits: circuits.map((c) => ({\n nodeType: c.nodeType,\n state: c.state,\n failureCount: c.failureCount,\n timeSinceLastStateChange: c.timeSinceLastStateChange,\n })),\n };\n });\n}\n\n/**\n * Gets dead letter queue health summary from the DLQ service.\n *\n * Uses the optional service pattern to check if DLQ is available.\n */\nexport function getDlqSummary(): Effect.Effect<\n DlqHealthSummary | undefined,\n never,\n never\n> {\n return Effect.gen(function* () {\n const dlqOption = yield* DeadLetterQueueService.optional;\n\n if (Option.isNone(dlqOption)) {\n // DLQ not configured\n return undefined;\n }\n\n const dlq = dlqOption.value;\n const statsResult = yield* Effect.either(dlq.getStats());\n\n if (statsResult._tag === \"Left\") {\n // Error getting stats - return degraded status\n return {\n status: \"degraded\" as HealthStatus,\n pendingItems: 0,\n exhaustedItems: 0,\n };\n }\n\n const stats = statsResult.right;\n\n // Determine status based on exhausted items\n let status: HealthStatus = \"healthy\";\n if (stats.byStatus.exhausted > 0) {\n status = \"degraded\";\n }\n\n return {\n status,\n pendingItems: stats.byStatus.pending,\n exhaustedItems: stats.byStatus.exhausted,\n oldestItem: stats.oldestItem?.toISOString(),\n };\n });\n}\n\n/**\n * Performs a full readiness check including all configured dependencies.\n *\n * @param config - Health check configuration\n * @returns Health response with component details\n */\nexport function performReadinessCheck(\n config: HealthCheckConfig = {},\n): Effect.Effect<HealthResponse, never, never> {\n const effectiveConfig = { ...DEFAULT_HEALTH_CHECK_CONFIG, ...config };\n\n return Effect.gen(function* () {\n const components: HealthComponents = {};\n\n // Check storage if enabled\n if (effectiveConfig.checkStorage) {\n components.storage = yield* checkStorageHealth(effectiveConfig);\n }\n\n // Check KV store if enabled\n if (effectiveConfig.checkKvStore) {\n components.kvStore = yield* checkKvStoreHealth(effectiveConfig);\n }\n\n // Check event broadcaster if enabled\n if (effectiveConfig.checkEventBroadcaster) {\n components.eventBroadcaster =\n yield* checkEventBroadcasterHealth(effectiveConfig);\n }\n\n // Aggregate status\n const status = aggregateHealthStatus(components);\n\n return {\n status,\n timestamp: getTimestamp(),\n version: config.version,\n uptime: getServerUptime(),\n components,\n };\n });\n}\n\n/**\n * Performs a full component health check including circuit breaker and DLQ.\n *\n * @param config - Health check configuration\n * @returns Health response with all component details\n */\nexport function performComponentsCheck(\n config: HealthCheckConfig = {},\n): Effect.Effect<HealthResponse, never, never> {\n const effectiveConfig = { ...DEFAULT_HEALTH_CHECK_CONFIG, ...config };\n\n return Effect.gen(function* () {\n const components: HealthComponents = {};\n\n // Check core components\n if (effectiveConfig.checkStorage) {\n components.storage = yield* checkStorageHealth(effectiveConfig);\n }\n\n if (effectiveConfig.checkKvStore) {\n components.kvStore = yield* checkKvStoreHealth(effectiveConfig);\n }\n\n if (effectiveConfig.checkEventBroadcaster) {\n components.eventBroadcaster =\n yield* checkEventBroadcasterHealth(effectiveConfig);\n }\n\n // Check optional components (circuit breaker and DLQ)\n const circuitBreakerSummary = yield* getCircuitBreakerSummary();\n if (circuitBreakerSummary) {\n components.circuitBreaker = circuitBreakerSummary;\n }\n\n const dlqSummary = yield* getDlqSummary();\n if (dlqSummary) {\n components.deadLetterQueue = dlqSummary;\n }\n\n // Aggregate status\n const status = aggregateHealthStatus(components);\n\n return {\n status,\n timestamp: getTimestamp(),\n version: config.version,\n uptime: getServerUptime(),\n components,\n };\n });\n}\n","/**\n * Health Check HTTP Handlers for Uploadista SDK.\n *\n * This module provides HTTP handlers for health check endpoints:\n * - `/health` (liveness) - Simple alive check, no dependencies\n * - `/ready` (readiness) - Full dependency check for accepting traffic\n * - `/health/components` - Detailed component status for debugging\n *\n * @module core/http-handlers/health-http-handlers\n */\n\nimport {\n formatHealthAsText,\n getHealthResponseFormat,\n type HealthCheckConfig,\n} from \"@uploadista/core/types\";\nimport { Effect } from \"effect\";\nimport { PERMISSIONS } from \"../../permissions/types\";\nimport { AuthContextService } from \"../../service\";\nimport {\n createLivenessResponse,\n performComponentsCheck,\n performReadinessCheck,\n} from \"../health-check-service\";\nimport type {\n HealthComponentsRequest,\n HealthComponentsResponse,\n HealthReadyRequest,\n HealthReadyResponse,\n HealthRequest,\n HealthResponse,\n} from \"../routes\";\n\n/**\n * Handle GET /health - Liveness probe\n *\n * Returns immediately with 200 OK if the server is alive.\n * Does not check any dependencies.\n */\nexport const handleHealthLiveness = (\n req: HealthRequest,\n config?: HealthCheckConfig,\n) =>\n Effect.sync(() => {\n const response = createLivenessResponse(config);\n const format = getHealthResponseFormat(req.acceptHeader);\n\n if (format === \"text\") {\n return {\n type: \"health\",\n status: 200,\n headers: { \"Content-Type\": \"text/plain\" },\n body: formatHealthAsText(response.status),\n } as HealthResponse;\n }\n\n return {\n type: \"health\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: response,\n } satisfies HealthResponse;\n });\n\n/**\n * Handle GET /ready - Readiness probe\n *\n * Checks all critical dependencies (storage, KV store) and returns:\n * - 200 OK if all dependencies are healthy\n * - 503 Service Unavailable if any critical dependency is unavailable\n *\n * Requires `engine:readiness` permission.\n */\nexport const handleHealthReadiness = (\n req: HealthReadyRequest,\n config?: HealthCheckConfig,\n) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for readiness endpoint\n yield* authService.requirePermission(PERMISSIONS.ENGINE.READINESS);\n\n const response = yield* performReadinessCheck(config);\n const format = getHealthResponseFormat(req.acceptHeader);\n\n // Determine HTTP status based on health status\n const httpStatus = response.status === \"unhealthy\" ? 503 : 200;\n\n if (format === \"text\") {\n return {\n type: \"health-ready\",\n status: httpStatus,\n headers: { \"Content-Type\": \"text/plain\" },\n body: formatHealthAsText(response.status),\n } as HealthReadyResponse;\n }\n\n return {\n type: \"health-ready\",\n status: httpStatus,\n headers: { \"Content-Type\": \"application/json\" },\n body: response,\n } satisfies HealthReadyResponse;\n });\n\n/**\n * Handle GET /health/components - Detailed component status\n *\n * Returns detailed health information for each component including:\n * - Storage backend\n * - KV store\n * - Event broadcaster\n * - Circuit breaker (if enabled)\n * - Dead letter queue (if enabled)\n *\n * Always returns 200 OK for debugging purposes (even if components are degraded).\n *\n * Requires `engine:readiness` permission.\n */\nexport const handleHealthComponents = (\n req: HealthComponentsRequest,\n config?: HealthCheckConfig,\n) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for components endpoint\n yield* authService.requirePermission(PERMISSIONS.ENGINE.READINESS);\n\n const response = yield* performComponentsCheck(config);\n const format = getHealthResponseFormat(req.acceptHeader);\n\n if (format === \"text\") {\n // For text format, just return the overall status\n return {\n type: \"health-components\",\n status: 200,\n headers: { \"Content-Type\": \"text/plain\" },\n body: formatHealthAsText(response.status),\n } as HealthComponentsResponse;\n }\n\n return {\n type: \"health-components\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: response,\n } satisfies HealthComponentsResponse;\n });\n","import { inputFileSchema } from \"@uploadista/core/types\";\nimport { UploadEngine } from \"@uploadista/core/upload\";\nimport { isSupportedAlgorithm } from \"@uploadista/core/utils\";\nimport { MetricsService } from \"@uploadista/observability\";\nimport { Effect } from \"effect\";\nimport { AuthCacheService } from \"../../cache\";\nimport { ValidationError } from \"../../error-types\";\nimport { QuotaExceededError } from \"../../permissions/errors\";\nimport { PERMISSIONS } from \"../../permissions/types\";\nimport { AuthContextService } from \"../../service\";\nimport { UsageHookService } from \"../../usage-hooks/service\";\nimport type {\n CreateUploadRequest,\n CreateUploadResponse,\n GetCapabilitiesRequest,\n GetCapabilitiesResponse,\n GetUploadRequest,\n GetUploadResponse,\n UploadChunkRequest,\n UploadChunkResponse,\n} from \"../routes\";\n\nexport const handleCreateUpload = (req: CreateUploadRequest) =>\n Effect.gen(function* () {\n const uploadEngine = yield* UploadEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n const usageHookService = yield* UsageHookService;\n const clientId = yield* authService.getClientId();\n\n // Check permission for creating uploads\n yield* authService.requirePermission(PERMISSIONS.UPLOAD.CREATE);\n\n if (clientId) {\n yield* Effect.logInfo(`[Upload] Creating upload for client: ${clientId}`);\n }\n\n const parsedInputFile = yield* Effect.sync(() =>\n inputFileSchema.safeParse(req.data),\n );\n\n if (!parsedInputFile.success) {\n return yield* Effect.fail(\n new ValidationError(\"Invalid input file schema\"),\n );\n }\n\n // Validate checksum algorithm if provided\n if (\n parsedInputFile.data.checksumAlgorithm &&\n !isSupportedAlgorithm(parsedInputFile.data.checksumAlgorithm)\n ) {\n return yield* Effect.fail(\n new ValidationError(\n `Unsupported checksum algorithm: ${parsedInputFile.data.checksumAlgorithm}. Supported algorithms: sha256`,\n ),\n );\n }\n\n // Execute onUploadStart hook for quota checking\n if (clientId) {\n const hookResult = yield* usageHookService.onUploadStart({\n clientId,\n operation: \"upload\",\n metadata: {\n fileSize: parsedInputFile.data.size,\n mimeType: parsedInputFile.data.type,\n fileName: parsedInputFile.data.fileName,\n },\n });\n\n if (hookResult.action === \"abort\") {\n return yield* Effect.fail(\n new QuotaExceededError(\n hookResult.reason,\n hookResult.code ?? \"QUOTA_EXCEEDED\",\n ),\n );\n }\n }\n\n const fileCreated = yield* uploadEngine.createUpload(\n parsedInputFile.data,\n clientId,\n );\n\n // Cache auth context for subsequent chunk uploads\n const authContext = yield* authService.getAuthContext();\n if (authContext) {\n yield* authCache.set(fileCreated.id, authContext);\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Upload] Upload created: ${fileCreated.id} for client: ${clientId}`,\n );\n }\n\n return {\n status: 200,\n body: fileCreated,\n } as CreateUploadResponse;\n });\n\nexport const handleGetCapabilities = ({ storageId }: GetCapabilitiesRequest) =>\n Effect.gen(function* () {\n const uploadEngine = yield* UploadEngine;\n const authService = yield* AuthContextService;\n const clientId = yield* authService.getClientId();\n\n // Check permission for reading upload capabilities\n yield* authService.requirePermission(PERMISSIONS.UPLOAD.READ);\n\n const capabilities = yield* uploadEngine.getCapabilities(\n storageId,\n clientId,\n );\n\n return {\n status: 200,\n body: {\n storageId,\n capabilities,\n timestamp: new Date().toISOString(),\n },\n } as GetCapabilitiesResponse;\n });\n\nexport const handleGetUpload = ({ uploadId }: GetUploadRequest) =>\n Effect.gen(function* () {\n const uploadEngine = yield* UploadEngine;\n const authService = yield* AuthContextService;\n\n // Check permission for reading upload status\n yield* authService.requirePermission(PERMISSIONS.UPLOAD.READ);\n\n const fileResult = yield* uploadEngine.getUpload(uploadId);\n\n return {\n status: 200,\n body: fileResult,\n } as GetUploadResponse;\n });\n\nexport const handleUploadChunk = (req: UploadChunkRequest) =>\n Effect.gen(function* () {\n const uploadEngine = yield* UploadEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n const metricsService = yield* MetricsService;\n const usageHookService = yield* UsageHookService;\n\n const { uploadId, data } = req;\n\n // Check permission for creating uploads (chunks are part of creation)\n yield* authService.requirePermission(PERMISSIONS.UPLOAD.CREATE);\n\n // Try current auth first, fallback to cached auth\n let clientId = yield* authService.getClientId();\n let authMetadata = yield* authService.getMetadata();\n if (!clientId) {\n const cachedAuth = yield* authCache.get(uploadId);\n clientId = cachedAuth?.clientId ?? null;\n authMetadata = cachedAuth?.metadata ?? {};\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Upload] Uploading chunk for upload: ${uploadId}, client: ${clientId}`,\n );\n }\n\n const startTime = Date.now();\n const fileResult = yield* uploadEngine.uploadChunk(\n uploadId,\n clientId,\n data,\n );\n\n // Clear cache and record metrics if upload is complete\n if (fileResult.size && fileResult.offset >= fileResult.size) {\n yield* authCache.delete(uploadId);\n if (clientId) {\n yield* Effect.logInfo(\n `[Upload] Upload completed, cleared auth cache: ${uploadId}`,\n );\n }\n\n // Record upload metrics if we have organization ID\n if (clientId && fileResult.size) {\n yield* Effect.logInfo(\n `[Upload] Recording metrics for org: ${clientId}, size: ${fileResult.size}`,\n );\n yield* Effect.forkDaemon(\n metricsService.recordUpload(clientId, fileResult.size, authMetadata),\n );\n\n // Execute onUploadComplete hook for usage tracking\n const duration = Date.now() - startTime;\n yield* Effect.forkDaemon(\n usageHookService.onUploadComplete({\n clientId,\n operation: \"upload\",\n metadata: {\n uploadId,\n fileSize: fileResult.size,\n duration,\n },\n }),\n );\n } else {\n yield* Effect.logWarning(\n `[Upload] Cannot record metrics - missing organizationId or size`,\n );\n }\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Upload] Chunk uploaded for upload: ${uploadId}, client: ${clientId}`,\n );\n }\n\n return {\n status: 200,\n body: fileResult,\n } as UploadChunkResponse;\n });\n","import type { HealthCheckConfig } from \"@uploadista/core/types\";\nimport { Effect } from \"effect\";\nimport type { UploadistaRequest, UploadistaResponse } from \"../routes\";\nimport {\n handleDlqCleanup,\n handleDlqDelete,\n handleDlqGet,\n handleDlqList,\n handleDlqResolve,\n handleDlqRetry,\n handleDlqRetryAll,\n handleDlqStats,\n} from \"./dlq-http-handlers\";\nimport {\n handleCancelFlow,\n handleGetFlow,\n handleJobStatus,\n handlePauseFlow,\n handleResumeFlow,\n handleRunFlow,\n} from \"./flow-http-handlers\";\nimport {\n handleHealthComponents,\n handleHealthLiveness,\n handleHealthReadiness,\n} from \"./health-http-handlers\";\nimport {\n handleCreateUpload,\n handleGetCapabilities,\n handleGetUpload,\n handleUploadChunk,\n} from \"./upload-http-handlers\";\n\nexport type { UploadistaRequest, UploadistaResponse } from \"../routes\";\n\nexport const handleUploadistaRequest = <TRequirements>(\n req: UploadistaRequest,\n options?: { healthCheckConfig?: HealthCheckConfig },\n) => {\n return Effect.gen(function* () {\n switch (req.type) {\n case \"create-upload\":\n return (yield* handleCreateUpload(req)) as UploadistaResponse;\n case \"get-capabilities\":\n return (yield* handleGetCapabilities(req)) as UploadistaResponse;\n case \"get-upload\":\n return (yield* handleGetUpload(req)) as UploadistaResponse;\n case \"upload-chunk\":\n return (yield* handleUploadChunk(req)) as UploadistaResponse;\n case \"get-flow\":\n return (yield* handleGetFlow(req)) as UploadistaResponse;\n case \"run-flow\":\n return (yield* handleRunFlow<TRequirements>(req)) as UploadistaResponse;\n case \"job-status\":\n return (yield* handleJobStatus(req)) as UploadistaResponse;\n case \"resume-flow\":\n return (yield* handleResumeFlow<TRequirements>(\n req,\n )) as UploadistaResponse;\n case \"pause-flow\":\n return (yield* handlePauseFlow(req)) as UploadistaResponse;\n case \"cancel-flow\":\n return (yield* handleCancelFlow(req)) as UploadistaResponse;\n // DLQ Admin routes\n case \"dlq-list\":\n return (yield* handleDlqList(req)) as UploadistaResponse;\n case \"dlq-get\":\n return (yield* handleDlqGet(req)) as UploadistaResponse;\n case \"dlq-retry\":\n return (yield* handleDlqRetry(req)) as UploadistaResponse;\n case \"dlq-retry-all\":\n return (yield* handleDlqRetryAll(req)) as UploadistaResponse;\n case \"dlq-delete\":\n return (yield* handleDlqDelete(req)) as UploadistaResponse;\n case \"dlq-resolve\":\n return (yield* handleDlqResolve(req)) as UploadistaResponse;\n case \"dlq-cleanup\":\n return (yield* handleDlqCleanup(req)) as UploadistaResponse;\n case \"dlq-stats\":\n return (yield* handleDlqStats(req)) as UploadistaResponse;\n // Health check routes\n case \"health\":\n return (yield* handleHealthLiveness(\n req,\n options?.healthCheckConfig,\n )) as UploadistaResponse;\n case \"health-ready\":\n return (yield* handleHealthReadiness(\n req,\n options?.healthCheckConfig,\n )) as UploadistaResponse;\n case \"health-components\":\n return (yield* handleHealthComponents(\n req,\n options?.healthCheckConfig,\n )) as UploadistaResponse;\n case \"not-found\":\n return {\n status: 404,\n headers: { \"Content-Type\": \"application/json\" },\n body: { error: \"Not found\" },\n } as UploadistaResponse;\n case \"bad-request\":\n return {\n status: 400,\n body: { error: \"Bad request\", message: req.message },\n } as UploadistaResponse;\n case \"method-not-allowed\":\n return {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n body: { error: \"Method not allowed\" },\n } as UploadistaResponse;\n case \"unsupported-content-type\":\n return {\n status: 415,\n headers: { \"Content-Type\": \"application/json\" },\n body: { error: \"Unsupported content type\" },\n } as UploadistaResponse;\n }\n });\n};\n","import type { PluginLayer, UploadistaError } from \"@uploadista/core\";\nimport {\n deadLetterQueueService,\n type Flow,\n FlowProvider,\n FlowWaitUntil,\n kvCircuitBreakerStoreLayer,\n} from \"@uploadista/core/flow\";\nimport {\n createDataStoreLayer,\n deadLetterQueueKvStore,\n type UploadFileDataStores,\n type UploadFileKVStore,\n} from \"@uploadista/core/types\";\nimport { GenerateIdLive } from \"@uploadista/core/utils\";\nimport { memoryEventBroadcaster } from \"@uploadista/event-broadcaster-memory\";\nimport { webSocketEventEmitter } from \"@uploadista/event-emitter-websocket\";\nimport { NodeSdkLive, NoOpMetricsServiceLive } from \"@uploadista/observability\";\nimport { Effect, Layer, ManagedRuntime } from \"effect\";\nimport type { z } from \"zod\";\nimport type { StandardResponse } from \"../adapter\";\nimport { AuthCacheServiceLive } from \"../cache\";\nimport { handleFlowError } from \"../http-utils\";\nimport { createFlowEngineLayer, createUploadEngineLayer } from \"../layer-utils\";\nimport { AuthContextServiceLive } from \"../service\";\nimport type { AuthContext } from \"../types\";\nimport { UsageHookServiceLive } from \"../usage-hooks/service\";\nimport { handleUploadistaRequest } from \"./http-handlers/http-handlers\";\nimport type { ExtractFlowPluginRequirements } from \"./plugin-types\";\nimport type { NotFoundResponse } from \"./routes\";\nimport type { UploadistaServer, UploadistaServerConfig } from \"./types\";\n\n/**\n * Creates the unified Uploadista server with framework-specific adapter.\n *\n * This is the single, unified API for creating an Uploadista server. It handles\n * all server initialization, layer composition, and runtime setup.\n *\n * ## Core Responsibilities\n *\n * The server handles:\n * - Layer composition (upload/flow servers, auth cache, metrics, plugins)\n * - Route parsing and matching\n * - Auth middleware execution with timeout protection\n * - Error handling and response formatting\n * - Effect program execution with optional tracing\n * - Plugin validation and dependency injection\n *\n * ## Plugin Validation\n *\n * The server supports two validation approaches:\n *\n * ### 1. Runtime Validation (Recommended for Most Cases)\n *\n * The server relies on Effect-TS's dependency injection to validate plugins\n * at runtime. If a required plugin is missing, Effect will fail with a clear\n * MissingService error.\n *\n * ```typescript\n * const server = await createUploadistaServer({\n * flows: getFlowById,\n * plugins: [sharpImagePlugin, zipPlugin],\n * dataStore: s3DataStore,\n * kvStore: redisKvStore,\n * adapter: honoAdapter({ ... })\n * });\n * // If plugins don't match flow requirements, Effect fails with clear error\n * ```\n *\n * ### 2. Compile-Time Validation (Optional)\n *\n * For IDE feedback during development, use the ValidatePlugins type utility:\n *\n * ```typescript\n * import {\n * createUploadistaServer,\n * ValidatePlugins,\n * ExtractFlowPluginRequirements\n * } from '@uploadista/server';\n *\n * // Extract requirements from flows\n * type Requirements = ExtractFlowPluginRequirements<typeof getFlowById>;\n *\n * // Define plugins\n * const plugins = [sharpImagePlugin, zipPlugin] as const;\n *\n * // Validate at compile time (optional, for IDE feedback)\n * type Validation = ValidatePlugins<typeof plugins, Requirements>;\n * // IDE shows error if plugins don't match requirements\n *\n * const server = await createUploadistaServer({\n * flows: getFlowById,\n * plugins,\n * // ...\n * });\n * ```\n *\n * ### 3. Early Runtime Validation (Optional)\n *\n * For better error messages before server starts:\n *\n * ```typescript\n * import { validatePluginsOrThrow } from '@uploadista/server/core';\n *\n * validatePluginsOrThrow({\n * plugins: [sharpImagePlugin],\n * expectedServices: ['ImagePlugin', 'ZipPlugin']\n * });\n * // Throws with helpful error message including import suggestions\n * ```\n *\n * ## Type Safety\n *\n * - Plugin requirements are inferred from flow definitions\n * - Effect-TS ensures dependencies are satisfied at runtime\n * - Type casting is intentional (see inline docs for rationale)\n * - Optional compile-time validation available via type utilities\n *\n * @template TContext - Framework-specific context type\n * @template TResponse - Framework-specific response type\n * @template TWebSocketHandler - WebSocket handler type (if supported)\n * @template TFlows - Flow function type with plugin requirements\n * @template TPlugins - Tuple of plugin layers provided\n *\n * @param config - Server configuration including adapter and business logic\n * @returns Promise resolving to server instance with handler and metadata\n *\n * @example Basic Usage\n * ```typescript\n * import { createUploadistaServer, honoAdapter } from \"@uploadista/server\";\n * import { sharpImagePlugin } from \"@uploadista/flow-images-sharp\";\n *\n * const server = await createUploadistaServer({\n * flows: getFlowById,\n * plugins: [sharpImagePlugin],\n * dataStore: { type: \"s3\", config: { bucket: \"uploads\" } },\n * kvStore: redisKvStore,\n * adapter: honoAdapter({\n * authMiddleware: async (c) => ({ clientId: \"user-123\" })\n * })\n * });\n *\n * // Use with Hono\n * app.all(\"/uploadista/*\", server.handler);\n * ```\n *\n * @example With Compile-Time Validation\n * ```typescript\n * import {\n * createUploadistaServer,\n * ValidatePlugins,\n * ExtractFlowPluginRequirements\n * } from \"@uploadista/server\";\n *\n * type Requirements = ExtractFlowPluginRequirements<typeof getFlowById>;\n * const plugins = [sharpImagePlugin, zipPlugin] as const;\n * type Validation = ValidatePlugins<typeof plugins, Requirements>;\n *\n * const server = await createUploadistaServer({\n * flows: getFlowById,\n * plugins,\n * // ... rest of config\n * });\n * ```\n *\n * @see ValidatePlugins - Compile-time plugin validation\n * @see ExtractFlowPluginRequirements - Extract requirements from flows\n * @see validatePluginRequirements - Runtime validation helper\n * @see API_DECISION_GUIDE.md - Complete guide for choosing validation approach\n */\nexport const createUploadistaServer = async <\n TContext,\n TResponse,\n TWebSocketHandler = unknown,\n TFlows extends (\n flowId: string,\n clientId: string | null,\n ) => Effect.Effect<\n // biome-ignore lint/suspicious/noExplicitAny: Flow requirements can be any plugin services\n Flow<z.ZodSchema<unknown>, z.ZodSchema<unknown>, any>,\n UploadistaError,\n // biome-ignore lint/suspicious/noExplicitAny: Flow return type allows any requirements\n any\n // biome-ignore lint/suspicious/noExplicitAny: Generic type constraint allows any flow function type with any requirements\n > = any,\n TPlugins extends readonly PluginLayer[] = readonly PluginLayer[],\n>({\n flows,\n dataStore,\n kvStore,\n // Default to an empty plugin list while preserving the generic type\n plugins = [] as unknown as TPlugins,\n eventEmitter,\n eventBroadcaster = memoryEventBroadcaster,\n withTracing = false,\n observabilityLayer,\n baseUrl: configBaseUrl = \"uploadista\",\n generateId = GenerateIdLive,\n metricsLayer,\n bufferedDataStore,\n adapter,\n authCacheConfig,\n circuitBreaker = true,\n deadLetterQueue = false,\n healthCheck,\n usageHooks,\n}: UploadistaServerConfig<\n TContext,\n TResponse,\n TWebSocketHandler,\n TFlows,\n TPlugins\n>): Promise<UploadistaServer<TContext, TResponse, TWebSocketHandler>> => {\n // Default eventEmitter to webSocketEventEmitter with the provided eventBroadcaster\n const finalEventEmitter =\n eventEmitter ?? webSocketEventEmitter(eventBroadcaster);\n\n // Normalize baseUrl (remove trailing slash)\n const baseUrl = configBaseUrl.endsWith(\"/\")\n ? configBaseUrl.slice(0, -1)\n : configBaseUrl;\n\n type FlowReq = ExtractFlowPluginRequirements<TFlows>;\n\n // Create flow provider layer from flows function\n const flowProviderLayer = Layer.effect(\n FlowProvider,\n Effect.succeed({\n getFlow: (flowId: string, clientId: string | null) => {\n // Cast the flows function to match FlowProvider expectations\n // The context requirements will be provided at the layer level\n return flows(flowId, clientId) as Effect.Effect<\n Flow<z.ZodSchema<unknown>, z.ZodSchema<unknown>, FlowReq>,\n UploadistaError\n >;\n },\n }),\n );\n\n // Validate that eventEmitter is provided (required for upload/flow servers)\n if (!finalEventEmitter) {\n throw new Error(\n \"eventEmitter is required. Provide an event emitter layer in the configuration.\",\n );\n }\n\n // Create data store layer\n const dataStoreLayer: Layer.Layer<\n UploadFileDataStores,\n never,\n UploadFileKVStore\n > = await createDataStoreLayer(dataStore);\n\n // Create upload server layer\n const uploadEngineLayer = createUploadEngineLayer({\n kvStore,\n eventEmitter: finalEventEmitter,\n dataStore: dataStoreLayer,\n bufferedDataStore,\n generateId,\n });\n\n // Create flow server layer\n const flowEngineLayer = createFlowEngineLayer({\n kvStore,\n eventEmitter: finalEventEmitter,\n flowProvider: flowProviderLayer,\n uploadEngine: uploadEngineLayer,\n });\n\n // Create auth cache layer (always present, even if auth is not enabled)\n const authCacheLayer = AuthCacheServiceLive(authCacheConfig);\n\n // Metrics layer (defaults to NoOp if not provided)\n const effectiveMetricsLayer = metricsLayer ?? NoOpMetricsServiceLive;\n\n // Create circuit breaker store layer if enabled (uses the provided kvStore)\n const circuitBreakerStoreLayer = circuitBreaker\n ? kvCircuitBreakerStoreLayer.pipe(Layer.provide(kvStore))\n : null;\n\n // Create dead letter queue layer if enabled (uses the provided kvStore)\n // The DLQ layer provides both the KV store wrapper and the service\n const dlqLayer = deadLetterQueue\n ? deadLetterQueueService.pipe(\n Layer.provide(deadLetterQueueKvStore),\n Layer.provide(kvStore),\n )\n : null;\n\n // Create usage hook layer (defaults to no-op if not configured)\n const usageHookLayer = UsageHookServiceLive(usageHooks);\n\n /**\n * Merge all server layers including plugins.\n *\n * This combines the core server infrastructure (upload server, flow server,\n * metrics, auth cache, circuit breaker, dead letter queue, usage hooks)\n * with user-provided plugin layers.\n */\n const serverLayerRaw = Layer.mergeAll(\n uploadEngineLayer,\n flowEngineLayer,\n effectiveMetricsLayer,\n authCacheLayer,\n usageHookLayer,\n ...plugins,\n ...(circuitBreakerStoreLayer ? [circuitBreakerStoreLayer] : []),\n ...(dlqLayer ? [dlqLayer] : []),\n );\n\n /**\n * Determine the tracing layer to use.\n * This must be included in the runtime layer (not per-request) so that the\n * BatchSpanProcessor can aggregate spans across requests and flush them properly.\n */\n const tracingLayer = withTracing ? (observabilityLayer ?? NodeSdkLive) : null;\n\n /**\n * Type Casting Rationale for Plugin System\n *\n * The type assertion below is intentional and safe. This is not a bug or workaround,\n * but follows Effect-TS's design for dynamic dependency injection.\n *\n * ## Why Type Casting is Necessary\n *\n * 1. **Plugin Requirements are Dynamic**\n * Different flows require different plugins (ImagePlugin, ZipPlugin, etc.).\n * These requirements are only known when flows are loaded at runtime.\n * Flow A might need ImagePlugin, Flow B might need ZipPlugin.\n *\n * 2. **TypeScript's Static Limitation**\n * TypeScript cannot statically verify that all possible flow combinations\n * will have their requirements satisfied. The plugin array is typed as\n * `readonly PluginLayer[]` which could be any combination of plugins.\n *\n * 3. **Effect-TS Runtime Resolution**\n * Effect-TS is designed to resolve service requirements at runtime using\n * its dependency injection system. When a flow executes and accesses a service:\n *\n * ```typescript\n * const imagePlugin = yield* ImagePlugin;\n * ```\n *\n * Effect checks if ImagePlugin exists in the provided layer context.\n * If missing, Effect fails with a clear MissingService error.\n *\n * 4. **Layer Composition Guarantees**\n * Layer.mergeAll() combines all layers. At runtime, Effect ensures that\n * when a service is requested, it's either:\n * - Provided by one of the merged layers, OR\n * - Results in a MissingService error with the service name\n *\n * ## Safety Guarantees\n *\n * This pattern is safe because:\n *\n * 1. **Runtime Validation** (Optional but Recommended)\n * We provide validatePluginRequirements() that checks plugins before\n * server initialization, giving excellent error messages early.\n *\n * 2. **Effect's Built-in Validation**\n * If runtime validation is skipped, Effect will fail during flow execution\n * with a MissingService error containing the service identifier.\n *\n * 3. **Optional Compile-Time Validation**\n * Developers can use ValidatePlugins<> type utility for IDE feedback:\n *\n * ```typescript\n * type Validation = ValidatePlugins<typeof plugins, Requirements>;\n * // Shows compile error if plugins don't match requirements\n * ```\n *\n * 4. **No Silent Failures**\n * There's no scenario where missing plugins cause silent failures.\n * Either runtime validation catches it, or Effect fails with clear error.\n *\n * ## This is Effect-TS's Idiomatic Pattern\n *\n * Effect-TS separates compile-time structure from runtime resolution:\n * - Compile-time: Types ensure layer structure is correct\n * - Runtime: Effect resolves actual dependencies and fails if missing\n *\n * The type system provides structure and IDE support, while Effect's\n * runtime handles actual requirement resolution.\n *\n * ## Further Reading\n *\n * - Effect-TS Context Management: https://effect.website/docs/guides/context-management\n * - Runtime Validation: See plugin-validation.ts for helper functions\n * - Type Utilities: See plugin-types.ts for compile-time validation\n *\n * @see validatePluginRequirements - Runtime validation helper\n * @see ValidatePlugins - Compile-time validation type utility\n */\n const serverLayerTyped = serverLayerRaw as unknown as Layer.Layer<\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic plugin requirements require any - see comprehensive explanation above\n any,\n never,\n never\n >;\n\n /**\n * Final server layer with optional tracing.\n * The tracing layer is merged at runtime level (not per-request) so that:\n * 1. The OpenTelemetry SDK is initialized once for the server\n * 2. The BatchSpanProcessor can aggregate spans across requests\n * 3. Spans are properly flushed when the runtime is disposed\n */\n const serverLayer = tracingLayer\n ? Layer.merge(serverLayerTyped, tracingLayer)\n : serverLayerTyped;\n\n // Create a shared managed runtime from the server layer\n // This ensures all requests use the same layer instances (including event broadcaster)\n // ManagedRuntime properly handles scoped resources and provides convenient run methods\n // When tracing is enabled, the OpenTelemetry SDK is part of this runtime and will be\n // properly shut down (flushing all pending spans) when dispose() is called\n\n const managedRuntime = ManagedRuntime.make(serverLayer);\n\n /**\n * Main request handler that processes HTTP requests through the adapter.\n * Delegates to adapter's httpHandler if provided, otherwise uses standard flow.\n */\n const handler = async <TRequirements>(ctx: TContext) => {\n // Fallback: Standard routing logic (for adapters without httpHandler)\n const program = Effect.gen(function* () {\n // Extract standard request from framework-specific request\n const uploadistaRequest = yield* adapter.extractRequest(ctx, { baseUrl });\n\n // Run auth middleware if provided\n let authContext: AuthContext | null = null;\n if (adapter.runAuthMiddleware) {\n const authMiddlewareWithTimeout = adapter.runAuthMiddleware(ctx).pipe(\n Effect.timeout(\"5 seconds\"),\n Effect.catchAll(() => {\n // Timeout error\n console.error(\"Auth middleware timeout exceeded (5 seconds)\");\n return Effect.succeed({\n _tag: \"TimeoutError\" as const,\n } as const);\n }),\n Effect.catchAllCause((cause) => {\n // Other errors\n console.error(\"Auth middleware error:\", cause);\n return Effect.succeed({\n _tag: \"AuthError\" as const,\n error: cause,\n } as const);\n }),\n );\n\n const authResult:\n | AuthContext\n | null\n | { _tag: \"TimeoutError\" }\n | { _tag: \"AuthError\"; error: unknown } =\n yield* authMiddlewareWithTimeout;\n\n // Handle timeout\n if (\n authResult &&\n typeof authResult === \"object\" &&\n \"_tag\" in authResult &&\n authResult._tag === \"TimeoutError\"\n ) {\n const errorResponse: StandardResponse = {\n status: 503,\n headers: { \"Content-Type\": \"application/json\" },\n body: {\n error: \"Authentication service unavailable\",\n message:\n \"Authentication took too long to respond. Please try again.\",\n },\n };\n return yield* adapter.sendResponse(errorResponse, ctx);\n }\n\n // Handle auth error\n if (\n authResult &&\n typeof authResult === \"object\" &&\n \"_tag\" in authResult &&\n authResult._tag === \"AuthError\"\n ) {\n const errorResponse: StandardResponse = {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n body: {\n error: \"Internal Server Error\",\n message: \"An error occurred during authentication\",\n },\n };\n return yield* adapter.sendResponse(errorResponse, ctx);\n }\n\n // Handle authentication failure (null result)\n if (authResult === null) {\n const errorResponse: StandardResponse = {\n status: 401,\n headers: { \"Content-Type\": \"application/json\" },\n body: {\n error: \"Unauthorized\",\n message: \"Invalid credentials\",\n },\n };\n return yield* adapter.sendResponse(errorResponse, ctx);\n }\n\n authContext = authResult;\n }\n\n // Create auth context layer for this request\n // If no auth middleware is configured, bypass permission checks (backward compatibility)\n const authContextLayer = AuthContextServiceLive(authContext, {\n bypassAuth: !adapter.runAuthMiddleware,\n });\n\n // Extract waitUntil callback if available (for Cloudflare Workers)\n // This must be extracted per-request since it comes from the framework context\n // biome-ignore lint/suspicious/noExplicitAny: Layer array needs to accept any service type from waitUntil\n const waitUntilLayers: Layer.Layer<any, never, never>[] = [];\n if (adapter.extractWaitUntil) {\n const waitUntilCallback = adapter.extractWaitUntil(ctx);\n if (waitUntilCallback) {\n waitUntilLayers.push(Layer.succeed(FlowWaitUntil, waitUntilCallback));\n }\n }\n\n // Combine auth context, auth cache, metrics layers, usage hooks, plugins, circuit breaker, DLQ, and waitUntil\n // This ensures that flow nodes have access to all required services\n const baseRequestContextLayer = Layer.mergeAll(\n authContextLayer,\n authCacheLayer,\n effectiveMetricsLayer,\n usageHookLayer,\n ...plugins,\n ...waitUntilLayers,\n );\n const withCircuitBreakerContext = circuitBreakerStoreLayer\n ? Layer.merge(baseRequestContextLayer, circuitBreakerStoreLayer)\n : baseRequestContextLayer;\n const requestContextLayer = dlqLayer\n ? Layer.merge(withCircuitBreakerContext, dlqLayer)\n : withCircuitBreakerContext;\n\n // Check for baseUrl/api/ prefix\n if (uploadistaRequest.type === \"not-found\") {\n const notFoundResponse: NotFoundResponse = {\n type: \"not-found\",\n status: 404,\n headers: { \"Content-Type\": \"application/json\" },\n body: { error: \"Not found\" },\n };\n return yield* adapter.sendResponse(notFoundResponse, ctx);\n }\n\n // Handle the request\n const response = yield* handleUploadistaRequest<TRequirements>(\n uploadistaRequest,\n { healthCheckConfig: healthCheck },\n ).pipe(Effect.provide(requestContextLayer));\n\n return yield* adapter.sendResponse(response, ctx);\n }).pipe(\n // Catch all errors and format them appropriately\n Effect.catchAll((error: unknown) => {\n const errorInfo = handleFlowError(error);\n const errorBody: Record<string, unknown> = {\n code: errorInfo.code,\n message: errorInfo.message,\n };\n if (errorInfo.details !== undefined) {\n errorBody.details = errorInfo.details;\n }\n const errorResponse: StandardResponse = {\n status: errorInfo.status,\n headers: { \"Content-Type\": \"application/json\" },\n body: errorBody,\n };\n return adapter.sendResponse(errorResponse, ctx);\n }),\n );\n\n // Use the shared managed runtime which includes all layers (including tracing if enabled)\n // Tracing is now part of the runtime layer, so spans are properly aggregated and flushed\n return managedRuntime.runPromise(program);\n };\n\n // Create WebSocket handler using the shared managed runtime\n const websocketHandler = await managedRuntime.runPromise(\n adapter.webSocketHandler({\n baseUrl,\n }),\n );\n\n return {\n handler,\n websocketHandler,\n baseUrl,\n dispose: () => managedRuntime.dispose(),\n };\n};\n","import type {\n PluginServices,\n PluginTuple,\n TypeSafeFlowFunction,\n ValidatePlugins,\n} from \"./plugin-types\";\nimport { createUploadistaServer } from \"./server\";\nimport type { UploadistaServer, UploadistaServerConfig } from \"./types\";\n\n/**\n * Type-safe configuration for Uploadista server with compile-time plugin validation.\n *\n * This configuration extends the base UploadistaServerConfig with stricter typing\n * that validates plugins match flow requirements at compile time.\n *\n * @template TContext - Framework-specific request context type\n * @template TResponse - Framework-specific response type\n * @template TWebSocket - Framework-specific WebSocket handler type\n * @template TPlugins - Tuple of plugin layers provided to the server\n * @template TFlowRequirements - Union of plugin services required by flows\n */\nexport type TypeSafeServerConfig<\n TContext,\n TResponse,\n TWebSocket,\n TPlugins extends PluginTuple,\n TFlowRequirements = PluginServices<TPlugins>,\n> = Omit<\n UploadistaServerConfig<TContext, TResponse, TWebSocket>,\n \"flows\" | \"plugins\"\n> & {\n /**\n * Tuple of plugin layers that provide services to flows.\n * The plugins must satisfy all requirements declared by the flows.\n */\n plugins: TPlugins;\n\n /**\n * Type-safe flow function with explicit requirements.\n * TypeScript validates that all required plugins are provided.\n */\n flows: TypeSafeFlowFunction<TFlowRequirements>;\n\n /**\n * Compile-time validation that plugins satisfy flow requirements.\n * If this field has type errors, required plugins are missing.\n */\n __validate?: ValidatePlugins<TPlugins, TFlowRequirements>;\n};\n\n/**\n * @deprecated Use `createUploadistaServer` with optional type utilities instead.\n *\n * This function is deprecated in favor of the unified `createUploadistaServer` API.\n * The new approach separates validation concerns from server creation, making the\n * API simpler while still providing compile-time validation when desired.\n *\n * ## Migration Guide\n *\n * ### Old Approach (Deprecated)\n * ```typescript\n * import { createTypeSafeServer } from \"@uploadista/server\";\n *\n * const server = await createTypeSafeServer({\n * plugins: [sharpImagePlugin] as const,\n * flows: myFlowFunction,\n * // ...\n * });\n * ```\n *\n * ### New Approach (Recommended)\n *\n * **Option 1: Runtime validation only (simplest)**\n * ```typescript\n * import { createUploadistaServer } from \"@uploadista/server\";\n *\n * const server = await createUploadistaServer({\n * plugins: [sharpImagePlugin, zipPlugin],\n * flows: myFlowFunction,\n * // ... Effect validates at runtime\n * });\n * ```\n *\n * **Option 2: With compile-time validation (optional)**\n * ```typescript\n * import {\n * createUploadistaServer,\n * ValidatePlugins,\n * ExtractFlowPluginRequirements\n * } from \"@uploadista/server\";\n *\n * type Requirements = ExtractFlowPluginRequirements<typeof myFlowFunction>;\n * const plugins = [sharpImagePlugin, zipPlugin] as const;\n * type Validation = ValidatePlugins<typeof plugins, Requirements>;\n * // IDE shows error if plugins don't match requirements\n *\n * const server = await createUploadistaServer({\n * plugins,\n * flows: myFlowFunction,\n * // ...\n * });\n * ```\n *\n * ## Why This Changed\n *\n * 1. **Simpler API**: One function instead of two reduces confusion\n * 2. **Separation of Concerns**: Validation is now optional and separate\n * 3. **Better Flexibility**: Choose validation approach per use case\n * 4. **Clearer Intent**: Explicit validation via type utilities\n * 5. **Same Safety**: Effect-TS still validates at runtime\n *\n * The new approach trusts Effect-TS's design for dynamic dependency injection\n * while providing optional compile-time validation through type utilities.\n *\n * @see createUploadistaServer - The unified server creation API\n * @see ValidatePlugins - Compile-time validation type utility\n * @see ExtractFlowPluginRequirements - Extract requirements from flows\n * @see API_DECISION_GUIDE.md - Complete migration and usage guide\n *\n * @template TContext - Framework-specific request context type\n * @template TResponse - Framework-specific response type\n * @template TWebSocket - Framework-specific WebSocket handler type\n * @template TPlugins - Tuple of plugin layers\n * @template TFlowRequirements - Union of services required by flows\n *\n * @param config - Type-safe server configuration\n * @returns Promise resolving to UploadistaServer instance\n */\nexport async function createTypeSafeServer<\n TContext,\n TResponse,\n TWebSocket = unknown,\n TPlugins extends PluginTuple = PluginTuple,\n TFlowRequirements = PluginServices<TPlugins>,\n>(\n config: TypeSafeServerConfig<\n TContext,\n TResponse,\n TWebSocket,\n TPlugins,\n TFlowRequirements\n > &\n // Enforce validation at function call site\n (ValidatePlugins<TPlugins, TFlowRequirements> extends true\n ? object\n : ValidatePlugins<TPlugins, TFlowRequirements>),\n): Promise<UploadistaServer<TContext, TResponse, TWebSocket>> {\n return createUploadistaServer(config);\n}\n\n/**\n * Helper function to define flow functions with explicit type requirements.\n * Provides better type inference and autocomplete for plugin services.\n *\n * @template TRequirements - Union of plugin services this flow needs\n *\n * @param fn - The flow function implementation\n * @returns The same function with explicit type annotation\n *\n * @example\n * ```typescript\n * import { ImagePlugin } from \"@uploadista/core/flow\";\n * import { defineFlow } from \"@uploadista/server\";\n *\n * // Explicitly declare that this flow requires ImagePlugin\n * const imageProcessingFlow = defineFlow<ImagePlugin>((flowId, clientId) =>\n * Effect.gen(function* () {\n * const imageService = yield* ImagePlugin; // Autocomplete works!\n * const optimized = yield* imageService.optimize(data, { quality: 80 });\n * return createFlow({ ... });\n * })\n * );\n * ```\n */\nexport function defineFlow<TRequirements = never>(\n fn: TypeSafeFlowFunction<TRequirements>,\n): TypeSafeFlowFunction<TRequirements> {\n return fn;\n}\n\n/**\n * Helper to create a flow that requires no plugins.\n * Useful for simple flows that only use built-in functionality.\n *\n * @example\n * ```typescript\n * import { defineSimpleFlow } from \"@uploadista/server\";\n *\n * const simpleFlow = defineSimpleFlow((flowId, clientId) =>\n * Effect.succeed(createFlow({\n * id: \"simple\",\n * nodes: [],\n * edges: [],\n * inputSchema: myInputSchema,\n * outputSchema: myOutputSchema\n * }))\n * );\n * ```\n */\nexport function defineSimpleFlow(\n fn: TypeSafeFlowFunction<never>,\n): TypeSafeFlowFunction<never> {\n return fn;\n}\n","/**\n * Runtime plugin validation utilities.\n *\n * This module provides runtime validation to ensure that all plugins required\n * by flows are actually provided to the server. While Effect-TS will catch\n * missing dependencies at runtime, this validation provides better error messages\n * and fails fast during server initialization.\n *\n * @module plugin-validation\n */\n\nimport type { PluginLayer } from \"@uploadista/core\";\nimport { Effect } from \"effect\";\n\n/**\n * Result of plugin validation.\n */\nexport type PluginValidationResult =\n | {\n success: true;\n }\n | {\n success: false;\n required: string[];\n provided: string[];\n missing: string[];\n suggestions: Array<{\n name: string;\n packageName: string;\n importStatement: string;\n }>;\n };\n\n/**\n * Known plugin mapping for generating helpful error messages.\n *\n * This maps service identifiers to their package names and variable names\n * for generating import suggestions.\n */\nconst KNOWN_PLUGINS: Record<\n string,\n { packageName: string; variableName: string }\n> = {\n ImagePlugin: {\n packageName: \"@uploadista/flow-images-sharp\",\n variableName: \"sharpImagePlugin\",\n },\n ImageAiPlugin: {\n packageName: \"@uploadista/flow-images-replicate\",\n variableName: \"replicateImagePlugin\",\n },\n ZipPlugin: {\n packageName: \"@uploadista/flow-utility-zipjs\",\n variableName: \"zipPlugin\",\n },\n CredentialProvider: {\n packageName: \"@uploadista/core\",\n variableName: \"credentialProviderLayer\",\n },\n};\n\n/**\n * Extracts service identifier from a plugin layer.\n *\n * This attempts to identify the service provided by a layer using various\n * heuristics. The exact implementation depends on how Effect-TS exposes\n * layer metadata.\n *\n * @param layer - The plugin layer to inspect\n * @returns Service identifier string or null if not identifiable\n */\nfunction extractServiceIdentifier(layer: PluginLayer): string | null {\n // Attempt to extract service identifier from layer\n // Note: Effect-TS doesn't expose this information in a standard way,\n // so we use Symbol.toStringTag or constructor name as fallbacks\n\n try {\n // Try to get the service tag if available\n // biome-ignore lint/suspicious/noExplicitAny: Layer introspection requires accessing internal properties\n const layerAny = layer as any;\n\n // Check for common patterns in Effect layers\n if (layerAny._tag) {\n return layerAny._tag;\n }\n\n if (layerAny.constructor?.name) {\n return layerAny.constructor.name;\n }\n\n // Try to extract from the layer's context if available\n if (layerAny.context?.services) {\n const services = Array.from(layerAny.context.services.keys());\n if (services.length > 0) {\n // biome-ignore lint/suspicious/noExplicitAny: Service introspection requires accessing internal properties\n const firstService = services[0] as any;\n if (firstService.key) {\n return firstService.key;\n }\n }\n }\n\n return null;\n } catch {\n // If we can't extract the identifier, return null\n return null;\n }\n}\n\n/**\n * Extracts service identifiers from an array of plugin layers.\n *\n * @param plugins - Array of plugin layers\n * @returns Array of service identifier strings\n */\nexport function extractServiceIdentifiers(\n plugins: readonly PluginLayer[],\n): string[] {\n return plugins\n .map((plugin) => extractServiceIdentifier(plugin))\n .filter((id): id is string => id !== null);\n}\n\n/**\n * Validates that all required plugins are provided.\n *\n * This is a runtime validation function that checks if the plugins array\n * contains all services required by the flows. It's called during server\n * initialization to provide early, clear error messages.\n *\n * Note: This validation is best-effort because we can't reliably extract\n * requirements from flow functions at runtime without executing them.\n * The main validation happens via Effect-TS's dependency injection.\n *\n * @param config - Validation configuration\n * @returns Validation result with detailed error information if validation fails\n *\n * @example\n * ```typescript\n * const result = validatePluginRequirements({\n * plugins: [sharpImagePlugin, zipPlugin],\n * expectedServices: ['ImagePlugin', 'ZipPlugin']\n * });\n *\n * if (!result.success) {\n * console.error('Missing plugins:', result.missing);\n * console.error('Suggestions:', result.suggestions);\n * }\n * ```\n */\nexport function validatePluginRequirements(config: {\n plugins: readonly PluginLayer[];\n expectedServices?: string[];\n}): PluginValidationResult {\n const { plugins, expectedServices = [] } = config;\n\n // Extract identifiers from provided plugins\n const providedServices = extractServiceIdentifiers(plugins);\n\n // Check for missing services\n const missing = expectedServices.filter(\n (required) => !providedServices.includes(required),\n );\n\n if (missing.length === 0) {\n return { success: true };\n }\n\n // Generate suggestions for missing plugins\n const suggestions = missing\n .map((service) => {\n const knownPlugin = KNOWN_PLUGINS[service];\n if (!knownPlugin) {\n return null;\n }\n\n return {\n name: service,\n packageName: knownPlugin.packageName,\n importStatement: `import { ${knownPlugin.variableName} } from '${knownPlugin.packageName}';`,\n };\n })\n .filter((s): s is NonNullable<typeof s> => s !== null);\n\n return {\n success: false,\n required: expectedServices,\n provided: providedServices,\n missing,\n suggestions,\n };\n}\n\n/**\n * Creates a formatted error message for plugin validation failures.\n *\n * This generates a detailed, human-readable error message that includes:\n * - List of required plugins\n * - List of provided plugins\n * - List of missing plugins\n * - Import statements for missing plugins (if known)\n * - Example server configuration\n *\n * @param result - Failed validation result\n * @returns Formatted error message string\n *\n * @example\n * ```typescript\n * const result = validatePluginRequirements({ ... });\n * if (!result.success) {\n * const message = formatPluginValidationError(result);\n * throw new Error(message);\n * }\n * ```\n */\nexport function formatPluginValidationError(\n result: Extract<PluginValidationResult, { success: false }>,\n): string {\n const lines: string[] = [\n \"Server initialization failed: Missing required plugins\",\n \"\",\n `Required: ${result.required.join(\", \")}`,\n `Provided: ${result.provided.length > 0 ? result.provided.join(\", \") : \"(none)\"}`,\n `Missing: ${result.missing.join(\", \")}`,\n \"\",\n ];\n\n if (result.suggestions.length > 0) {\n lines.push(\"Add the missing plugins to your configuration:\");\n lines.push(\"\");\n for (const suggestion of result.suggestions) {\n lines.push(` ${suggestion.importStatement}`);\n }\n lines.push(\"\");\n lines.push(\" const server = await createUploadistaServer({\");\n lines.push(\n ` plugins: [${[...result.provided, ...result.missing.map((m) => KNOWN_PLUGINS[m]?.variableName || m)].join(\", \")}],`,\n );\n lines.push(\" // ...\");\n lines.push(\" });\");\n } else {\n lines.push(\"Note: Could not determine package names for missing plugins.\");\n lines.push(\"Please ensure all required plugin layers are provided.\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Effect-based plugin validation that can be composed with other Effects.\n *\n * This provides an Effect-TS native way to validate plugins, allowing it\n * to be composed with other Effects in the server initialization pipeline.\n *\n * @param config - Validation configuration\n * @returns Effect that succeeds if validation passes, fails with UploadistaError if not\n *\n * @example\n * ```typescript\n * const validatedServer = Effect.gen(function* () {\n * yield* validatePluginRequirementsEffect({\n * plugins: [sharpImagePlugin],\n * expectedServices: ['ImagePlugin', 'ZipPlugin']\n * });\n *\n * return yield* createServerEffect(...);\n * });\n * ```\n */\nexport function validatePluginRequirementsEffect(config: {\n plugins: readonly PluginLayer[];\n expectedServices?: string[];\n}): Effect.Effect<void, Error> {\n return Effect.sync(() => {\n const result = validatePluginRequirements(config);\n\n if (!result.success) {\n const message = formatPluginValidationError(result);\n throw new Error(message);\n }\n });\n}\n\n/**\n * Validates plugin configuration at runtime during server initialization.\n *\n * This is a convenience function that performs validation and throws a\n * descriptive error if validation fails. Use this at the beginning of\n * createUploadistaServer to fail fast with clear error messages.\n *\n * @param config - Validation configuration\n * @throws Error with detailed message if validation fails\n *\n * @example\n * ```typescript\n * export const createUploadistaServer = async (config) => {\n * // Validate plugins early\n * validatePluginsOrThrow({\n * plugins: config.plugins,\n * expectedServices: ['ImagePlugin', 'ZipPlugin']\n * });\n *\n * // Continue with server creation...\n * };\n * ```\n */\nexport function validatePluginsOrThrow(config: {\n plugins: readonly PluginLayer[];\n expectedServices?: string[];\n}): void {\n const result = validatePluginRequirements(config);\n\n if (!result.success) {\n const message = formatPluginValidationError(result);\n throw new Error(message);\n }\n}\n","import type { FlowEngineShape } from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport type { WebSocketConnection } from \"../websocket-routes\";\n\n/**\n * Handles subscription to flow events\n * Subscribes the WebSocket connection to receive real-time flow execution events\n */\nexport const handleSubscribeToFlowEvents = (\n flowEngine: FlowEngineShape,\n jobId: string | undefined,\n connection: WebSocketConnection,\n) => {\n return Effect.gen(function* () {\n if (!jobId) {\n yield* Effect.sync(() => {\n connection.send(\n JSON.stringify({\n type: \"error\",\n message: \"Job ID is required for flow event subscription\",\n code: \"MISSING_JOB_ID\",\n }),\n );\n });\n return;\n }\n\n yield* flowEngine.subscribeToFlowEvents(jobId, connection);\n });\n};\n\n/**\n * Handles unsubscription from flow events\n * Removes the WebSocket connection from receiving flow events\n */\nexport const handleUnsubscribeFromFlowEvents = (\n flowEngine: FlowEngineShape,\n jobId: string | undefined,\n) => {\n return Effect.gen(function* () {\n if (!jobId) {\n return;\n }\n\n yield* flowEngine.unsubscribeFromFlowEvents(jobId);\n });\n};\n","import type { UploadEngineShape } from \"@uploadista/core/upload\";\nimport { Effect } from \"effect\";\nimport type { WebSocketConnection } from \"../websocket-routes\";\n\n/**\n * Handles subscription to upload events\n * Subscribes the WebSocket connection to receive real-time upload progress events\n */\nexport const handleSubscribeToUploadEvents = (\n uploadEngine: UploadEngineShape,\n uploadId: string | undefined,\n connection: WebSocketConnection,\n) => {\n return Effect.gen(function* () {\n if (!uploadId) {\n yield* Effect.sync(() => {\n connection.send(\n JSON.stringify({\n type: \"error\",\n message: \"Upload ID is required for upload event subscription\",\n code: \"MISSING_UPLOAD_ID\",\n }),\n );\n });\n return;\n }\n\n yield* uploadEngine.subscribeToUploadEvents(uploadId, connection);\n });\n};\n\n/**\n * Handles unsubscription from upload events\n * Removes the WebSocket connection from receiving upload events\n */\nexport const handleUnsubscribeFromUploadEvents = (\n uploadEngine: UploadEngineShape,\n uploadId: string | undefined,\n) => {\n return Effect.gen(function* () {\n if (!uploadId) {\n return;\n }\n\n yield* uploadEngine.unsubscribeFromUploadEvents(uploadId);\n });\n};\n","import { UploadistaError } from \"@uploadista/core/errors\";\nimport type { FlowEngineShape } from \"@uploadista/core/flow\";\nimport type { UploadEngineShape } from \"@uploadista/core/upload\";\nimport { Effect } from \"effect\";\nimport type {\n WebSocketConnection,\n WebSocketConnectionRequest,\n} from \"../websocket-routes\";\nimport {\n handleSubscribeToFlowEvents,\n handleUnsubscribeFromFlowEvents,\n} from \"./flow-websocket-handlers\";\nimport {\n handleSubscribeToUploadEvents,\n handleUnsubscribeFromUploadEvents,\n} from \"./upload-websocket-handlers\";\n\nexport type {\n WebSocketConnection,\n WebSocketConnectionRequest,\n} from \"../websocket-routes\";\n\n/**\n * Handles WebSocket connection opening\n * Subscribes to the appropriate events based on the connection request\n */\nexport const handleWebSocketOpen = (\n request: WebSocketConnectionRequest,\n uploadEngine: UploadEngineShape,\n flowEngine: FlowEngineShape,\n) => {\n const { connection, isFlowRoute, isUploadRoute, jobId, uploadId, eventId } =\n request;\n\n return Effect.gen(function* () {\n // Subscribe to flow events if this is a flow route\n if (isFlowRoute) {\n yield* handleSubscribeToFlowEvents(flowEngine, jobId, connection);\n }\n\n // Subscribe to upload events if this is an upload route\n if (isUploadRoute) {\n yield* handleSubscribeToUploadEvents(uploadEngine, uploadId, connection);\n }\n\n // Send connection confirmation\n connection.send(\n JSON.stringify({\n type: \"connection\",\n message: \"Uploadista WebSocket connected\",\n id: eventId,\n jobId,\n uploadId,\n timestamp: new Date().toISOString(),\n }),\n );\n }).pipe(\n Effect.catchAll((error) =>\n Effect.sync(() => {\n console.error(\"Error subscribing to events:\", error);\n const errorMessage =\n error instanceof UploadistaError\n ? error.body\n : \"Failed to subscribe to events\";\n connection.send(\n JSON.stringify({\n type: \"error\",\n message: errorMessage,\n code:\n error instanceof UploadistaError\n ? error.code\n : \"SUBSCRIPTION_ERROR\",\n }),\n );\n }),\n ),\n );\n};\n\n/**\n * Handles incoming WebSocket messages\n * Currently supports ping/pong for connection keep-alive\n */\nexport const handleWebSocketMessage = (\n message: string,\n connection: WebSocketConnection,\n) => {\n return Effect.sync(() => {\n try {\n const parsed = JSON.parse(message);\n if (parsed.type === \"ping\") {\n connection.send(\n JSON.stringify({\n type: \"pong\",\n timestamp: new Date().toISOString(),\n }),\n );\n }\n } catch (error) {\n console.error(\"Error handling WebSocket message:\", error);\n connection.send(\n JSON.stringify({\n type: \"error\",\n message: \"Invalid message format\",\n }),\n );\n }\n });\n};\n\n/**\n * Handles WebSocket connection closing\n * Unsubscribes from all events and cleans up resources\n */\nexport const handleWebSocketClose = (\n request: WebSocketConnectionRequest,\n uploadEngine: UploadEngineShape,\n flowEngine: FlowEngineShape,\n) => {\n const { isFlowRoute, isUploadRoute, jobId, uploadId } = request;\n\n return Effect.gen(function* () {\n // Unsubscribe from flow events if this was a flow route\n if (isFlowRoute) {\n yield* handleUnsubscribeFromFlowEvents(flowEngine, jobId);\n }\n\n // Unsubscribe from upload events if this was an upload route\n if (isUploadRoute) {\n yield* handleUnsubscribeFromUploadEvents(uploadEngine, uploadId);\n }\n }).pipe(\n Effect.catchAll((error) =>\n Effect.sync(() => {\n console.error(\n \"Error unsubscribing from events:\",\n error instanceof UploadistaError ? error.body : error,\n );\n }),\n ),\n );\n};\n\n/**\n * Handles WebSocket errors\n */\nexport const handleWebSocketError = (error: unknown, eventId?: string) => {\n return Effect.sync(() => {\n console.error(`WebSocket error for event ${eventId}:`, error);\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["uploadEngine","matchHasPermission","matchHasAnyPermission","flowEngine","uploadEngine","flowEngine","uploadEngine","flowEngine","uploadEngine"],"sources":["../src/cache.ts","../src/http-utils.ts","../src/layer-utils.ts","../src/error-types.ts","../src/permissions/errors.ts","../src/permissions/types.ts","../src/permissions/matcher.ts","../src/service.ts","../src/usage-hooks/types.ts","../src/usage-hooks/service.ts","../src/core/http-handlers/dlq-http-handlers.ts","../src/core/http-handlers/flow-http-handlers.ts","../src/core/health-check-service.ts","../src/core/http-handlers/health-http-handlers.ts","../src/core/http-handlers/upload-http-handlers.ts","../src/core/http-handlers/http-handlers.ts","../src/core/server.ts","../src/core/create-type-safe-server.ts","../src/core/plugin-validation.ts","../src/core/websocket-handlers/flow-websocket-handlers.ts","../src/core/websocket-handlers/upload-websocket-handlers.ts","../src/core/websocket-handlers/websocket-handlers.ts"],"sourcesContent":["import { Context, Effect, Layer } from \"effect\";\nimport type { AuthContext } from \"./types\";\n\n/**\n * Configuration options for the auth cache.\n */\nexport type AuthCacheConfig = {\n /**\n * Maximum number of entries in the cache.\n * When exceeded, oldest entries are removed (LRU eviction).\n * @default 10000\n */\n maxSize?: number;\n\n /**\n * Time-to-live for cache entries in milliseconds.\n * Entries older than this will be automatically evicted.\n * @default 3600000 (1 hour)\n */\n ttl?: number;\n};\n\n/**\n * Cache entry with auth context and timestamp.\n */\ntype CacheEntry = {\n authContext: AuthContext;\n timestamp: number;\n};\n\n/**\n * Auth Cache Service\n *\n * Provides caching of authentication contexts for upload and flow jobs.\n * This allows subsequent operations (chunk uploads, flow continuations)\n * to reuse the auth context from the initial request without re-authenticating.\n *\n * @example\n * ```typescript\n * import { Effect } from \"effect\";\n * import { AuthCacheService } from \"@uploadista/server\";\n *\n * const handler = Effect.gen(function* () {\n * const authCache = yield* AuthCacheService;\n * const authContext = { userId: \"user-123\" };\n *\n * // Cache auth for upload\n * yield* authCache.set(\"upload-abc\", authContext);\n *\n * // Retrieve cached auth later\n * const cached = yield* authCache.get(\"upload-abc\");\n * console.log(cached?.userId); // \"user-123\"\n *\n * // Clear when done\n * yield* authCache.delete(\"upload-abc\");\n * });\n * ```\n */\nexport class AuthCacheService extends Context.Tag(\"AuthCacheService\")<\n AuthCacheService,\n {\n /**\n * Store an auth context for a job ID.\n */\n readonly set: (\n jobId: string,\n authContext: AuthContext,\n ) => Effect.Effect<void>;\n\n /**\n * Retrieve a cached auth context by job ID.\n * Returns null if not found or expired.\n */\n readonly get: (jobId: string) => Effect.Effect<AuthContext | null>;\n\n /**\n * Delete a cached auth context by job ID.\n */\n readonly delete: (jobId: string) => Effect.Effect<void>;\n\n /**\n * Clear all cached auth contexts.\n */\n readonly clear: () => Effect.Effect<void>;\n\n /**\n * Get the current number of cached entries.\n */\n readonly size: () => Effect.Effect<number>;\n }\n>() {}\n\n/**\n * Creates an AuthCacheService Layer with in-memory storage.\n *\n * @param config - Optional configuration for cache behavior\n * @returns Effect Layer providing AuthCacheService\n */\nexport const AuthCacheServiceLive = (\n config: AuthCacheConfig = {},\n): Layer.Layer<AuthCacheService> => {\n const maxSize = config.maxSize ?? 10000;\n const ttl = config.ttl ?? 3600000; // 1 hour default\n\n // In-memory cache storage\n const cache = new Map<string, CacheEntry>();\n\n /**\n * Evict expired entries based on TTL.\n */\n const evictExpired = (): void => {\n const now = Date.now();\n for (const [jobId, entry] of cache.entries()) {\n if (now - entry.timestamp > ttl) {\n cache.delete(jobId);\n }\n }\n };\n\n /**\n * Enforce max size limit using LRU eviction.\n * Removes oldest entry when cache exceeds max size.\n */\n const enforceSizeLimit = (): void => {\n if (cache.size <= maxSize) return;\n\n // Find and remove oldest entry\n let oldestKey: string | null = null;\n let oldestTime = Number.POSITIVE_INFINITY;\n\n for (const [jobId, entry] of cache.entries()) {\n if (entry.timestamp < oldestTime) {\n oldestTime = entry.timestamp;\n oldestKey = jobId;\n }\n }\n\n if (oldestKey) {\n cache.delete(oldestKey);\n }\n };\n\n return Layer.succeed(AuthCacheService, {\n set: (jobId: string, authContext: AuthContext) =>\n Effect.sync(() => {\n // Evict expired entries periodically\n if (cache.size % 100 === 0) {\n evictExpired();\n }\n\n cache.set(jobId, {\n authContext,\n timestamp: Date.now(),\n });\n\n // Enforce size limit after adding\n enforceSizeLimit();\n }),\n\n get: (jobId: string) =>\n Effect.sync(() => {\n const entry = cache.get(jobId);\n if (!entry) return null;\n\n // Check if expired\n const now = Date.now();\n if (now - entry.timestamp > ttl) {\n cache.delete(jobId);\n return null;\n }\n\n return entry.authContext;\n }),\n\n delete: (jobId: string) =>\n Effect.sync(() => {\n cache.delete(jobId);\n }),\n\n clear: () =>\n Effect.sync(() => {\n cache.clear();\n }),\n\n size: () =>\n Effect.sync(() => {\n return cache.size;\n }),\n });\n};\n\n/**\n * No-op implementation of AuthCacheService.\n * Does not cache anything - all operations are no-ops.\n * Used when caching is disabled or not needed.\n */\nexport const NoAuthCacheServiceLive: Layer.Layer<AuthCacheService> =\n Layer.succeed(AuthCacheService, {\n set: () => Effect.void,\n get: () => Effect.succeed(null),\n delete: () => Effect.void,\n clear: () => Effect.void,\n size: () => Effect.succeed(0),\n });\n","/**\n * Shared HTTP utilities for server adapters\n *\n * This module provides routing and error handling utilities used across\n * Hono, Express, and Fastify adapters for request parsing and response formatting.\n */\n\n/**\n * Parses URL segments from a pathname, filtering out empty segments.\n * Useful for extracting route components from request paths.\n *\n * @param pathname - The URL pathname (e.g., \"/uploadista/api/upload/abc123\")\n * @returns Array of non-empty path segments\n *\n * @example\n * ```typescript\n * const segments = parseUrlSegments(\"/uploadista/api/upload/abc123\");\n * // => [\"uploadista\", \"api\", \"upload\", \"abc123\"]\n * ```\n */\nexport const parseUrlSegments = (pathname: string): string[] => {\n return pathname.split(\"/\").filter(Boolean);\n};\n\n/**\n * Extracts the last segment from a URL pathname.\n *\n * @param pathname - The URL pathname to parse\n * @returns The last non-empty segment, or undefined if none exists\n *\n * @example\n * ```typescript\n * const id = getLastSegment(\"/uploadista/api/upload/abc123\");\n * // => \"abc123\"\n * ```\n */\nexport const getLastSegment = (pathname: string): string | undefined => {\n const segments = parseUrlSegments(pathname);\n return segments[segments.length - 1];\n};\n\n/**\n * Checks if a pathname includes a specific base path and API prefix.\n * Used to determine if a request should be handled by the Uploadista adapter.\n *\n * @param pathname - The request pathname\n * @param basePath - The base path configured for the adapter (e.g., \"uploadista\")\n * @returns true if the path includes `{basePath}/api/`\n *\n * @example\n * ```typescript\n * const isUploadistaPath = hasBasePath(\"/uploadista/api/upload\", \"uploadista\");\n * // => true\n * ```\n */\nexport const hasBasePath = (pathname: string, basePath: string): boolean => {\n return pathname.includes(`${basePath}/api/`);\n};\n\n/**\n * Removes the base path prefix and returns clean route segments.\n * Transforms \"/uploadista/api/upload/abc123\" → [\"upload\", \"abc123\"]\n *\n * @param pathname - The full request pathname\n * @param basePath - The base path to remove (e.g., \"uploadista\")\n * @returns Array of route segments without base path prefix\n *\n * @example\n * ```typescript\n * const route = getRouteSegments(\"/uploadista/api/upload/abc123\", \"uploadista\");\n * // => [\"upload\", \"abc123\"]\n * ```\n */\nexport const getRouteSegments = (\n pathname: string,\n basePath: string,\n): string[] => {\n return pathname.replace(`${basePath}/api/`, \"\").split(\"/\").filter(Boolean);\n};\n\n/**\n * Standard error handler for flow and job operations.\n * Maps application errors to appropriate HTTP status codes and error formats.\n *\n * Supports errors with `code`, `message`, `status`, and `details` properties.\n * Maps error codes to HTTP status codes (e.g., NOT_FOUND → 404, VALIDATION_ERROR → 400).\n *\n * @param error - The error object to handle (can be any type)\n * @returns Standardized error response with status, code, message, and optional details\n *\n * @example\n * ```typescript\n * import { handleFlowError } from \"@uploadista/server\";\n *\n * const response = handleFlowError({\n * code: \"FLOW_JOB_NOT_FOUND\",\n * message: \"Job not found\",\n * });\n * // => { status: 404, code: \"FLOW_JOB_NOT_FOUND\", message: \"Job not found\" }\n * ```\n */\nexport const handleFlowError = (\n error: unknown,\n): { status: number; code: string; message: string; details?: unknown } => {\n let status = 500;\n let code = \"UNKNOWN_ERROR\";\n let message = \"Internal server error\";\n let details: unknown;\n\n if (typeof error === \"object\" && error !== null) {\n const errorObj = error as Record<string, unknown>;\n\n // Extract error code\n if (\"code\" in errorObj && typeof errorObj.code === \"string\") {\n code = errorObj.code;\n }\n\n // Extract message\n if (\"message\" in errorObj && typeof errorObj.message === \"string\") {\n message = errorObj.message;\n } else if (\"body\" in errorObj && typeof errorObj.body === \"string\") {\n // Support UploadistaError's body property\n message = errorObj.body;\n }\n\n // Extract details if present\n if (\"details\" in errorObj) {\n details = errorObj.details;\n }\n\n // Map error codes to HTTP status codes\n if (\"status\" in errorObj && typeof errorObj.status === \"number\") {\n status = errorObj.status;\n } else if (\"code\" in errorObj) {\n // Fallback: derive status from common error codes\n switch (errorObj.code) {\n case \"FILE_NOT_FOUND\":\n case \"FLOW_JOB_NOT_FOUND\":\n case \"UPLOAD_ID_NOT_FOUND\":\n status = 404;\n break;\n case \"FLOW_JOB_ERROR\":\n case \"VALIDATION_ERROR\":\n case \"INVALID_METADATA\":\n case \"INVALID_LENGTH\":\n case \"ABORTED\":\n case \"INVALID_TERMINATION\":\n status = 400;\n break;\n case \"INVALID_OFFSET\":\n status = 409;\n break;\n case \"ERR_SIZE_EXCEEDED\":\n case \"ERR_MAX_SIZE_EXCEEDED\":\n status = 413;\n break;\n case \"FILE_NO_LONGER_EXISTS\":\n status = 410;\n break;\n case \"MISSING_OFFSET\":\n case \"INVALID_CONTENT_TYPE\":\n status = 403;\n break;\n default:\n status = 500;\n }\n }\n\n // Special handling for specific error messages\n if (\"message\" in errorObj && errorObj.message === \"Invalid JSON body\") {\n status = 400;\n code = \"VALIDATION_ERROR\";\n }\n }\n\n const result: {\n status: number;\n code: string;\n message: string;\n details?: unknown;\n } = {\n status,\n code,\n message,\n };\n\n if (details !== undefined) {\n result.details = details;\n }\n\n return result;\n};\n\n/**\n * Extracts job ID from URL segments for job status endpoint.\n * Expected URL format: `/uploadista/api/jobs/:jobId/status`\n *\n * @param urlSegments - Parsed URL segments (without base path)\n * @returns The job ID if found, or undefined\n *\n * @example\n * ```typescript\n * const jobId = extractJobIdFromStatus([\"jobs\", \"job-123\", \"status\"]);\n * // => \"job-123\"\n * ```\n */\nexport const extractJobIdFromStatus = (\n urlSegments: string[],\n): string | undefined => {\n return urlSegments[urlSegments.length - 2];\n};\n\n/**\n * Extracts job ID and node ID from URL segments for resume flow endpoint.\n * Expected URL format: `/uploadista/api/jobs/:jobId/resume/:nodeId`\n *\n * @param urlSegments - Parsed URL segments (without base path)\n * @returns Object with extracted jobId and nodeId (either can be undefined if not found)\n *\n * @example\n * ```typescript\n * const { jobId, nodeId } = extractJobAndNodeId([\n * \"jobs\",\n * \"job-123\",\n * \"resume\",\n * \"node-456\",\n * ]);\n * // => { jobId: \"job-123\", nodeId: \"node-456\" }\n * ```\n */\nexport const extractJobAndNodeId = (\n urlSegments: string[],\n): { jobId: string | undefined; nodeId: string | undefined } => {\n return {\n jobId: urlSegments[urlSegments.length - 3],\n nodeId: urlSegments[urlSegments.length - 1],\n };\n};\n\n/**\n * Extracts flow ID and storage ID from URL segments.\n * Expected URL format: `/uploadista/api/flow/:flowId/:storageId`\n *\n * Mutates the input array (removes last 2 elements).\n *\n * @param urlSegments - Parsed URL segments (will be mutated)\n * @returns Object with extracted flowId and storageId\n *\n * @example\n * ```typescript\n * const segments = [\"flow\", \"flow-123\", \"storage-456\"];\n * const { flowId, storageId } = extractFlowAndStorageId(segments);\n * // => { flowId: \"flow-123\", storageId: \"storage-456\" }\n * // segments is now [\"flow\"]\n * ```\n */\nexport const extractFlowAndStorageId = (\n urlSegments: string[],\n): { flowId: string | undefined; storageId: string | undefined } => {\n return {\n storageId: urlSegments.pop(),\n flowId: urlSegments.pop(),\n };\n};\n","import type { FlowProvider } from \"@uploadista/core/flow\";\nimport { flowEngine } from \"@uploadista/core/flow\";\nimport {\n type BaseEventEmitterService,\n type BaseKvStoreService,\n flowEventEmitter,\n flowJobKvStore,\n type UploadFileDataStore,\n type UploadFileDataStores,\n type UploadFileKVStore,\n uploadEventEmitter,\n uploadFileKvStore,\n} from \"@uploadista/core/types\";\nimport { type UploadEngine, uploadEngine } from \"@uploadista/core/upload\";\nimport type { GenerateId } from \"@uploadista/core/utils\";\nimport { Layer } from \"effect\";\n\n/**\n * Configuration for creating upload engine layers.\n * Specifies all dependencies needed by the upload engine Effect Layer.\n *\n * @property kvStore - Key-value store for upload metadata\n * @property eventEmitter - Event emitter for upload progress events\n * @property dataStore - File data storage implementation\n * @property bufferedDataStore - Optional buffered storage for performance optimization\n * @property generateId - Optional custom ID generator (uses default if omitted)\n *\n * @example\n * ```typescript\n * import { createUploadEngineLayer } from \"@uploadista/server\";\n *\n * const uploadLayerConfig: UploadEngineLayerConfig = {\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * dataStore: s3DataStore,\n * };\n * ```\n */\nexport interface UploadEngineLayerConfig {\n kvStore: Layer.Layer<BaseKvStoreService>;\n eventEmitter: Layer.Layer<BaseEventEmitterService>;\n dataStore: Layer.Layer<UploadFileDataStores, never, UploadFileKVStore>;\n bufferedDataStore?: Layer.Layer<\n UploadFileDataStore,\n never,\n UploadFileKVStore\n >;\n generateId?: Layer.Layer<GenerateId>;\n}\n\n/**\n * Configuration for creating flow server layers.\n * Specifies all dependencies needed by the flow processing server.\n *\n * @property kvStore - Key-value store for flow job metadata\n * @property eventEmitter - Event emitter for flow progress events\n * @property flowProvider - Factory function for creating flows\n * @property uploadEngine - Upload engine layer (used by flows for uploads)\n *\n * @example\n * ```typescript\n * import { createFlowServerLayer } from \"@uploadista/server\";\n *\n * const flowLayerConfig: FlowServerLayerConfig = {\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * flowProvider: createFlowsEffect,\n * uploadEngine: uploadEngineLayer,\n * };\n * ```\n */\nexport interface FlowServerLayerConfig {\n kvStore: Layer.Layer<BaseKvStoreService>;\n eventEmitter: Layer.Layer<BaseEventEmitterService>;\n flowProvider: Layer.Layer<FlowProvider>;\n uploadEngine: Layer.Layer<UploadEngine>;\n}\n\n/**\n * Creates the upload server layer with all dependencies composed.\n * This layer handles file uploads with chunked transfer, resumption, and metadata tracking.\n *\n * The created layer includes:\n * - Upload KV store (metadata tracking)\n * - Data store (file storage)\n * - Event emitter (progress notifications)\n * - Optional buffered data store (performance optimization)\n * - Optional custom ID generator\n *\n * @param config - Upload server layer configuration\n * @returns Effect Layer providing UploadEngine\n *\n * @example\n * ```typescript\n * import { createUploadEngineLayer } from \"@uploadista/server\";\n * import { Layer } from \"effect\";\n *\n * const uploadEngineLayer = createUploadEngineLayer({\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * dataStore: s3DataStore,\n * });\n *\n * // Use in application\n * const app = Layer.provide(appLogic, uploadEngineLayer);\n * ```\n */\nexport const createUploadEngineLayer = ({\n kvStore,\n eventEmitter,\n dataStore,\n bufferedDataStore,\n generateId,\n}: UploadEngineLayerConfig) => {\n // Set up upload server dependencies\n const uploadFileKVStoreLayer = Layer.provide(uploadFileKvStore, kvStore);\n const uploadDataStoreLayer = Layer.provide(dataStore, uploadFileKVStoreLayer);\n const uploadBufferedDataStoreLayer = bufferedDataStore\n ? Layer.provide(bufferedDataStore, uploadFileKVStoreLayer)\n : Layer.empty;\n const uploadEventEmitterLayer = Layer.provide(\n uploadEventEmitter,\n eventEmitter,\n );\n\n const uploadEngineLayers = Layer.mergeAll(\n uploadDataStoreLayer,\n uploadFileKVStoreLayer,\n uploadEventEmitterLayer,\n ...(generateId ? [generateId] : []),\n uploadBufferedDataStoreLayer,\n );\n\n return Layer.provide(uploadEngine, uploadEngineLayers);\n};\n\n/**\n * Creates the flow server layer with all dependencies composed.\n * This layer handles file processing workflows with multi-stage pipelines.\n *\n * The created layer includes:\n * - Flow job KV store (job metadata and state)\n * - Event emitter (progress notifications)\n * - Flow provider (flow definitions)\n * - Upload server (for uploads within flows)\n *\n * @param config - Flow server layer configuration\n * @returns Effect Layer providing FlowServer\n *\n * @example\n * ```typescript\n * import { createFlowServerLayer } from \"@uploadista/server\";\n * import { Layer } from \"effect\";\n *\n * const flowServerLayer = createFlowServerLayer({\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * flowProvider: createFlowsEffect,\n * uploadEngine: uploadEngineLayer,\n * });\n *\n * // Use in application\n * const app = Layer.provide(appLogic, flowServerLayer);\n * ```\n */\nexport const createFlowEngineLayer = ({\n kvStore,\n eventEmitter,\n flowProvider,\n uploadEngine,\n}: FlowServerLayerConfig) => {\n // Set up flow server dependencies\n const flowJobKVStoreLayer = Layer.provide(flowJobKvStore, kvStore);\n const flowEventEmitterLayer = Layer.provide(flowEventEmitter, eventEmitter);\n\n const flowEngineLayers = Layer.mergeAll(\n flowProvider,\n flowEventEmitterLayer,\n flowJobKVStoreLayer,\n uploadEngine,\n );\n\n return Layer.provide(flowEngine, flowEngineLayers);\n};\n","import type { UploadistaError } from \"@uploadista/core/errors\";\n\n/**\n * Base adapter error class for HTTP adapters.\n * All adapter-specific errors should extend this class or one of its subclasses.\n *\n * @example\n * ```typescript\n * throw new AdapterError(\"Something went wrong\", 500, \"INTERNAL_ERROR\");\n * ```\n */\nexport class AdapterError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number = 500,\n public readonly errorCode: string = \"INTERNAL_ERROR\",\n ) {\n super(message);\n this.name = \"AdapterError\";\n }\n}\n\n/**\n * Validation error - indicates invalid request data or parameters.\n * Returns HTTP 400 Bad Request status.\n *\n * @example\n * ```typescript\n * if (!isValidUploadId(id)) {\n * throw new ValidationError(\"Invalid upload ID format\");\n * }\n * ```\n */\nexport class ValidationError extends AdapterError {\n constructor(message: string) {\n super(message, 400, \"VALIDATION_ERROR\");\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * Not found error - indicates a requested resource does not exist.\n * Returns HTTP 404 Not Found status.\n *\n * @example\n * ```typescript\n * if (!upload) {\n * throw new NotFoundError(\"Upload\");\n * }\n * ```\n */\nexport class NotFoundError extends AdapterError {\n constructor(resource: string) {\n super(`${resource} not found`, 404, \"NOT_FOUND\");\n this.name = \"NotFoundError\";\n }\n}\n\n/**\n * Bad request error - indicates a malformed request.\n * Returns HTTP 400 Bad Request status.\n * Similar to ValidationError but for request structure issues.\n *\n * @example\n * ```typescript\n * try {\n * const data = JSON.parse(body);\n * } catch {\n * throw new BadRequestError(\"Invalid JSON body\");\n * }\n * ```\n */\nexport class BadRequestError extends AdapterError {\n constructor(message: string) {\n super(message, 400, \"BAD_REQUEST\");\n this.name = \"BadRequestError\";\n }\n}\n\n/**\n * Creates a standardized error response object for AdapterError.\n * Includes error message, error code, and ISO timestamp.\n *\n * @param error - The AdapterError to format\n * @returns Standardized error response body\n *\n * @example\n * ```typescript\n * import { createErrorResponseBody } from \"@uploadista/server\";\n *\n * try {\n * // ... operation\n * } catch (err) {\n * const errorResponse = createErrorResponseBody(err);\n * res.status(err.statusCode).json(errorResponse);\n * }\n * ```\n */\nexport const createErrorResponseBody = (error: AdapterError) => ({\n error: error.message,\n code: error.errorCode,\n timestamp: new Date().toISOString(),\n});\n\n/**\n * Creates a standardized error response body from UploadistaError.\n * Formats core library errors for HTTP responses with optional details.\n *\n * @param error - The UploadistaError to format\n * @returns Standardized error response body with error, code, timestamp, and optional details\n *\n * @example\n * ```typescript\n * import { createUploadistaErrorResponseBody } from \"@uploadista/server\";\n *\n * try {\n * const result = yield* uploadEngine.handleUpload(input);\n * } catch (err) {\n * if (err instanceof UploadistaError) {\n * const errorResponse = createUploadistaErrorResponseBody(err);\n * res.status(400).json(errorResponse);\n * }\n * }\n * ```\n */\nexport const createUploadistaErrorResponseBody = (error: UploadistaError) => {\n const response: {\n error: string;\n code: string;\n timestamp: string;\n details?: unknown;\n } = {\n error: error.body,\n code: error.code,\n timestamp: new Date().toISOString(),\n };\n\n if (error.details !== undefined) {\n response.details = error.details;\n }\n\n return response;\n};\n\n/**\n * Creates a generic error response body for unknown/unexpected errors.\n * Used as a fallback when error type cannot be determined.\n *\n * @param message - Error message to include in response (defaults to \"Internal server error\")\n * @returns Standardized error response body with generic INTERNAL_ERROR code\n *\n * @example\n * ```typescript\n * import { createGenericErrorResponseBody } from \"@uploadista/server\";\n *\n * try {\n * // ... operation\n * } catch (err) {\n * const errorResponse = createGenericErrorResponseBody(\n * err instanceof Error ? err.message : \"Unknown error\"\n * );\n * res.status(500).json(errorResponse);\n * }\n * ```\n */\nexport const createGenericErrorResponseBody = (\n message = \"Internal server error\",\n) => ({\n error: message,\n code: \"INTERNAL_ERROR\",\n timestamp: new Date().toISOString(),\n});\n","/**\n * Authorization Error Types\n *\n * Error classes for permission and authorization failures.\n */\n\nimport { AdapterError } from \"../error-types\";\n\n/**\n * Authorization error - indicates the user lacks required permissions.\n * Returns HTTP 403 Forbidden status.\n *\n * @example\n * ```typescript\n * if (!hasPermission(permissions, \"engine:metrics\")) {\n * throw new AuthorizationError(\"engine:metrics\");\n * }\n * ```\n */\nexport class AuthorizationError extends AdapterError {\n /**\n * The permission that was required but not granted.\n */\n public readonly requiredPermission: string;\n\n constructor(requiredPermission: string, message?: string) {\n super(\n message ?? `Permission denied: ${requiredPermission} required`,\n 403,\n \"PERMISSION_DENIED\",\n );\n this.name = \"AuthorizationError\";\n this.requiredPermission = requiredPermission;\n }\n}\n\n/**\n * Authentication required error - indicates no authentication context.\n * Returns HTTP 401 Unauthorized status.\n *\n * @example\n * ```typescript\n * if (!authContext) {\n * throw new AuthenticationRequiredError();\n * }\n * ```\n */\nexport class AuthenticationRequiredError extends AdapterError {\n constructor(message = \"Authentication required\") {\n super(message, 401, \"AUTHENTICATION_REQUIRED\");\n this.name = \"AuthenticationRequiredError\";\n }\n}\n\n/**\n * Organization mismatch error - indicates accessing a resource from another organization.\n * Returns HTTP 403 Forbidden status.\n *\n * @example\n * ```typescript\n * if (resource.organizationId !== clientId) {\n * throw new OrganizationMismatchError();\n * }\n * ```\n */\nexport class OrganizationMismatchError extends AdapterError {\n constructor(\n message = \"Access denied: resource belongs to another organization\",\n ) {\n super(message, 403, \"ORGANIZATION_MISMATCH\");\n this.name = \"OrganizationMismatchError\";\n }\n}\n\n/**\n * Quota exceeded error - indicates usage quota has been exceeded.\n * Returns HTTP 402 Payment Required status.\n *\n * @example\n * ```typescript\n * if (usage > quota) {\n * throw new QuotaExceededError(\"Storage quota exceeded\");\n * }\n * ```\n */\nexport class QuotaExceededError extends AdapterError {\n constructor(message = \"Quota exceeded\", code = \"QUOTA_EXCEEDED\") {\n super(message, 402, code);\n this.name = \"QuotaExceededError\";\n }\n}\n\n/**\n * Creates a standardized error response body for AuthorizationError.\n * Includes the required permission in the response.\n *\n * @param error - The AuthorizationError to format\n * @returns Standardized error response body\n */\nexport const createAuthorizationErrorResponseBody = (\n error: AuthorizationError,\n) => ({\n error: error.message,\n code: error.errorCode,\n requiredPermission: error.requiredPermission,\n timestamp: new Date().toISOString(),\n});\n","/**\n * Permission Types and Constants\n *\n * Defines the permission model for fine-grained access control in the uploadista engine.\n * Permissions follow a hierarchical format: `resource:action` with support for wildcards.\n */\n\n// ============================================================================\n// Engine Permissions - Admin operations\n// ============================================================================\n\n/**\n * Engine permissions for administrative operations.\n * These control access to health, readiness, metrics, and DLQ endpoints.\n */\nexport const ENGINE_PERMISSIONS = {\n /** Full admin access to all engine operations */\n ALL: \"engine:*\",\n /** Access health endpoint */\n HEALTH: \"engine:health\",\n /** Access readiness endpoint */\n READINESS: \"engine:readiness\",\n /** Access metrics endpoint */\n METRICS: \"engine:metrics\",\n /** Full DLQ access (implies read and write) */\n DLQ: \"engine:dlq\",\n /** Read DLQ entries */\n DLQ_READ: \"engine:dlq:read\",\n /** Retry/delete DLQ entries */\n DLQ_WRITE: \"engine:dlq:write\",\n} as const;\n\n// ============================================================================\n// Flow Permissions - Flow execution operations\n// ============================================================================\n\n/**\n * Flow permissions for flow execution operations.\n */\nexport const FLOW_PERMISSIONS = {\n /** Full access to all flow operations */\n ALL: \"flow:*\",\n /** Execute flows */\n EXECUTE: \"flow:execute\",\n /** Cancel running flows */\n CANCEL: \"flow:cancel\",\n /** Check flow status */\n STATUS: \"flow:status\",\n} as const;\n\n// ============================================================================\n// Upload Permissions - File upload operations\n// ============================================================================\n\n/**\n * Upload permissions for file upload operations.\n */\nexport const UPLOAD_PERMISSIONS = {\n /** Full access to all upload operations */\n ALL: \"upload:*\",\n /** Create uploads */\n CREATE: \"upload:create\",\n /** Read upload status */\n READ: \"upload:read\",\n /** Cancel uploads */\n CANCEL: \"upload:cancel\",\n} as const;\n\n// ============================================================================\n// Combined Permissions Object\n// ============================================================================\n\n/**\n * All available permissions organized by category.\n *\n * @example\n * ```typescript\n * import { PERMISSIONS } from \"@uploadista/server\";\n *\n * const adminPermissions = [PERMISSIONS.ENGINE.ALL];\n * const userPermissions = [PERMISSIONS.FLOW.ALL, PERMISSIONS.UPLOAD.ALL];\n * ```\n */\nexport const PERMISSIONS = {\n ENGINE: ENGINE_PERMISSIONS,\n FLOW: FLOW_PERMISSIONS,\n UPLOAD: UPLOAD_PERMISSIONS,\n} as const;\n\n// ============================================================================\n// Permission Type Definitions\n// ============================================================================\n\n/** All engine permission strings */\nexport type EnginePermission =\n (typeof ENGINE_PERMISSIONS)[keyof typeof ENGINE_PERMISSIONS];\n\n/** All flow permission strings */\nexport type FlowPermission =\n (typeof FLOW_PERMISSIONS)[keyof typeof FLOW_PERMISSIONS];\n\n/** All upload permission strings */\nexport type UploadPermission =\n (typeof UPLOAD_PERMISSIONS)[keyof typeof UPLOAD_PERMISSIONS];\n\n/**\n * Union type of all valid permission strings.\n * Includes standard permissions and allows custom permissions via string.\n */\nexport type Permission =\n | EnginePermission\n | FlowPermission\n | UploadPermission\n | (string & {}); // Allow custom permissions while maintaining autocomplete\n\n/**\n * Predefined permission sets for common use cases.\n */\nexport const PERMISSION_SETS = {\n /** Full admin access - all engine, flow, and upload permissions */\n ADMIN: [ENGINE_PERMISSIONS.ALL] as const,\n\n /** Organization owner - all flow and upload permissions */\n ORGANIZATION_OWNER: [FLOW_PERMISSIONS.ALL, UPLOAD_PERMISSIONS.ALL] as const,\n\n /** Organization member - same as owner for now */\n ORGANIZATION_MEMBER: [FLOW_PERMISSIONS.ALL, UPLOAD_PERMISSIONS.ALL] as const,\n\n /** API key - limited to execute flows and create uploads */\n API_KEY: [FLOW_PERMISSIONS.EXECUTE, UPLOAD_PERMISSIONS.CREATE] as const,\n} as const;\n\n/**\n * Hierarchical permission relationships.\n * When a parent permission is granted, all child permissions are implied.\n */\nexport const PERMISSION_HIERARCHY: Record<string, readonly string[]> = {\n [ENGINE_PERMISSIONS.DLQ]: [\n ENGINE_PERMISSIONS.DLQ_READ,\n ENGINE_PERMISSIONS.DLQ_WRITE,\n ],\n} as const;\n","/**\n * Permission Matching Logic\n *\n * Implements permission matching with support for:\n * - Exact match: `engine:health` matches `engine:health`\n * - Wildcard match: `engine:*` matches `engine:health`, `engine:metrics`, etc.\n * - Hierarchical match: `engine:dlq` implies `engine:dlq:read` and `engine:dlq:write`\n */\n\nimport { PERMISSION_HIERARCHY } from \"./types\";\n\n/**\n * Checks if a granted permission matches a required permission.\n *\n * @param granted - The permission that has been granted to the user\n * @param required - The permission that is required for the operation\n * @returns true if the granted permission satisfies the required permission\n *\n * @example\n * ```typescript\n * matchesPermission(\"engine:*\", \"engine:health\") // true (wildcard)\n * matchesPermission(\"engine:health\", \"engine:health\") // true (exact)\n * matchesPermission(\"engine:dlq\", \"engine:dlq:read\") // true (hierarchical)\n * matchesPermission(\"flow:execute\", \"engine:health\") // false\n * ```\n */\nexport const matchesPermission = (\n granted: string,\n required: string,\n): boolean => {\n // Exact match\n if (granted === required) {\n return true;\n }\n\n // Wildcard match: `engine:*` matches `engine:health`\n if (granted.endsWith(\":*\")) {\n const prefix = granted.slice(0, -1); // Remove the `*`, keep the `:`\n if (required.startsWith(prefix)) {\n return true;\n }\n }\n\n // Hierarchical match: `engine:dlq` implies `engine:dlq:read`\n const impliedPermissions = PERMISSION_HIERARCHY[granted];\n if (impliedPermissions?.includes(required)) {\n return true;\n }\n\n return false;\n};\n\n/**\n * Checks if any of the granted permissions satisfy the required permission.\n *\n * @param grantedPermissions - Array of permissions granted to the user\n * @param required - The permission that is required for the operation\n * @returns true if any granted permission satisfies the required permission\n *\n * @example\n * ```typescript\n * hasPermission([\"flow:*\", \"upload:create\"], \"flow:execute\") // true\n * hasPermission([\"upload:create\"], \"flow:execute\") // false\n * ```\n */\nexport const hasPermission = (\n grantedPermissions: readonly string[],\n required: string,\n): boolean => {\n return grantedPermissions.some((granted) =>\n matchesPermission(granted, required),\n );\n};\n\n/**\n * Checks if any of the granted permissions satisfy any of the required permissions.\n *\n * @param grantedPermissions - Array of permissions granted to the user\n * @param requiredPermissions - Array of permissions, any of which would be sufficient\n * @returns true if any granted permission satisfies any required permission\n *\n * @example\n * ```typescript\n * hasAnyPermission([\"upload:create\"], [\"flow:execute\", \"upload:create\"]) // true\n * hasAnyPermission([\"upload:read\"], [\"flow:execute\", \"upload:create\"]) // false\n * ```\n */\nexport const hasAnyPermission = (\n grantedPermissions: readonly string[],\n requiredPermissions: readonly string[],\n): boolean => {\n return requiredPermissions.some((required) =>\n hasPermission(grantedPermissions, required),\n );\n};\n\n/**\n * Checks if all of the required permissions are satisfied.\n *\n * @param grantedPermissions - Array of permissions granted to the user\n * @param requiredPermissions - Array of permissions, all of which must be satisfied\n * @returns true if all required permissions are satisfied\n *\n * @example\n * ```typescript\n * hasAllPermissions([\"flow:*\", \"upload:*\"], [\"flow:execute\", \"upload:create\"]) // true\n * hasAllPermissions([\"flow:execute\"], [\"flow:execute\", \"upload:create\"]) // false\n * ```\n */\nexport const hasAllPermissions = (\n grantedPermissions: readonly string[],\n requiredPermissions: readonly string[],\n): boolean => {\n return requiredPermissions.every((required) =>\n hasPermission(grantedPermissions, required),\n );\n};\n\n/**\n * Expands a permission to include all implied permissions.\n * Useful for display or audit purposes.\n *\n * @param permission - The permission to expand\n * @returns Array of the permission and all implied permissions\n *\n * @example\n * ```typescript\n * expandPermission(\"engine:dlq\") // [\"engine:dlq\", \"engine:dlq:read\", \"engine:dlq:write\"]\n * expandPermission(\"engine:health\") // [\"engine:health\"]\n * ```\n */\nexport const expandPermission = (permission: string): string[] => {\n const result = [permission];\n const implied = PERMISSION_HIERARCHY[permission];\n if (implied) {\n result.push(...implied);\n }\n return result;\n};\n","import { Context, Effect, Layer } from \"effect\";\nimport {\n AuthenticationRequiredError,\n AuthorizationError,\n} from \"./permissions/errors\";\nimport {\n hasAnyPermission as matchHasAnyPermission,\n hasPermission as matchHasPermission,\n} from \"./permissions/matcher\";\nimport type { AuthContext } from \"./types\";\n\n/**\n * Authentication Context Service\n *\n * Provides access to the current authentication context throughout\n * the upload and flow processing pipeline. The service is provided\n * via Effect Layer and can be accessed using Effect.service().\n *\n * @example\n * ```typescript\n * import { Effect } from \"effect\";\n * import { AuthContextService } from \"@uploadista/server\";\n *\n * const uploadHandler = Effect.gen(function* () {\n * const authService = yield* AuthContextService;\n * const clientId = yield* authService.getClientId();\n * if (clientId) {\n * console.log(`Processing upload for client: ${clientId}`);\n * }\n * });\n * ```\n */\nexport class AuthContextService extends Context.Tag(\"AuthContextService\")<\n AuthContextService,\n {\n /**\n * Get the current client ID from auth context.\n * Returns null if no authentication context is available.\n */\n readonly getClientId: () => Effect.Effect<string | null>;\n\n /**\n * Get the current auth metadata.\n * Returns empty object if no authentication context or no metadata.\n */\n readonly getMetadata: () => Effect.Effect<Record<string, unknown>>;\n\n /**\n * Check if the current client has a specific permission.\n * Supports exact match, wildcard match, and hierarchical match.\n * Returns false if no authentication context or permission not found.\n *\n * @example\n * ```typescript\n * // Exact match\n * yield* authService.hasPermission(\"engine:health\")\n *\n * // Wildcard: user with \"engine:*\" will match \"engine:health\"\n * yield* authService.hasPermission(\"engine:health\")\n *\n * // Hierarchical: user with \"engine:dlq\" will match \"engine:dlq:read\"\n * yield* authService.hasPermission(\"engine:dlq:read\")\n * ```\n */\n readonly hasPermission: (permission: string) => Effect.Effect<boolean>;\n\n /**\n * Check if the current client has any of the specified permissions.\n * Returns true if at least one permission is granted.\n */\n readonly hasAnyPermission: (\n permissions: readonly string[],\n ) => Effect.Effect<boolean>;\n\n /**\n * Require a specific permission, failing with AuthorizationError if not granted.\n * Use this when you want to fail fast on missing permissions.\n *\n * @throws AuthorizationError if permission is not granted\n * @throws AuthenticationRequiredError if no auth context\n *\n * @example\n * ```typescript\n * const protectedHandler = Effect.gen(function* () {\n * const authService = yield* AuthContextService;\n * yield* authService.requirePermission(\"engine:metrics\");\n * // Only reaches here if permission is granted\n * return yield* getMetrics();\n * });\n * ```\n */\n readonly requirePermission: (\n permission: string,\n ) => Effect.Effect<void, AuthorizationError | AuthenticationRequiredError>;\n\n /**\n * Require authentication, failing with AuthenticationRequiredError if not authenticated.\n *\n * @throws AuthenticationRequiredError if no auth context\n */\n readonly requireAuthentication: () => Effect.Effect<\n AuthContext,\n AuthenticationRequiredError\n >;\n\n /**\n * Get all permissions granted to the current client.\n * Returns empty array if no authentication context or no permissions.\n */\n readonly getPermissions: () => Effect.Effect<readonly string[]>;\n\n /**\n * Get the full authentication context if available.\n * Returns null if no authentication context is available.\n */\n readonly getAuthContext: () => Effect.Effect<AuthContext | null>;\n }\n>() {}\n\n/**\n * Options for creating an AuthContextService Layer.\n */\nexport interface AuthContextServiceOptions {\n /**\n * When true, bypasses all permission checks (grants all permissions).\n * Used when no auth middleware is configured (backward compatibility).\n * @default false\n */\n bypassAuth?: boolean;\n}\n\n/**\n * Creates an AuthContextService Layer from an AuthContext.\n * This is typically called by adapters after successful authentication.\n *\n * @param authContext - The authentication context from middleware\n * @param options - Optional configuration for auth behavior\n * @returns Effect Layer providing AuthContextService\n */\nexport const AuthContextServiceLive = (\n authContext: AuthContext | null,\n options?: AuthContextServiceOptions,\n): Layer.Layer<AuthContextService> => {\n const permissions = authContext?.permissions ?? [];\n const bypassAuth = options?.bypassAuth ?? false;\n\n return Layer.succeed(AuthContextService, {\n getClientId: () => Effect.succeed(authContext?.clientId ?? null),\n\n getMetadata: () => Effect.succeed(authContext?.metadata ?? {}),\n\n hasPermission: (permission: string) =>\n bypassAuth\n ? Effect.succeed(true)\n : Effect.succeed(matchHasPermission(permissions, permission)),\n\n hasAnyPermission: (requiredPermissions: readonly string[]) =>\n bypassAuth\n ? Effect.succeed(true)\n : Effect.succeed(\n matchHasAnyPermission(permissions, requiredPermissions),\n ),\n\n requirePermission: (permission: string) =>\n Effect.gen(function* () {\n // If bypass mode is enabled, grant all permissions\n if (bypassAuth) {\n yield* Effect.logDebug(\n `[Auth] Bypass mode: permission '${permission}' auto-granted`,\n );\n return;\n }\n if (!authContext) {\n yield* Effect.logDebug(\n `[Auth] Permission check failed: authentication required for '${permission}'`,\n );\n return yield* Effect.fail(new AuthenticationRequiredError());\n }\n if (!matchHasPermission(permissions, permission)) {\n yield* Effect.logDebug(\n `[Auth] Permission denied: '${permission}' for client '${authContext.clientId}'`,\n );\n return yield* Effect.fail(new AuthorizationError(permission));\n }\n yield* Effect.logDebug(\n `[Auth] Permission granted: '${permission}' for client '${authContext.clientId}'`,\n );\n }),\n\n requireAuthentication: () =>\n authContext\n ? Effect.succeed(authContext)\n : Effect.fail(new AuthenticationRequiredError()),\n\n getPermissions: () => Effect.succeed(permissions),\n\n getAuthContext: () => Effect.succeed(authContext),\n });\n};\n\n/**\n * No-auth implementation of AuthContextService.\n * Returns null/empty values for all operations.\n * Used when no authentication middleware is configured (backward compatibility).\n */\nexport const NoAuthContextServiceLive: Layer.Layer<AuthContextService> =\n AuthContextServiceLive(null);\n","/**\n * Usage Hook Types\n *\n * Types for lifecycle hooks that fire during upload and flow processing.\n * Used for usage tracking, quota enforcement, and billing integration.\n */\n\nimport type { Effect } from \"effect\";\n\n// ============================================================================\n// Usage Hook Result Types\n// ============================================================================\n\n/**\n * Result of a usage hook that can abort processing.\n * Used by onUploadStart and onFlowStart hooks.\n */\nexport type UsageHookResult =\n | { readonly action: \"continue\" }\n | {\n readonly action: \"abort\";\n readonly reason: string;\n readonly code?: string;\n };\n\n/**\n * Helper to create a continue result.\n */\nexport const continueResult = (): UsageHookResult => ({ action: \"continue\" });\n\n/**\n * Helper to create an abort result.\n */\nexport const abortResult = (\n reason: string,\n code?: string,\n): UsageHookResult => ({\n action: \"abort\",\n reason,\n code,\n});\n\n// ============================================================================\n// Usage Context Types\n// ============================================================================\n\n/**\n * Base metadata shared across all usage contexts.\n */\nexport interface BaseUsageMetadata {\n /** File size in bytes (if known) */\n fileSize?: number;\n /** MIME type of the file */\n mimeType?: string;\n /** Original file name */\n fileName?: string;\n}\n\n/**\n * Metadata specific to upload operations.\n */\nexport interface UploadUsageMetadata extends BaseUsageMetadata {\n /** Unique upload identifier */\n uploadId?: string;\n /** Duration of the upload in milliseconds */\n duration?: number;\n}\n\n/**\n * Metadata specific to flow operations.\n */\nexport interface FlowUsageMetadata extends BaseUsageMetadata {\n /** Flow identifier being executed */\n flowId?: string;\n /** Unique job identifier */\n jobId?: string;\n /** Number of nodes in the flow */\n nodeCount?: number;\n /** Total size of input files */\n inputFileSize?: number;\n /** Total size of output files (on complete) */\n outputSize?: number;\n /** Number of nodes that were executed (on complete) */\n nodesExecuted?: number;\n /** Duration of flow execution in milliseconds (on complete) */\n duration?: number;\n /** Flow completion status (on complete) */\n status?: \"success\" | \"failed\" | \"cancelled\";\n}\n\n/**\n * Context passed to usage hooks containing client and operation information.\n */\nexport interface UsageContext<\n TMetadata extends BaseUsageMetadata = BaseUsageMetadata,\n> {\n /** Organization/client identifier */\n clientId: string;\n /** Type of operation */\n operation: \"upload\" | \"flow\";\n /** Operation-specific metadata */\n metadata: TMetadata;\n}\n\n/**\n * Upload-specific usage context.\n */\nexport type UploadUsageContext = UsageContext<UploadUsageMetadata>;\n\n/**\n * Flow-specific usage context.\n */\nexport type FlowUsageContext = UsageContext<FlowUsageMetadata>;\n\n// ============================================================================\n// Usage Hook Function Types\n// ============================================================================\n\n/**\n * Hook called before upload processing begins.\n * Can return abort to reject the upload (e.g., quota exceeded).\n */\nexport type OnUploadStartHook = (\n ctx: UploadUsageContext,\n) => Effect.Effect<UsageHookResult>;\n\n/**\n * Hook called after upload completes successfully.\n * Used for recording usage. Errors are logged but don't fail the upload.\n */\nexport type OnUploadCompleteHook = (\n ctx: UploadUsageContext,\n) => Effect.Effect<void>;\n\n/**\n * Hook called before flow execution begins.\n * Can return abort to reject the flow (e.g., subscription expired).\n */\nexport type OnFlowStartHook = (\n ctx: FlowUsageContext,\n) => Effect.Effect<UsageHookResult>;\n\n/**\n * Hook called after flow completes (success, failure, or cancellation).\n * Used for recording usage. Errors are logged but don't fail the response.\n */\nexport type OnFlowCompleteHook = (ctx: FlowUsageContext) => Effect.Effect<void>;\n\n// ============================================================================\n// Usage Hooks Configuration\n// ============================================================================\n\n/**\n * Configuration for usage tracking hooks.\n * All hooks are optional - unconfigured hooks are no-ops.\n *\n * @example\n * ```typescript\n * const usageHooks: UsageHooks = {\n * onUploadStart: (ctx) => Effect.gen(function* () {\n * const hasQuota = yield* checkQuota(ctx.clientId, ctx.metadata.fileSize);\n * if (!hasQuota) {\n * return abortResult(\"Storage quota exceeded\", \"QUOTA_EXCEEDED\");\n * }\n * return continueResult();\n * }),\n * onUploadComplete: (ctx) => Effect.gen(function* () {\n * yield* recordUsage(ctx.clientId, ctx.metadata.fileSize);\n * }),\n * };\n * ```\n */\nexport interface UsageHooks {\n /**\n * Called before upload processing begins.\n * Return abort to reject the upload.\n */\n onUploadStart?: OnUploadStartHook;\n\n /**\n * Called after upload completes successfully.\n * Errors are logged but don't fail the upload.\n */\n onUploadComplete?: OnUploadCompleteHook;\n\n /**\n * Called before flow execution begins.\n * Return abort to reject the flow.\n */\n onFlowStart?: OnFlowStartHook;\n\n /**\n * Called after flow completes (success, failure, or cancellation).\n * Errors are logged but don't fail the response.\n */\n onFlowComplete?: OnFlowCompleteHook;\n}\n\n/**\n * Configuration for the usage hook service.\n */\nexport interface UsageHookConfig {\n /**\n * The usage hooks to execute.\n */\n hooks?: UsageHooks;\n\n /**\n * Timeout for hook execution in milliseconds.\n * If a hook takes longer than this, it will be considered failed.\n * Default: 5000ms (5 seconds)\n */\n timeout?: number;\n}\n\n/**\n * Default timeout for usage hooks (5 seconds).\n */\nexport const DEFAULT_USAGE_HOOK_TIMEOUT = 5000;\n","/**\n * Usage Hook Service\n *\n * Effect service for executing usage tracking hooks with timeout handling.\n */\n\nimport { Context, Effect, Layer } from \"effect\";\nimport type {\n FlowUsageContext,\n UploadUsageContext,\n UsageHookConfig,\n UsageHookResult,\n} from \"./types\";\nimport { continueResult, DEFAULT_USAGE_HOOK_TIMEOUT } from \"./types\";\n\n/**\n * Usage Hook Service\n *\n * Provides methods to execute usage hooks during upload and flow processing.\n * Handles timeout and error recovery gracefully.\n */\nexport class UsageHookService extends Context.Tag(\"UsageHookService\")<\n UsageHookService,\n {\n /**\n * Execute onUploadStart hook if configured.\n * Returns continue result if no hook is configured or on error/timeout.\n */\n readonly onUploadStart: (\n ctx: UploadUsageContext,\n ) => Effect.Effect<UsageHookResult>;\n\n /**\n * Execute onUploadComplete hook if configured.\n * Errors are logged but swallowed (fire-and-forget).\n */\n readonly onUploadComplete: (ctx: UploadUsageContext) => Effect.Effect<void>;\n\n /**\n * Execute onFlowStart hook if configured.\n * Returns continue result if no hook is configured or on error/timeout.\n */\n readonly onFlowStart: (\n ctx: FlowUsageContext,\n ) => Effect.Effect<UsageHookResult>;\n\n /**\n * Execute onFlowComplete hook if configured.\n * Errors are logged but swallowed (fire-and-forget).\n */\n readonly onFlowComplete: (ctx: FlowUsageContext) => Effect.Effect<void>;\n }\n>() {}\n\n/**\n * Creates a UsageHookService Layer from configuration.\n *\n * @param config - Usage hook configuration with optional hooks and timeout\n * @returns Effect Layer providing UsageHookService\n */\nexport const UsageHookServiceLive = (\n config?: UsageHookConfig,\n): Layer.Layer<UsageHookService> => {\n const hooks = config?.hooks;\n const timeout = config?.timeout ?? DEFAULT_USAGE_HOOK_TIMEOUT;\n\n return Layer.succeed(UsageHookService, {\n onUploadStart: (ctx: UploadUsageContext) => {\n if (!hooks?.onUploadStart) {\n return Effect.succeed(continueResult());\n }\n\n return hooks.onUploadStart(ctx).pipe(\n // Add timeout - proceed on timeout (fail-open)\n Effect.timeout(timeout),\n Effect.map((result) => result ?? continueResult()),\n // On any error, log and continue (fail-open for availability)\n Effect.catchAll((error) =>\n Effect.gen(function* () {\n yield* Effect.logWarning(\n `onUploadStart hook failed: ${error}. Proceeding with upload.`,\n );\n return continueResult();\n }),\n ),\n );\n },\n\n onUploadComplete: (ctx: UploadUsageContext) => {\n if (!hooks?.onUploadComplete) {\n return Effect.void;\n }\n\n return hooks.onUploadComplete(ctx).pipe(\n // Add timeout\n Effect.timeout(timeout),\n Effect.asVoid,\n // On any error, just log (fire-and-forget)\n Effect.catchAll((error) =>\n Effect.logWarning(\n `onUploadComplete hook failed: ${error}. Upload already completed.`,\n ),\n ),\n );\n },\n\n onFlowStart: (ctx: FlowUsageContext) => {\n if (!hooks?.onFlowStart) {\n return Effect.succeed(continueResult());\n }\n\n return hooks.onFlowStart(ctx).pipe(\n // Add timeout - proceed on timeout (fail-open)\n Effect.timeout(timeout),\n Effect.map((result) => result ?? continueResult()),\n // On any error, log and continue (fail-open for availability)\n Effect.catchAll((error) =>\n Effect.gen(function* () {\n yield* Effect.logWarning(\n `onFlowStart hook failed: ${error}. Proceeding with flow.`,\n );\n return continueResult();\n }),\n ),\n );\n },\n\n onFlowComplete: (ctx: FlowUsageContext) => {\n if (!hooks?.onFlowComplete) {\n return Effect.void;\n }\n\n return hooks.onFlowComplete(ctx).pipe(\n // Add timeout\n Effect.timeout(timeout),\n Effect.asVoid,\n // On any error, just log (fire-and-forget)\n Effect.catchAll((error) =>\n Effect.logWarning(\n `onFlowComplete hook failed: ${error}. Flow already completed.`,\n ),\n ),\n );\n },\n });\n};\n\n/**\n * No-op implementation of UsageHookService.\n * All hooks are no-ops that return continue/void.\n * Used when no usage hooks are configured (default backward compatibility).\n */\nexport const NoUsageHookServiceLive: Layer.Layer<UsageHookService> =\n UsageHookServiceLive();\n","import { DeadLetterQueueService } from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport { PERMISSIONS } from \"../../permissions/types\";\nimport { AuthContextService } from \"../../service\";\nimport type {\n DlqCleanupRequest,\n DlqCleanupResponse,\n DlqDeleteRequest,\n DlqDeleteResponse,\n DlqGetRequest,\n DlqGetResponse,\n DlqListRequest,\n DlqListResponse,\n DlqResolveRequest,\n DlqResolveResponse,\n DlqRetryAllRequest,\n DlqRetryAllResponse,\n DlqRetryRequest,\n DlqRetryResponse,\n DlqStatsRequest,\n DlqStatsResponse,\n} from \"../routes\";\n\n/**\n * Handle GET /api/dlq - List DLQ items\n */\nexport const handleDlqList = (req: DlqListRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for reading DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_READ);\n\n const dlq = yield* DeadLetterQueueService;\n const result = yield* dlq.list(req.options);\n\n return {\n type: \"dlq-list\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: result,\n } satisfies DlqListResponse;\n });\n\n/**\n * Handle GET /api/dlq/:itemId - Get a specific DLQ item\n */\nexport const handleDlqGet = (req: DlqGetRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for reading DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_READ);\n\n const dlq = yield* DeadLetterQueueService;\n const item = yield* dlq.get(req.itemId);\n\n return {\n type: \"dlq-get\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: item,\n } satisfies DlqGetResponse;\n });\n\n/**\n * Handle POST /api/dlq/:itemId/retry - Retry a specific DLQ item\n */\nexport const handleDlqRetry = (req: DlqRetryRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for writing to DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_WRITE);\n\n const dlq = yield* DeadLetterQueueService;\n\n // Mark item as retrying\n yield* dlq.markRetrying(req.itemId);\n\n // TODO: Implement actual retry logic by re-executing the flow\n // This would require access to FlowServer and the original job context\n // For now, we just mark it as retrying and return success\n // The actual retry would be handled by a background scheduler\n\n return {\n type: \"dlq-retry\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: { success: true },\n } satisfies DlqRetryResponse;\n });\n\n/**\n * Handle POST /api/dlq/retry-all - Retry all matching DLQ items\n */\nexport const handleDlqRetryAll = (req: DlqRetryAllRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for writing to DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_WRITE);\n\n const dlq = yield* DeadLetterQueueService;\n\n // List items matching the filter\n const { items } = yield* dlq.list({\n status: req.options?.status,\n flowId: req.options?.flowId,\n });\n\n let succeeded = 0;\n let failed = 0;\n\n // Mark each item for retry\n for (const item of items) {\n const result = yield* Effect.either(dlq.markRetrying(item.id));\n if (result._tag === \"Right\") {\n succeeded++;\n } else {\n failed++;\n }\n }\n\n return {\n type: \"dlq-retry-all\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: {\n retried: items.length,\n succeeded,\n failed,\n },\n } satisfies DlqRetryAllResponse;\n });\n\n/**\n * Handle DELETE /api/dlq/:itemId - Delete a DLQ item\n */\nexport const handleDlqDelete = (req: DlqDeleteRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for writing to DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_WRITE);\n\n const dlq = yield* DeadLetterQueueService;\n yield* dlq.delete(req.itemId);\n\n return {\n type: \"dlq-delete\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: { success: true },\n } satisfies DlqDeleteResponse;\n });\n\n/**\n * Handle POST /api/dlq/:itemId/resolve - Manually resolve a DLQ item\n */\nexport const handleDlqResolve = (req: DlqResolveRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for writing to DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_WRITE);\n\n const dlq = yield* DeadLetterQueueService;\n const item = yield* dlq.markResolved(req.itemId);\n\n return {\n type: \"dlq-resolve\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: item,\n } satisfies DlqResolveResponse;\n });\n\n/**\n * Handle POST /api/dlq/cleanup - Cleanup old DLQ items\n */\nexport const handleDlqCleanup = (req: DlqCleanupRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for writing to DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_WRITE);\n\n const dlq = yield* DeadLetterQueueService;\n const result = yield* dlq.cleanup(req.options);\n\n return {\n type: \"dlq-cleanup\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: result,\n } satisfies DlqCleanupResponse;\n });\n\n/**\n * Handle GET /api/dlq/stats - Get DLQ statistics\n */\nexport const handleDlqStats = (_req: DlqStatsRequest) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for reading DLQ\n yield* authService.requirePermission(PERMISSIONS.ENGINE.DLQ_READ);\n\n const dlq = yield* DeadLetterQueueService;\n const stats = yield* dlq.getStats();\n\n return {\n type: \"dlq-stats\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: stats,\n } satisfies DlqStatsResponse;\n });\n","import { FlowEngine } from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport { AuthCacheService } from \"../../cache\";\nimport { QuotaExceededError } from \"../../permissions/errors\";\nimport { PERMISSIONS } from \"../../permissions/types\";\nimport { AuthContextService } from \"../../service\";\nimport { UsageHookService } from \"../../usage-hooks/service\";\nimport type {\n CancelFlowRequest,\n CancelFlowResponse,\n GetFlowRequest,\n GetFlowResponse,\n GetJobStatusRequest,\n GetJobStatusResponse,\n PauseFlowRequest,\n PauseFlowResponse,\n ResumeFlowRequest,\n ResumeFlowResponse,\n RunFlowRequest,\n RunFlowResponse,\n} from \"../routes\";\n\nexport const handleGetFlow = ({ flowId }: GetFlowRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const clientId = yield* authService.getClientId();\n\n // Check permission for reading flow status\n yield* authService.requirePermission(PERMISSIONS.FLOW.STATUS);\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Getting flow data: ${flowId}, client: ${clientId}`,\n );\n }\n\n const flowData = yield* flowEngine.getFlowData(flowId, clientId);\n\n return {\n status: 200,\n body: flowData,\n } as GetFlowResponse;\n });\n};\n\nexport const handleRunFlow = <TRequirements>({\n flowId,\n storageId,\n inputs,\n}: RunFlowRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n const usageHookService = yield* UsageHookService;\n const clientId = yield* authService.getClientId();\n\n // Check permission for executing flows\n yield* authService.requirePermission(PERMISSIONS.FLOW.EXECUTE);\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Executing flow: ${flowId}, storage: ${storageId}, client: ${clientId}`,\n );\n yield* Effect.logInfo(JSON.stringify(inputs, null, 2));\n } else {\n yield* Effect.logInfo(\n `[Flow] Executing flow: ${flowId}, storage: ${storageId}`,\n );\n yield* Effect.logInfo(\n `[Flow] Inputs: ${JSON.stringify(inputs, null, 2)}`,\n );\n }\n\n // Execute onFlowStart hook for quota checking\n if (clientId) {\n const hookResult = yield* usageHookService.onFlowStart({\n clientId,\n operation: \"flow\",\n metadata: {\n flowId,\n },\n });\n\n if (hookResult.action === \"abort\") {\n return yield* Effect.fail(\n new QuotaExceededError(\n hookResult.reason,\n hookResult.code ?? \"SUBSCRIPTION_REQUIRED\",\n ),\n );\n }\n }\n\n // Run flow returns immediately with jobId\n\n yield* Effect.logInfo(`[Flow] Calling flowServer.runFlow...`);\n const result = yield* flowEngine\n .runFlow<TRequirements>({\n flowId,\n storageId,\n clientId,\n inputs,\n })\n .pipe(\n Effect.tap(() =>\n Effect.logInfo(`[Flow] runFlow completed successfully`),\n ),\n Effect.tapError((error) =>\n Effect.logError(`[Flow] runFlow failed with error: ${error}`),\n ),\n );\n\n // Cache auth context for subsequent flow operations (continue, status)\n const authContext = yield* authService.getAuthContext();\n if (authContext) {\n yield* authCache.set(result.id, authContext);\n }\n\n yield* Effect.logInfo(`[Flow] Flow started with jobId: ${result.id}`);\n\n // Note: onFlowComplete hook is called when the flow actually completes,\n // which happens asynchronously. The hook should be invoked from the\n // flow execution pipeline, not here where we just start the flow.\n\n return {\n status: 200,\n body: result,\n } as RunFlowResponse;\n });\n};\n\nexport const handleJobStatus = ({ jobId }: GetJobStatusRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n const clientId = yield* authService.getClientId();\n\n // Check permission for checking flow status\n yield* authService.requirePermission(PERMISSIONS.FLOW.STATUS);\n\n if (!jobId) {\n throw new Error(\"No job id\");\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Getting job status: ${jobId}, client: ${clientId}`,\n );\n }\n\n const result = yield* flowEngine.getJobStatus(jobId);\n\n // Clear cache if flow is completed or failed\n if (result.status === \"completed\" || result.status === \"failed\") {\n yield* authCache.delete(jobId);\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Flow ${result.status}, cleared auth cache: ${jobId}`,\n );\n }\n }\n\n return {\n status: 200,\n body: result,\n } as GetJobStatusResponse;\n });\n};\n\nexport const handleResumeFlow = <TRequirements>({\n jobId,\n nodeId,\n newData,\n}: ResumeFlowRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n\n // Check permission for executing flows (resume is part of execution)\n yield* authService.requirePermission(PERMISSIONS.FLOW.EXECUTE);\n\n // Try current auth first, fallback to cached auth\n let clientId = yield* authService.getClientId();\n if (!clientId) {\n const cachedAuth = yield* authCache.get(jobId);\n clientId = cachedAuth?.clientId ?? null;\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Continuing flow: jobId=${jobId}, nodeId=${nodeId}, client: ${clientId}`,\n );\n }\n\n if (newData === undefined) {\n throw new Error(\"Missing newData\");\n }\n\n const result = yield* flowEngine.resumeFlow<TRequirements>({\n jobId,\n nodeId,\n newData,\n clientId,\n });\n\n // Clear cache if flow is completed or failed\n if (result.status === \"completed\" || result.status === \"failed\") {\n yield* authCache.delete(jobId);\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Flow ${result.status}, cleared auth cache: ${jobId}`,\n );\n }\n }\n\n return {\n status: 200,\n body: result,\n } as ResumeFlowResponse;\n });\n};\n\nexport const handlePauseFlow = ({ jobId }: PauseFlowRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n\n // Check permission for cancelling flows (pause is related to cancel)\n yield* authService.requirePermission(PERMISSIONS.FLOW.CANCEL);\n\n // Try current auth first, fallback to cached auth\n let clientId = yield* authService.getClientId();\n if (!clientId) {\n const cachedAuth = yield* authCache.get(jobId);\n clientId = cachedAuth?.clientId ?? null;\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Pausing flow: jobId=${jobId}, client: ${clientId}`,\n );\n }\n\n const result = yield* flowEngine.pauseFlow(jobId, clientId);\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Flow paused: ${jobId}, status: ${result.status}`,\n );\n }\n\n return {\n status: 200,\n body: result,\n } as PauseFlowResponse;\n });\n};\n\nexport const handleCancelFlow = ({ jobId }: CancelFlowRequest) => {\n return Effect.gen(function* () {\n const flowEngine = yield* FlowEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n const usageHookService = yield* UsageHookService;\n\n // Check permission for cancelling flows\n yield* authService.requirePermission(PERMISSIONS.FLOW.CANCEL);\n\n if (!jobId) {\n throw new Error(\"No job id\");\n }\n\n // Try current auth first, fallback to cached auth\n let clientId = yield* authService.getClientId();\n if (!clientId) {\n const cachedAuth = yield* authCache.get(jobId);\n clientId = cachedAuth?.clientId ?? null;\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Cancelling flow: jobId=${jobId}, client: ${clientId}`,\n );\n }\n\n const result = yield* flowEngine.cancelFlow(jobId, clientId);\n\n // Clear cache since flow is cancelled\n yield* authCache.delete(jobId);\n if (clientId) {\n yield* Effect.logInfo(\n `[Flow] Flow cancelled, cleared auth cache: ${jobId}`,\n );\n\n // Execute onFlowComplete hook for cancelled flow\n yield* Effect.forkDaemon(\n usageHookService.onFlowComplete({\n clientId,\n operation: \"flow\",\n metadata: {\n jobId,\n status: \"cancelled\",\n },\n }),\n );\n }\n\n return {\n status: 200,\n body: result,\n } as CancelFlowResponse;\n });\n};\n","/**\n * Health Check Service for Uploadista SDK.\n *\n * This module provides health checking functionality for:\n * - Storage backends\n * - KV stores\n * - Event broadcasters\n * - Circuit breaker state\n * - Dead letter queue state\n *\n * @module core/health-check-service\n */\n\nimport { DeadLetterQueueService } from \"@uploadista/core/flow\";\nimport type {\n CircuitBreakerHealthSummary,\n ComponentHealth,\n DlqHealthSummary,\n HealthCheckConfig,\n HealthComponents,\n HealthResponse,\n HealthStatus,\n} from \"@uploadista/core/types\";\nimport {\n CircuitBreakerStoreService,\n DEFAULT_HEALTH_CHECK_CONFIG,\n} from \"@uploadista/core/types\";\nimport { Effect, Option } from \"effect\";\n\n// Track server start time for uptime calculation\nconst serverStartTime = Date.now();\n\n/**\n * Gets the server uptime in milliseconds.\n */\nexport function getServerUptime(): number {\n return Date.now() - serverStartTime;\n}\n\n/**\n * Creates a timestamp string in ISO 8601 format.\n */\nexport function getTimestamp(): string {\n return new Date().toISOString();\n}\n\n/**\n * Creates a simple liveness health response.\n *\n * This is used for the `/health` endpoint which should return immediately\n * without checking any dependencies.\n */\nexport function createLivenessResponse(\n config?: HealthCheckConfig,\n): HealthResponse {\n return {\n status: \"healthy\",\n timestamp: getTimestamp(),\n version: config?.version,\n uptime: getServerUptime(),\n };\n}\n\n/**\n * Aggregates component health statuses into an overall status.\n *\n * @param components - The components to aggregate\n * @returns The overall health status\n */\nexport function aggregateHealthStatus(\n components: HealthComponents,\n): HealthStatus {\n const statuses: HealthStatus[] = [];\n\n // Core components (critical for readiness)\n if (components.storage) statuses.push(components.storage.status);\n if (components.kvStore) statuses.push(components.kvStore.status);\n\n // Optional components (don't fail readiness)\n // eventBroadcaster, circuitBreaker, and DLQ being degraded doesn't make system unhealthy\n\n // If any critical component is unhealthy, overall is unhealthy\n if (statuses.includes(\"unhealthy\")) {\n return \"unhealthy\";\n }\n\n // Check if any component (including optional) is degraded\n const allStatuses: HealthStatus[] = [...statuses];\n if (components.eventBroadcaster)\n allStatuses.push(components.eventBroadcaster.status);\n if (components.circuitBreaker)\n allStatuses.push(components.circuitBreaker.status);\n if (components.deadLetterQueue)\n allStatuses.push(components.deadLetterQueue.status);\n\n if (allStatuses.includes(\"degraded\")) {\n return \"degraded\";\n }\n\n return \"healthy\";\n}\n\n/**\n * Checks storage backend health by performing a simple operation.\n *\n * Currently returns healthy as we don't have direct access to storage\n * in health check context. This can be enhanced to do actual connectivity\n * checks when storage service is available.\n */\nexport function checkStorageHealth(\n _config: HealthCheckConfig,\n): Effect.Effect<ComponentHealth, never, never> {\n const startTime = Date.now();\n\n // TODO: When storage service is available in health context,\n // perform actual health check (e.g., list bucket, check credentials)\n // For now, return healthy with a note\n return Effect.succeed({\n status: \"healthy\" as HealthStatus,\n latency: Date.now() - startTime,\n message: \"Storage backend configured\",\n lastCheck: getTimestamp(),\n });\n}\n\n/**\n * Checks KV store health by performing a simple operation.\n *\n * Currently returns healthy as we don't have direct access to KV store\n * in health check context. This can be enhanced to do actual connectivity\n * checks when KV store service is available.\n */\nexport function checkKvStoreHealth(\n _config: HealthCheckConfig,\n): Effect.Effect<ComponentHealth, never, never> {\n const startTime = Date.now();\n\n // TODO: When KV store service is available in health context,\n // perform actual health check (e.g., get/set test key)\n // For now, return healthy with a note\n return Effect.succeed({\n status: \"healthy\" as HealthStatus,\n latency: Date.now() - startTime,\n message: \"KV store configured\",\n lastCheck: getTimestamp(),\n });\n}\n\n/**\n * Checks event broadcaster health.\n *\n * Currently returns healthy as we don't have direct access to event broadcaster\n * in health check context.\n */\nexport function checkEventBroadcasterHealth(\n _config: HealthCheckConfig,\n): Effect.Effect<ComponentHealth, never, never> {\n const startTime = Date.now();\n\n // TODO: When event broadcaster service is available in health context,\n // perform actual health check\n return Effect.succeed({\n status: \"healthy\" as HealthStatus,\n latency: Date.now() - startTime,\n message: \"Event broadcaster configured\",\n lastCheck: getTimestamp(),\n });\n}\n\n/**\n * Gets circuit breaker health summary from the circuit breaker store.\n *\n * Uses the optional service pattern to check if circuit breaker is available.\n */\nexport function getCircuitBreakerSummary(): Effect.Effect<\n CircuitBreakerHealthSummary | undefined,\n never,\n never\n> {\n return Effect.gen(function* () {\n const cbStoreOption = yield* Effect.serviceOption(\n CircuitBreakerStoreService,\n );\n\n if (Option.isNone(cbStoreOption)) {\n // Circuit breaker not configured\n return undefined;\n }\n\n const cbStore = cbStoreOption.value;\n const statsResult = yield* Effect.either(cbStore.getAllStats());\n\n if (statsResult._tag === \"Left\") {\n // Error getting stats - return degraded status\n return {\n status: \"degraded\" as HealthStatus,\n openCircuits: 0,\n totalCircuits: 0,\n };\n }\n\n const stats = statsResult.right;\n const circuits = Array.from(stats.values());\n const openCircuits = circuits.filter((c) => c.state === \"open\").length;\n const totalCircuits = circuits.length;\n\n // Determine status based on open circuits\n let status: HealthStatus = \"healthy\";\n if (openCircuits > 0) {\n status = \"degraded\";\n }\n\n return {\n status,\n openCircuits,\n totalCircuits,\n circuits: circuits.map((c) => ({\n nodeType: c.nodeType,\n state: c.state,\n failureCount: c.failureCount,\n timeSinceLastStateChange: c.timeSinceLastStateChange,\n })),\n };\n });\n}\n\n/**\n * Gets dead letter queue health summary from the DLQ service.\n *\n * Uses the optional service pattern to check if DLQ is available.\n */\nexport function getDlqSummary(): Effect.Effect<\n DlqHealthSummary | undefined,\n never,\n never\n> {\n return Effect.gen(function* () {\n const dlqOption = yield* DeadLetterQueueService.optional;\n\n if (Option.isNone(dlqOption)) {\n // DLQ not configured\n return undefined;\n }\n\n const dlq = dlqOption.value;\n const statsResult = yield* Effect.either(dlq.getStats());\n\n if (statsResult._tag === \"Left\") {\n // Error getting stats - return degraded status\n return {\n status: \"degraded\" as HealthStatus,\n pendingItems: 0,\n exhaustedItems: 0,\n };\n }\n\n const stats = statsResult.right;\n\n // Determine status based on exhausted items\n let status: HealthStatus = \"healthy\";\n if (stats.byStatus.exhausted > 0) {\n status = \"degraded\";\n }\n\n return {\n status,\n pendingItems: stats.byStatus.pending,\n exhaustedItems: stats.byStatus.exhausted,\n oldestItem: stats.oldestItem?.toISOString(),\n };\n });\n}\n\n/**\n * Performs a full readiness check including all configured dependencies.\n *\n * @param config - Health check configuration\n * @returns Health response with component details\n */\nexport function performReadinessCheck(\n config: HealthCheckConfig = {},\n): Effect.Effect<HealthResponse, never, never> {\n const effectiveConfig = { ...DEFAULT_HEALTH_CHECK_CONFIG, ...config };\n\n return Effect.gen(function* () {\n const components: HealthComponents = {};\n\n // Check storage if enabled\n if (effectiveConfig.checkStorage) {\n components.storage = yield* checkStorageHealth(effectiveConfig);\n }\n\n // Check KV store if enabled\n if (effectiveConfig.checkKvStore) {\n components.kvStore = yield* checkKvStoreHealth(effectiveConfig);\n }\n\n // Check event broadcaster if enabled\n if (effectiveConfig.checkEventBroadcaster) {\n components.eventBroadcaster =\n yield* checkEventBroadcasterHealth(effectiveConfig);\n }\n\n // Aggregate status\n const status = aggregateHealthStatus(components);\n\n return {\n status,\n timestamp: getTimestamp(),\n version: config.version,\n uptime: getServerUptime(),\n components,\n };\n });\n}\n\n/**\n * Performs a full component health check including circuit breaker and DLQ.\n *\n * @param config - Health check configuration\n * @returns Health response with all component details\n */\nexport function performComponentsCheck(\n config: HealthCheckConfig = {},\n): Effect.Effect<HealthResponse, never, never> {\n const effectiveConfig = { ...DEFAULT_HEALTH_CHECK_CONFIG, ...config };\n\n return Effect.gen(function* () {\n const components: HealthComponents = {};\n\n // Check core components\n if (effectiveConfig.checkStorage) {\n components.storage = yield* checkStorageHealth(effectiveConfig);\n }\n\n if (effectiveConfig.checkKvStore) {\n components.kvStore = yield* checkKvStoreHealth(effectiveConfig);\n }\n\n if (effectiveConfig.checkEventBroadcaster) {\n components.eventBroadcaster =\n yield* checkEventBroadcasterHealth(effectiveConfig);\n }\n\n // Check optional components (circuit breaker and DLQ)\n const circuitBreakerSummary = yield* getCircuitBreakerSummary();\n if (circuitBreakerSummary) {\n components.circuitBreaker = circuitBreakerSummary;\n }\n\n const dlqSummary = yield* getDlqSummary();\n if (dlqSummary) {\n components.deadLetterQueue = dlqSummary;\n }\n\n // Aggregate status\n const status = aggregateHealthStatus(components);\n\n return {\n status,\n timestamp: getTimestamp(),\n version: config.version,\n uptime: getServerUptime(),\n components,\n };\n });\n}\n","/**\n * Health Check HTTP Handlers for Uploadista SDK.\n *\n * This module provides HTTP handlers for health check endpoints:\n * - `/health` (liveness) - Simple alive check, no dependencies\n * - `/ready` (readiness) - Full dependency check for accepting traffic\n * - `/health/components` - Detailed component status for debugging\n *\n * @module core/http-handlers/health-http-handlers\n */\n\nimport {\n formatHealthAsText,\n getHealthResponseFormat,\n type HealthCheckConfig,\n} from \"@uploadista/core/types\";\nimport { Effect } from \"effect\";\nimport { PERMISSIONS } from \"../../permissions/types\";\nimport { AuthContextService } from \"../../service\";\nimport {\n createLivenessResponse,\n performComponentsCheck,\n performReadinessCheck,\n} from \"../health-check-service\";\nimport type {\n HealthComponentsRequest,\n HealthComponentsResponse,\n HealthReadyRequest,\n HealthReadyResponse,\n HealthRequest,\n HealthResponse,\n} from \"../routes\";\n\n/**\n * Handle GET /health - Liveness probe\n *\n * Returns immediately with 200 OK if the server is alive.\n * Does not check any dependencies.\n */\nexport const handleHealthLiveness = (\n req: HealthRequest,\n config?: HealthCheckConfig,\n) =>\n Effect.sync(() => {\n const response = createLivenessResponse(config);\n const format = getHealthResponseFormat(req.acceptHeader);\n\n if (format === \"text\") {\n return {\n type: \"health\",\n status: 200,\n headers: { \"Content-Type\": \"text/plain\" },\n body: formatHealthAsText(response.status),\n } as HealthResponse;\n }\n\n return {\n type: \"health\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: response,\n } satisfies HealthResponse;\n });\n\n/**\n * Handle GET /ready - Readiness probe\n *\n * Checks all critical dependencies (storage, KV store) and returns:\n * - 200 OK if all dependencies are healthy\n * - 503 Service Unavailable if any critical dependency is unavailable\n *\n * Requires `engine:readiness` permission.\n */\nexport const handleHealthReadiness = (\n req: HealthReadyRequest,\n config?: HealthCheckConfig,\n) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for readiness endpoint\n yield* authService.requirePermission(PERMISSIONS.ENGINE.READINESS);\n\n const response = yield* performReadinessCheck(config);\n const format = getHealthResponseFormat(req.acceptHeader);\n\n // Determine HTTP status based on health status\n const httpStatus = response.status === \"unhealthy\" ? 503 : 200;\n\n if (format === \"text\") {\n return {\n type: \"health-ready\",\n status: httpStatus,\n headers: { \"Content-Type\": \"text/plain\" },\n body: formatHealthAsText(response.status),\n } as HealthReadyResponse;\n }\n\n return {\n type: \"health-ready\",\n status: httpStatus,\n headers: { \"Content-Type\": \"application/json\" },\n body: response,\n } satisfies HealthReadyResponse;\n });\n\n/**\n * Handle GET /health/components - Detailed component status\n *\n * Returns detailed health information for each component including:\n * - Storage backend\n * - KV store\n * - Event broadcaster\n * - Circuit breaker (if enabled)\n * - Dead letter queue (if enabled)\n *\n * Always returns 200 OK for debugging purposes (even if components are degraded).\n *\n * Requires `engine:readiness` permission.\n */\nexport const handleHealthComponents = (\n req: HealthComponentsRequest,\n config?: HealthCheckConfig,\n) =>\n Effect.gen(function* () {\n const authService = yield* AuthContextService;\n\n // Check permission for components endpoint\n yield* authService.requirePermission(PERMISSIONS.ENGINE.READINESS);\n\n const response = yield* performComponentsCheck(config);\n const format = getHealthResponseFormat(req.acceptHeader);\n\n if (format === \"text\") {\n // For text format, just return the overall status\n return {\n type: \"health-components\",\n status: 200,\n headers: { \"Content-Type\": \"text/plain\" },\n body: formatHealthAsText(response.status),\n } as HealthComponentsResponse;\n }\n\n return {\n type: \"health-components\",\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: response,\n } satisfies HealthComponentsResponse;\n });\n","import { inputFileSchema } from \"@uploadista/core/types\";\nimport { UploadEngine } from \"@uploadista/core/upload\";\nimport { isSupportedAlgorithm } from \"@uploadista/core/utils\";\nimport { MetricsService } from \"@uploadista/observability\";\nimport { Effect } from \"effect\";\nimport { AuthCacheService } from \"../../cache\";\nimport { ValidationError } from \"../../error-types\";\nimport { QuotaExceededError } from \"../../permissions/errors\";\nimport { PERMISSIONS } from \"../../permissions/types\";\nimport { AuthContextService } from \"../../service\";\nimport { UsageHookService } from \"../../usage-hooks/service\";\nimport type {\n CreateUploadRequest,\n CreateUploadResponse,\n GetCapabilitiesRequest,\n GetCapabilitiesResponse,\n GetUploadRequest,\n GetUploadResponse,\n UploadChunkRequest,\n UploadChunkResponse,\n} from \"../routes\";\n\nexport const handleCreateUpload = (req: CreateUploadRequest) =>\n Effect.gen(function* () {\n const uploadEngine = yield* UploadEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n const usageHookService = yield* UsageHookService;\n const clientId = yield* authService.getClientId();\n\n // Check permission for creating uploads\n yield* authService.requirePermission(PERMISSIONS.UPLOAD.CREATE);\n\n if (clientId) {\n yield* Effect.logInfo(`[Upload] Creating upload for client: ${clientId}`);\n }\n\n const parsedInputFile = yield* Effect.sync(() =>\n inputFileSchema.safeParse(req.data),\n );\n\n if (!parsedInputFile.success) {\n return yield* Effect.fail(\n new ValidationError(\"Invalid input file schema\"),\n );\n }\n\n // Validate checksum algorithm if provided\n if (\n parsedInputFile.data.checksumAlgorithm &&\n !isSupportedAlgorithm(parsedInputFile.data.checksumAlgorithm)\n ) {\n return yield* Effect.fail(\n new ValidationError(\n `Unsupported checksum algorithm: ${parsedInputFile.data.checksumAlgorithm}. Supported algorithms: sha256`,\n ),\n );\n }\n\n // Execute onUploadStart hook for quota checking\n if (clientId) {\n const hookResult = yield* usageHookService.onUploadStart({\n clientId,\n operation: \"upload\",\n metadata: {\n fileSize: parsedInputFile.data.size,\n mimeType: parsedInputFile.data.type,\n fileName: parsedInputFile.data.fileName,\n },\n });\n\n if (hookResult.action === \"abort\") {\n return yield* Effect.fail(\n new QuotaExceededError(\n hookResult.reason,\n hookResult.code ?? \"QUOTA_EXCEEDED\",\n ),\n );\n }\n }\n\n const fileCreated = yield* uploadEngine.createUpload(\n parsedInputFile.data,\n clientId,\n );\n\n // Cache auth context for subsequent chunk uploads\n const authContext = yield* authService.getAuthContext();\n if (authContext) {\n yield* authCache.set(fileCreated.id, authContext);\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Upload] Upload created: ${fileCreated.id} for client: ${clientId}`,\n );\n }\n\n return {\n status: 200,\n body: fileCreated,\n } as CreateUploadResponse;\n });\n\nexport const handleGetCapabilities = ({ storageId }: GetCapabilitiesRequest) =>\n Effect.gen(function* () {\n const uploadEngine = yield* UploadEngine;\n const authService = yield* AuthContextService;\n const clientId = yield* authService.getClientId();\n\n // Check permission for reading upload capabilities\n yield* authService.requirePermission(PERMISSIONS.UPLOAD.READ);\n\n const capabilities = yield* uploadEngine.getCapabilities(\n storageId,\n clientId,\n );\n\n return {\n status: 200,\n body: {\n storageId,\n capabilities,\n timestamp: new Date().toISOString(),\n },\n } as GetCapabilitiesResponse;\n });\n\nexport const handleGetUpload = ({ uploadId }: GetUploadRequest) =>\n Effect.gen(function* () {\n const uploadEngine = yield* UploadEngine;\n const authService = yield* AuthContextService;\n\n // Check permission for reading upload status\n yield* authService.requirePermission(PERMISSIONS.UPLOAD.READ);\n\n const fileResult = yield* uploadEngine.getUpload(uploadId);\n\n return {\n status: 200,\n body: fileResult,\n } as GetUploadResponse;\n });\n\nexport const handleUploadChunk = (req: UploadChunkRequest) =>\n Effect.gen(function* () {\n const uploadEngine = yield* UploadEngine;\n const authService = yield* AuthContextService;\n const authCache = yield* AuthCacheService;\n const metricsService = yield* MetricsService;\n const usageHookService = yield* UsageHookService;\n\n const { uploadId, data } = req;\n\n // Check permission for creating uploads (chunks are part of creation)\n yield* authService.requirePermission(PERMISSIONS.UPLOAD.CREATE);\n\n // Try current auth first, fallback to cached auth\n let clientId = yield* authService.getClientId();\n let authMetadata = yield* authService.getMetadata();\n if (!clientId) {\n const cachedAuth = yield* authCache.get(uploadId);\n clientId = cachedAuth?.clientId ?? null;\n authMetadata = cachedAuth?.metadata ?? {};\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Upload] Uploading chunk for upload: ${uploadId}, client: ${clientId}`,\n );\n }\n\n const startTime = Date.now();\n const fileResult = yield* uploadEngine.uploadChunk(\n uploadId,\n clientId,\n data,\n );\n\n // Clear cache and record metrics if upload is complete\n if (fileResult.size && fileResult.offset >= fileResult.size) {\n yield* authCache.delete(uploadId);\n if (clientId) {\n yield* Effect.logInfo(\n `[Upload] Upload completed, cleared auth cache: ${uploadId}`,\n );\n }\n\n // Record upload metrics if we have organization ID\n if (clientId && fileResult.size) {\n yield* Effect.logInfo(\n `[Upload] Recording metrics for org: ${clientId}, size: ${fileResult.size}`,\n );\n yield* Effect.forkDaemon(\n metricsService.recordUpload(clientId, fileResult.size, authMetadata),\n );\n\n // Execute onUploadComplete hook for usage tracking\n const duration = Date.now() - startTime;\n yield* Effect.forkDaemon(\n usageHookService.onUploadComplete({\n clientId,\n operation: \"upload\",\n metadata: {\n uploadId,\n fileSize: fileResult.size,\n duration,\n },\n }),\n );\n } else {\n yield* Effect.logWarning(\n `[Upload] Cannot record metrics - missing organizationId or size`,\n );\n }\n }\n\n if (clientId) {\n yield* Effect.logInfo(\n `[Upload] Chunk uploaded for upload: ${uploadId}, client: ${clientId}`,\n );\n }\n\n return {\n status: 200,\n body: fileResult,\n } as UploadChunkResponse;\n });\n","import type { HealthCheckConfig } from \"@uploadista/core/types\";\nimport { Effect } from \"effect\";\nimport type { UploadistaRequest, UploadistaResponse } from \"../routes\";\nimport {\n handleDlqCleanup,\n handleDlqDelete,\n handleDlqGet,\n handleDlqList,\n handleDlqResolve,\n handleDlqRetry,\n handleDlqRetryAll,\n handleDlqStats,\n} from \"./dlq-http-handlers\";\nimport {\n handleCancelFlow,\n handleGetFlow,\n handleJobStatus,\n handlePauseFlow,\n handleResumeFlow,\n handleRunFlow,\n} from \"./flow-http-handlers\";\nimport {\n handleHealthComponents,\n handleHealthLiveness,\n handleHealthReadiness,\n} from \"./health-http-handlers\";\nimport {\n handleCreateUpload,\n handleGetCapabilities,\n handleGetUpload,\n handleUploadChunk,\n} from \"./upload-http-handlers\";\n\nexport type { UploadistaRequest, UploadistaResponse } from \"../routes\";\n\nexport const handleUploadistaRequest = <TRequirements>(\n req: UploadistaRequest,\n options?: { healthCheckConfig?: HealthCheckConfig },\n) => {\n return Effect.gen(function* () {\n switch (req.type) {\n case \"create-upload\":\n return (yield* handleCreateUpload(req)) as UploadistaResponse;\n case \"get-capabilities\":\n return (yield* handleGetCapabilities(req)) as UploadistaResponse;\n case \"get-upload\":\n return (yield* handleGetUpload(req)) as UploadistaResponse;\n case \"upload-chunk\":\n return (yield* handleUploadChunk(req)) as UploadistaResponse;\n case \"get-flow\":\n return (yield* handleGetFlow(req)) as UploadistaResponse;\n case \"run-flow\":\n return (yield* handleRunFlow<TRequirements>(req)) as UploadistaResponse;\n case \"job-status\":\n return (yield* handleJobStatus(req)) as UploadistaResponse;\n case \"resume-flow\":\n return (yield* handleResumeFlow<TRequirements>(\n req,\n )) as UploadistaResponse;\n case \"pause-flow\":\n return (yield* handlePauseFlow(req)) as UploadistaResponse;\n case \"cancel-flow\":\n return (yield* handleCancelFlow(req)) as UploadistaResponse;\n // DLQ Admin routes\n case \"dlq-list\":\n return (yield* handleDlqList(req)) as UploadistaResponse;\n case \"dlq-get\":\n return (yield* handleDlqGet(req)) as UploadistaResponse;\n case \"dlq-retry\":\n return (yield* handleDlqRetry(req)) as UploadistaResponse;\n case \"dlq-retry-all\":\n return (yield* handleDlqRetryAll(req)) as UploadistaResponse;\n case \"dlq-delete\":\n return (yield* handleDlqDelete(req)) as UploadistaResponse;\n case \"dlq-resolve\":\n return (yield* handleDlqResolve(req)) as UploadistaResponse;\n case \"dlq-cleanup\":\n return (yield* handleDlqCleanup(req)) as UploadistaResponse;\n case \"dlq-stats\":\n return (yield* handleDlqStats(req)) as UploadistaResponse;\n // Health check routes\n case \"health\":\n return (yield* handleHealthLiveness(\n req,\n options?.healthCheckConfig,\n )) as UploadistaResponse;\n case \"health-ready\":\n return (yield* handleHealthReadiness(\n req,\n options?.healthCheckConfig,\n )) as UploadistaResponse;\n case \"health-components\":\n return (yield* handleHealthComponents(\n req,\n options?.healthCheckConfig,\n )) as UploadistaResponse;\n case \"not-found\":\n return {\n status: 404,\n headers: { \"Content-Type\": \"application/json\" },\n body: { error: \"Not found\" },\n } as UploadistaResponse;\n case \"bad-request\":\n return {\n status: 400,\n body: { error: \"Bad request\", message: req.message },\n } as UploadistaResponse;\n case \"method-not-allowed\":\n return {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n body: { error: \"Method not allowed\" },\n } as UploadistaResponse;\n case \"unsupported-content-type\":\n return {\n status: 415,\n headers: { \"Content-Type\": \"application/json\" },\n body: { error: \"Unsupported content type\" },\n } as UploadistaResponse;\n }\n });\n};\n","import type { PluginLayer, UploadistaError } from \"@uploadista/core\";\nimport {\n deadLetterQueueService,\n type Flow,\n FlowProvider,\n FlowWaitUntil,\n kvCircuitBreakerStoreLayer,\n} from \"@uploadista/core/flow\";\nimport {\n createDataStoreLayer,\n deadLetterQueueKvStore,\n type UploadFileDataStores,\n type UploadFileKVStore,\n} from \"@uploadista/core/types\";\nimport { GenerateIdLive } from \"@uploadista/core/utils\";\nimport { memoryEventBroadcaster } from \"@uploadista/event-broadcaster-memory\";\nimport { webSocketEventEmitter } from \"@uploadista/event-emitter-websocket\";\nimport { NodeSdkLive, NoOpMetricsServiceLive } from \"@uploadista/observability\";\nimport { Effect, Layer, ManagedRuntime } from \"effect\";\nimport type { z } from \"zod\";\nimport type { StandardResponse } from \"../adapter\";\nimport { AuthCacheServiceLive } from \"../cache\";\nimport { handleFlowError } from \"../http-utils\";\nimport { createFlowEngineLayer, createUploadEngineLayer } from \"../layer-utils\";\nimport { AuthContextServiceLive } from \"../service\";\nimport type { AuthContext } from \"../types\";\nimport { UsageHookServiceLive } from \"../usage-hooks/service\";\nimport { handleUploadistaRequest } from \"./http-handlers/http-handlers\";\nimport type { ExtractFlowPluginRequirements } from \"./plugin-types\";\nimport type { NotFoundResponse } from \"./routes\";\nimport type { UploadistaServer, UploadistaServerConfig } from \"./types\";\n\n/**\n * Creates the unified Uploadista server with framework-specific adapter.\n *\n * This is the single, unified API for creating an Uploadista server. It handles\n * all server initialization, layer composition, and runtime setup.\n *\n * ## Core Responsibilities\n *\n * The server handles:\n * - Layer composition (upload/flow servers, auth cache, metrics, plugins)\n * - Route parsing and matching\n * - Auth middleware execution with timeout protection\n * - Error handling and response formatting\n * - Effect program execution with optional tracing\n * - Plugin validation and dependency injection\n *\n * ## Plugin Validation\n *\n * The server supports two validation approaches:\n *\n * ### 1. Runtime Validation (Recommended for Most Cases)\n *\n * The server relies on Effect-TS's dependency injection to validate plugins\n * at runtime. If a required plugin is missing, Effect will fail with a clear\n * MissingService error.\n *\n * ```typescript\n * const server = await createUploadistaServer({\n * flows: getFlowById,\n * plugins: [sharpImagePlugin, zipPlugin],\n * dataStore: s3DataStore,\n * kvStore: redisKvStore,\n * adapter: honoAdapter({ ... })\n * });\n * // If plugins don't match flow requirements, Effect fails with clear error\n * ```\n *\n * ### 2. Compile-Time Validation (Optional)\n *\n * For IDE feedback during development, use the ValidatePlugins type utility:\n *\n * ```typescript\n * import {\n * createUploadistaServer,\n * ValidatePlugins,\n * ExtractFlowPluginRequirements\n * } from '@uploadista/server';\n *\n * // Extract requirements from flows\n * type Requirements = ExtractFlowPluginRequirements<typeof getFlowById>;\n *\n * // Define plugins\n * const plugins = [sharpImagePlugin, zipPlugin] as const;\n *\n * // Validate at compile time (optional, for IDE feedback)\n * type Validation = ValidatePlugins<typeof plugins, Requirements>;\n * // IDE shows error if plugins don't match requirements\n *\n * const server = await createUploadistaServer({\n * flows: getFlowById,\n * plugins,\n * // ...\n * });\n * ```\n *\n * ### 3. Early Runtime Validation (Optional)\n *\n * For better error messages before server starts:\n *\n * ```typescript\n * import { validatePluginsOrThrow } from '@uploadista/server/core';\n *\n * validatePluginsOrThrow({\n * plugins: [sharpImagePlugin],\n * expectedServices: ['ImagePlugin', 'ZipPlugin']\n * });\n * // Throws with helpful error message including import suggestions\n * ```\n *\n * ## Type Safety\n *\n * - Plugin requirements are inferred from flow definitions\n * - Effect-TS ensures dependencies are satisfied at runtime\n * - Type casting is intentional (see inline docs for rationale)\n * - Optional compile-time validation available via type utilities\n *\n * @template TContext - Framework-specific context type\n * @template TResponse - Framework-specific response type\n * @template TWebSocketHandler - WebSocket handler type (if supported)\n * @template TFlows - Flow function type with plugin requirements\n * @template TPlugins - Tuple of plugin layers provided\n *\n * @param config - Server configuration including adapter and business logic\n * @returns Promise resolving to server instance with handler and metadata\n *\n * @example Basic Usage\n * ```typescript\n * import { createUploadistaServer, honoAdapter } from \"@uploadista/server\";\n * import { sharpImagePlugin } from \"@uploadista/flow-images-sharp\";\n *\n * const server = await createUploadistaServer({\n * flows: getFlowById,\n * plugins: [sharpImagePlugin],\n * dataStore: { type: \"s3\", config: { bucket: \"uploads\" } },\n * kvStore: redisKvStore,\n * adapter: honoAdapter({\n * authMiddleware: async (c) => ({ clientId: \"user-123\" })\n * })\n * });\n *\n * // Use with Hono\n * app.all(\"/uploadista/*\", server.handler);\n * ```\n *\n * @example With Compile-Time Validation\n * ```typescript\n * import {\n * createUploadistaServer,\n * ValidatePlugins,\n * ExtractFlowPluginRequirements\n * } from \"@uploadista/server\";\n *\n * type Requirements = ExtractFlowPluginRequirements<typeof getFlowById>;\n * const plugins = [sharpImagePlugin, zipPlugin] as const;\n * type Validation = ValidatePlugins<typeof plugins, Requirements>;\n *\n * const server = await createUploadistaServer({\n * flows: getFlowById,\n * plugins,\n * // ... rest of config\n * });\n * ```\n *\n * @see ValidatePlugins - Compile-time plugin validation\n * @see ExtractFlowPluginRequirements - Extract requirements from flows\n * @see validatePluginRequirements - Runtime validation helper\n * @see API_DECISION_GUIDE.md - Complete guide for choosing validation approach\n */\nexport const createUploadistaServer = async <\n TContext,\n TResponse,\n TWebSocketHandler = unknown,\n TFlows extends (\n flowId: string,\n clientId: string | null,\n ) => Effect.Effect<\n // biome-ignore lint/suspicious/noExplicitAny: Flow requirements can be any plugin services\n Flow<z.ZodSchema<unknown>, z.ZodSchema<unknown>, any>,\n UploadistaError,\n // biome-ignore lint/suspicious/noExplicitAny: Flow return type allows any requirements\n any\n // biome-ignore lint/suspicious/noExplicitAny: Generic type constraint allows any flow function type with any requirements\n > = any,\n TPlugins extends readonly PluginLayer[] = readonly PluginLayer[],\n>({\n flows,\n dataStore,\n kvStore,\n // Default to an empty plugin list while preserving the generic type\n plugins = [] as unknown as TPlugins,\n eventEmitter,\n eventBroadcaster = memoryEventBroadcaster,\n withTracing = false,\n observabilityLayer,\n baseUrl: configBaseUrl = \"uploadista\",\n generateId = GenerateIdLive,\n metricsLayer,\n bufferedDataStore,\n adapter,\n authCacheConfig,\n circuitBreaker = true,\n deadLetterQueue = false,\n healthCheck,\n usageHooks,\n}: UploadistaServerConfig<\n TContext,\n TResponse,\n TWebSocketHandler,\n TFlows,\n TPlugins\n>): Promise<UploadistaServer<TContext, TResponse, TWebSocketHandler>> => {\n // Default eventEmitter to webSocketEventEmitter with the provided eventBroadcaster\n const finalEventEmitter =\n eventEmitter ?? webSocketEventEmitter(eventBroadcaster);\n\n // Normalize baseUrl (remove trailing slash)\n const baseUrl = configBaseUrl.endsWith(\"/\")\n ? configBaseUrl.slice(0, -1)\n : configBaseUrl;\n\n type FlowReq = ExtractFlowPluginRequirements<TFlows>;\n\n // Create flow provider layer from flows function\n const flowProviderLayer = Layer.effect(\n FlowProvider,\n Effect.succeed({\n getFlow: (flowId: string, clientId: string | null) => {\n // Cast the flows function to match FlowProvider expectations\n // The context requirements will be provided at the layer level\n return flows(flowId, clientId) as Effect.Effect<\n Flow<z.ZodSchema<unknown>, z.ZodSchema<unknown>, FlowReq>,\n UploadistaError\n >;\n },\n }),\n );\n\n // Validate that eventEmitter is provided (required for upload/flow servers)\n if (!finalEventEmitter) {\n throw new Error(\n \"eventEmitter is required. Provide an event emitter layer in the configuration.\",\n );\n }\n\n // Create data store layer\n const dataStoreLayer: Layer.Layer<\n UploadFileDataStores,\n never,\n UploadFileKVStore\n > = await createDataStoreLayer(dataStore);\n\n // Create upload server layer\n const uploadEngineLayer = createUploadEngineLayer({\n kvStore,\n eventEmitter: finalEventEmitter,\n dataStore: dataStoreLayer,\n bufferedDataStore,\n generateId,\n });\n\n // Create flow server layer\n const flowEngineLayer = createFlowEngineLayer({\n kvStore,\n eventEmitter: finalEventEmitter,\n flowProvider: flowProviderLayer,\n uploadEngine: uploadEngineLayer,\n });\n\n // Create auth cache layer (always present, even if auth is not enabled)\n const authCacheLayer = AuthCacheServiceLive(authCacheConfig);\n\n // Metrics layer (defaults to NoOp if not provided)\n const effectiveMetricsLayer = metricsLayer ?? NoOpMetricsServiceLive;\n\n // Create circuit breaker store layer if enabled (uses the provided kvStore)\n const circuitBreakerStoreLayer = circuitBreaker\n ? kvCircuitBreakerStoreLayer.pipe(Layer.provide(kvStore))\n : null;\n\n // Create dead letter queue layer if enabled (uses the provided kvStore)\n // The DLQ layer provides both the KV store wrapper and the service\n const dlqLayer = deadLetterQueue\n ? deadLetterQueueService.pipe(\n Layer.provide(deadLetterQueueKvStore),\n Layer.provide(kvStore),\n )\n : null;\n\n // Create usage hook layer (defaults to no-op if not configured)\n const usageHookLayer = UsageHookServiceLive(usageHooks);\n\n /**\n * Merge all server layers including plugins.\n *\n * This combines the core server infrastructure (upload server, flow server,\n * metrics, auth cache, circuit breaker, dead letter queue, usage hooks)\n * with user-provided plugin layers.\n */\n const serverLayerRaw = Layer.mergeAll(\n uploadEngineLayer,\n flowEngineLayer,\n effectiveMetricsLayer,\n authCacheLayer,\n usageHookLayer,\n ...plugins,\n ...(circuitBreakerStoreLayer ? [circuitBreakerStoreLayer] : []),\n ...(dlqLayer ? [dlqLayer] : []),\n );\n\n /**\n * Determine the tracing layer to use.\n * This must be included in the runtime layer (not per-request) so that the\n * BatchSpanProcessor can aggregate spans across requests and flush them properly.\n */\n const tracingLayer = withTracing ? (observabilityLayer ?? NodeSdkLive) : null;\n\n /**\n * Type Casting Rationale for Plugin System\n *\n * The type assertion below is intentional and safe. This is not a bug or workaround,\n * but follows Effect-TS's design for dynamic dependency injection.\n *\n * ## Why Type Casting is Necessary\n *\n * 1. **Plugin Requirements are Dynamic**\n * Different flows require different plugins (ImagePlugin, ZipPlugin, etc.).\n * These requirements are only known when flows are loaded at runtime.\n * Flow A might need ImagePlugin, Flow B might need ZipPlugin.\n *\n * 2. **TypeScript's Static Limitation**\n * TypeScript cannot statically verify that all possible flow combinations\n * will have their requirements satisfied. The plugin array is typed as\n * `readonly PluginLayer[]` which could be any combination of plugins.\n *\n * 3. **Effect-TS Runtime Resolution**\n * Effect-TS is designed to resolve service requirements at runtime using\n * its dependency injection system. When a flow executes and accesses a service:\n *\n * ```typescript\n * const imagePlugin = yield* ImagePlugin;\n * ```\n *\n * Effect checks if ImagePlugin exists in the provided layer context.\n * If missing, Effect fails with a clear MissingService error.\n *\n * 4. **Layer Composition Guarantees**\n * Layer.mergeAll() combines all layers. At runtime, Effect ensures that\n * when a service is requested, it's either:\n * - Provided by one of the merged layers, OR\n * - Results in a MissingService error with the service name\n *\n * ## Safety Guarantees\n *\n * This pattern is safe because:\n *\n * 1. **Runtime Validation** (Optional but Recommended)\n * We provide validatePluginRequirements() that checks plugins before\n * server initialization, giving excellent error messages early.\n *\n * 2. **Effect's Built-in Validation**\n * If runtime validation is skipped, Effect will fail during flow execution\n * with a MissingService error containing the service identifier.\n *\n * 3. **Optional Compile-Time Validation**\n * Developers can use ValidatePlugins<> type utility for IDE feedback:\n *\n * ```typescript\n * type Validation = ValidatePlugins<typeof plugins, Requirements>;\n * // Shows compile error if plugins don't match requirements\n * ```\n *\n * 4. **No Silent Failures**\n * There's no scenario where missing plugins cause silent failures.\n * Either runtime validation catches it, or Effect fails with clear error.\n *\n * ## This is Effect-TS's Idiomatic Pattern\n *\n * Effect-TS separates compile-time structure from runtime resolution:\n * - Compile-time: Types ensure layer structure is correct\n * - Runtime: Effect resolves actual dependencies and fails if missing\n *\n * The type system provides structure and IDE support, while Effect's\n * runtime handles actual requirement resolution.\n *\n * ## Further Reading\n *\n * - Effect-TS Context Management: https://effect.website/docs/guides/context-management\n * - Runtime Validation: See plugin-validation.ts for helper functions\n * - Type Utilities: See plugin-types.ts for compile-time validation\n *\n * @see validatePluginRequirements - Runtime validation helper\n * @see ValidatePlugins - Compile-time validation type utility\n */\n const serverLayerTyped = serverLayerRaw as unknown as Layer.Layer<\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic plugin requirements require any - see comprehensive explanation above\n any,\n never,\n never\n >;\n\n /**\n * Final server layer with optional tracing.\n * The tracing layer is merged at runtime level (not per-request) so that:\n * 1. The OpenTelemetry SDK is initialized once for the server\n * 2. The BatchSpanProcessor can aggregate spans across requests\n * 3. Spans are properly flushed when the runtime is disposed\n */\n const serverLayer = tracingLayer\n ? Layer.merge(serverLayerTyped, tracingLayer)\n : serverLayerTyped;\n\n // Create a shared managed runtime from the server layer\n // This ensures all requests use the same layer instances (including event broadcaster)\n // ManagedRuntime properly handles scoped resources and provides convenient run methods\n // When tracing is enabled, the OpenTelemetry SDK is part of this runtime and will be\n // properly shut down (flushing all pending spans) when dispose() is called\n\n const managedRuntime = ManagedRuntime.make(serverLayer);\n\n /**\n * Main request handler that processes HTTP requests through the adapter.\n * Delegates to adapter's httpHandler if provided, otherwise uses standard flow.\n */\n const handler = async <TRequirements>(ctx: TContext) => {\n // Fallback: Standard routing logic (for adapters without httpHandler)\n const program = Effect.gen(function* () {\n // Extract standard request from framework-specific request\n const uploadistaRequest = yield* adapter.extractRequest(ctx, { baseUrl });\n\n // Run auth middleware if provided\n let authContext: AuthContext | null = null;\n if (adapter.runAuthMiddleware) {\n const authMiddlewareWithTimeout = adapter.runAuthMiddleware(ctx).pipe(\n Effect.timeout(\"5 seconds\"),\n Effect.catchAll(() => {\n // Timeout error\n console.error(\"Auth middleware timeout exceeded (5 seconds)\");\n return Effect.succeed({\n _tag: \"TimeoutError\" as const,\n } as const);\n }),\n Effect.catchAllCause((cause) => {\n // Other errors\n console.error(\"Auth middleware error:\", cause);\n return Effect.succeed({\n _tag: \"AuthError\" as const,\n error: cause,\n } as const);\n }),\n );\n\n const authResult:\n | AuthContext\n | null\n | { _tag: \"TimeoutError\" }\n | { _tag: \"AuthError\"; error: unknown } =\n yield* authMiddlewareWithTimeout;\n\n // Handle timeout\n if (\n authResult &&\n typeof authResult === \"object\" &&\n \"_tag\" in authResult &&\n authResult._tag === \"TimeoutError\"\n ) {\n const errorResponse: StandardResponse = {\n status: 503,\n headers: { \"Content-Type\": \"application/json\" },\n body: {\n error: \"Authentication service unavailable\",\n message:\n \"Authentication took too long to respond. Please try again.\",\n },\n };\n return yield* adapter.sendResponse(errorResponse, ctx);\n }\n\n // Handle auth error\n if (\n authResult &&\n typeof authResult === \"object\" &&\n \"_tag\" in authResult &&\n authResult._tag === \"AuthError\"\n ) {\n const errorResponse: StandardResponse = {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n body: {\n error: \"Internal Server Error\",\n message: \"An error occurred during authentication\",\n },\n };\n return yield* adapter.sendResponse(errorResponse, ctx);\n }\n\n // Handle authentication failure (null result)\n if (authResult === null) {\n const errorResponse: StandardResponse = {\n status: 401,\n headers: { \"Content-Type\": \"application/json\" },\n body: {\n error: \"Unauthorized\",\n message: \"Invalid credentials\",\n },\n };\n return yield* adapter.sendResponse(errorResponse, ctx);\n }\n\n authContext = authResult;\n }\n\n // Create auth context layer for this request\n // If no auth middleware is configured, bypass permission checks (backward compatibility)\n const authContextLayer = AuthContextServiceLive(authContext, {\n bypassAuth: !adapter.runAuthMiddleware,\n });\n\n // Extract waitUntil callback if available (for Cloudflare Workers)\n // This must be extracted per-request since it comes from the framework context\n // biome-ignore lint/suspicious/noExplicitAny: Layer array needs to accept any service type from waitUntil\n const waitUntilLayers: Layer.Layer<any, never, never>[] = [];\n if (adapter.extractWaitUntil) {\n const waitUntilCallback = adapter.extractWaitUntil(ctx);\n if (waitUntilCallback) {\n waitUntilLayers.push(Layer.succeed(FlowWaitUntil, waitUntilCallback));\n }\n }\n\n // Combine auth context, auth cache, metrics layers, usage hooks, plugins, circuit breaker, DLQ, and waitUntil\n // This ensures that flow nodes have access to all required services\n const baseRequestContextLayer = Layer.mergeAll(\n authContextLayer,\n authCacheLayer,\n effectiveMetricsLayer,\n usageHookLayer,\n ...plugins,\n ...waitUntilLayers,\n );\n const withCircuitBreakerContext = circuitBreakerStoreLayer\n ? Layer.merge(baseRequestContextLayer, circuitBreakerStoreLayer)\n : baseRequestContextLayer;\n const requestContextLayer = dlqLayer\n ? Layer.merge(withCircuitBreakerContext, dlqLayer)\n : withCircuitBreakerContext;\n\n // Check for baseUrl/api/ prefix\n if (uploadistaRequest.type === \"not-found\") {\n const notFoundResponse: NotFoundResponse = {\n type: \"not-found\",\n status: 404,\n headers: { \"Content-Type\": \"application/json\" },\n body: { error: \"Not found\" },\n };\n return yield* adapter.sendResponse(notFoundResponse, ctx);\n }\n\n // Handle the request\n const response = yield* handleUploadistaRequest<TRequirements>(\n uploadistaRequest,\n { healthCheckConfig: healthCheck },\n ).pipe(Effect.provide(requestContextLayer));\n\n return yield* adapter.sendResponse(response, ctx);\n }).pipe(\n // Catch all errors and format them appropriately\n Effect.catchAll((error: unknown) => {\n const errorInfo = handleFlowError(error);\n const errorBody: Record<string, unknown> = {\n code: errorInfo.code,\n message: errorInfo.message,\n };\n if (errorInfo.details !== undefined) {\n errorBody.details = errorInfo.details;\n }\n const errorResponse: StandardResponse = {\n status: errorInfo.status,\n headers: { \"Content-Type\": \"application/json\" },\n body: errorBody,\n };\n return adapter.sendResponse(errorResponse, ctx);\n }),\n );\n\n // Use the shared managed runtime which includes all layers (including tracing if enabled)\n // Tracing is now part of the runtime layer, so spans are properly aggregated and flushed\n return managedRuntime.runPromise(program);\n };\n\n // Create WebSocket handler using the shared managed runtime\n const websocketHandler = await managedRuntime.runPromise(\n adapter.webSocketHandler({\n baseUrl,\n }),\n );\n\n return {\n handler,\n websocketHandler,\n baseUrl,\n dispose: () => managedRuntime.dispose(),\n };\n};\n","import type {\n PluginServices,\n PluginTuple,\n TypeSafeFlowFunction,\n ValidatePlugins,\n} from \"./plugin-types\";\nimport { createUploadistaServer } from \"./server\";\nimport type { UploadistaServer, UploadistaServerConfig } from \"./types\";\n\n/**\n * Type-safe configuration for Uploadista server with compile-time plugin validation.\n *\n * This configuration extends the base UploadistaServerConfig with stricter typing\n * that validates plugins match flow requirements at compile time.\n *\n * @template TContext - Framework-specific request context type\n * @template TResponse - Framework-specific response type\n * @template TWebSocket - Framework-specific WebSocket handler type\n * @template TPlugins - Tuple of plugin layers provided to the server\n * @template TFlowRequirements - Union of plugin services required by flows\n */\nexport type TypeSafeServerConfig<\n TContext,\n TResponse,\n TWebSocket,\n TPlugins extends PluginTuple,\n TFlowRequirements = PluginServices<TPlugins>,\n> = Omit<\n UploadistaServerConfig<TContext, TResponse, TWebSocket>,\n \"flows\" | \"plugins\"\n> & {\n /**\n * Tuple of plugin layers that provide services to flows.\n * The plugins must satisfy all requirements declared by the flows.\n */\n plugins: TPlugins;\n\n /**\n * Type-safe flow function with explicit requirements.\n * TypeScript validates that all required plugins are provided.\n */\n flows: TypeSafeFlowFunction<TFlowRequirements>;\n\n /**\n * Compile-time validation that plugins satisfy flow requirements.\n * If this field has type errors, required plugins are missing.\n */\n __validate?: ValidatePlugins<TPlugins, TFlowRequirements>;\n};\n\n/**\n * @deprecated Use `createUploadistaServer` with optional type utilities instead.\n *\n * This function is deprecated in favor of the unified `createUploadistaServer` API.\n * The new approach separates validation concerns from server creation, making the\n * API simpler while still providing compile-time validation when desired.\n *\n * ## Migration Guide\n *\n * ### Old Approach (Deprecated)\n * ```typescript\n * import { createTypeSafeServer } from \"@uploadista/server\";\n *\n * const server = await createTypeSafeServer({\n * plugins: [sharpImagePlugin] as const,\n * flows: myFlowFunction,\n * // ...\n * });\n * ```\n *\n * ### New Approach (Recommended)\n *\n * **Option 1: Runtime validation only (simplest)**\n * ```typescript\n * import { createUploadistaServer } from \"@uploadista/server\";\n *\n * const server = await createUploadistaServer({\n * plugins: [sharpImagePlugin, zipPlugin],\n * flows: myFlowFunction,\n * // ... Effect validates at runtime\n * });\n * ```\n *\n * **Option 2: With compile-time validation (optional)**\n * ```typescript\n * import {\n * createUploadistaServer,\n * ValidatePlugins,\n * ExtractFlowPluginRequirements\n * } from \"@uploadista/server\";\n *\n * type Requirements = ExtractFlowPluginRequirements<typeof myFlowFunction>;\n * const plugins = [sharpImagePlugin, zipPlugin] as const;\n * type Validation = ValidatePlugins<typeof plugins, Requirements>;\n * // IDE shows error if plugins don't match requirements\n *\n * const server = await createUploadistaServer({\n * plugins,\n * flows: myFlowFunction,\n * // ...\n * });\n * ```\n *\n * ## Why This Changed\n *\n * 1. **Simpler API**: One function instead of two reduces confusion\n * 2. **Separation of Concerns**: Validation is now optional and separate\n * 3. **Better Flexibility**: Choose validation approach per use case\n * 4. **Clearer Intent**: Explicit validation via type utilities\n * 5. **Same Safety**: Effect-TS still validates at runtime\n *\n * The new approach trusts Effect-TS's design for dynamic dependency injection\n * while providing optional compile-time validation through type utilities.\n *\n * @see createUploadistaServer - The unified server creation API\n * @see ValidatePlugins - Compile-time validation type utility\n * @see ExtractFlowPluginRequirements - Extract requirements from flows\n * @see API_DECISION_GUIDE.md - Complete migration and usage guide\n *\n * @template TContext - Framework-specific request context type\n * @template TResponse - Framework-specific response type\n * @template TWebSocket - Framework-specific WebSocket handler type\n * @template TPlugins - Tuple of plugin layers\n * @template TFlowRequirements - Union of services required by flows\n *\n * @param config - Type-safe server configuration\n * @returns Promise resolving to UploadistaServer instance\n */\nexport async function createTypeSafeServer<\n TContext,\n TResponse,\n TWebSocket = unknown,\n TPlugins extends PluginTuple = PluginTuple,\n TFlowRequirements = PluginServices<TPlugins>,\n>(\n config: TypeSafeServerConfig<\n TContext,\n TResponse,\n TWebSocket,\n TPlugins,\n TFlowRequirements\n > &\n // Enforce validation at function call site\n (ValidatePlugins<TPlugins, TFlowRequirements> extends true\n ? object\n : ValidatePlugins<TPlugins, TFlowRequirements>),\n): Promise<UploadistaServer<TContext, TResponse, TWebSocket>> {\n return createUploadistaServer(config);\n}\n\n/**\n * Helper function to define flow functions with explicit type requirements.\n * Provides better type inference and autocomplete for plugin services.\n *\n * @template TRequirements - Union of plugin services this flow needs\n *\n * @param fn - The flow function implementation\n * @returns The same function with explicit type annotation\n *\n * @example\n * ```typescript\n * import { ImagePlugin } from \"@uploadista/core/flow\";\n * import { defineFlow } from \"@uploadista/server\";\n *\n * // Explicitly declare that this flow requires ImagePlugin\n * const imageProcessingFlow = defineFlow<ImagePlugin>((flowId, clientId) =>\n * Effect.gen(function* () {\n * const imageService = yield* ImagePlugin; // Autocomplete works!\n * const optimized = yield* imageService.optimize(data, { quality: 80 });\n * return createFlow({ ... });\n * })\n * );\n * ```\n */\nexport function defineFlow<TRequirements = never>(\n fn: TypeSafeFlowFunction<TRequirements>,\n): TypeSafeFlowFunction<TRequirements> {\n return fn;\n}\n\n/**\n * Helper to create a flow that requires no plugins.\n * Useful for simple flows that only use built-in functionality.\n *\n * @example\n * ```typescript\n * import { defineSimpleFlow } from \"@uploadista/server\";\n *\n * const simpleFlow = defineSimpleFlow((flowId, clientId) =>\n * Effect.succeed(createFlow({\n * id: \"simple\",\n * nodes: [],\n * edges: [],\n * inputSchema: myInputSchema,\n * outputSchema: myOutputSchema\n * }))\n * );\n * ```\n */\nexport function defineSimpleFlow(\n fn: TypeSafeFlowFunction<never>,\n): TypeSafeFlowFunction<never> {\n return fn;\n}\n","/**\n * Runtime plugin validation utilities.\n *\n * This module provides runtime validation to ensure that all plugins required\n * by flows are actually provided to the server. While Effect-TS will catch\n * missing dependencies at runtime, this validation provides better error messages\n * and fails fast during server initialization.\n *\n * @module plugin-validation\n */\n\nimport type { PluginLayer } from \"@uploadista/core\";\nimport { Effect } from \"effect\";\n\n/**\n * Result of plugin validation.\n */\nexport type PluginValidationResult =\n | {\n success: true;\n }\n | {\n success: false;\n required: string[];\n provided: string[];\n missing: string[];\n suggestions: Array<{\n name: string;\n packageName: string;\n importStatement: string;\n }>;\n };\n\n/**\n * Known plugin mapping for generating helpful error messages.\n *\n * This maps service identifiers to their package names and variable names\n * for generating import suggestions.\n */\nconst KNOWN_PLUGINS: Record<\n string,\n { packageName: string; variableName: string }\n> = {\n ImagePlugin: {\n packageName: \"@uploadista/flow-images-sharp\",\n variableName: \"sharpImagePlugin\",\n },\n ImageAiPlugin: {\n packageName: \"@uploadista/flow-images-replicate\",\n variableName: \"replicateImagePlugin\",\n },\n ZipPlugin: {\n packageName: \"@uploadista/flow-utility-zipjs\",\n variableName: \"zipPlugin\",\n },\n CredentialProvider: {\n packageName: \"@uploadista/core\",\n variableName: \"credentialProviderLayer\",\n },\n};\n\n/**\n * Extracts service identifier from a plugin layer.\n *\n * This attempts to identify the service provided by a layer using various\n * heuristics. The exact implementation depends on how Effect-TS exposes\n * layer metadata.\n *\n * @param layer - The plugin layer to inspect\n * @returns Service identifier string or null if not identifiable\n */\nfunction extractServiceIdentifier(layer: PluginLayer): string | null {\n // Attempt to extract service identifier from layer\n // Note: Effect-TS doesn't expose this information in a standard way,\n // so we use Symbol.toStringTag or constructor name as fallbacks\n\n try {\n // Try to get the service tag if available\n // biome-ignore lint/suspicious/noExplicitAny: Layer introspection requires accessing internal properties\n const layerAny = layer as any;\n\n // Check for common patterns in Effect layers\n if (layerAny._tag) {\n return layerAny._tag;\n }\n\n if (layerAny.constructor?.name) {\n return layerAny.constructor.name;\n }\n\n // Try to extract from the layer's context if available\n if (layerAny.context?.services) {\n const services = Array.from(layerAny.context.services.keys());\n if (services.length > 0) {\n // biome-ignore lint/suspicious/noExplicitAny: Service introspection requires accessing internal properties\n const firstService = services[0] as any;\n if (firstService.key) {\n return firstService.key;\n }\n }\n }\n\n return null;\n } catch {\n // If we can't extract the identifier, return null\n return null;\n }\n}\n\n/**\n * Extracts service identifiers from an array of plugin layers.\n *\n * @param plugins - Array of plugin layers\n * @returns Array of service identifier strings\n */\nexport function extractServiceIdentifiers(\n plugins: readonly PluginLayer[],\n): string[] {\n return plugins\n .map((plugin) => extractServiceIdentifier(plugin))\n .filter((id): id is string => id !== null);\n}\n\n/**\n * Validates that all required plugins are provided.\n *\n * This is a runtime validation function that checks if the plugins array\n * contains all services required by the flows. It's called during server\n * initialization to provide early, clear error messages.\n *\n * Note: This validation is best-effort because we can't reliably extract\n * requirements from flow functions at runtime without executing them.\n * The main validation happens via Effect-TS's dependency injection.\n *\n * @param config - Validation configuration\n * @returns Validation result with detailed error information if validation fails\n *\n * @example\n * ```typescript\n * const result = validatePluginRequirements({\n * plugins: [sharpImagePlugin, zipPlugin],\n * expectedServices: ['ImagePlugin', 'ZipPlugin']\n * });\n *\n * if (!result.success) {\n * console.error('Missing plugins:', result.missing);\n * console.error('Suggestions:', result.suggestions);\n * }\n * ```\n */\nexport function validatePluginRequirements(config: {\n plugins: readonly PluginLayer[];\n expectedServices?: string[];\n}): PluginValidationResult {\n const { plugins, expectedServices = [] } = config;\n\n // Extract identifiers from provided plugins\n const providedServices = extractServiceIdentifiers(plugins);\n\n // Check for missing services\n const missing = expectedServices.filter(\n (required) => !providedServices.includes(required),\n );\n\n if (missing.length === 0) {\n return { success: true };\n }\n\n // Generate suggestions for missing plugins\n const suggestions = missing\n .map((service) => {\n const knownPlugin = KNOWN_PLUGINS[service];\n if (!knownPlugin) {\n return null;\n }\n\n return {\n name: service,\n packageName: knownPlugin.packageName,\n importStatement: `import { ${knownPlugin.variableName} } from '${knownPlugin.packageName}';`,\n };\n })\n .filter((s): s is NonNullable<typeof s> => s !== null);\n\n return {\n success: false,\n required: expectedServices,\n provided: providedServices,\n missing,\n suggestions,\n };\n}\n\n/**\n * Creates a formatted error message for plugin validation failures.\n *\n * This generates a detailed, human-readable error message that includes:\n * - List of required plugins\n * - List of provided plugins\n * - List of missing plugins\n * - Import statements for missing plugins (if known)\n * - Example server configuration\n *\n * @param result - Failed validation result\n * @returns Formatted error message string\n *\n * @example\n * ```typescript\n * const result = validatePluginRequirements({ ... });\n * if (!result.success) {\n * const message = formatPluginValidationError(result);\n * throw new Error(message);\n * }\n * ```\n */\nexport function formatPluginValidationError(\n result: Extract<PluginValidationResult, { success: false }>,\n): string {\n const lines: string[] = [\n \"Server initialization failed: Missing required plugins\",\n \"\",\n `Required: ${result.required.join(\", \")}`,\n `Provided: ${result.provided.length > 0 ? result.provided.join(\", \") : \"(none)\"}`,\n `Missing: ${result.missing.join(\", \")}`,\n \"\",\n ];\n\n if (result.suggestions.length > 0) {\n lines.push(\"Add the missing plugins to your configuration:\");\n lines.push(\"\");\n for (const suggestion of result.suggestions) {\n lines.push(` ${suggestion.importStatement}`);\n }\n lines.push(\"\");\n lines.push(\" const server = await createUploadistaServer({\");\n lines.push(\n ` plugins: [${[...result.provided, ...result.missing.map((m) => KNOWN_PLUGINS[m]?.variableName || m)].join(\", \")}],`,\n );\n lines.push(\" // ...\");\n lines.push(\" });\");\n } else {\n lines.push(\"Note: Could not determine package names for missing plugins.\");\n lines.push(\"Please ensure all required plugin layers are provided.\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Effect-based plugin validation that can be composed with other Effects.\n *\n * This provides an Effect-TS native way to validate plugins, allowing it\n * to be composed with other Effects in the server initialization pipeline.\n *\n * @param config - Validation configuration\n * @returns Effect that succeeds if validation passes, fails with UploadistaError if not\n *\n * @example\n * ```typescript\n * const validatedServer = Effect.gen(function* () {\n * yield* validatePluginRequirementsEffect({\n * plugins: [sharpImagePlugin],\n * expectedServices: ['ImagePlugin', 'ZipPlugin']\n * });\n *\n * return yield* createServerEffect(...);\n * });\n * ```\n */\nexport function validatePluginRequirementsEffect(config: {\n plugins: readonly PluginLayer[];\n expectedServices?: string[];\n}): Effect.Effect<void, Error> {\n return Effect.sync(() => {\n const result = validatePluginRequirements(config);\n\n if (!result.success) {\n const message = formatPluginValidationError(result);\n throw new Error(message);\n }\n });\n}\n\n/**\n * Validates plugin configuration at runtime during server initialization.\n *\n * This is a convenience function that performs validation and throws a\n * descriptive error if validation fails. Use this at the beginning of\n * createUploadistaServer to fail fast with clear error messages.\n *\n * @param config - Validation configuration\n * @throws Error with detailed message if validation fails\n *\n * @example\n * ```typescript\n * export const createUploadistaServer = async (config) => {\n * // Validate plugins early\n * validatePluginsOrThrow({\n * plugins: config.plugins,\n * expectedServices: ['ImagePlugin', 'ZipPlugin']\n * });\n *\n * // Continue with server creation...\n * };\n * ```\n */\nexport function validatePluginsOrThrow(config: {\n plugins: readonly PluginLayer[];\n expectedServices?: string[];\n}): void {\n const result = validatePluginRequirements(config);\n\n if (!result.success) {\n const message = formatPluginValidationError(result);\n throw new Error(message);\n }\n}\n","import type { FlowEngineShape } from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport type { WebSocketConnection } from \"../websocket-routes\";\n\n/**\n * Handles subscription to flow events\n * Subscribes the WebSocket connection to receive real-time flow execution events\n */\nexport const handleSubscribeToFlowEvents = (\n flowEngine: FlowEngineShape,\n jobId: string | undefined,\n connection: WebSocketConnection,\n) => {\n return Effect.gen(function* () {\n if (!jobId) {\n yield* Effect.sync(() => {\n connection.send(\n JSON.stringify({\n type: \"error\",\n message: \"Job ID is required for flow event subscription\",\n code: \"MISSING_JOB_ID\",\n }),\n );\n });\n return;\n }\n\n yield* flowEngine.subscribeToFlowEvents(jobId, connection);\n });\n};\n\n/**\n * Handles unsubscription from flow events\n * Removes the WebSocket connection from receiving flow events\n */\nexport const handleUnsubscribeFromFlowEvents = (\n flowEngine: FlowEngineShape,\n jobId: string | undefined,\n) => {\n return Effect.gen(function* () {\n if (!jobId) {\n return;\n }\n\n yield* flowEngine.unsubscribeFromFlowEvents(jobId);\n });\n};\n","import type { UploadEngineShape } from \"@uploadista/core/upload\";\nimport { Effect } from \"effect\";\nimport type { WebSocketConnection } from \"../websocket-routes\";\n\n/**\n * Handles subscription to upload events\n * Subscribes the WebSocket connection to receive real-time upload progress events\n */\nexport const handleSubscribeToUploadEvents = (\n uploadEngine: UploadEngineShape,\n uploadId: string | undefined,\n connection: WebSocketConnection,\n) => {\n return Effect.gen(function* () {\n if (!uploadId) {\n yield* Effect.sync(() => {\n connection.send(\n JSON.stringify({\n type: \"error\",\n message: \"Upload ID is required for upload event subscription\",\n code: \"MISSING_UPLOAD_ID\",\n }),\n );\n });\n return;\n }\n\n yield* uploadEngine.subscribeToUploadEvents(uploadId, connection);\n });\n};\n\n/**\n * Handles unsubscription from upload events\n * Removes the WebSocket connection from receiving upload events\n */\nexport const handleUnsubscribeFromUploadEvents = (\n uploadEngine: UploadEngineShape,\n uploadId: string | undefined,\n) => {\n return Effect.gen(function* () {\n if (!uploadId) {\n return;\n }\n\n yield* uploadEngine.unsubscribeFromUploadEvents(uploadId);\n });\n};\n","import { UploadistaError } from \"@uploadista/core/errors\";\nimport type { FlowEngineShape } from \"@uploadista/core/flow\";\nimport type { UploadEngineShape } from \"@uploadista/core/upload\";\nimport { Effect } from \"effect\";\nimport type {\n WebSocketConnection,\n WebSocketConnectionRequest,\n} from \"../websocket-routes\";\nimport {\n handleSubscribeToFlowEvents,\n handleUnsubscribeFromFlowEvents,\n} from \"./flow-websocket-handlers\";\nimport {\n handleSubscribeToUploadEvents,\n handleUnsubscribeFromUploadEvents,\n} from \"./upload-websocket-handlers\";\n\nexport type {\n WebSocketConnection,\n WebSocketConnectionRequest,\n} from \"../websocket-routes\";\n\n/**\n * Handles WebSocket connection opening\n * Subscribes to the appropriate events based on the connection request\n */\nexport const handleWebSocketOpen = (\n request: WebSocketConnectionRequest,\n uploadEngine: UploadEngineShape,\n flowEngine: FlowEngineShape,\n) => {\n const { connection, isFlowRoute, isUploadRoute, jobId, uploadId, eventId } =\n request;\n\n return Effect.gen(function* () {\n // Subscribe to flow events if this is a flow route\n if (isFlowRoute) {\n yield* handleSubscribeToFlowEvents(flowEngine, jobId, connection);\n }\n\n // Subscribe to upload events if this is an upload route\n if (isUploadRoute) {\n yield* handleSubscribeToUploadEvents(uploadEngine, uploadId, connection);\n }\n\n // Send connection confirmation\n connection.send(\n JSON.stringify({\n type: \"connection\",\n message: \"Uploadista WebSocket connected\",\n id: eventId,\n jobId,\n uploadId,\n timestamp: new Date().toISOString(),\n }),\n );\n }).pipe(\n Effect.catchAll((error) =>\n Effect.sync(() => {\n console.error(\"Error subscribing to events:\", error);\n const errorMessage =\n error instanceof UploadistaError\n ? error.body\n : \"Failed to subscribe to events\";\n connection.send(\n JSON.stringify({\n type: \"error\",\n message: errorMessage,\n code:\n error instanceof UploadistaError\n ? error.code\n : \"SUBSCRIPTION_ERROR\",\n }),\n );\n }),\n ),\n );\n};\n\n/**\n * Handles incoming WebSocket messages\n * Currently supports ping/pong for connection keep-alive\n */\nexport const handleWebSocketMessage = (\n message: string,\n connection: WebSocketConnection,\n) => {\n return Effect.sync(() => {\n try {\n const parsed = JSON.parse(message);\n if (parsed.type === \"ping\") {\n connection.send(\n JSON.stringify({\n type: \"pong\",\n timestamp: new Date().toISOString(),\n }),\n );\n }\n } catch (error) {\n console.error(\"Error handling WebSocket message:\", error);\n connection.send(\n JSON.stringify({\n type: \"error\",\n message: \"Invalid message format\",\n }),\n );\n }\n });\n};\n\n/**\n * Handles WebSocket connection closing\n * Unsubscribes from all events and cleans up resources\n */\nexport const handleWebSocketClose = (\n request: WebSocketConnectionRequest,\n uploadEngine: UploadEngineShape,\n flowEngine: FlowEngineShape,\n) => {\n const { isFlowRoute, isUploadRoute, jobId, uploadId } = request;\n\n return Effect.gen(function* () {\n // Unsubscribe from flow events if this was a flow route\n if (isFlowRoute) {\n yield* handleUnsubscribeFromFlowEvents(flowEngine, jobId);\n }\n\n // Unsubscribe from upload events if this was an upload route\n if (isUploadRoute) {\n yield* handleUnsubscribeFromUploadEvents(uploadEngine, uploadId);\n }\n }).pipe(\n Effect.catchAll((error) =>\n Effect.sync(() => {\n console.error(\n \"Error unsubscribing from events:\",\n error instanceof UploadistaError ? error.body : error,\n );\n }),\n ),\n );\n};\n\n/**\n * Handles WebSocket errors\n */\nexport const handleWebSocketError = (error: unknown, eventId?: string) => {\n return Effect.sync(() => {\n console.error(`WebSocket error for event ${eventId}:`, error);\n });\n};\n"],"mappings":"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"}
|