@uploadista/server 0.0.3 → 0.0.4

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.
Files changed (86) hide show
  1. package/.turbo/turbo-build.log +30 -2
  2. package/dist/auth/index.cjs +1 -0
  3. package/dist/auth/index.d.cts +2 -0
  4. package/dist/auth/index.d.ts +2 -2
  5. package/dist/auth/index.js +1 -1
  6. package/dist/auth-B3XCQncE.cjs +1 -0
  7. package/dist/auth-C77S4vQd.js +2 -0
  8. package/dist/auth-C77S4vQd.js.map +1 -0
  9. package/dist/{auth/get-auth-credentials.js → index-50KlDIjc.d.cts} +41 -16
  10. package/dist/index-50KlDIjc.d.cts.map +1 -0
  11. package/dist/{auth/get-auth-credentials.d.ts → index-CvDNB1lJ.d.ts} +20 -13
  12. package/dist/index-CvDNB1lJ.d.ts.map +1 -0
  13. package/dist/index.cjs +1 -0
  14. package/dist/index.d.cts +620 -0
  15. package/dist/index.d.cts.map +1 -0
  16. package/dist/index.d.ts +619 -8
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -8
  19. package/dist/index.js.map +1 -0
  20. package/package.json +5 -4
  21. package/tsdown.config.ts +12 -0
  22. package/dist/auth/cache.d.ts +0 -87
  23. package/dist/auth/cache.d.ts.map +0 -1
  24. package/dist/auth/cache.js +0 -121
  25. package/dist/auth/cache.test.d.ts +0 -2
  26. package/dist/auth/cache.test.d.ts.map +0 -1
  27. package/dist/auth/cache.test.js +0 -209
  28. package/dist/auth/get-auth-credentials.d.ts.map +0 -1
  29. package/dist/auth/index.d.ts.map +0 -1
  30. package/dist/auth/jwt/index.d.ts +0 -38
  31. package/dist/auth/jwt/index.d.ts.map +0 -1
  32. package/dist/auth/jwt/index.js +0 -36
  33. package/dist/auth/jwt/types.d.ts +0 -77
  34. package/dist/auth/jwt/types.d.ts.map +0 -1
  35. package/dist/auth/jwt/types.js +0 -1
  36. package/dist/auth/jwt/validate.d.ts +0 -58
  37. package/dist/auth/jwt/validate.d.ts.map +0 -1
  38. package/dist/auth/jwt/validate.js +0 -226
  39. package/dist/auth/jwt/validate.test.d.ts +0 -2
  40. package/dist/auth/jwt/validate.test.d.ts.map +0 -1
  41. package/dist/auth/jwt/validate.test.js +0 -492
  42. package/dist/auth/service.d.ts +0 -63
  43. package/dist/auth/service.d.ts.map +0 -1
  44. package/dist/auth/service.js +0 -43
  45. package/dist/auth/service.test.d.ts +0 -2
  46. package/dist/auth/service.test.d.ts.map +0 -1
  47. package/dist/auth/service.test.js +0 -195
  48. package/dist/auth/types.d.ts +0 -38
  49. package/dist/auth/types.d.ts.map +0 -1
  50. package/dist/auth/types.js +0 -1
  51. package/dist/cache.d.ts +0 -87
  52. package/dist/cache.d.ts.map +0 -1
  53. package/dist/cache.js +0 -121
  54. package/dist/cache.test.d.ts +0 -2
  55. package/dist/cache.test.d.ts.map +0 -1
  56. package/dist/cache.test.js +0 -209
  57. package/dist/cloudflare-config.d.ts +0 -72
  58. package/dist/cloudflare-config.d.ts.map +0 -1
  59. package/dist/cloudflare-config.js +0 -67
  60. package/dist/error-types.d.ts +0 -138
  61. package/dist/error-types.d.ts.map +0 -1
  62. package/dist/error-types.js +0 -155
  63. package/dist/hono-adapter.d.ts +0 -48
  64. package/dist/hono-adapter.d.ts.map +0 -1
  65. package/dist/hono-adapter.js +0 -58
  66. package/dist/http-utils.d.ts +0 -148
  67. package/dist/http-utils.d.ts.map +0 -1
  68. package/dist/http-utils.js +0 -233
  69. package/dist/layer-utils.d.ts +0 -121
  70. package/dist/layer-utils.d.ts.map +0 -1
  71. package/dist/layer-utils.js +0 -80
  72. package/dist/metrics/service.d.ts +0 -26
  73. package/dist/metrics/service.d.ts.map +0 -1
  74. package/dist/metrics/service.js +0 -20
  75. package/dist/plugins-typing.d.ts +0 -11
  76. package/dist/plugins-typing.d.ts.map +0 -1
  77. package/dist/plugins-typing.js +0 -1
  78. package/dist/service.d.ts +0 -63
  79. package/dist/service.d.ts.map +0 -1
  80. package/dist/service.js +0 -43
  81. package/dist/service.test.d.ts +0 -2
  82. package/dist/service.test.d.ts.map +0 -1
  83. package/dist/service.test.js +0 -195
  84. package/dist/types.d.ts +0 -38
  85. package/dist/types.d.ts.map +0 -1
  86. package/dist/types.js +0 -1
