@uploadista/adapters-express 0.0.20-beta.6 → 0.0.20-beta.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.cjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -12
- package/src/express-websocket-handler.ts +8 -8
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e=require(`effect`),t=require(`@uploadista/core`),n=require(`@uploadista/server`);const r=async e=>{if(e.body&&typeof e.body==`object`)return e.body;let t=[];for await(let n of e)t.push(n);let n=Buffer.concat(t).toString();return JSON.parse(n)},i=(t,{baseUrl:n})=>e.Effect.promise(async()=>{let e=new URL(t.request.url,`http://${t.request.get(`host`)}`),i=t.request.get(`Accept`),a=`/${n}/`;if(e.pathname.startsWith(a)&&t.request.method===`GET`){let t=e.pathname.slice(a.length);if(t===`health`||t===`healthz`)return{type:`health`,acceptHeader:i};if(t===`ready`||t===`readyz`)return{type:`health-ready`,acceptHeader:i};if(t===`health/components`)return{type:`health-components`,acceptHeader:i}}let o=`/${n}/api/`;if(!e.pathname.includes(o))return{type:`not-found`};let s=e.pathname.replace(`${n}/api/`,``).split(`/`).filter(Boolean);if(s[0]===`upload`||s.includes(`upload`))switch(t.request.method){case`POST`:return{type:`create-upload`,data:await r(t.request)};case`GET`:if(s[s.length-1]===`capabilities`){let t=e.searchParams.get(`storageId`),n=s[s.length-2],r=t||(n===`upload`?null:n);return r?{type:`get-capabilities`,storageId:r}:{type:`bad-request`,message:`Storage ID is required`}}return s.length<2?{type:`bad-request`,message:`Upload ID is required`}:{type:`get-upload`,uploadId:s[1]};case`PATCH`:{if(s.length<2)return{type:`bad-request`,message:`Upload ID is required`};let e=new ReadableStream({start(e){t.request.on(`data`,t=>{e.enqueue(t)}),t.request.on(`end`,()=>{e.close()}),t.request.on(`error`,t=>{e.error(t)})}});return{type:`upload-chunk`,uploadId:s[1],data:e}}default:return{type:`method-not-allowed`}}else if(s[0]===`flow`||s.includes(`flow`))switch(t.request.method){case`GET`:return{type:`get-flow`,flowId:s[1]};case`POST`:{let e=await r(t.request);return!e||typeof e!=`object`||!(`inputs`in e)?{type:`bad-request`,message:`Inputs are required`}:{type:`run-flow`,flowId:s[1],storageId:s[2],inputs:e.inputs}}default:return{type:`method-not-allowed`}}else if(s[0]===`dlq`||s.includes(`dlq`))switch(t.request.method){case`GET`:return s.length===1?{type:`dlq-list`,options:{status:e.searchParams.get(`status`),flowId:e.searchParams.get(`flowId`),clientId:e.searchParams.get(`clientId`),limit:e.searchParams.get(`limit`)?Number.parseInt(e.searchParams.get(`limit`)):void 0,offset:e.searchParams.get(`offset`)?Number.parseInt(e.searchParams.get(`offset`)):void 0}}:s[1]===`stats`?{type:`dlq-stats`}:{type:`dlq-get`,itemId:s[1]};case`POST`:return s[1]===`cleanup`?{type:`dlq-cleanup`,options:await r(t.request).catch(()=>({}))}:s[1]===`retry-all`?{type:`dlq-retry-all`,options:await r(t.request).catch(()=>({}))}:s[2]===`retry`?{type:`dlq-retry`,itemId:s[1]}:s[2]===`resolve`?{type:`dlq-resolve`,itemId:s[1]}:{type:`method-not-allowed`};case`DELETE`:return s.length<2?{type:`bad-request`,message:`Item ID is required`}:{type:`dlq-delete`,itemId:s[1]};default:return{type:`method-not-allowed`}}else if(s[0]===`jobs`||s.includes(`jobs`)){if(t.request.method===`GET`&&e.pathname.endsWith(`/status`))return s.length<3?{type:`bad-request`,message:`Job ID is required`}:{type:`job-status`,jobId:s[1]};if(t.request.method===`PATCH`&&s.includes(`resume`)){let e=s[1];if(!e)return{type:`bad-request`,message:`Job ID is required`};let n=s[3];if(!n)return{type:`bad-request`,message:`Node ID is required`};let i=t.request.get(`content-type`),a;if(i?.includes(`application/octet-stream`))a=t.request;else if(i?.includes(`application/json`)){let e=await r(t.request);if(!e||typeof e!=`object`||!(`newData`in e))return{type:`bad-request`,message:`Missing newData`};a=e.newData}else return{type:`unsupported-content-type`};return{type:`resume-flow`,jobId:e,nodeId:n,newData:a}}else if(t.request.method===`POST`&&e.pathname.endsWith(`/pause`))return{type:`pause-flow`,jobId:s[1]};else if(t.request.method===`POST`&&e.pathname.endsWith(`/cancel`))return{type:`cancel-flow`,jobId:s[1]};return{type:`method-not-allowed`}}else return{type:`not-found`}}),a=(t,n)=>e.Effect.sync(()=>{let e=t.headers||{};e[`Content-Type`]||=`application/json`;for(let[t,r]of Object.entries(e))n.response.set(t,r);return n.response.status(t.status).send(t.body)});function o(e,t){let n=RegExp(`[?&]${t}=([^&]*)`),r=e.match(n);return r?.[1]?decodeURIComponent(r[1]):void 0}const s=new Map,c=(e,t)=>{let n=new URL(e.url||``,`http://${e.headers.host}`),r=`${t}/ws/`;if(!n.pathname.includes(r))return{type:`invalid-path`,expectedPrefix:r};let i=n.pathname.replace(r,``).split(`/`).filter(Boolean),a=i.includes(`upload`),s=i.includes(`flow`),c=o(e.url||``,`jobId`),l=o(e.url||``,`uploadId`);if(!c&&!l&&i.length>=2){let e=i[0],t=i[1];e===`flow`?c=t:e===`upload`&&(l=t)}let u=c||l;return{baseUrl:t,pathname:n.pathname,routeSegments:i,isUploadRoute:a,isFlowRoute:s,jobId:c,uploadId:l,eventId:u,connection:null}},l=async(e,t)=>{try{let n=new URL(e.url||``,`http://${e.headers.host}`).searchParams.get(`token`),r=null;if(r=n?await t({request:{...e,header:t=>t.toLowerCase()===`authorization`?`Bearer ${n}`:e.headers[t.toLowerCase()]},response:{}}):await t({request:e,response:{}}),!r){let e=n?`token`:`cookies`;return{success:!1,error:{message:`Authentication failed: invalid or expired ${e}`,code:4001,authMethod:e}}}return console.log(`WebSocket authenticated for user: ${r.clientId}`),{success:!0,authResult:r}}catch(e){return console.error(`WebSocket auth error:`,e),{success:!1,error:{message:`Authentication error`,code:4001,authMethod:`unknown`}}}},u=(r,i)=>e.Effect.gen(function*(){let a=yield*t.
|
|
1
|
+
let e=require(`effect`),t=require(`@uploadista/core`),n=require(`@uploadista/server`);const r=async e=>{if(e.body&&typeof e.body==`object`)return e.body;let t=[];for await(let n of e)t.push(n);let n=Buffer.concat(t).toString();return JSON.parse(n)},i=(t,{baseUrl:n})=>e.Effect.promise(async()=>{let e=new URL(t.request.url,`http://${t.request.get(`host`)}`),i=t.request.get(`Accept`),a=`/${n}/`;if(e.pathname.startsWith(a)&&t.request.method===`GET`){let t=e.pathname.slice(a.length);if(t===`health`||t===`healthz`)return{type:`health`,acceptHeader:i};if(t===`ready`||t===`readyz`)return{type:`health-ready`,acceptHeader:i};if(t===`health/components`)return{type:`health-components`,acceptHeader:i}}let o=`/${n}/api/`;if(!e.pathname.includes(o))return{type:`not-found`};let s=e.pathname.replace(`${n}/api/`,``).split(`/`).filter(Boolean);if(s[0]===`upload`||s.includes(`upload`))switch(t.request.method){case`POST`:return{type:`create-upload`,data:await r(t.request)};case`GET`:if(s[s.length-1]===`capabilities`){let t=e.searchParams.get(`storageId`),n=s[s.length-2],r=t||(n===`upload`?null:n);return r?{type:`get-capabilities`,storageId:r}:{type:`bad-request`,message:`Storage ID is required`}}return s.length<2?{type:`bad-request`,message:`Upload ID is required`}:{type:`get-upload`,uploadId:s[1]};case`PATCH`:{if(s.length<2)return{type:`bad-request`,message:`Upload ID is required`};let e=new ReadableStream({start(e){t.request.on(`data`,t=>{e.enqueue(t)}),t.request.on(`end`,()=>{e.close()}),t.request.on(`error`,t=>{e.error(t)})}});return{type:`upload-chunk`,uploadId:s[1],data:e}}default:return{type:`method-not-allowed`}}else if(s[0]===`flow`||s.includes(`flow`))switch(t.request.method){case`GET`:return{type:`get-flow`,flowId:s[1]};case`POST`:{let e=await r(t.request);return!e||typeof e!=`object`||!(`inputs`in e)?{type:`bad-request`,message:`Inputs are required`}:{type:`run-flow`,flowId:s[1],storageId:s[2],inputs:e.inputs}}default:return{type:`method-not-allowed`}}else if(s[0]===`dlq`||s.includes(`dlq`))switch(t.request.method){case`GET`:return s.length===1?{type:`dlq-list`,options:{status:e.searchParams.get(`status`),flowId:e.searchParams.get(`flowId`),clientId:e.searchParams.get(`clientId`),limit:e.searchParams.get(`limit`)?Number.parseInt(e.searchParams.get(`limit`)):void 0,offset:e.searchParams.get(`offset`)?Number.parseInt(e.searchParams.get(`offset`)):void 0}}:s[1]===`stats`?{type:`dlq-stats`}:{type:`dlq-get`,itemId:s[1]};case`POST`:return s[1]===`cleanup`?{type:`dlq-cleanup`,options:await r(t.request).catch(()=>({}))}:s[1]===`retry-all`?{type:`dlq-retry-all`,options:await r(t.request).catch(()=>({}))}:s[2]===`retry`?{type:`dlq-retry`,itemId:s[1]}:s[2]===`resolve`?{type:`dlq-resolve`,itemId:s[1]}:{type:`method-not-allowed`};case`DELETE`:return s.length<2?{type:`bad-request`,message:`Item ID is required`}:{type:`dlq-delete`,itemId:s[1]};default:return{type:`method-not-allowed`}}else if(s[0]===`jobs`||s.includes(`jobs`)){if(t.request.method===`GET`&&e.pathname.endsWith(`/status`))return s.length<3?{type:`bad-request`,message:`Job ID is required`}:{type:`job-status`,jobId:s[1]};if(t.request.method===`PATCH`&&s.includes(`resume`)){let e=s[1];if(!e)return{type:`bad-request`,message:`Job ID is required`};let n=s[3];if(!n)return{type:`bad-request`,message:`Node ID is required`};let i=t.request.get(`content-type`),a;if(i?.includes(`application/octet-stream`))a=t.request;else if(i?.includes(`application/json`)){let e=await r(t.request);if(!e||typeof e!=`object`||!(`newData`in e))return{type:`bad-request`,message:`Missing newData`};a=e.newData}else return{type:`unsupported-content-type`};return{type:`resume-flow`,jobId:e,nodeId:n,newData:a}}else if(t.request.method===`POST`&&e.pathname.endsWith(`/pause`))return{type:`pause-flow`,jobId:s[1]};else if(t.request.method===`POST`&&e.pathname.endsWith(`/cancel`))return{type:`cancel-flow`,jobId:s[1]};return{type:`method-not-allowed`}}else return{type:`not-found`}}),a=(t,n)=>e.Effect.sync(()=>{let e=t.headers||{};e[`Content-Type`]||=`application/json`;for(let[t,r]of Object.entries(e))n.response.set(t,r);return n.response.status(t.status).send(t.body)});function o(e,t){let n=RegExp(`[?&]${t}=([^&]*)`),r=e.match(n);return r?.[1]?decodeURIComponent(r[1]):void 0}const s=new Map,c=(e,t)=>{let n=new URL(e.url||``,`http://${e.headers.host}`),r=`${t}/ws/`;if(!n.pathname.includes(r))return{type:`invalid-path`,expectedPrefix:r};let i=n.pathname.replace(r,``).split(`/`).filter(Boolean),a=i.includes(`upload`),s=i.includes(`flow`),c=o(e.url||``,`jobId`),l=o(e.url||``,`uploadId`);if(!c&&!l&&i.length>=2){let e=i[0],t=i[1];e===`flow`?c=t:e===`upload`&&(l=t)}let u=c||l;return{baseUrl:t,pathname:n.pathname,routeSegments:i,isUploadRoute:a,isFlowRoute:s,jobId:c,uploadId:l,eventId:u,connection:null}},l=async(e,t)=>{try{let n=new URL(e.url||``,`http://${e.headers.host}`).searchParams.get(`token`),r=null;if(r=n?await t({request:{...e,header:t=>t.toLowerCase()===`authorization`?`Bearer ${n}`:e.headers[t.toLowerCase()]},response:{}}):await t({request:e,response:{}}),!r){let e=n?`token`:`cookies`;return{success:!1,error:{message:`Authentication failed: invalid or expired ${e}`,code:4001,authMethod:e}}}return console.log(`WebSocket authenticated for user: ${r.clientId}`),{success:!0,authResult:r}}catch(e){return console.error(`WebSocket auth error:`,e),{success:!1,error:{message:`Authentication error`,code:4001,authMethod:`unknown`}}}},u=(r,i)=>e.Effect.gen(function*(){let a=yield*t.UploadEngine,o=yield*t.FlowEngine;return(t,u)=>{let d=c(u,r);if(console.log(`🔍 WebSocket request details:`,d),`type`in d&&d.type===`invalid-path`){t.send(JSON.stringify({type:`invalid-path`,message:`WebSocket path must start with ${d.expectedPrefix}`,expectedPrefix:d.expectedPrefix})),t.close(1e3,`Invalid path`);return}let f=d,p={id:`conn_${Date.now()}_${Math.random().toString(36).substring(2,11)}`,send:e=>{t.readyState===t.OPEN?(console.log(`📤 Sending WebSocket message to connection ${p.id}:`,e.substring(0,100)),t.send(e)):console.warn(`⚠️ Cannot send message, WebSocket not open. State: ${t.readyState}`)},close:(e,n)=>t.close(e,n),get readyState(){return t.readyState}};f.connection=p,(async()=>{if(i){let e=await l(u,i);if(!e.success){t.send(JSON.stringify({type:`auth-failed`,message:e.error?.message,code:`AUTH_FAILED`,authMethod:e.error?.authMethod})),t.close(e.error?.code||4001,e.error?.message);return}e.authResult&&s.set(p.id,e.authResult)}console.log(`🔍 WebSocket open for eventId:`,f.eventId,`with connection id:`,p.id);let r=(0,n.handleWebSocketOpen)(f,a,o);e.Effect.runFork(r)})(),t.on(`message`,t=>{let r=(0,n.handleWebSocketMessage)(t,p);e.Effect.runFork(r)}),t.on(`close`,()=>{f.connection?.id&&(s.delete(f.connection.id),console.log(`Cleared auth cache for WebSocket connection: ${f.connection.id}`));let t=(0,n.handleWebSocketClose)(f,a,o);e.Effect.runFork(t)}),t.on(`error`,(...t)=>{let r=t[0],i=(0,n.handleWebSocketError)(r,f.eventId);e.Effect.runFork(i)})}}),d=(t={})=>{let{authMiddleware:n}=t;return{extractRequest:i,sendResponse:a,webSocketHandler:({baseUrl:e})=>u(e,n),runAuthMiddleware:n?t=>e.Effect.tryPromise(()=>n(t)).pipe(e.Effect.catchAll(t=>(console.error(`Express auth middleware failed:`,t),e.Effect.succeed(null)))):void 0}};exports.expressAdapter=d;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import {
|
|
2
|
+
import { FlowEngine, UploadEngine } from "@uploadista/core";
|
|
3
3
|
import { AuthResult, ServerAdapter } from "@uploadista/server";
|
|
4
4
|
import { Request, Response } from "express";
|
|
5
5
|
import { IncomingMessage } from "node:http";
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Effect as e}from"effect";import{
|
|
1
|
+
import{Effect as e}from"effect";import{FlowEngine as t,UploadEngine as n}from"@uploadista/core";import{handleWebSocketClose as r,handleWebSocketError as i,handleWebSocketMessage as a,handleWebSocketOpen as o}from"@uploadista/server";const s=async e=>{if(e.body&&typeof e.body==`object`)return e.body;let t=[];for await(let n of e)t.push(n);let n=Buffer.concat(t).toString();return JSON.parse(n)},c=(t,{baseUrl:n})=>e.promise(async()=>{let e=new URL(t.request.url,`http://${t.request.get(`host`)}`),r=t.request.get(`Accept`),i=`/${n}/`;if(e.pathname.startsWith(i)&&t.request.method===`GET`){let t=e.pathname.slice(i.length);if(t===`health`||t===`healthz`)return{type:`health`,acceptHeader:r};if(t===`ready`||t===`readyz`)return{type:`health-ready`,acceptHeader:r};if(t===`health/components`)return{type:`health-components`,acceptHeader:r}}let a=`/${n}/api/`;if(!e.pathname.includes(a))return{type:`not-found`};let o=e.pathname.replace(`${n}/api/`,``).split(`/`).filter(Boolean);if(o[0]===`upload`||o.includes(`upload`))switch(t.request.method){case`POST`:return{type:`create-upload`,data:await s(t.request)};case`GET`:if(o[o.length-1]===`capabilities`){let t=e.searchParams.get(`storageId`),n=o[o.length-2],r=t||(n===`upload`?null:n);return r?{type:`get-capabilities`,storageId:r}:{type:`bad-request`,message:`Storage ID is required`}}return o.length<2?{type:`bad-request`,message:`Upload ID is required`}:{type:`get-upload`,uploadId:o[1]};case`PATCH`:{if(o.length<2)return{type:`bad-request`,message:`Upload ID is required`};let e=new ReadableStream({start(e){t.request.on(`data`,t=>{e.enqueue(t)}),t.request.on(`end`,()=>{e.close()}),t.request.on(`error`,t=>{e.error(t)})}});return{type:`upload-chunk`,uploadId:o[1],data:e}}default:return{type:`method-not-allowed`}}else if(o[0]===`flow`||o.includes(`flow`))switch(t.request.method){case`GET`:return{type:`get-flow`,flowId:o[1]};case`POST`:{let e=await s(t.request);return!e||typeof e!=`object`||!(`inputs`in e)?{type:`bad-request`,message:`Inputs are required`}:{type:`run-flow`,flowId:o[1],storageId:o[2],inputs:e.inputs}}default:return{type:`method-not-allowed`}}else if(o[0]===`dlq`||o.includes(`dlq`))switch(t.request.method){case`GET`:return o.length===1?{type:`dlq-list`,options:{status:e.searchParams.get(`status`),flowId:e.searchParams.get(`flowId`),clientId:e.searchParams.get(`clientId`),limit:e.searchParams.get(`limit`)?Number.parseInt(e.searchParams.get(`limit`)):void 0,offset:e.searchParams.get(`offset`)?Number.parseInt(e.searchParams.get(`offset`)):void 0}}:o[1]===`stats`?{type:`dlq-stats`}:{type:`dlq-get`,itemId:o[1]};case`POST`:return o[1]===`cleanup`?{type:`dlq-cleanup`,options:await s(t.request).catch(()=>({}))}:o[1]===`retry-all`?{type:`dlq-retry-all`,options:await s(t.request).catch(()=>({}))}:o[2]===`retry`?{type:`dlq-retry`,itemId:o[1]}:o[2]===`resolve`?{type:`dlq-resolve`,itemId:o[1]}:{type:`method-not-allowed`};case`DELETE`:return o.length<2?{type:`bad-request`,message:`Item ID is required`}:{type:`dlq-delete`,itemId:o[1]};default:return{type:`method-not-allowed`}}else if(o[0]===`jobs`||o.includes(`jobs`)){if(t.request.method===`GET`&&e.pathname.endsWith(`/status`))return o.length<3?{type:`bad-request`,message:`Job ID is required`}:{type:`job-status`,jobId:o[1]};if(t.request.method===`PATCH`&&o.includes(`resume`)){let e=o[1];if(!e)return{type:`bad-request`,message:`Job ID is required`};let n=o[3];if(!n)return{type:`bad-request`,message:`Node ID is required`};let r=t.request.get(`content-type`),i;if(r?.includes(`application/octet-stream`))i=t.request;else if(r?.includes(`application/json`)){let e=await s(t.request);if(!e||typeof e!=`object`||!(`newData`in e))return{type:`bad-request`,message:`Missing newData`};i=e.newData}else return{type:`unsupported-content-type`};return{type:`resume-flow`,jobId:e,nodeId:n,newData:i}}else if(t.request.method===`POST`&&e.pathname.endsWith(`/pause`))return{type:`pause-flow`,jobId:o[1]};else if(t.request.method===`POST`&&e.pathname.endsWith(`/cancel`))return{type:`cancel-flow`,jobId:o[1]};return{type:`method-not-allowed`}}else return{type:`not-found`}}),l=(t,n)=>e.sync(()=>{let e=t.headers||{};e[`Content-Type`]||=`application/json`;for(let[t,r]of Object.entries(e))n.response.set(t,r);return n.response.status(t.status).send(t.body)});function u(e,t){let n=RegExp(`[?&]${t}=([^&]*)`),r=e.match(n);return r?.[1]?decodeURIComponent(r[1]):void 0}const d=new Map,f=(e,t)=>{let n=new URL(e.url||``,`http://${e.headers.host}`),r=`${t}/ws/`;if(!n.pathname.includes(r))return{type:`invalid-path`,expectedPrefix:r};let i=n.pathname.replace(r,``).split(`/`).filter(Boolean),a=i.includes(`upload`),o=i.includes(`flow`),s=u(e.url||``,`jobId`),c=u(e.url||``,`uploadId`);if(!s&&!c&&i.length>=2){let e=i[0],t=i[1];e===`flow`?s=t:e===`upload`&&(c=t)}let l=s||c;return{baseUrl:t,pathname:n.pathname,routeSegments:i,isUploadRoute:a,isFlowRoute:o,jobId:s,uploadId:c,eventId:l,connection:null}},p=async(e,t)=>{try{let n=new URL(e.url||``,`http://${e.headers.host}`).searchParams.get(`token`),r=null;if(r=n?await t({request:{...e,header:t=>t.toLowerCase()===`authorization`?`Bearer ${n}`:e.headers[t.toLowerCase()]},response:{}}):await t({request:e,response:{}}),!r){let e=n?`token`:`cookies`;return{success:!1,error:{message:`Authentication failed: invalid or expired ${e}`,code:4001,authMethod:e}}}return console.log(`WebSocket authenticated for user: ${r.clientId}`),{success:!0,authResult:r}}catch(e){return console.error(`WebSocket auth error:`,e),{success:!1,error:{message:`Authentication error`,code:4001,authMethod:`unknown`}}}},m=(s,c)=>e.gen(function*(){let l=yield*n,u=yield*t;return(t,n)=>{let m=f(n,s);if(console.log(`🔍 WebSocket request details:`,m),`type`in m&&m.type===`invalid-path`){t.send(JSON.stringify({type:`invalid-path`,message:`WebSocket path must start with ${m.expectedPrefix}`,expectedPrefix:m.expectedPrefix})),t.close(1e3,`Invalid path`);return}let h=m,g={id:`conn_${Date.now()}_${Math.random().toString(36).substring(2,11)}`,send:e=>{t.readyState===t.OPEN?(console.log(`📤 Sending WebSocket message to connection ${g.id}:`,e.substring(0,100)),t.send(e)):console.warn(`⚠️ Cannot send message, WebSocket not open. State: ${t.readyState}`)},close:(e,n)=>t.close(e,n),get readyState(){return t.readyState}};h.connection=g,(async()=>{if(c){let e=await p(n,c);if(!e.success){t.send(JSON.stringify({type:`auth-failed`,message:e.error?.message,code:`AUTH_FAILED`,authMethod:e.error?.authMethod})),t.close(e.error?.code||4001,e.error?.message);return}e.authResult&&d.set(g.id,e.authResult)}console.log(`🔍 WebSocket open for eventId:`,h.eventId,`with connection id:`,g.id);let r=o(h,l,u);e.runFork(r)})(),t.on(`message`,t=>{let n=a(t,g);e.runFork(n)}),t.on(`close`,()=>{h.connection?.id&&(d.delete(h.connection.id),console.log(`Cleared auth cache for WebSocket connection: ${h.connection.id}`));let t=r(h,l,u);e.runFork(t)}),t.on(`error`,(...t)=>{let n=t[0],r=i(n,h.eventId);e.runFork(r)})}}),h=(t={})=>{let{authMiddleware:n}=t;return{extractRequest:c,sendResponse:l,webSocketHandler:({baseUrl:e})=>m(e,n),runAuthMiddleware:n?t=>e.tryPromise(()=>n(t)).pipe(e.catchAll(t=>(console.error(`Express auth middleware failed:`,t),e.succeed(null)))):void 0}};export{h as expressAdapter};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["chunks: Buffer[]","newData: unknown","authResult: AuthResult | null","connection: WebSocketConnection"],"sources":["../src/express-http-handler.ts","../src/express-websocket-handler.ts","../src/express-adapter.ts"],"sourcesContent":["import type { UploadistaRequest, UploadistaResponse } from \"@uploadista/server\";\nimport { Effect } from \"effect\";\nimport type { ExpressContext } from \"./express-adapter\";\n\n/**\n * Helper to parse JSON body if not already parsed\n */\nconst parseJsonBody = async (\n req: ExpressContext[\"request\"],\n): Promise<unknown> => {\n // If body is already parsed, return it\n if (req.body && typeof req.body === \"object\") {\n return req.body;\n }\n\n // Manually parse JSON body\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk as Buffer);\n }\n const body = Buffer.concat(chunks).toString();\n return JSON.parse(body);\n};\n\nexport const extractExpressRequest = (\n ctx: ExpressContext,\n { baseUrl }: { baseUrl: string },\n) => {\n // Run the routing logic as an Effect program\n return Effect.promise(async () => {\n // Get request details\n const url = new URL(ctx.request.url, `http://${ctx.request.get(\"host\")}`);\n const acceptHeader = ctx.request.get(\"Accept\");\n\n // Check for health check endpoints first (at /{baseUrl}/health, not under /api/)\n const healthPrefix = `/${baseUrl}/`;\n if (url.pathname.startsWith(healthPrefix) && ctx.request.method === \"GET\") {\n const healthPath = url.pathname.slice(healthPrefix.length);\n\n // /health or /healthz - Liveness probe\n if (healthPath === \"health\" || healthPath === \"healthz\") {\n return {\n type: \"health\",\n acceptHeader,\n } as UploadistaRequest;\n }\n\n // /ready or /readyz - Readiness probe\n if (healthPath === \"ready\" || healthPath === \"readyz\") {\n return {\n type: \"health-ready\",\n acceptHeader,\n } as UploadistaRequest;\n }\n\n // /health/components - Detailed component status\n if (healthPath === \"health/components\") {\n return {\n type: \"health-components\",\n acceptHeader,\n } as UploadistaRequest;\n }\n }\n\n // Check for baseUrl/api/ prefix for other routes\n const expectedPrefix = `/${baseUrl}/api/`;\n if (!url.pathname.includes(expectedPrefix)) {\n return {\n type: \"not-found\",\n } as UploadistaRequest;\n }\n\n // Remove the prefix and get the actual route segments\n const routeSegments = url.pathname\n .replace(`${baseUrl}/api/`, \"\")\n .split(\"/\")\n .filter(Boolean);\n\n // Route based on first segment\n if (routeSegments[0] === \"upload\" || routeSegments.includes(\"upload\")) {\n switch (ctx.request.method) {\n case \"POST\": {\n // Parse JSON body if not already parsed\n const data = await parseJsonBody(ctx.request);\n return {\n type: \"create-upload\",\n data,\n } as UploadistaRequest;\n }\n case \"GET\": {\n const lastSegment = routeSegments[routeSegments.length - 1];\n\n if (lastSegment === \"capabilities\") {\n const storageId = url.searchParams.get(\"storageId\");\n const storageIdFromPath = routeSegments[routeSegments.length - 2];\n\n // Only use path segment if it's not \"upload\"\n const finalStorageId =\n storageId || (storageIdFromPath !== \"upload\" ? storageIdFromPath : null);\n\n if (!finalStorageId) {\n return {\n type: \"bad-request\",\n message: \"Storage ID is required\",\n } as UploadistaRequest;\n }\n return {\n type: \"get-capabilities\",\n storageId: finalStorageId,\n } as UploadistaRequest;\n }\n if (routeSegments.length < 2) {\n return {\n type: \"bad-request\",\n message: \"Upload ID is required\",\n } as UploadistaRequest;\n }\n return {\n type: \"get-upload\",\n uploadId: routeSegments[1],\n } as UploadistaRequest;\n }\n case \"PATCH\": {\n if (routeSegments.length < 2) {\n return {\n type: \"bad-request\",\n message: \"Upload ID is required\",\n } as UploadistaRequest;\n }\n // Convert Node.js Readable stream to web ReadableStream\n const body = new ReadableStream({\n start(controller) {\n ctx.request.on(\"data\", (chunk: Buffer) => {\n controller.enqueue(chunk);\n });\n ctx.request.on(\"end\", () => {\n controller.close();\n });\n ctx.request.on(\"error\", (error: Error) => {\n controller.error(error);\n });\n },\n });\n\n return {\n type: \"upload-chunk\",\n uploadId: routeSegments[1],\n data: body,\n } as UploadistaRequest;\n }\n default:\n return {\n type: \"method-not-allowed\",\n } as UploadistaRequest;\n }\n } else if (routeSegments[0] === \"flow\" || routeSegments.includes(\"flow\")) {\n switch (ctx.request.method) {\n case \"GET\":\n return {\n type: \"get-flow\",\n flowId: routeSegments[1],\n } as UploadistaRequest;\n case \"POST\": {\n // Parse JSON body if not already parsed\n const params = await parseJsonBody(ctx.request);\n if (!params || typeof params !== \"object\" || !(\"inputs\" in params)) {\n return {\n type: \"bad-request\",\n message: \"Inputs are required\",\n } as UploadistaRequest;\n }\n return {\n type: \"run-flow\",\n flowId: routeSegments[1],\n storageId: routeSegments[2],\n inputs: (params as { inputs: unknown }).inputs,\n } as UploadistaRequest;\n }\n default:\n return {\n type: \"method-not-allowed\",\n } as UploadistaRequest;\n }\n } else if (routeSegments[0] === \"dlq\" || routeSegments.includes(\"dlq\")) {\n // DLQ Admin routes: /api/dlq, /api/dlq/:itemId, /api/dlq/:itemId/retry, etc.\n switch (ctx.request.method) {\n case \"GET\": {\n if (routeSegments.length === 1) {\n // GET /api/dlq - List DLQ items\n const status = url.searchParams.get(\"status\") as string | undefined;\n const flowId = url.searchParams.get(\"flowId\") as string | undefined;\n const clientId = url.searchParams.get(\"clientId\") as\n | string\n | undefined;\n const limit = url.searchParams.get(\"limit\")\n ? Number.parseInt(url.searchParams.get(\"limit\") as string)\n : undefined;\n const offset = url.searchParams.get(\"offset\")\n ? Number.parseInt(url.searchParams.get(\"offset\") as string)\n : undefined;\n return {\n type: \"dlq-list\",\n options: { status, flowId, clientId, limit, offset },\n } as UploadistaRequest;\n }\n if (routeSegments[1] === \"stats\") {\n // GET /api/dlq/stats - Get DLQ statistics\n return {\n type: \"dlq-stats\",\n } as UploadistaRequest;\n }\n // GET /api/dlq/:itemId - Get specific DLQ item\n return {\n type: \"dlq-get\",\n itemId: routeSegments[1],\n } as UploadistaRequest;\n }\n case \"POST\": {\n if (routeSegments[1] === \"cleanup\") {\n // POST /api/dlq/cleanup - Cleanup old items\n const body = await parseJsonBody(ctx.request).catch(() => ({}));\n return {\n type: \"dlq-cleanup\",\n options: body,\n } as UploadistaRequest;\n }\n if (routeSegments[1] === \"retry-all\") {\n // POST /api/dlq/retry-all - Retry all matching items\n const body = await parseJsonBody(ctx.request).catch(() => ({}));\n return {\n type: \"dlq-retry-all\",\n options: body,\n } as UploadistaRequest;\n }\n if (routeSegments[2] === \"retry\") {\n // POST /api/dlq/:itemId/retry - Retry specific item\n return {\n type: \"dlq-retry\",\n itemId: routeSegments[1],\n } as UploadistaRequest;\n }\n if (routeSegments[2] === \"resolve\") {\n // POST /api/dlq/:itemId/resolve - Manually resolve item\n return {\n type: \"dlq-resolve\",\n itemId: routeSegments[1],\n } as UploadistaRequest;\n }\n return {\n type: \"method-not-allowed\",\n } as UploadistaRequest;\n }\n case \"DELETE\": {\n // DELETE /api/dlq/:itemId - Delete a DLQ item\n if (routeSegments.length < 2) {\n return {\n type: \"bad-request\",\n message: \"Item ID is required\",\n } as UploadistaRequest;\n }\n return {\n type: \"dlq-delete\",\n itemId: routeSegments[1],\n } as UploadistaRequest;\n }\n default:\n return {\n type: \"method-not-allowed\",\n } as UploadistaRequest;\n }\n } else if (routeSegments[0] === \"jobs\" || routeSegments.includes(\"jobs\")) {\n if (ctx.request.method === \"GET\" && url.pathname.endsWith(\"/status\")) {\n // Need at least 3 segments: jobs, jobId, status\n if (routeSegments.length < 3) {\n return {\n type: \"bad-request\",\n message: \"Job ID is required\",\n } as UploadistaRequest;\n }\n const jobId = routeSegments[1];\n return {\n type: \"job-status\",\n jobId,\n } as UploadistaRequest;\n } else if (\n ctx.request.method === \"PATCH\" &&\n routeSegments.includes(\"resume\")\n ) {\n const jobId = routeSegments[1];\n if (!jobId) {\n return {\n type: \"bad-request\",\n message: \"Job ID is required\",\n } as UploadistaRequest;\n }\n const nodeId = routeSegments[3];\n if (!nodeId) {\n return {\n type: \"bad-request\",\n message: \"Node ID is required\",\n } as UploadistaRequest;\n }\n\n const contentType = ctx.request.get(\"content-type\");\n let newData: unknown;\n\n // Handle different content types\n if (contentType?.includes(\"application/octet-stream\")) {\n // For streaming data, pass the req object (Express handles streams)\n // Express doesn't expose ReadableStream like Hono, use req itself\n newData = ctx.request;\n } else if (contentType?.includes(\"application/json\")) {\n // Parse JSON body if not already parsed\n const body = await parseJsonBody(ctx.request);\n\n if (!body || typeof body !== \"object\" || !(\"newData\" in body)) {\n return {\n type: \"bad-request\",\n message: \"Missing newData\",\n } as UploadistaRequest;\n }\n\n newData = (body as { newData: unknown }).newData;\n } else {\n return {\n type: \"unsupported-content-type\",\n } as UploadistaRequest;\n }\n\n return {\n type: \"resume-flow\",\n jobId,\n nodeId,\n newData,\n } as UploadistaRequest;\n } else if (\n ctx.request.method === \"POST\" &&\n url.pathname.endsWith(\"/pause\")\n ) {\n return {\n type: \"pause-flow\",\n jobId: routeSegments[1],\n } as UploadistaRequest;\n } else if (\n ctx.request.method === \"POST\" &&\n url.pathname.endsWith(\"/cancel\")\n ) {\n return {\n type: \"cancel-flow\",\n jobId: routeSegments[1],\n } as UploadistaRequest;\n }\n return {\n type: \"method-not-allowed\",\n } as UploadistaRequest;\n } else {\n return {\n type: \"not-found\",\n } as UploadistaRequest;\n }\n });\n};\n\nexport const sendExpressResponse = (\n response: UploadistaResponse,\n ctx: ExpressContext,\n) =>\n Effect.sync(() => {\n // Set default Content-Type header if not provided\n const headers = response.headers || {};\n if (!headers[\"Content-Type\"]) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n // Set headers\n for (const [key, value] of Object.entries(headers)) {\n ctx.response.set(key, value);\n }\n\n return ctx.response.status(response.status).send(response.body);\n });\n","import type { IncomingMessage } from \"node:http\";\nimport { FlowServer, UploadServer } from \"@uploadista/core\";\nimport type { AuthResult } from \"@uploadista/server\";\nimport {\n handleWebSocketClose,\n handleWebSocketError,\n handleWebSocketMessage,\n handleWebSocketOpen,\n type WebSocketConnection,\n type WebSocketConnectionRequest,\n} from \"@uploadista/server\";\nimport { Effect } from \"effect\";\nimport type { Request, Response } from \"express\";\nimport type { WebSocket } from \"ws\";\nimport type { ExpressContext } from \"./express-adapter\";\n\nexport type ExpressWebSocketHandler = (\n ws: WebSocket,\n req: IncomingMessage,\n) => void;\n\nfunction extractQueryParam(url: string, param: string): string | undefined {\n const regex = new RegExp(`[?&]${param}=([^&]*)`);\n const match = url.match(regex);\n return match?.[1] ? decodeURIComponent(match[1]) : undefined;\n}\n\n/**\n * Cache for storing auth context per WebSocket connection\n */\nconst wsAuthCache = new Map<string, AuthResult>();\n\n/**\n * Extracts WebSocket connection request details from Express/Node.js request\n */\nconst extractWebSocketRequest = (\n req: IncomingMessage,\n baseUrl: string,\n):\n | WebSocketConnectionRequest\n | { type: \"invalid-path\"; expectedPrefix: string } => {\n const url = new URL(req.url || \"\", `http://${req.headers.host}`);\n const expectedPrefix = `${baseUrl}/ws/`;\n\n // Check for ws/uploadista prefix\n if (!url.pathname.includes(expectedPrefix)) {\n return {\n type: \"invalid-path\",\n expectedPrefix,\n };\n }\n\n // Remove the prefix and get the actual route segments\n const routeSegments = url.pathname\n .replace(expectedPrefix, \"\")\n .split(\"/\")\n .filter(Boolean);\n\n const isUploadRoute = routeSegments.includes(\"upload\");\n const isFlowRoute = routeSegments.includes(\"flow\");\n\n // Extract jobId and uploadId from URL path or query parameters\n // Path format: /uploadista/ws/flow/{jobId} or /uploadista/ws/upload/{uploadId}\n let jobId = extractQueryParam(req.url || \"\", \"jobId\");\n let uploadId = extractQueryParam(req.url || \"\", \"uploadId\");\n\n // If not in query params, extract from path segments\n if (!jobId && !uploadId && routeSegments.length >= 2) {\n const routeType = routeSegments[0]; // 'flow' or 'upload'\n const id = routeSegments[1]; // the actual ID\n\n if (routeType === \"flow\") {\n jobId = id;\n } else if (routeType === \"upload\") {\n uploadId = id;\n }\n }\n\n // Use jobId if available, otherwise use uploadId\n const eventId = jobId || uploadId;\n\n return {\n baseUrl,\n pathname: url.pathname,\n routeSegments,\n isUploadRoute,\n isFlowRoute,\n jobId,\n uploadId,\n eventId,\n // Connection will be set when WebSocket opens\n connection: null as unknown as WebSocketConnection,\n };\n};\n\n/**\n * Authenticates a WebSocket connection using the provided auth middleware\n */\nconst authenticateWebSocket = async (\n req: IncomingMessage,\n authMiddleware: (ctx: ExpressContext) => Promise<AuthResult>,\n): Promise<{\n success: boolean;\n authResult?: AuthResult;\n error?: { message: string; code: number; authMethod: string };\n}> => {\n try {\n // Extract token from query parameter\n const url = new URL(req.url || \"\", `http://${req.headers.host}`);\n const token = url.searchParams.get(\"token\");\n\n let authResult: AuthResult | null = null;\n\n if (token) {\n // Token-based authentication\n // Create a mock request with Authorization header\n const mockReq = {\n ...req,\n header: (name: string) => {\n if (name.toLowerCase() === \"authorization\") {\n return `Bearer ${token}`;\n }\n return req.headers[name.toLowerCase()];\n },\n } as unknown as Request;\n\n authResult = await authMiddleware({\n request: mockReq as unknown as Request,\n response: {} as Response,\n });\n } else {\n // Cookie-based authentication\n // Pass the original request so auth middleware can read cookies\n authResult = await authMiddleware({\n request: req as unknown as Request,\n response: {} as Response,\n });\n }\n\n if (!authResult) {\n const authMethod = token ? \"token\" : \"cookies\";\n return {\n success: false,\n error: {\n message: `Authentication failed: invalid or expired ${authMethod}`,\n code: 4001,\n authMethod,\n },\n };\n }\n\n console.log(`WebSocket authenticated for user: ${authResult.clientId}`);\n return { success: true, authResult };\n } catch (error) {\n console.error(\"WebSocket auth error:\", error);\n return {\n success: false,\n error: {\n message: \"Authentication error\",\n code: 4001,\n authMethod: \"unknown\",\n },\n };\n }\n};\n\n/**\n * Creates an Express WebSocket handler that delegates to core WebSocket handlers\n */\nexport const expressWebSocketHandler = (\n baseUrl: string,\n authMiddleware?: (ctx: ExpressContext) => Promise<AuthResult>,\n): Effect.Effect<ExpressWebSocketHandler, never, UploadServer | FlowServer> => {\n return Effect.gen(function* () {\n // Get the server instances from the Effect context\n const uploadServer = yield* UploadServer;\n const flowServer = yield* FlowServer;\n\n return (ws: WebSocket, req: IncomingMessage) => {\n // Extract request details (adapter's responsibility)\n const requestOrError = extractWebSocketRequest(req, baseUrl);\n\n console.log(\"🔍 WebSocket request details:\", requestOrError);\n\n // Handle invalid path\n if (\"type\" in requestOrError && requestOrError.type === \"invalid-path\") {\n ws.send(\n JSON.stringify({\n type: \"invalid-path\",\n message: `WebSocket path must start with ${requestOrError.expectedPrefix}`,\n expectedPrefix: requestOrError.expectedPrefix,\n }),\n );\n ws.close(1000, \"Invalid path\");\n return;\n }\n\n // Type narrowing: at this point, requestOrError is WebSocketConnectionRequest\n const request = requestOrError as WebSocketConnectionRequest;\n\n // Create framework-agnostic connection wrapper\n // Use a getter for readyState so it always reflects the current state\n const connection: WebSocketConnection = {\n id: `conn_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,\n send: (data: string) => {\n if (ws.readyState === ws.OPEN) {\n console.log(\n `📤 Sending WebSocket message to connection ${connection.id}:`,\n data.substring(0, 100),\n );\n ws.send(data);\n } else {\n console.warn(\n `⚠️ Cannot send message, WebSocket not open. State: ${ws.readyState}`,\n );\n }\n },\n close: (code?: number, reason?: string) => ws.close(code, reason),\n get readyState() {\n return ws.readyState;\n },\n };\n\n // Update request with connection\n request.connection = connection;\n\n // Handle WebSocket open\n (async () => {\n // Validate authentication if auth middleware is configured\n if (authMiddleware) {\n const authResult = await authenticateWebSocket(req, authMiddleware);\n\n if (!authResult.success) {\n ws.send(\n JSON.stringify({\n type: \"auth-failed\",\n message: authResult.error?.message,\n code: \"AUTH_FAILED\",\n authMethod: authResult.error?.authMethod,\n }),\n );\n ws.close(authResult.error?.code || 4001, authResult.error?.message);\n return;\n }\n\n // Cache auth context for this connection\n if (authResult.authResult) {\n wsAuthCache.set(connection.id, authResult.authResult);\n }\n }\n\n console.log(\n \"🔍 WebSocket open for eventId:\",\n request.eventId,\n \"with connection id:\",\n connection.id,\n );\n\n // Delegate to core handler for business logic\n const openEffect = handleWebSocketOpen(\n request,\n uploadServer,\n flowServer,\n );\n Effect.runFork(openEffect);\n })();\n\n // Handle WebSocket message\n ws.on(\"message\", (data: unknown) => {\n const messageEffect = handleWebSocketMessage(\n data as string,\n connection,\n );\n Effect.runFork(messageEffect);\n });\n\n // Handle WebSocket close\n ws.on(\"close\", () => {\n // Clear cached auth context for this connection\n if (request.connection?.id) {\n wsAuthCache.delete(request.connection.id);\n console.log(\n `Cleared auth cache for WebSocket connection: ${request.connection.id}`,\n );\n }\n\n // Delegate to core handler for cleanup\n const closeEffect = handleWebSocketClose(\n request,\n uploadServer,\n flowServer,\n );\n Effect.runFork(closeEffect);\n });\n\n // Handle WebSocket error\n ws.on(\"error\", (...args: unknown[]) => {\n const error = args[0] as Error;\n const errorEffect = handleWebSocketError(error, request.eventId);\n Effect.runFork(errorEffect);\n });\n };\n });\n};\n","import type { AuthResult, ServerAdapter } from \"@uploadista/server\";\nimport { Effect } from \"effect\";\nimport type { Request, Response } from \"express\";\nimport {\n extractExpressRequest,\n sendExpressResponse,\n} from \"./express-http-handler\";\nimport {\n type ExpressWebSocketHandler,\n expressWebSocketHandler,\n} from \"./express-websocket-handler\";\n\nexport type ExpressContext = {\n request: Request;\n response: Response;\n next?: (error?: Error) => void;\n};\n\n/**\n * Options for creating an Express server adapter.\n */\nexport interface ExpressAdapterOptions {\n /**\n * Optional authentication middleware function.\n * Called for each request to authenticate the user.\n *\n * @param ctx - Express context\n * @returns Promise resolving to AuthResult (AuthContext or null)\n */\n authMiddleware?: (ctx: ExpressContext) => Promise<AuthResult>;\n}\n\n// WebSocket interface from ws package\nexport interface WebSocket {\n readyState: number;\n OPEN: number;\n send: (data: string) => void;\n close: (code?: number, reason?: string) => void;\n on: (event: string, listener: (...args: unknown[]) => void) => void;\n off: (event: string, listener: (...args: unknown[]) => void) => void;\n}\n\n/**\n * Creates an Express server adapter that implements the ServerAdapter interface.\n *\n * This adapter translates between Express's Request/Response API and the core server's\n * standard request/response model. It supports:\n * - Request extraction from Express Request\n * - Response sending via Express Response\n * - Optional authentication middleware\n * - WebSocket handling\n *\n * @param options - Adapter configuration options\n * @returns ServerAdapter implementation for Express\n *\n * @example\n * ```typescript\n * import { expressAdapter } from \"@uploadista/adapters-express\";\n * import { createUploadistaServer } from \"@uploadista/server\";\n *\n * const adapter = expressAdapter({\n * authMiddleware: async (req, res) => {\n * const userId = req.header(\"x-user-id\");\n * return userId ? { clientId: userId } : null;\n * }\n * });\n *\n * const server = await createUploadistaServer({\n * flows: getFlows,\n * dataStore: { type: \"s3\", config: { bucket: \"uploads\" } },\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter(),\n * adapter\n * });\n * ```\n */\nexport const expressAdapter = (\n options: ExpressAdapterOptions = {},\n): ServerAdapter<ExpressContext, Response, ExpressWebSocketHandler> => {\n const { authMiddleware } = options;\n\n return {\n /**\n * Extract standard request details from Express Request.\n *\n * Converts Express's Request into a framework-agnostic UploadistaRequest\n * by parsing the URL, headers, and body.\n */\n extractRequest: extractExpressRequest,\n\n /**\n * Send standard response using Express Response format.\n *\n * Converts a UploadistaResponse into an object with status, headers, and body\n * that the Express handler can send.\n */\n sendResponse: sendExpressResponse,\n\n /**\n * WebSocket handler for Express with ws package.\n */\n webSocketHandler: ({ baseUrl }: { baseUrl: string }) =>\n expressWebSocketHandler(baseUrl, authMiddleware),\n\n /**\n * Run framework-specific auth middleware.\n *\n * If provided, executes the Express-specific authentication middleware\n * with access to the full Express Request and Response objects.\n */\n runAuthMiddleware: authMiddleware\n ? (ctx: ExpressContext) =>\n Effect.tryPromise(() => authMiddleware(ctx)).pipe(\n Effect.catchAll((error) => {\n console.error(\"Express auth middleware failed:\", error);\n // Return null to indicate auth failure (not an error in the Effect sense)\n return Effect.succeed(null);\n }),\n )\n : undefined,\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["chunks: Buffer[]","newData: unknown","authResult: AuthResult | null","connection: WebSocketConnection"],"sources":["../src/express-http-handler.ts","../src/express-websocket-handler.ts","../src/express-adapter.ts"],"sourcesContent":["import type { UploadistaRequest, UploadistaResponse } from \"@uploadista/server\";\nimport { Effect } from \"effect\";\nimport type { ExpressContext } from \"./express-adapter\";\n\n/**\n * Helper to parse JSON body if not already parsed\n */\nconst parseJsonBody = async (\n req: ExpressContext[\"request\"],\n): Promise<unknown> => {\n // If body is already parsed, return it\n if (req.body && typeof req.body === \"object\") {\n return req.body;\n }\n\n // Manually parse JSON body\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk as Buffer);\n }\n const body = Buffer.concat(chunks).toString();\n return JSON.parse(body);\n};\n\nexport const extractExpressRequest = (\n ctx: ExpressContext,\n { baseUrl }: { baseUrl: string },\n) => {\n // Run the routing logic as an Effect program\n return Effect.promise(async () => {\n // Get request details\n const url = new URL(ctx.request.url, `http://${ctx.request.get(\"host\")}`);\n const acceptHeader = ctx.request.get(\"Accept\");\n\n // Check for health check endpoints first (at /{baseUrl}/health, not under /api/)\n const healthPrefix = `/${baseUrl}/`;\n if (url.pathname.startsWith(healthPrefix) && ctx.request.method === \"GET\") {\n const healthPath = url.pathname.slice(healthPrefix.length);\n\n // /health or /healthz - Liveness probe\n if (healthPath === \"health\" || healthPath === \"healthz\") {\n return {\n type: \"health\",\n acceptHeader,\n } as UploadistaRequest;\n }\n\n // /ready or /readyz - Readiness probe\n if (healthPath === \"ready\" || healthPath === \"readyz\") {\n return {\n type: \"health-ready\",\n acceptHeader,\n } as UploadistaRequest;\n }\n\n // /health/components - Detailed component status\n if (healthPath === \"health/components\") {\n return {\n type: \"health-components\",\n acceptHeader,\n } as UploadistaRequest;\n }\n }\n\n // Check for baseUrl/api/ prefix for other routes\n const expectedPrefix = `/${baseUrl}/api/`;\n if (!url.pathname.includes(expectedPrefix)) {\n return {\n type: \"not-found\",\n } as UploadistaRequest;\n }\n\n // Remove the prefix and get the actual route segments\n const routeSegments = url.pathname\n .replace(`${baseUrl}/api/`, \"\")\n .split(\"/\")\n .filter(Boolean);\n\n // Route based on first segment\n if (routeSegments[0] === \"upload\" || routeSegments.includes(\"upload\")) {\n switch (ctx.request.method) {\n case \"POST\": {\n // Parse JSON body if not already parsed\n const data = await parseJsonBody(ctx.request);\n return {\n type: \"create-upload\",\n data,\n } as UploadistaRequest;\n }\n case \"GET\": {\n const lastSegment = routeSegments[routeSegments.length - 1];\n\n if (lastSegment === \"capabilities\") {\n const storageId = url.searchParams.get(\"storageId\");\n const storageIdFromPath = routeSegments[routeSegments.length - 2];\n\n // Only use path segment if it's not \"upload\"\n const finalStorageId =\n storageId || (storageIdFromPath !== \"upload\" ? storageIdFromPath : null);\n\n if (!finalStorageId) {\n return {\n type: \"bad-request\",\n message: \"Storage ID is required\",\n } as UploadistaRequest;\n }\n return {\n type: \"get-capabilities\",\n storageId: finalStorageId,\n } as UploadistaRequest;\n }\n if (routeSegments.length < 2) {\n return {\n type: \"bad-request\",\n message: \"Upload ID is required\",\n } as UploadistaRequest;\n }\n return {\n type: \"get-upload\",\n uploadId: routeSegments[1],\n } as UploadistaRequest;\n }\n case \"PATCH\": {\n if (routeSegments.length < 2) {\n return {\n type: \"bad-request\",\n message: \"Upload ID is required\",\n } as UploadistaRequest;\n }\n // Convert Node.js Readable stream to web ReadableStream\n const body = new ReadableStream({\n start(controller) {\n ctx.request.on(\"data\", (chunk: Buffer) => {\n controller.enqueue(chunk);\n });\n ctx.request.on(\"end\", () => {\n controller.close();\n });\n ctx.request.on(\"error\", (error: Error) => {\n controller.error(error);\n });\n },\n });\n\n return {\n type: \"upload-chunk\",\n uploadId: routeSegments[1],\n data: body,\n } as UploadistaRequest;\n }\n default:\n return {\n type: \"method-not-allowed\",\n } as UploadistaRequest;\n }\n } else if (routeSegments[0] === \"flow\" || routeSegments.includes(\"flow\")) {\n switch (ctx.request.method) {\n case \"GET\":\n return {\n type: \"get-flow\",\n flowId: routeSegments[1],\n } as UploadistaRequest;\n case \"POST\": {\n // Parse JSON body if not already parsed\n const params = await parseJsonBody(ctx.request);\n if (!params || typeof params !== \"object\" || !(\"inputs\" in params)) {\n return {\n type: \"bad-request\",\n message: \"Inputs are required\",\n } as UploadistaRequest;\n }\n return {\n type: \"run-flow\",\n flowId: routeSegments[1],\n storageId: routeSegments[2],\n inputs: (params as { inputs: unknown }).inputs,\n } as UploadistaRequest;\n }\n default:\n return {\n type: \"method-not-allowed\",\n } as UploadistaRequest;\n }\n } else if (routeSegments[0] === \"dlq\" || routeSegments.includes(\"dlq\")) {\n // DLQ Admin routes: /api/dlq, /api/dlq/:itemId, /api/dlq/:itemId/retry, etc.\n switch (ctx.request.method) {\n case \"GET\": {\n if (routeSegments.length === 1) {\n // GET /api/dlq - List DLQ items\n const status = url.searchParams.get(\"status\") as string | undefined;\n const flowId = url.searchParams.get(\"flowId\") as string | undefined;\n const clientId = url.searchParams.get(\"clientId\") as\n | string\n | undefined;\n const limit = url.searchParams.get(\"limit\")\n ? Number.parseInt(url.searchParams.get(\"limit\") as string)\n : undefined;\n const offset = url.searchParams.get(\"offset\")\n ? Number.parseInt(url.searchParams.get(\"offset\") as string)\n : undefined;\n return {\n type: \"dlq-list\",\n options: { status, flowId, clientId, limit, offset },\n } as UploadistaRequest;\n }\n if (routeSegments[1] === \"stats\") {\n // GET /api/dlq/stats - Get DLQ statistics\n return {\n type: \"dlq-stats\",\n } as UploadistaRequest;\n }\n // GET /api/dlq/:itemId - Get specific DLQ item\n return {\n type: \"dlq-get\",\n itemId: routeSegments[1],\n } as UploadistaRequest;\n }\n case \"POST\": {\n if (routeSegments[1] === \"cleanup\") {\n // POST /api/dlq/cleanup - Cleanup old items\n const body = await parseJsonBody(ctx.request).catch(() => ({}));\n return {\n type: \"dlq-cleanup\",\n options: body,\n } as UploadistaRequest;\n }\n if (routeSegments[1] === \"retry-all\") {\n // POST /api/dlq/retry-all - Retry all matching items\n const body = await parseJsonBody(ctx.request).catch(() => ({}));\n return {\n type: \"dlq-retry-all\",\n options: body,\n } as UploadistaRequest;\n }\n if (routeSegments[2] === \"retry\") {\n // POST /api/dlq/:itemId/retry - Retry specific item\n return {\n type: \"dlq-retry\",\n itemId: routeSegments[1],\n } as UploadistaRequest;\n }\n if (routeSegments[2] === \"resolve\") {\n // POST /api/dlq/:itemId/resolve - Manually resolve item\n return {\n type: \"dlq-resolve\",\n itemId: routeSegments[1],\n } as UploadistaRequest;\n }\n return {\n type: \"method-not-allowed\",\n } as UploadistaRequest;\n }\n case \"DELETE\": {\n // DELETE /api/dlq/:itemId - Delete a DLQ item\n if (routeSegments.length < 2) {\n return {\n type: \"bad-request\",\n message: \"Item ID is required\",\n } as UploadistaRequest;\n }\n return {\n type: \"dlq-delete\",\n itemId: routeSegments[1],\n } as UploadistaRequest;\n }\n default:\n return {\n type: \"method-not-allowed\",\n } as UploadistaRequest;\n }\n } else if (routeSegments[0] === \"jobs\" || routeSegments.includes(\"jobs\")) {\n if (ctx.request.method === \"GET\" && url.pathname.endsWith(\"/status\")) {\n // Need at least 3 segments: jobs, jobId, status\n if (routeSegments.length < 3) {\n return {\n type: \"bad-request\",\n message: \"Job ID is required\",\n } as UploadistaRequest;\n }\n const jobId = routeSegments[1];\n return {\n type: \"job-status\",\n jobId,\n } as UploadistaRequest;\n } else if (\n ctx.request.method === \"PATCH\" &&\n routeSegments.includes(\"resume\")\n ) {\n const jobId = routeSegments[1];\n if (!jobId) {\n return {\n type: \"bad-request\",\n message: \"Job ID is required\",\n } as UploadistaRequest;\n }\n const nodeId = routeSegments[3];\n if (!nodeId) {\n return {\n type: \"bad-request\",\n message: \"Node ID is required\",\n } as UploadistaRequest;\n }\n\n const contentType = ctx.request.get(\"content-type\");\n let newData: unknown;\n\n // Handle different content types\n if (contentType?.includes(\"application/octet-stream\")) {\n // For streaming data, pass the req object (Express handles streams)\n // Express doesn't expose ReadableStream like Hono, use req itself\n newData = ctx.request;\n } else if (contentType?.includes(\"application/json\")) {\n // Parse JSON body if not already parsed\n const body = await parseJsonBody(ctx.request);\n\n if (!body || typeof body !== \"object\" || !(\"newData\" in body)) {\n return {\n type: \"bad-request\",\n message: \"Missing newData\",\n } as UploadistaRequest;\n }\n\n newData = (body as { newData: unknown }).newData;\n } else {\n return {\n type: \"unsupported-content-type\",\n } as UploadistaRequest;\n }\n\n return {\n type: \"resume-flow\",\n jobId,\n nodeId,\n newData,\n } as UploadistaRequest;\n } else if (\n ctx.request.method === \"POST\" &&\n url.pathname.endsWith(\"/pause\")\n ) {\n return {\n type: \"pause-flow\",\n jobId: routeSegments[1],\n } as UploadistaRequest;\n } else if (\n ctx.request.method === \"POST\" &&\n url.pathname.endsWith(\"/cancel\")\n ) {\n return {\n type: \"cancel-flow\",\n jobId: routeSegments[1],\n } as UploadistaRequest;\n }\n return {\n type: \"method-not-allowed\",\n } as UploadistaRequest;\n } else {\n return {\n type: \"not-found\",\n } as UploadistaRequest;\n }\n });\n};\n\nexport const sendExpressResponse = (\n response: UploadistaResponse,\n ctx: ExpressContext,\n) =>\n Effect.sync(() => {\n // Set default Content-Type header if not provided\n const headers = response.headers || {};\n if (!headers[\"Content-Type\"]) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n // Set headers\n for (const [key, value] of Object.entries(headers)) {\n ctx.response.set(key, value);\n }\n\n return ctx.response.status(response.status).send(response.body);\n });\n","import type { IncomingMessage } from \"node:http\";\nimport { FlowEngine, UploadEngine } from \"@uploadista/core\";\nimport type { AuthResult } from \"@uploadista/server\";\nimport {\n handleWebSocketClose,\n handleWebSocketError,\n handleWebSocketMessage,\n handleWebSocketOpen,\n type WebSocketConnection,\n type WebSocketConnectionRequest,\n} from \"@uploadista/server\";\nimport { Effect } from \"effect\";\nimport type { Request, Response } from \"express\";\nimport type { WebSocket } from \"ws\";\nimport type { ExpressContext } from \"./express-adapter\";\n\nexport type ExpressWebSocketHandler = (\n ws: WebSocket,\n req: IncomingMessage,\n) => void;\n\nfunction extractQueryParam(url: string, param: string): string | undefined {\n const regex = new RegExp(`[?&]${param}=([^&]*)`);\n const match = url.match(regex);\n return match?.[1] ? decodeURIComponent(match[1]) : undefined;\n}\n\n/**\n * Cache for storing auth context per WebSocket connection\n */\nconst wsAuthCache = new Map<string, AuthResult>();\n\n/**\n * Extracts WebSocket connection request details from Express/Node.js request\n */\nconst extractWebSocketRequest = (\n req: IncomingMessage,\n baseUrl: string,\n):\n | WebSocketConnectionRequest\n | { type: \"invalid-path\"; expectedPrefix: string } => {\n const url = new URL(req.url || \"\", `http://${req.headers.host}`);\n const expectedPrefix = `${baseUrl}/ws/`;\n\n // Check for ws/uploadista prefix\n if (!url.pathname.includes(expectedPrefix)) {\n return {\n type: \"invalid-path\",\n expectedPrefix,\n };\n }\n\n // Remove the prefix and get the actual route segments\n const routeSegments = url.pathname\n .replace(expectedPrefix, \"\")\n .split(\"/\")\n .filter(Boolean);\n\n const isUploadRoute = routeSegments.includes(\"upload\");\n const isFlowRoute = routeSegments.includes(\"flow\");\n\n // Extract jobId and uploadId from URL path or query parameters\n // Path format: /uploadista/ws/flow/{jobId} or /uploadista/ws/upload/{uploadId}\n let jobId = extractQueryParam(req.url || \"\", \"jobId\");\n let uploadId = extractQueryParam(req.url || \"\", \"uploadId\");\n\n // If not in query params, extract from path segments\n if (!jobId && !uploadId && routeSegments.length >= 2) {\n const routeType = routeSegments[0]; // 'flow' or 'upload'\n const id = routeSegments[1]; // the actual ID\n\n if (routeType === \"flow\") {\n jobId = id;\n } else if (routeType === \"upload\") {\n uploadId = id;\n }\n }\n\n // Use jobId if available, otherwise use uploadId\n const eventId = jobId || uploadId;\n\n return {\n baseUrl,\n pathname: url.pathname,\n routeSegments,\n isUploadRoute,\n isFlowRoute,\n jobId,\n uploadId,\n eventId,\n // Connection will be set when WebSocket opens\n connection: null as unknown as WebSocketConnection,\n };\n};\n\n/**\n * Authenticates a WebSocket connection using the provided auth middleware\n */\nconst authenticateWebSocket = async (\n req: IncomingMessage,\n authMiddleware: (ctx: ExpressContext) => Promise<AuthResult>,\n): Promise<{\n success: boolean;\n authResult?: AuthResult;\n error?: { message: string; code: number; authMethod: string };\n}> => {\n try {\n // Extract token from query parameter\n const url = new URL(req.url || \"\", `http://${req.headers.host}`);\n const token = url.searchParams.get(\"token\");\n\n let authResult: AuthResult | null = null;\n\n if (token) {\n // Token-based authentication\n // Create a mock request with Authorization header\n const mockReq = {\n ...req,\n header: (name: string) => {\n if (name.toLowerCase() === \"authorization\") {\n return `Bearer ${token}`;\n }\n return req.headers[name.toLowerCase()];\n },\n } as unknown as Request;\n\n authResult = await authMiddleware({\n request: mockReq as unknown as Request,\n response: {} as Response,\n });\n } else {\n // Cookie-based authentication\n // Pass the original request so auth middleware can read cookies\n authResult = await authMiddleware({\n request: req as unknown as Request,\n response: {} as Response,\n });\n }\n\n if (!authResult) {\n const authMethod = token ? \"token\" : \"cookies\";\n return {\n success: false,\n error: {\n message: `Authentication failed: invalid or expired ${authMethod}`,\n code: 4001,\n authMethod,\n },\n };\n }\n\n console.log(`WebSocket authenticated for user: ${authResult.clientId}`);\n return { success: true, authResult };\n } catch (error) {\n console.error(\"WebSocket auth error:\", error);\n return {\n success: false,\n error: {\n message: \"Authentication error\",\n code: 4001,\n authMethod: \"unknown\",\n },\n };\n }\n};\n\n/**\n * Creates an Express WebSocket handler that delegates to core WebSocket handlers\n */\nexport const expressWebSocketHandler = (\n baseUrl: string,\n authMiddleware?: (ctx: ExpressContext) => Promise<AuthResult>,\n): Effect.Effect<ExpressWebSocketHandler, never, UploadEngine | FlowEngine> => {\n return Effect.gen(function* () {\n // Get the server instances from the Effect context\n const uploadEngine = yield* UploadEngine;\n const flowEngine = yield* FlowEngine;\n\n return (ws: WebSocket, req: IncomingMessage) => {\n // Extract request details (adapter's responsibility)\n const requestOrError = extractWebSocketRequest(req, baseUrl);\n\n console.log(\"🔍 WebSocket request details:\", requestOrError);\n\n // Handle invalid path\n if (\"type\" in requestOrError && requestOrError.type === \"invalid-path\") {\n ws.send(\n JSON.stringify({\n type: \"invalid-path\",\n message: `WebSocket path must start with ${requestOrError.expectedPrefix}`,\n expectedPrefix: requestOrError.expectedPrefix,\n }),\n );\n ws.close(1000, \"Invalid path\");\n return;\n }\n\n // Type narrowing: at this point, requestOrError is WebSocketConnectionRequest\n const request = requestOrError as WebSocketConnectionRequest;\n\n // Create framework-agnostic connection wrapper\n // Use a getter for readyState so it always reflects the current state\n const connection: WebSocketConnection = {\n id: `conn_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,\n send: (data: string) => {\n if (ws.readyState === ws.OPEN) {\n console.log(\n `📤 Sending WebSocket message to connection ${connection.id}:`,\n data.substring(0, 100),\n );\n ws.send(data);\n } else {\n console.warn(\n `⚠️ Cannot send message, WebSocket not open. State: ${ws.readyState}`,\n );\n }\n },\n close: (code?: number, reason?: string) => ws.close(code, reason),\n get readyState() {\n return ws.readyState;\n },\n };\n\n // Update request with connection\n request.connection = connection;\n\n // Handle WebSocket open\n (async () => {\n // Validate authentication if auth middleware is configured\n if (authMiddleware) {\n const authResult = await authenticateWebSocket(req, authMiddleware);\n\n if (!authResult.success) {\n ws.send(\n JSON.stringify({\n type: \"auth-failed\",\n message: authResult.error?.message,\n code: \"AUTH_FAILED\",\n authMethod: authResult.error?.authMethod,\n }),\n );\n ws.close(authResult.error?.code || 4001, authResult.error?.message);\n return;\n }\n\n // Cache auth context for this connection\n if (authResult.authResult) {\n wsAuthCache.set(connection.id, authResult.authResult);\n }\n }\n\n console.log(\n \"🔍 WebSocket open for eventId:\",\n request.eventId,\n \"with connection id:\",\n connection.id,\n );\n\n // Delegate to core handler for business logic\n const openEffect = handleWebSocketOpen(\n request,\n uploadEngine,\n flowEngine,\n );\n Effect.runFork(openEffect);\n })();\n\n // Handle WebSocket message\n ws.on(\"message\", (data: unknown) => {\n const messageEffect = handleWebSocketMessage(\n data as string,\n connection,\n );\n Effect.runFork(messageEffect);\n });\n\n // Handle WebSocket close\n ws.on(\"close\", () => {\n // Clear cached auth context for this connection\n if (request.connection?.id) {\n wsAuthCache.delete(request.connection.id);\n console.log(\n `Cleared auth cache for WebSocket connection: ${request.connection.id}`,\n );\n }\n\n // Delegate to core handler for cleanup\n const closeEffect = handleWebSocketClose(\n request,\n uploadEngine,\n flowEngine,\n );\n Effect.runFork(closeEffect);\n });\n\n // Handle WebSocket error\n ws.on(\"error\", (...args: unknown[]) => {\n const error = args[0] as Error;\n const errorEffect = handleWebSocketError(error, request.eventId);\n Effect.runFork(errorEffect);\n });\n };\n });\n};\n","import type { AuthResult, ServerAdapter } from \"@uploadista/server\";\nimport { Effect } from \"effect\";\nimport type { Request, Response } from \"express\";\nimport {\n extractExpressRequest,\n sendExpressResponse,\n} from \"./express-http-handler\";\nimport {\n type ExpressWebSocketHandler,\n expressWebSocketHandler,\n} from \"./express-websocket-handler\";\n\nexport type ExpressContext = {\n request: Request;\n response: Response;\n next?: (error?: Error) => void;\n};\n\n/**\n * Options for creating an Express server adapter.\n */\nexport interface ExpressAdapterOptions {\n /**\n * Optional authentication middleware function.\n * Called for each request to authenticate the user.\n *\n * @param ctx - Express context\n * @returns Promise resolving to AuthResult (AuthContext or null)\n */\n authMiddleware?: (ctx: ExpressContext) => Promise<AuthResult>;\n}\n\n// WebSocket interface from ws package\nexport interface WebSocket {\n readyState: number;\n OPEN: number;\n send: (data: string) => void;\n close: (code?: number, reason?: string) => void;\n on: (event: string, listener: (...args: unknown[]) => void) => void;\n off: (event: string, listener: (...args: unknown[]) => void) => void;\n}\n\n/**\n * Creates an Express server adapter that implements the ServerAdapter interface.\n *\n * This adapter translates between Express's Request/Response API and the core server's\n * standard request/response model. It supports:\n * - Request extraction from Express Request\n * - Response sending via Express Response\n * - Optional authentication middleware\n * - WebSocket handling\n *\n * @param options - Adapter configuration options\n * @returns ServerAdapter implementation for Express\n *\n * @example\n * ```typescript\n * import { expressAdapter } from \"@uploadista/adapters-express\";\n * import { createUploadistaServer } from \"@uploadista/server\";\n *\n * const adapter = expressAdapter({\n * authMiddleware: async (req, res) => {\n * const userId = req.header(\"x-user-id\");\n * return userId ? { clientId: userId } : null;\n * }\n * });\n *\n * const server = await createUploadistaServer({\n * flows: getFlows,\n * dataStore: { type: \"s3\", config: { bucket: \"uploads\" } },\n * kvStore: redisKvStore,\n * eventEmitter: webSocketEventEmitter(),\n * adapter\n * });\n * ```\n */\nexport const expressAdapter = (\n options: ExpressAdapterOptions = {},\n): ServerAdapter<ExpressContext, Response, ExpressWebSocketHandler> => {\n const { authMiddleware } = options;\n\n return {\n /**\n * Extract standard request details from Express Request.\n *\n * Converts Express's Request into a framework-agnostic UploadistaRequest\n * by parsing the URL, headers, and body.\n */\n extractRequest: extractExpressRequest,\n\n /**\n * Send standard response using Express Response format.\n *\n * Converts a UploadistaResponse into an object with status, headers, and body\n * that the Express handler can send.\n */\n sendResponse: sendExpressResponse,\n\n /**\n * WebSocket handler for Express with ws package.\n */\n webSocketHandler: ({ baseUrl }: { baseUrl: string }) =>\n expressWebSocketHandler(baseUrl, authMiddleware),\n\n /**\n * Run framework-specific auth middleware.\n *\n * If provided, executes the Express-specific authentication middleware\n * with access to the full Express Request and Response objects.\n */\n runAuthMiddleware: authMiddleware\n ? (ctx: ExpressContext) =>\n Effect.tryPromise(() => authMiddleware(ctx)).pipe(\n Effect.catchAll((error) => {\n console.error(\"Express auth middleware failed:\", error);\n // Return null to indicate auth failure (not an error in the Effect sense)\n return Effect.succeed(null);\n }),\n )\n : undefined,\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uploadista/adapters-express",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.20-beta.
|
|
4
|
+
"version": "0.0.20-beta.8",
|
|
5
5
|
"description": "Express adapter for Uploadista",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Uploadista",
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"ws": "8.18.3",
|
|
18
|
-
"@uploadista/
|
|
19
|
-
"@uploadista/
|
|
20
|
-
"@uploadista/
|
|
21
|
-
"@uploadista/event-broadcaster-memory": "0.0.20-beta.
|
|
22
|
-
"@uploadista/
|
|
18
|
+
"@uploadista/core": "0.0.20-beta.8",
|
|
19
|
+
"@uploadista/observability": "0.0.20-beta.8",
|
|
20
|
+
"@uploadista/server": "0.0.20-beta.8",
|
|
21
|
+
"@uploadista/event-broadcaster-memory": "0.0.20-beta.8",
|
|
22
|
+
"@uploadista/event-emitter-websocket": "0.0.20-beta.8"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"effect": "^3.0.0",
|
|
@@ -29,17 +29,17 @@
|
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@effect/vitest": "0.27.0",
|
|
31
31
|
"@types/express": "^5.0.0",
|
|
32
|
-
"@types/node": "24.10.
|
|
32
|
+
"@types/node": "24.10.4",
|
|
33
33
|
"@types/ws": "8.18.1",
|
|
34
|
-
"effect": "3.19.
|
|
35
|
-
"tsdown": "0.
|
|
34
|
+
"effect": "3.19.12",
|
|
35
|
+
"tsdown": "0.18.0",
|
|
36
36
|
"typescript": "5.9.3",
|
|
37
37
|
"vitest": "4.0.15",
|
|
38
|
-
"zod": "4.
|
|
39
|
-
"@uploadista/typescript-config": "0.0.20-beta.
|
|
38
|
+
"zod": "4.2.0",
|
|
39
|
+
"@uploadista/typescript-config": "0.0.20-beta.8"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "tsdown",
|
|
42
|
+
"build": "tsc --noEmit && tsdown",
|
|
43
43
|
"check": "biome check --write ./src",
|
|
44
44
|
"clean": "rimraf -rf dist && rimraf -rf .turbo && rimraf tsconfig.tsbuildinfo",
|
|
45
45
|
"format": "biome format --write ./src",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage } from "node:http";
|
|
2
|
-
import {
|
|
2
|
+
import { FlowEngine, UploadEngine } from "@uploadista/core";
|
|
3
3
|
import type { AuthResult } from "@uploadista/server";
|
|
4
4
|
import {
|
|
5
5
|
handleWebSocketClose,
|
|
@@ -170,11 +170,11 @@ const authenticateWebSocket = async (
|
|
|
170
170
|
export const expressWebSocketHandler = (
|
|
171
171
|
baseUrl: string,
|
|
172
172
|
authMiddleware?: (ctx: ExpressContext) => Promise<AuthResult>,
|
|
173
|
-
): Effect.Effect<ExpressWebSocketHandler, never,
|
|
173
|
+
): Effect.Effect<ExpressWebSocketHandler, never, UploadEngine | FlowEngine> => {
|
|
174
174
|
return Effect.gen(function* () {
|
|
175
175
|
// Get the server instances from the Effect context
|
|
176
|
-
const
|
|
177
|
-
const
|
|
176
|
+
const uploadEngine = yield* UploadEngine;
|
|
177
|
+
const flowEngine = yield* FlowEngine;
|
|
178
178
|
|
|
179
179
|
return (ws: WebSocket, req: IncomingMessage) => {
|
|
180
180
|
// Extract request details (adapter's responsibility)
|
|
@@ -259,8 +259,8 @@ export const expressWebSocketHandler = (
|
|
|
259
259
|
// Delegate to core handler for business logic
|
|
260
260
|
const openEffect = handleWebSocketOpen(
|
|
261
261
|
request,
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
uploadEngine,
|
|
263
|
+
flowEngine,
|
|
264
264
|
);
|
|
265
265
|
Effect.runFork(openEffect);
|
|
266
266
|
})();
|
|
@@ -287,8 +287,8 @@ export const expressWebSocketHandler = (
|
|
|
287
287
|
// Delegate to core handler for cleanup
|
|
288
288
|
const closeEffect = handleWebSocketClose(
|
|
289
289
|
request,
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
uploadEngine,
|
|
291
|
+
flowEngine,
|
|
292
292
|
);
|
|
293
293
|
Effect.runFork(closeEffect);
|
|
294
294
|
});
|