@uploadista/adapters-express 0.0.8 → 0.0.10

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/README.md CHANGED
@@ -1,455 +1,388 @@
1
1
  # @uploadista/adapters-express
2
2
 
3
- Uploadista adapter for Express - Run upload servers on Node.js with Express.
3
+ Uploadista adapter for Express - The most popular Node.js web framework.
4
4
 
5
- Provides a complete file upload and flow processing server for Express with manual WebSocket setup, authentication middleware, and support for standard Node.js hosting (Heroku, Railway, VPS, etc.).
5
+ Provides a lightweight adapter for integrating Uploadista's file upload and flow processing capabilities with Express applications. Built on the unified adapter pattern for consistent behavior across all frameworks.
6
6
 
7
7
  ## Features
8
8
 
9
- - **Node.js Compatible** - Run on any Node.js 18+ environment
10
- - **Express Middleware** - Integrates with Express request/response patterns
11
- - **WebSocket Support** - Use `ws` package for real-time progress
12
- - **Authentication** - Flexible middleware for JWT or custom auth
13
- - **Multi-Cloud Storage** - S3, Azure, GCS, or filesystem
14
- - **Redis Support** - Distributed deployments with Redis KV store
9
+ - **Express 4 & 5** - Compatible with both major versions
10
+ - **WebSocket Support** - Real-time progress via `ws` package
11
+ - **Authentication** - Flexible middleware for JWT, sessions, or custom auth
12
+ - **Multi-Cloud Storage** - S3, Azure, GCS, or filesystem backends
13
+ - **Event Broadcasting** - Real-time updates via memory or Redis
15
14
  - **TypeScript** - Full type safety with comprehensive JSDoc
15
+ - **Lightweight** - Minimal adapter code delegates to core server
16
16
 
17
17
  ## Installation
18
18
 
19
19
  ```bash
20
- npm install @uploadista/adapters-express express ws
20
+ npm install @uploadista/adapters-express @uploadista/server express ws
21
21
  # or
22
- pnpm add @uploadista/adapters-express express ws
22
+ pnpm add @uploadista/adapters-express @uploadista/server express ws
23
23
  ```
24
24
 
25
25
  ## Requirements
26
26
 
27
+ - Express 4.x or 5.x
27
28
  - Node.js 18+
28
- - Express 4.0 or 5.0+
29
+ - `ws` package for WebSocket support
29
30
  - TypeScript 5.0+ (optional but recommended)
30
31
 
31
32
  ## Quick Start
32
33
 
33
- ### 1. Basic Express Server
34
+ ### Basic Server
34
35
 