@@ -0,0 +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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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uploadista/server",
3
3
  "type": "module",
4
- "version": "0.0.3",
4
+ "version": "0.0.4",
5
5
  "description": "Core Server package for Uploadista",
6
6
  "license": "MIT",
7
7
  "author": "Uploadista",
@@ -20,22 +20,23 @@
20
20
  "dependencies": {
21
21
  "effect": "3.18.4",
22
22
  "zod": "4.1.12",
23
- "@uploadista/core": "0.0.3"
23
+ "@uploadista/core": "0.0.4"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@cloudflare/workers-types": "4.20251011.0",
27
27
  "@types/express": "^5.0.0",
28
28
  "@types/node": "24.8.1",
29
+ "tsdown": "0.15.9",
29
30
  "typescript": "5.9.3",
30
31
  "vitest": "3.2.4",
31
- "@uploadista/typescript-config": "0.0.3"
32
+ "@uploadista/typescript-config": "0.0.4"
32
33
  },
33
34
  "peerDependencies": {
34
35
  "express": "^4.0.0 || ^5.0.0",
35
36
  "hono": "^4.0.0"
36
37
  },
37
38
  "scripts": {
38
- "build": "tsc -b",
39
+ "build": "tsdown",
39
40
  "format": "biome format --write ./src",
40
41
  "lint": "biome lint --write ./src",
41
42
  "check": "biome check --write ./src",
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ index: "src/index.ts",
6
+ "auth/index": "src/auth/index.ts",
7
+ },
8
+ minify: true,
9
+ format: ["esm", "cjs"],
10
+ dts: true,
11
+ outDir: "dist",
12
+ });
@@ -1,87 +0,0 @@
1
- import { Context, Effect, Layer } from "effect";
2
- import type { AuthContext } from "./types";
3
- /**
4
- * Configuration options for the auth cache.
5
- */
6
- export type AuthCacheConfig = {
7
- /**
8
- * Maximum number of entries in the cache.
9
- * When exceeded, oldest entries are removed (LRU eviction).
10
- * @default 10000
11
- */
12
- maxSize?: number;
13
- /**
14
- * Time-to-live for cache entries in milliseconds.
15
- * Entries older than this will be automatically evicted.
16
- * @default 3600000 (1 hour)
17
- */
18
- ttl?: number;
19
- };
20
- declare const AuthCacheService_base: Context.TagClass<AuthCacheService, "AuthCacheService", {
21
- /**
22
- * Store an auth context for a job ID.
23
- */
24
- readonly set: (jobId: string, authContext: AuthContext) => Effect.Effect<void>;
25
- /**
26
- * Retrieve a cached auth context by job ID.
27
- * Returns null if not found or expired.
28
- */
29
- readonly get: (jobId: string) => Effect.Effect<AuthContext | null>;
30
- /**
31
- * Delete a cached auth context by job ID.
32
- */
33
- readonly delete: (jobId: string) => Effect.Effect<void>;
34
- /**
35
- * Clear all cached auth contexts.
36
- */
37
- readonly clear: () => Effect.Effect<void>;
38
- /**
39
- * Get the current number of cached entries.
40
- */
41
- readonly size: () => Effect.Effect<number>;
42
- }>;
43
- /**
44
- * Auth Cache Service
45
- *
46
- * Provides caching of authentication contexts for upload and flow jobs.
47
- * This allows subsequent operations (chunk uploads, flow continuations)
48
- * to reuse the auth context from the initial request without re-authenticating.
49
- *
50
- * @example
51
- * ```typescript
52
- * import { Effect } from "effect";
53
- * import { AuthCacheService } from "@uploadista/server";
54
- *
55
- * const handler = Effect.gen(function* () {
56
- * const authCache = yield* AuthCacheService;
57
- * const authContext = { userId: "user-123" };
58
- *
59
- * // Cache auth for upload
60
- * yield* authCache.set("upload-abc", authContext);
61
- *
62
- * // Retrieve cached auth later
63
- * const cached = yield* authCache.get("upload-abc");
64
- * console.log(cached?.userId); // "user-123"
65
- *
66
- * // Clear when done
67
- * yield* authCache.delete("upload-abc");
68
- * });
69
- * ```
70
- */
71
- export declare class AuthCacheService extends AuthCacheService_base {
72
- }
73
- /**
74
- * Creates an AuthCacheService Layer with in-memory storage.
75
- *
76
- * @param config - Optional configuration for cache behavior
77
- * @returns Effect Layer providing AuthCacheService
78
- */
79
- export declare const AuthCacheServiceLive: (config?: AuthCacheConfig) => Layer.Layer<AuthCacheService>;
80
- /**
81
- * No-op implementation of AuthCacheService.
82
- * Does not cache anything - all operations are no-ops.
83
- * Used when caching is disabled or not needed.
84
- */
85
- export declare const NoAuthCacheServiceLive: Layer.Layer<AuthCacheService>;
86
- export {};
87
- //# sourceMappingURL=cache.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/auth/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;;IAyCE;;OAEG;kBACW,CACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,WAAW,KACrB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IAExB;;;OAGG;kBACW,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC;IAElE;;OAEG;qBACc,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IAEvD;;OAEG;oBACa,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IAEzC;;OAEG;mBACY,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;;AA1D9C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,gBAAiB,SAAQ,qBAgCnC;CAAG;AAEN;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAQ,eAAoB,KAC3B,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAyF9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAO7D,CAAC"}
@@ -1,121 +0,0 @@
1
- import { Context, Effect, Layer } from "effect";
2
- /**
3
- * Auth Cache Service
4
- *
5
- * Provides caching of authentication contexts for upload and flow jobs.
6
- * This allows subsequent operations (chunk uploads, flow continuations)
7
- * to reuse the auth context from the initial request without re-authenticating.
8
- *
9
- * @example
10
- * ```typescript
11
- * import { Effect } from "effect";
12
- * import { AuthCacheService } from "@uploadista/server";
13
- *
14
- * const handler = Effect.gen(function* () {
15
- * const authCache = yield* AuthCacheService;
16
- * const authContext = { userId: "user-123" };
17
- *
18
- * // Cache auth for upload
19
- * yield* authCache.set("upload-abc", authContext);
20
- *
21
- * // Retrieve cached auth later
22
- * const cached = yield* authCache.get("upload-abc");
23
- * console.log(cached?.userId); // "user-123"
24
- *
25
- * // Clear when done
26
- * yield* authCache.delete("upload-abc");
27
- * });
28
- * ```
29
- */
30
- export class AuthCacheService extends Context.Tag("AuthCacheService")() {
31
- }
32
- /**
33
- * Creates an AuthCacheService Layer with in-memory storage.
34
- *
35
- * @param config - Optional configuration for cache behavior
36
- * @returns Effect Layer providing AuthCacheService
37
- */
38
- export const AuthCacheServiceLive = (config = {}) => {
39
- const maxSize = config.maxSize ?? 10000;
40
- const ttl = config.ttl ?? 3600000; // 1 hour default
41
- // In-memory cache storage
42
- const cache = new Map();
43
- /**
44
- * Evict expired entries based on TTL.
45
- */
46
- const evictExpired = () => {
47
- const now = Date.now();
48
- for (const [jobId, entry] of cache.entries()) {
49
- if (now - entry.timestamp > ttl) {
50
- cache.delete(jobId);
51
- }
52
- }
53
- };
54
- /**
55
- * Enforce max size limit using LRU eviction.
56
- * Removes oldest entry when cache exceeds max size.
57
- */
58
- const enforceSizeLimit = () => {
59
- if (cache.size <= maxSize)
60
- return;
61
- // Find and remove oldest entry
62
- let oldestKey = null;
63
- let oldestTime = Number.POSITIVE_INFINITY;
64
- for (const [jobId, entry] of cache.entries()) {
65
- if (entry.timestamp < oldestTime) {
66
- oldestTime = entry.timestamp;
67
- oldestKey = jobId;
68
- }
69
- }
70
- if (oldestKey) {
71
- cache.delete(oldestKey);
72
- }
73
- };
74
- return Layer.succeed(AuthCacheService, {
75
- set: (jobId, authContext) => Effect.sync(() => {
76
- // Evict expired entries periodically
77
- if (cache.size % 100 === 0) {
78
- evictExpired();
79
- }
80
- cache.set(jobId, {
81
- authContext,
82
- timestamp: Date.now(),
83
- });
84
- // Enforce size limit after adding
85
- enforceSizeLimit();
86
- }),
87
- get: (jobId) => Effect.sync(() => {
88
- const entry = cache.get(jobId);
89
- if (!entry)
90
- return null;
91
- // Check if expired
92
- const now = Date.now();
93
- if (now - entry.timestamp > ttl) {
94
- cache.delete(jobId);
95
- return null;
96
- }
97
- return entry.authContext;
98
- }),
99
- delete: (jobId) => Effect.sync(() => {
100
- cache.delete(jobId);
101
- }),
102
- clear: () => Effect.sync(() => {
103
- cache.clear();
104
- }),
105
- size: () => Effect.sync(() => {
106
- return cache.size;
107
- }),
108
- });
109
- };
110
- /**
111
- * No-op implementation of AuthCacheService.
112
- * Does not cache anything - all operations are no-ops.
113
- * Used when caching is disabled or not needed.
114
- */
115
- export const NoAuthCacheServiceLive = Layer.succeed(AuthCacheService, {
116
- set: () => Effect.void,
117
- get: () => Effect.succeed(null),
118
- delete: () => Effect.void,
119
- clear: () => Effect.void,
120
- size: () => Effect.succeed(0),
121
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=cache.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cache.test.d.ts","sourceRoot":"","sources":["../../src/auth/cache.test.ts"],"names":[],"mappings":""}
@@ -1,209 +0,0 @@
1
- import { Effect } from "effect";
2
- import { describe, expect, it } from "vitest";
3
- import { AuthCacheService, AuthCacheServiceLive, NoAuthCacheServiceLive, } from "./cache";
4
- describe("AuthCacheService", () => {
5
- describe("AuthCacheServiceLive", () => {
6
- it("should set and get auth context", async () => {
7
- const authContext = {
8
- clientId: "user-123",
9
- metadata: { role: "admin" },
10
- };
11
- const program = Effect.gen(function* () {
12
- const cache = yield* AuthCacheService;
13
- // Set auth context
14
- yield* cache.set("job-1", authContext);
15
- // Get auth context
16
- const retrieved = yield* cache.get("job-1");
17
- expect(retrieved).toEqual(authContext);
18
- });
19
- await Effect.runPromise(program.pipe(Effect.provide(AuthCacheServiceLive())));
20
- });
21
- it("should return null for non-existent job ID", async () => {
22
- const program = Effect.gen(function* () {
23
- const cache = yield* AuthCacheService;
24
- const retrieved = yield* cache.get("non-existent");
25
- expect(retrieved).toBeNull();
26
- });
27
- await Effect.runPromise(program.pipe(Effect.provide(AuthCacheServiceLive())));
28
- });
29
- it("should delete cached auth context", async () => {
30
- const authContext = {
31
- clientId: "user-123",
32
- };
33
- const program = Effect.gen(function* () {
34
- const cache = yield* AuthCacheService;
35
- // Set and verify
36
- yield* cache.set("job-1", authContext);
37
- const before = yield* cache.get("job-1");
38
- expect(before).toEqual(authContext);
39
- // Delete
40
- yield* cache.delete("job-1");
41
- // Verify deleted
42
- const after = yield* cache.get("job-1");
43
- expect(after).toBeNull();
44
- });
45
- await Effect.runPromise(program.pipe(Effect.provide(AuthCacheServiceLive())));
46
- });
47
- it("should clear all cached auth contexts", async () => {
48
- const authContext1 = { clientId: "user-1" };
49
- const authContext2 = { clientId: "user-2" };
50
- const program = Effect.gen(function* () {
51
- const cache = yield* AuthCacheService;
52
- // Set multiple entries
53
- yield* cache.set("job-1", authContext1);
54
- yield* cache.set("job-2", authContext2);
55
- const sizeBefore = yield* cache.size();
56
- expect(sizeBefore).toBe(2);
57
- // Clear all
58
- yield* cache.clear();
59
- const sizeAfter = yield* cache.size();
60
- expect(sizeAfter).toBe(0);
61
- // Verify both deleted
62
- const job1 = yield* cache.get("job-1");
63
- const job2 = yield* cache.get("job-2");
64
- expect(job1).toBeNull();
65
- expect(job2).toBeNull();
66
- });
67
- await Effect.runPromise(program.pipe(Effect.provide(AuthCacheServiceLive())));
68
- });
69
- it("should track cache size", async () => {
70
- const program = Effect.gen(function* () {
71
- const cache = yield* AuthCacheService;
72
- let size = yield* cache.size();
73
- expect(size).toBe(0);
74
- yield* cache.set("job-1", { clientId: "user-1" });
75
- size = yield* cache.size();
76
- expect(size).toBe(1);
77
- yield* cache.set("job-2", { clientId: "user-2" });
78
- size = yield* cache.size();
79
- expect(size).toBe(2);
80
- yield* cache.delete("job-1");
81
- size = yield* cache.size();
82
- expect(size).toBe(1);
83
- yield* cache.clear();
84
- size = yield* cache.size();
85
- expect(size).toBe(0);
86
- });
87
- await Effect.runPromise(program.pipe(Effect.provide(AuthCacheServiceLive())));
88
- });
89
- it("should evict expired entries", async () => {
90
- // Set very short TTL for testing
91
- const shortTtl = 50; // 50ms
92
- const authContext = {
93
- clientId: "user-123",
94
- };
95
- const program = Effect.gen(function* () {
96
- const cache = yield* AuthCacheService;
97
- // Set auth context
98
- yield* cache.set("job-1", authContext);
99
- // Verify it's there immediately
100
- const immediate = yield* cache.get("job-1");
101
- expect(immediate).toEqual(authContext);
102
- // Wait for TTL to expire
103
- yield* Effect.sleep(100); // Wait 100ms (longer than 50ms TTL)
104
- // Should be evicted now
105
- const afterExpiry = yield* cache.get("job-1");
106
- expect(afterExpiry).toBeNull();
107
- });
108
- await Effect.runPromise(program.pipe(Effect.provide(AuthCacheServiceLive({ ttl: shortTtl }))));
109
- });
110
- it("should enforce max size limit with LRU eviction", async () => {
111
- const maxSize = 3;
112
- const program = Effect.gen(function* () {
113
- const cache = yield* AuthCacheService;
114
- // Add entries up to max size
115
- yield* cache.set("job-1", { clientId: "user-1" });
116
- yield* Effect.sleep(10); // Small delay to ensure ordering
117
- yield* cache.set("job-2", { clientId: "user-2" });
118
- yield* Effect.sleep(10);
119
- yield* cache.set("job-3", { clientId: "user-3" });
120
- let size = yield* cache.size();
121
- expect(size).toBe(3);
122
- // Add one more - should evict oldest (job-1)
123
- yield* Effect.sleep(10);
124
- yield* cache.set("job-4", { clientId: "user-4" });
125
- size = yield* cache.size();
126
- expect(size).toBe(3); // Still at max size
127
- // job-1 should be evicted (oldest)
128
- const job1 = yield* cache.get("job-1");
129
- expect(job1).toBeNull();
130
- // Others should still exist
131
- const job2 = yield* cache.get("job-2");
132
- const job3 = yield* cache.get("job-3");
133
- const job4 = yield* cache.get("job-4");
134
- expect(job2).toEqual({ clientId: "user-2" });
135
- expect(job3).toEqual({ clientId: "user-3" });
136
- expect(job4).toEqual({ clientId: "user-4" });
137
- });
138
- await Effect.runPromise(program.pipe(Effect.provide(AuthCacheServiceLive({ maxSize }))));
139
- });
140
- it("should handle multiple auth contexts independently", async () => {
141
- const authContext1 = {
142
- clientId: "user-1",
143
- metadata: { role: "admin" },
144
- };
145
- const authContext2 = {
146
- clientId: "user-2",
147
- permissions: ["read", "write"],
148
- };
149
- const program = Effect.gen(function* () {
150
- const cache = yield* AuthCacheService;
151
- yield* cache.set("upload-123", authContext1);
152
- yield* cache.set("flow-456", authContext2);
153
- const upload = yield* cache.get("upload-123");
154
- const flow = yield* cache.get("flow-456");
155
- expect(upload).toEqual(authContext1);
156
- expect(flow).toEqual(authContext2);
157
- });
158
- await Effect.runPromise(program.pipe(Effect.provide(AuthCacheServiceLive())));
159
- });
160
- it("should update existing entry when setting same job ID", async () => {
161
- const authContext1 = { clientId: "user-1" };
162
- const authContext2 = { clientId: "user-2" };
163
- const program = Effect.gen(function* () {
164
- const cache = yield* AuthCacheService;
165
- yield* cache.set("job-1", authContext1);
166
- const first = yield* cache.get("job-1");
167
- expect(first).toEqual(authContext1);
168
- // Update with new auth context
169
- yield* cache.set("job-1", authContext2);
170
- const second = yield* cache.get("job-1");
171
- expect(second).toEqual(authContext2);
172
- // Should still be only 1 entry
173
- const size = yield* cache.size();
174
- expect(size).toBe(1);
175
- });
176
- await Effect.runPromise(program.pipe(Effect.provide(AuthCacheServiceLive())));
177
- });
178
- });
179
- describe("NoAuthCacheServiceLive", () => {
180
- it("should not cache anything", async () => {
181
- const authContext = {
182
- clientId: "user-123",
183
- };
184
- const program = Effect.gen(function* () {
185
- const cache = yield* AuthCacheService;
186
- // Try to set
187
- yield* cache.set("job-1", authContext);
188
- // Should always return null
189
- const retrieved = yield* cache.get("job-1");
190
- expect(retrieved).toBeNull();
191
- // Size should always be 0
192
- const size = yield* cache.size();
193
- expect(size).toBe(0);
194
- });
195
- await Effect.runPromise(program.pipe(Effect.provide(NoAuthCacheServiceLive)));
196
- });
197
- it("should handle delete and clear without errors", async () => {
198
- const program = Effect.gen(function* () {
199
- const cache = yield* AuthCacheService;
200
- // These should not throw
201
- yield* cache.delete("non-existent");
202
- yield* cache.clear();
203
- const size = yield* cache.size();
204
- expect(size).toBe(0);
205
- });
206
- await Effect.runPromise(program.pipe(Effect.provide(NoAuthCacheServiceLive)));
207
- });
208
- });
209
- });
@@ -1 +0,0 @@
1
- {"version":3,"file":"get-auth-credentials.d.ts","sourceRoot":"","sources":["../../src/auth/get-auth-credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,uBAAuB,GAC/B;IACE,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5C,GACD;IACE,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,kBAAkB,GAAU,oDAItC;IACD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,KAAG,OAAO,CAAC,uBAAuB,CAqBlC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
@@ -1,38 +0,0 @@
1
- /**
2
- * JWT Utilities for Uploadista Authentication
3
- *
4
- * This module provides JWT token validation and AuthContext extraction utilities
5
- * for implementing SaaS-mode authentication in uploadista adapters.
6
- *
7
- * @example Basic JWT validation
8
- * ```typescript
9
- * import { validateJwtToken } from '@uploadista/server/auth/jwt';
10
- *
11
- * const result = await validateJwtToken(token, {
12
- * secret: process.env.JWT_SECRET,
13
- * issuer: 'https://auth.myapp.com',
14
- * audience: 'uploadista-api',
15
- * });
16
- *
17
- * if (result.success) {
18
- * console.log('Valid token for user:', result.userId);
19
- * }
20
- * ```
21
- *
22
- * @example Extract AuthContext
23
- * ```typescript
24
- * import { extractAuthContextFromJwt } from '@uploadista/server/auth/jwt';
25
- *
26
- * const authContext = await extractAuthContextFromJwt(token, {
27
- * secret: process.env.JWT_SECRET,
28
- * });
29
- *
30
- * if (authContext) {
31
- * // Use auth context in your middleware
32
- * return authContext;
33
- * }
34
- * ```
35
- */
36
- export type { JwtValidationConfig, JwtValidationError, JwtValidationResult, } from "./types";
37
- export { extractAuthContextFromJwt, validateJwtToken } from "./validate";
38
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/jwt/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
@@ -1,36 +0,0 @@
1
- /**
2
- * JWT Utilities for Uploadista Authentication
3
- *
4
- * This module provides JWT token validation and AuthContext extraction utilities
5
- * for implementing SaaS-mode authentication in uploadista adapters.
6
- *
7
- * @example Basic JWT validation
8
- * ```typescript
9
- * import { validateJwtToken } from '@uploadista/server/auth/jwt';
10
- *
11
- * const result = await validateJwtToken(token, {
12
- * secret: process.env.JWT_SECRET,
13
- * issuer: 'https://auth.myapp.com',
14
- * audience: 'uploadista-api',
15
- * });
16
- *
17
- * if (result.success) {
18
- * console.log('Valid token for user:', result.userId);
19
- * }
20
- * ```
21
- *
22
- * @example Extract AuthContext
23
- * ```typescript
24
- * import { extractAuthContextFromJwt } from '@uploadista/server/auth/jwt';
25
- *
26
- * const authContext = await extractAuthContextFromJwt(token, {
27
- * secret: process.env.JWT_SECRET,
28
- * });
29
- *
30
- * if (authContext) {
31
- * // Use auth context in your middleware
32
- * return authContext;
33
- * }
34
- * ```
35
- */
36
- export { extractAuthContextFromJwt, validateJwtToken } from "./validate";