@uploadista/server 0.0.6 → 0.0.8
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/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/http-utils.ts +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -376,8 +376,8 @@ declare const handleFlowError: (error: unknown) => {
|
|
|
376
376
|
*/
|
|
377
377
|
declare const extractJobIdFromStatus: (urlSegments: string[]) => string | undefined;
|
|
378
378
|
/**
|
|
379
|
-
* Extracts job ID and node ID from URL segments for
|
|
380
|
-
* Expected URL format: `/uploadista/api/jobs/:jobId/
|
|
379
|
+
* Extracts job ID and node ID from URL segments for resume flow endpoint.
|
|
380
|
+
* Expected URL format: `/uploadista/api/jobs/:jobId/resume/:nodeId`
|
|
381
381
|
*
|
|
382
382
|
* @param urlSegments - Parsed URL segments (without base path)
|
|
383
383
|
* @returns Object with extracted jobId and nodeId (either can be undefined if not found)
|
|
@@ -387,7 +387,7 @@ declare const extractJobIdFromStatus: (urlSegments: string[]) => string | undefi
|
|
|
387
387
|
* const { jobId, nodeId } = extractJobAndNodeId([
|
|
388
388
|
* "jobs",
|
|
389
389
|
* "job-123",
|
|
390
|
-
* "
|
|
390
|
+
* "resume",
|
|
391
391
|
* "node-456",
|
|
392
392
|
* ]);
|
|
393
393
|
* // => { jobId: "job-123", nodeId: "node-456" }
|
package/dist/index.d.ts
CHANGED
|
@@ -376,8 +376,8 @@ declare const handleFlowError: (error: unknown) => {
|
|
|
376
376
|
*/
|
|
377
377
|
declare const extractJobIdFromStatus: (urlSegments: string[]) => string | undefined;
|
|
378
378
|
/**
|
|
379
|
-
* Extracts job ID and node ID from URL segments for
|
|
380
|
-
* Expected URL format: `/uploadista/api/jobs/:jobId/
|
|
379
|
+
* Extracts job ID and node ID from URL segments for resume flow endpoint.
|
|
380
|
+
* Expected URL format: `/uploadista/api/jobs/:jobId/resume/:nodeId`
|
|
381
381
|
*
|
|
382
382
|
* @param urlSegments - Parsed URL segments (without base path)
|
|
383
383
|
* @returns Object with extracted jobId and nodeId (either can be undefined if not found)
|
|
@@ -387,7 +387,7 @@ declare const extractJobIdFromStatus: (urlSegments: string[]) => string | undefi
|
|
|
387
387
|
* const { jobId, nodeId } = extractJobAndNodeId([
|
|
388
388
|
* "jobs",
|
|
389
389
|
* "job-123",
|
|
390
|
-
* "
|
|
390
|
+
* "resume",
|
|
391
391
|
* "node-456",
|
|
392
392
|
* ]);
|
|
393
393
|
* // => { jobId: "job-123", nodeId: "node-456" }
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["oldestKey: string | null","NoAuthCacheServiceLive: Layer.Layer<AuthCacheService>","statusCode: number","errorCode: string","response: {\n error: string;\n code: string;\n timestamp: string;\n details?: unknown;\n }","details: unknown","result: {\n status: number;\n code: string;\n message: string;\n details?: unknown;\n }","uploadServer","NoAuthContextServiceLive: Layer.Layer<AuthContextService>"],"sources":["../src/cache.ts","../src/error-types.ts","../src/http-utils.ts","../src/layer-utils.ts","../src/service.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","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* uploadServer.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 * 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 continue flow endpoint.\n * Expected URL format: `/uploadista/api/jobs/:jobId/continue/: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 * \"continue\",\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 { flowServer } 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 UploadServer, uploadServer } from \"@uploadista/core/upload\";\nimport type { GenerateId } from \"@uploadista/core/utils\";\nimport { Layer } from \"effect\";\n\n/**\n * Configuration for creating upload server layers.\n * Specifies all dependencies needed by the upload server 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 { createUploadServerLayer } from \"@uploadista/server\";\n *\n * const uploadLayerConfig: UploadServerLayerConfig = {\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * dataStore: s3DataStore,\n * };\n * ```\n */\nexport interface UploadServerLayerConfig {\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 uploadServer - Upload server 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 * uploadServer: uploadServerLayer,\n * };\n * ```\n */\nexport interface FlowServerLayerConfig {\n kvStore: Layer.Layer<BaseKvStoreService>;\n eventEmitter: Layer.Layer<BaseEventEmitterService>;\n flowProvider: Layer.Layer<FlowProvider>;\n uploadServer: Layer.Layer<UploadServer>;\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 UploadServer\n *\n * @example\n * ```typescript\n * import { createUploadServerLayer } from \"@uploadista/server\";\n * import { Layer } from \"effect\";\n *\n * const uploadServerLayer = createUploadServerLayer({\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * dataStore: s3DataStore,\n * });\n *\n * // Use in application\n * const app = Layer.provide(appLogic, uploadServerLayer);\n * ```\n */\nexport const createUploadServerLayer = ({\n kvStore,\n eventEmitter,\n dataStore,\n bufferedDataStore,\n generateId,\n}: UploadServerLayerConfig) => {\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 uploadServerLayers = Layer.mergeAll(\n uploadDataStoreLayer,\n uploadFileKVStoreLayer,\n uploadEventEmitterLayer,\n ...(generateId ? [generateId] : []),\n uploadBufferedDataStoreLayer,\n );\n\n return Layer.provide(uploadServer, uploadServerLayers);\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 * uploadServer: uploadServerLayer,\n * });\n *\n * // Use in application\n * const app = Layer.provide(appLogic, flowServerLayer);\n * ```\n */\nexport const createFlowServerLayer = ({\n kvStore,\n eventEmitter,\n flowProvider,\n uploadServer,\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 flowServerLayers = Layer.mergeAll(\n flowProvider,\n flowEventEmitterLayer,\n flowJobKVStoreLayer,\n uploadServer,\n );\n\n return Layer.provide(flowServer, flowServerLayers);\n};\n","import { Context, Effect, Layer } from \"effect\";\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 * Returns false if no authentication context or permission not found.\n */\n readonly hasPermission: (permission: string) => Effect.Effect<boolean>;\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 * 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 * @returns Effect Layer providing AuthContextService\n */\nexport const AuthContextServiceLive = (\n authContext: AuthContext | null,\n): Layer.Layer<AuthContextService> =>\n Layer.succeed(AuthContextService, {\n getClientId: () => Effect.succeed(authContext?.clientId ?? null),\n getMetadata: () => Effect.succeed(authContext?.metadata ?? {}),\n hasPermission: (permission: string) =>\n Effect.succeed(authContext?.permissions?.includes(permission) ?? false),\n getAuthContext: () => Effect.succeed(authContext),\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["oldestKey: string | null","NoAuthCacheServiceLive: Layer.Layer<AuthCacheService>","statusCode: number","errorCode: string","response: {\n error: string;\n code: string;\n timestamp: string;\n details?: unknown;\n }","details: unknown","result: {\n status: number;\n code: string;\n message: string;\n details?: unknown;\n }","uploadServer","NoAuthContextServiceLive: Layer.Layer<AuthContextService>"],"sources":["../src/cache.ts","../src/error-types.ts","../src/http-utils.ts","../src/layer-utils.ts","../src/service.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","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* uploadServer.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 * 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 { flowServer } 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 UploadServer, uploadServer } from \"@uploadista/core/upload\";\nimport type { GenerateId } from \"@uploadista/core/utils\";\nimport { Layer } from \"effect\";\n\n/**\n * Configuration for creating upload server layers.\n * Specifies all dependencies needed by the upload server 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 { createUploadServerLayer } from \"@uploadista/server\";\n *\n * const uploadLayerConfig: UploadServerLayerConfig = {\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * dataStore: s3DataStore,\n * };\n * ```\n */\nexport interface UploadServerLayerConfig {\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 uploadServer - Upload server 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 * uploadServer: uploadServerLayer,\n * };\n * ```\n */\nexport interface FlowServerLayerConfig {\n kvStore: Layer.Layer<BaseKvStoreService>;\n eventEmitter: Layer.Layer<BaseEventEmitterService>;\n flowProvider: Layer.Layer<FlowProvider>;\n uploadServer: Layer.Layer<UploadServer>;\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 UploadServer\n *\n * @example\n * ```typescript\n * import { createUploadServerLayer } from \"@uploadista/server\";\n * import { Layer } from \"effect\";\n *\n * const uploadServerLayer = createUploadServerLayer({\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter,\n * dataStore: s3DataStore,\n * });\n *\n * // Use in application\n * const app = Layer.provide(appLogic, uploadServerLayer);\n * ```\n */\nexport const createUploadServerLayer = ({\n kvStore,\n eventEmitter,\n dataStore,\n bufferedDataStore,\n generateId,\n}: UploadServerLayerConfig) => {\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 uploadServerLayers = Layer.mergeAll(\n uploadDataStoreLayer,\n uploadFileKVStoreLayer,\n uploadEventEmitterLayer,\n ...(generateId ? [generateId] : []),\n uploadBufferedDataStoreLayer,\n );\n\n return Layer.provide(uploadServer, uploadServerLayers);\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 * uploadServer: uploadServerLayer,\n * });\n *\n * // Use in application\n * const app = Layer.provide(appLogic, flowServerLayer);\n * ```\n */\nexport const createFlowServerLayer = ({\n kvStore,\n eventEmitter,\n flowProvider,\n uploadServer,\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 flowServerLayers = Layer.mergeAll(\n flowProvider,\n flowEventEmitterLayer,\n flowJobKVStoreLayer,\n uploadServer,\n );\n\n return Layer.provide(flowServer, flowServerLayers);\n};\n","import { Context, Effect, Layer } from \"effect\";\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 * Returns false if no authentication context or permission not found.\n */\n readonly hasPermission: (permission: string) => Effect.Effect<boolean>;\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 * 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 * @returns Effect Layer providing AuthContextService\n */\nexport const AuthContextServiceLive = (\n authContext: AuthContext | null,\n): Layer.Layer<AuthContextService> =>\n Layer.succeed(AuthContextService, {\n getClientId: () => Effect.succeed(authContext?.clientId ?? null),\n getMetadata: () => Effect.succeed(authContext?.metadata ?? {}),\n hasPermission: (permission: string) =>\n Effect.succeed(authContext?.permissions?.includes(permission) ?? false),\n getAuthContext: () => Effect.succeed(authContext),\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"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uploadista/server",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.8",
|
|
5
5
|
"description": "Core Server package for Uploadista",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Uploadista",
|
|
@@ -20,16 +20,16 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"effect": "3.18.4",
|
|
22
22
|
"zod": "4.1.12",
|
|
23
|
-
"@uploadista/core": "0.0.
|
|
23
|
+
"@uploadista/core": "0.0.8"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@cloudflare/workers-types": "4.20251014.0",
|
|
27
27
|
"@types/express": "^5.0.0",
|
|
28
28
|
"@types/node": "24.9.1",
|
|
29
|
-
"tsdown": "0.15.
|
|
29
|
+
"tsdown": "0.15.10",
|
|
30
30
|
"typescript": "5.9.3",
|
|
31
|
-
"vitest": "
|
|
32
|
-
"@uploadista/typescript-config": "0.0.
|
|
31
|
+
"vitest": "4.0.3",
|
|
32
|
+
"@uploadista/typescript-config": "0.0.8"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"express": "^4.0.0 || ^5.0.0",
|
package/src/http-utils.ts
CHANGED
|
@@ -211,8 +211,8 @@ export const extractJobIdFromStatus = (
|
|
|
211
211
|
};
|
|
212
212
|
|
|
213
213
|
/**
|
|
214
|
-
* Extracts job ID and node ID from URL segments for
|
|
215
|
-
* Expected URL format: `/uploadista/api/jobs/:jobId/
|
|
214
|
+
* Extracts job ID and node ID from URL segments for resume flow endpoint.
|
|
215
|
+
* Expected URL format: `/uploadista/api/jobs/:jobId/resume/:nodeId`
|
|
216
216
|
*
|
|
217
217
|
* @param urlSegments - Parsed URL segments (without base path)
|
|
218
218
|
* @returns Object with extracted jobId and nodeId (either can be undefined if not found)
|
|
@@ -222,7 +222,7 @@ export const extractJobIdFromStatus = (
|
|
|
222
222
|
* const { jobId, nodeId } = extractJobAndNodeId([
|
|
223
223
|
* "jobs",
|
|
224
224
|
* "job-123",
|
|
225
|
-
* "
|
|
225
|
+
* "resume",
|
|
226
226
|
* "node-456",
|
|
227
227
|
* ]);
|
|
228
228
|
* // => { jobId: "job-123", nodeId: "node-456" }
|