35
36
  ```typescript
37
+ import { createServer } from "node:http";
38
+ import { expressAdapter } from "@uploadista/adapters-express";
39
+ import { fileStore } from "@uploadista/data-store-filesystem";
40
+ import { fileKvStore } from "@uploadista/kv-store-filesystem";
41
+ import { createUploadistaServer } from "@uploadista/server";
36
42
  import express from "express";
37
- import { createExpressUploadistaAdapter } from "@uploadista/adapters-express";
38
- import { redisKvStore } from "@uploadista/kv-store-redis";
39
- import { s3DataStore } from "@uploadista/data-store-s3";
40
- import { webSocketEventEmitter } from "@uploadista/event-emitter-websocket";
41
- import { memoryEventBroadcaster } from "@uploadista/event-broadcaster-memory";
43
+ import { WebSocketServer } from "ws";
44
+ import { flows } from "./flows";
42
45
 
43
46
  const app = express();
47
+ const server = createServer(app);
44
48
 
45
- // Create adapter
46
- const adapter = await createExpressUploadistaAdapter({
47
- baseUrl: "uploadista",
48
- dataStore: s3DataStore,
49
- kvStore: redisKvStore,
50
- eventEmitter: webSocketEventEmitter,
51
- eventBroadcaster: memoryEventBroadcaster,
52
- flows: (flowId, clientId) => createFlowsEffect(flowId, clientId),
49
+ // Middleware
50
+ app.use(express.json());
51
+
52
+ // Create KV store and data store
53
+ const kvStore = fileKvStore({ directory: "./uploads" });
54
+ const dataStore = fileStore({
55
+ directory: "./uploads",
56
+ deliveryUrl: "http://localhost:3000/uploads",
53
57
  });
54
58
 
55
- // Mount HTTP handler
56
- app.use(`/${adapter.baseUrl}`, (req, res) => {
57
- adapter.handler(req, res);
59
+ // Create uploadista server with Express adapter
60
+ const uploadistaServer = await createUploadistaServer({
61
+ dataStore,
62
+ flows,
63
+ kvStore,
64
+ adapter: expressAdapter({}), // <-- New adapter pattern
58
65
  });
59
66
 
60
- // WebSocket server
61
- import http from "http";
62
- import WebSocket from "ws";
67
+ // Mount HTTP endpoints (Express 5)
68
+ app.all("/uploadista/api/*splat", (request, response, next) => {
69
+ uploadistaServer.handler({ request, response, next });
70
+ });
63
71
 
64
- const server = http.createServer(app);
65
- const wss = new WebSocket.Server({ server });
72
+ // For Express 4, use:
73
+ // app.all("/uploadista/api/*", (request, response, next) => {
74
+ // uploadistaServer.handler({ request, response, next });
75
+ // });
66
76
 
67
- wss.on("connection", (ws, req) => {
68
- adapter.websocketConnectionHandler(ws, req);
69
- });
77
+ // WebSocket server setup
78
+ const wss = new WebSocketServer({ server });
79
+ wss.on("connection", uploadistaServer.websocketHandler);
70
80
 
81
+ // Start server
71
82
  server.listen(3000, () => {
72
- console.log("Server running on http://localhost:3000");
83
+ console.log("Server running on port 3000");
73
84
  });
74
85
  ```
75
86
 
76
- ### 2. Add Authentication
87
+ ### With Authentication
77
88
 
78
89
  ```typescript
79
- const adapter = await createExpressUploadistaAdapter({
80
- baseUrl: "uploadista",
81
- dataStore: s3DataStore,
82
- kvStore: redisKvStore,
83
- authMiddleware: async (req, res) => {
84
- const token = req.headers.authorization?.split(" ")[1];
85
- if (!token) return null;
86
-
87
- try {
88
- const payload = await verifyToken(token);
89
- return {
90
- clientId: payload.sub,
91
- permissions: payload.permissions,
92
- };
93
- } catch {
94
- res.status(401).json({ error: "Unauthorized" });
95
- return null;
96
- }
90
+ import { expressAdapter } from "@uploadista/adapters-express";
91
+ import { createUploadistaServer } from "@uploadista/server";
92
+
93
+ const uploadistaServer = await createUploadistaServer({
94
+ dataStore,
95
+ flows,
96
+ kvStore,
97
+ adapter: expressAdapter({
98
+ // Optional auth middleware
99
+ authMiddleware: async ({ request, response }) => {
100
+ const token = request.headers.authorization?.split(" ")[1];
101
+ if (!token) return null;
102
+
103
+ try {
104
+ // Verify JWT or session
105
+ const payload = await verifyToken(token);
106
+ return {
107
+ clientId: payload.sub,
108
+ permissions: payload.permissions,
109
+ metadata: { tier: payload.tier },
110
+ };
111
+ } catch {
112
+ return null; // Null = authentication failed
113
+ }
114
+ },
115
+ }),
116
+ // Optional auth caching
117
+ authCacheConfig: {
118
+ maxSize: 5000,
119
+ ttl: 3600000, // 1 hour
97
120
  },
98
- authCacheConfig: { maxSize: 5000, ttl: 3600000 },
99
121
  });
100
122
  ```
101
123
 
102
- ### 3. Express Middleware Setup
124
+ ### With Session-Based Auth
103
125
 
104
126
  ```typescript
105
- import express, { Request, Response, NextFunction } from "express";
106
-
107
- const app = express();
108
-
109
- // Body parser for JSON
110
- app.use(express.json({ limit: "50mb" }));
111
-
112
- // CORS
113
- app.use(cors({
114
- origin: process.env.ALLOWED_ORIGINS?.split(","),
115
- allowedHeaders: ["Content-Type", "Authorization"],
116
- }));
127
+ import session from "express-session";
128
+
129
+ // Configure session middleware
130
+ app.use(
131
+ session({
132
+ secret: process.env.SESSION_SECRET!,
133
+ resave: false,
134
+ saveUninitialized: false,
135
+ })
136
+ );
137
+
138
+ const uploadistaServer = await createUploadistaServer({
139
+ dataStore,
140
+ flows,
141
+ kvStore,
142
+ adapter: expressAdapter({
143
+ authMiddleware: async ({ request }) => {
144
+ // Access session from Express request
145
+ if (!request.session?.userId) {
146
+ return null;
147
+ }
117
148
 
118
- // Logging
119
- app.use((req, res, next) => {
120
- console.log(`${req.method} ${req.path}`);
121
- next();
149
+ return {
150
+ clientId: request.session.userId,
151
+ metadata: { sessionId: request.sessionID },
152
+ };
153
+ },
154
+ }),
122
155
  });
156
+ ```
123
157
 
124
- // Mount adapter
125
- app.use(`/${adapter.baseUrl}`, (req: Request, res: Response) => {
126
- adapter.handler(req, res);
127
- });
158
+ ### With Redis Event Broadcasting
128
159
 
129
- // Error handler
130
- app.use((err: any, req: Request, res: Response, next: NextFunction) => {
131
- console.error(err);
132
- res.status(500).json({ error: "Internal server error" });
160
+ ```typescript
161
+ import { createClient } from "redis";
162
+ import { redisEventBroadcaster } from "@uploadista/event-broadcaster-redis";
163
+
164
+ const redisClient = createClient({ url: process.env.REDIS_URL });
165
+ const redisSubscriber = createClient({ url: process.env.REDIS_URL });
166
+
167
+ await redisClient.connect();
168
+ await redisSubscriber.connect();
169
+
170
+ const uploadistaServer = await createUploadistaServer({
171
+ dataStore,
172
+ flows,
173
+ kvStore,
174
+ adapter: expressAdapter({}),
175
+ eventBroadcaster: redisEventBroadcaster({
176
+ redis: redisClient,
177
+ subscriberRedis: redisSubscriber,
178
+ }),
133
179
  });
134
180
  ```
135
181
 
136
182
  ## Configuration
137
183
 
138
- ### `ExpressUploadistaAdapterOptions`
184
+ ### `expressAdapter(options?)`
139
185
 
140
- ```typescript
141
- type ExpressUploadistaAdapterOptions = {
142
- // Required
143
- flows: (flowId: string, clientId: string | null) =>
144
- Effect.Effect<unknown, unknown, unknown>;
145
- dataStore: Layer.Layer<UploadFileDataStores, never, UploadFileKVStore>;
146
- kvStore: Layer.Layer<BaseKvStoreService>;
147
-
148
- // Optional
149
- baseUrl?: string; // Default: "uploadista"
150
- eventEmitter?: Layer.Layer<BaseEventEmitterService>;
151
- eventBroadcaster?: Layer.Layer<any>;
152
- generateId?: Layer.Layer<GenerateId>;
153
- authMiddleware?: (
154
- req: IncomingMessage,
155
- res: ServerResponse,
156
- ) => Promise<AuthResult>;
157
- authCacheConfig?: AuthCacheConfig;
158
- bufferedDataStore?: Layer.Layer<UploadFileDataStore>;
159
- };
160
- ```
186
+ Creates an Express adapter instance.
161
187
 
162
- ## API Routes
188
+ **Options:**
189
+ - `authMiddleware?: (ctx: ExpressContext) => Promise<AuthResult>` - Optional authentication middleware
163
190
 
164
- Routes are available at `/{baseUrl}/api/`:
191
+ **Returns:** `ServerAdapter<ExpressContext, Response, ExpressWebSocketHandler>`
165
192
 
166
- ```
167
- POST /uploadista/api/upload
168
- Create new upload
193
+ ### ExpressContext
169
194
 
170
- GET /uploadista/api/upload/:uploadId
171
- Get upload status
195
+ The context object passed to auth middleware:
172
196
 
173
- PATCH /uploadista/api/upload/:uploadId
174
- Upload chunk
197
+ ```typescript
198
+ interface ExpressContext {
199
+ request: Request;
200
+ response: Response;
201
+ next?: (error?: Error) => void;
202
+ }
203
+ ```
204
+
205
+ ### Authentication Middleware
175
206
 
176
- POST /uploadista/api/flow/:flowId/:storageId
177
- Execute flow
207
+ The `authMiddleware` function receives `ExpressContext` and should return:
208
+ - `AuthContext` object on success with `clientId` and optional `permissions`, `metadata`
209
+ - `null` on authentication failure (returns 401 to client)
210
+ - Throws error on system failure (returns 500 to client)
178
211
 
179
- GET /uploadista/api/jobs/:jobId/status
180
- Get job status
212
+ ```typescript
213
+ type AuthResult = AuthContext | null;
181
214
 
182
- PATCH /uploadista/api/jobs/:jobId/continue/:nodeId
183
- Continue flow
215
+ interface AuthContext {
216
+ clientId: string;
217
+ permissions?: string[];
218
+ metadata?: Record<string, unknown>;
219
+ }
184
220
  ```
185
221
 
186
- ## WebSocket Integration
222
+ The middleware has a 5-second timeout to prevent hanging requests.
223
+
224
+ ## WebSocket Support
187
225
 
188
- ### Using `ws` Package
226
+ ### Basic Setup
189
227
 
190
228
  ```typescript
191
- import WebSocket from "ws";
192
- import http from "http";
229
+ import { createServer } from "node:http";
230
+ import { WebSocketServer } from "ws";
193
231
 
194
- const server = http.createServer(app);
195
- const wss = new WebSocket.Server({ server });
232
+ const app = express();
233
+ const server = createServer(app);
196
234
 
197
- // Handle WebSocket connections
198
- wss.on("connection", (ws, req) => {
199
- // Pass to adapter
200
- adapter.websocketConnectionHandler(ws, req);
235
+ const uploadistaServer = await createUploadistaServer({
236
+ dataStore,
237
+ flows,
238
+ kvStore,
239
+ adapter: expressAdapter({}),
240
+ });
201
241
 
202
- ws.on("message", (data) => {
203
- // Adapter handles messages internally
204
- });
242
+ // Create WebSocket server
243
+ const wss = new WebSocketServer({ server });
205
244
 
206
- ws.on("close", () => {
207
- console.log("Client disconnected");
208
- });
209
- });
245
+ // Connect uploadista handler
246
+ wss.on("connection", uploadistaServer.websocketHandler);
210
247
 
211
248
  server.listen(3000);
212
249
  ```
213
250
 
214
- ### Using Socket.io (Alternative)
251
+ ### Path-Based Routing
215
252
 
216
- ```typescript
217
- import { Server } from "socket.io";
253
+ The WebSocket handler automatically routes based on path:
254
+ - `/uploadista/ws/upload/:uploadId` - Upload progress events
255
+ - `/uploadista/ws/flow/:jobId` - Flow execution events
218
256
 
219
- const io = new Server(server, {
220
- cors: { origin: "*" },
221
- });
257
+ ```typescript
258
+ // Client connects to:
259
+ const ws = new WebSocket("ws://localhost:3000/uploadista/ws/upload/abc123");
260
+ // or
261
+ const ws = new WebSocket("ws://localhost:3000/uploadista/ws/flow/job456");
262
+ ```
222
263
 
223
- io.on("connection", (socket) => {
224
- socket.on("subscribe", (channels) => {
225
- channels.forEach((ch) => socket.join(ch));
226
- });
264
+ ### Authentication
227
265
 
228
- socket.on("disconnect", () => {
229
- console.log("Client disconnected");
230
- });
231
- });
266
+ WebSocket connections support both token and cookie-based authentication:
232
267
 
233
- // Emit events from adapter
234
- const eventEmitter = /* ... */;
235
- eventEmitter.on("upload:progress", (data) => {
236
- io.emit(`upload:${data.uploadId}`, data);
237
- });
268
+ **Token-based:**
269
+ ```typescript
270
+ // Client sends token in query param
271
+ const ws = new WebSocket(
272
+ "ws://localhost:3000/uploadista/ws/upload/abc123?token=YOUR_JWT"
273
+ );
238
274
  ```
239
275
 
240
- ## Complete Server Example
241
-
276
+ **Cookie-based:**
242
277
  ```typescript
243
- import express, { Express, Request, Response } from "express";
244
- import http from "http";
245
- import WebSocket from "ws";
246
- import cors from "cors";
247
- import { createExpressUploadistaAdapter } from "@uploadista/adapters-express";
248
- import { redisKvStore } from "@uploadista/kv-store-redis";
249
- import { s3DataStore } from "@uploadista/data-store-s3";
250
- import { webSocketEventEmitter } from "@uploadista/event-emitter-websocket";
251
- import { memoryEventBroadcaster } from "@uploadista/event-broadcaster-memory";
252
- import { verify } from "jsonwebtoken";
253
-
254
- const app: Express = express();
278
+ // Cookies are automatically sent with WebSocket upgrade request
279
+ // Your auth middleware can read them from request.headers.cookie
280
+ const ws = new WebSocket("ws://localhost:3000/uploadista/ws/upload/abc123");
281
+ ```
255
282
 
256
- // Middleware
257
- app.use(express.json({ limit: "50mb" }));
258
- app.use(cors());
283
+ ## Express Version Compatibility
259
284
 
260
- // Health check
261
- app.get("/health", (req: Request, res: Response) => {
262
- res.json({ status: "ok" });
263
- });
285
+ ### Express 4
264
286
 
265
- // Create adapter
266
- const adapter = await createExpressUploadistaAdapter({
267
- baseUrl: "uploadista",
268
- dataStore: s3DataStore,
269
- kvStore: redisKvStore,
270
- eventEmitter: webSocketEventEmitter,
271
- eventBroadcaster: memoryEventBroadcaster,
272
- flows: createFlowsEffect,
273
- authMiddleware: async (req, res) => {
274
- const token = req.headers.authorization?.split(" ")[1];
275
- if (!token) return null;
276
-
277
- try {
278
- const payload = verify(token, process.env.JWT_SECRET!);
279
- return {
280
- clientId: (payload as any).sub,
281
- permissions: (payload as any).permissions || [],
282
- };
283
- } catch {
284
- res.statusCode = 401;
285
- res.end(JSON.stringify({ error: "Unauthorized" }));
286
- return null;
287
- }
288
- },
289
- authCacheConfig: { maxSize: 5000, ttl: 3600000 },
290
- });
291
-
292
- // Mount adapter
293
- app.use(`/${adapter.baseUrl}`, (req: Request, res: Response) => {
294
- adapter.handler(req, res);
295
- });
287
+ Use wildcard routes without named parameters:
296
288
 
297
- // Error handler
298
- app.use((err: any, req: Request, res: Response) => {
299
- console.error(err);
300
- res.status(500).json({ error: "Internal server error" });
289
+ ```typescript
290
+ app.all("/uploadista/api/*", (request, response, next) => {
291
+ uploadistaServer.handler({ request, response, next });
301
292
  });
293
+ ```
302
294
 
303
- // HTTP + WebSocket server
304
- const server = http.createServer(app);
305
- const wss = new WebSocket.Server({ server });
295
+ ### Express 5
306
296
 
307
- wss.on("connection", (ws, req) => {
308
- adapter.websocketConnectionHandler(ws, req);
309
- });
297
+ Use named wildcards (`*splat`):
310
298
 
311
- const PORT = process.env.PORT || 3000;
312
- server.listen(PORT, () => {
313
- console.log(`Server running on http://localhost:${PORT}`);
299
+ ```typescript
300
+ app.all("/uploadista/api/*splat", (request, response, next) => {
301
+ uploadistaServer.handler({ request, response, next });
314
302
  });
315
-
316
- export default server;
317
303
  ```
318
304
 
319
- ## Environment Configuration
305
+ ## Example Project
320
306
 
321
- ### .env File
307
+ See the complete [Express server example](https://github.com/uploadista/uploadista-sdk/tree/main/examples/express-server) for:
308
+ - Full server setup
309
+ - Authentication middleware
310
+ - WebSocket integration
311
+ - Error handling
312
+ - Graceful shutdown
322
313
 
323
- ```env
324
- NODE_ENV=production
325
- PORT=3000
314
+ ## API Reference
326
315
 
327
- # AWS S3
328
- AWS_ACCESS_KEY_ID=your-key
329
- AWS_SECRET_ACCESS_KEY=your-secret
330
- AWS_REGION=us-east-1
331
- S3_BUCKET=uploads-prod
316
+ ### Core Server Integration
332
317
 
333
- # Redis (for KV store and events)
334
- REDIS_URL=redis://localhost:6379
318
+ The Express adapter integrates with `@uploadista/server`:
335
319
 
336
- # JWT Authentication
337
- JWT_SECRET=your-jwt-secret
320
+ ```typescript
321
+ import { createUploadistaServer } from "@uploadista/server";
322
+ import { expressAdapter } from "@uploadista/adapters-express";
338
323
 
339
- # CORS
340
- ALLOWED_ORIGINS=https://app.example.com,https://dashboard.example.com
324
+ const server = await createUploadistaServer({
325
+ adapter: expressAdapter(/* options */),
326
+ // ... other config
327
+ });
341
328
  ```
342
329
 
343
- ## Docker Deployment
344
-
345
- ### Dockerfile
346
-
347
- ```dockerfile
348
- FROM node:20-alpine
349
- WORKDIR /app
350
-
351
- COPY package*.json ./
352
- RUN npm ci --only=production
330
+ See [`@uploadista/server` documentation](../server) for full configuration options.
353
331
 
354
- COPY dist ./dist
355
-
356
- ENV NODE_ENV=production
357
- EXPOSE 3000
358
-
359
- CMD ["node", "dist/server.js"]
360
- ```
361
-
362
- ### docker-compose.yml
363
-
364
- ```yaml
365
- version: "3.8"
366
- services:
367
- app:
368
- build: .
369
- ports:
370
- - "3000:3000"
371
- environment:
372
- REDIS_URL: redis://redis:6379
373
- JWT_SECRET: ${JWT_SECRET}
374
- AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
375
- AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
376
- depends_on:
377
- - redis
378
-
379
- redis:
380
- image: redis:7-alpine
381
- ports:
382
- - "6379:6379"
383
- ```
332
+ ## Migration from v1
384
333
 
385
- ## Request Examples
334
+ If you're migrating from the legacy `createExpressUploadistaAdapter` API:
386
335
 
387
- ### Create Upload
336
+ **Before (v1):**
337
+ ```typescript
338
+ const adapter = await createExpressUploadistaAdapter({
339
+ baseUrl: "uploadista",
340
+ dataStore,
341
+ kvStore,
342
+ flows,
343
+ authMiddleware,
344
+ });
388
345
 
389
- ```bash
390
- curl -X POST http://localhost:3000/uploadista/api/upload \
391
- -H "Content-Type: application/json" \
392
- -H "Authorization: Bearer TOKEN" \
393
- -d '{
394
- "filename": "document.pdf",
395
- "size": 5242880,
396
- "metadata": {"type": "document"}
397
- }'
346
+ app.all(`/${adapter.baseUrl}/*`, adapter.handler);
398
347
  ```
399
348
 
400
- ### Upload Chunk
349
+ **After (v2 - current):**
350
+ ```typescript
351
+ const server = await createUploadistaServer({
352
+ dataStore,
353
+ kvStore,
354
+ flows,
355
+ adapter: expressAdapter({ authMiddleware }),
356
+ });
401
357
 
402
- ```bash
403
- curl -X PATCH http://localhost:3000/uploadista/api/upload/upload-123 \
404
- -H "Content-Range: bytes 0-1048575/5242880" \
405
- -H "Authorization: Bearer TOKEN" \
406
- --data-binary @chunk.bin
358
+ app.all("/uploadista/api/*splat", (request, response, next) => {
359
+ server.handler({ request, response, next });
360
+ });
407
361
  ```
408
362
 
409
- ## Error Codes
410
-
411
- - `400 VALIDATION_ERROR` - Invalid request
412
- - `404 NOT_FOUND` - Upload/flow not found
413
- - `409 CONFLICT` - Invalid chunk offset
414
- - `413 PAYLOAD_TOO_LARGE` - File too large
415
- - `500 INTERNAL_ERROR` - Server error
416
-
417
- ## Performance Tips
363
+ ### Key Changes
364
+ - Configuration moved to `createUploadistaServer()`
365
+ - Adapter only handles Express-specific translation
366
+ - `baseUrl` now configured in `createUploadistaServer()` (defaults to "uploadista")
367
+ - Handler now expects `{ request, response, next }` object
368
+ - WebSocket handler accessed via `server.websocketHandler`
418
369
 
419
- 1. Use clustering for multiple CPU cores
420
- 2. Enable Redis for distributed deployments
421
- 3. Configure appropriate chunk sizes
422
- 4. Use reverse proxy (nginx) for load balancing
423
- 5. Monitor memory and disk usage
370
+ ## TypeScript Support
424
371
 
425
- ## Deployment Options
372
+ The adapter is fully typed:
426
373
 
427
- - **VPS**: Deploy with PM2 or systemd
428
- - **Heroku**: Use Procfile and Redis add-on
429
- - **Railway**: Direct GitHub integration
430
- - **Docker**: Container deployment
431
- - **AWS**: ECS, Lambda (with custom runtime)
432
- - **DigitalOcean**: App Platform or VPS
433
-
434
- ## Related Packages
435
-
436
- - **[@uploadista/server](../server/)** - Core server utilities
437
- - **[@uploadista/adapters-hono](../adapters-hono/)** - Hono adapter
438
- - **[@uploadista/adapters-fastify](../adapters-fastify/)** - Fastify adapter
439
- - **[@uploadista/core](../../core/)** - Core engine
440
- - **[@uploadista/kv-store-redis](../../kv-stores/redis/)** - Redis KV store
441
- - **[@uploadista/data-store-s3](../../data-stores/s3/)** - AWS S3 storage
442
-
443
- ## Troubleshooting
444
-
445
- ### WebSocket Connection Refused
446
- Ensure `ws` server is created and `websocketConnectionHandler` is called on connection.
447
-
448
- ### Memory Leaks
449
- Check WebSocket connections are properly closed. Use `nodejs --inspect` for profiling.
450
-
451
- ### Slow Uploads
452
- Use Redis for distributed deployments. Increase chunk size. Monitor network throughput.
374
+ ```typescript
375
+ import type { Request, Response, NextFunction } from "express";
376
+
377
+ const adapter = expressAdapter({
378
+ authMiddleware: async ({ request, response, next }) => {
379
+ // Full Express types available
380
+ request.session; // typed
381
+ response.locals; // typed
382
+ return { clientId: "user-123" };
383
+ },
384
+ });
385
+ ```
453
386
 
454
387
  ## License
455
388