@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/CHANGELOG.md +68 -0
- package/README.md +269 -336
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +58 -121
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +75 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +16 -13
- package/src/express-adapter.ts +122 -0
- package/src/express-http-handler.ts +252 -0
- package/src/express-websocket-handler.ts +299 -0
- package/src/index.ts +1 -14
- package/dist/index.d.ts +0 -136
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/src/error-types.ts +0 -103
- package/src/flow-http-handlers.ts +0 -267
- package/src/upload-http-handlers.ts +0 -186
- package/src/uploadista-adapter-layer.ts +0 -32
- package/src/uploadista-adapter.ts +0 -642
- package/src/uploadista-websocket-handler.ts +0 -209
package/README.md
CHANGED
|
@@ -1,455 +1,388 @@
|
|
|
1
1
|
# @uploadista/adapters-express
|
|
2
2
|
|
|
3
|
-
Uploadista adapter for Express -
|
|
3
|
+
Uploadista adapter for Express - The most popular Node.js web framework.
|
|
4
4
|
|
|
5
|
-
Provides a
|
|
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
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
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
|
-
-
|
|
29
|
+
- `ws` package for WebSocket support
|
|
29
30
|
- TypeScript 5.0+ (optional but recommended)
|
|
30
31
|
|
|
31
32
|
## Quick Start
|
|
32
33
|
|
|
33
|
-
###
|
|
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 {
|
|
38
|
-
import {
|
|
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
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
|
|
72
|
+
// For Express 4, use:
|
|
73
|
+
// app.all("/uploadista/api/*", (request, response, next) => {
|
|
74
|
+
// uploadistaServer.handler({ request, response, next });
|
|
75
|
+
// });
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
83
|
+
console.log("Server running on port 3000");
|
|
73
84
|
});
|
|
74
85
|
```
|
|
75
86
|
|
|
76
|
-
###
|
|
87
|
+
### With Authentication
|
|
77
88
|
|
|
78
89
|
```typescript
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
###
|
|
124
|
+
### With Session-Based Auth
|
|
103
125
|
|
|
104
126
|
```typescript
|
|
105
|
-
import
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
149
|
+
return {
|
|
150
|
+
clientId: request.session.userId,
|
|
151
|
+
metadata: { sessionId: request.sessionID },
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
122
155
|
});
|
|
156
|
+
```
|
|
123
157
|
|
|
124
|
-
|
|
125
|
-
app.use(`/${adapter.baseUrl}`, (req: Request, res: Response) => {
|
|
126
|
-
adapter.handler(req, res);
|
|
127
|
-
});
|
|
158
|
+
### With Redis Event Broadcasting
|
|
128
159
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
### `
|
|
184
|
+
### `expressAdapter(options?)`
|
|
139
185
|
|
|
140
|
-
|
|
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
|
-
|
|
188
|
+
**Options:**
|
|
189
|
+
- `authMiddleware?: (ctx: ExpressContext) => Promise<AuthResult>` - Optional authentication middleware
|
|
163
190
|
|
|
164
|
-
|
|
191
|
+
**Returns:** `ServerAdapter<ExpressContext, Response, ExpressWebSocketHandler>`
|
|
165
192
|
|
|
166
|
-
|
|
167
|
-
POST /uploadista/api/upload
|
|
168
|
-
Create new upload
|
|
193
|
+
### ExpressContext
|
|
169
194
|
|
|
170
|
-
|
|
171
|
-
Get upload status
|
|
195
|
+
The context object passed to auth middleware:
|
|
172
196
|
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
|
|
180
|
-
|
|
212
|
+
```typescript
|
|
213
|
+
type AuthResult = AuthContext | null;
|
|
181
214
|
|
|
182
|
-
|
|
183
|
-
|
|
215
|
+
interface AuthContext {
|
|
216
|
+
clientId: string;
|
|
217
|
+
permissions?: string[];
|
|
218
|
+
metadata?: Record<string, unknown>;
|
|
219
|
+
}
|
|
184
220
|
```
|
|
185
221
|
|
|
186
|
-
|
|
222
|
+
The middleware has a 5-second timeout to prevent hanging requests.
|
|
223
|
+
|
|
224
|
+
## WebSocket Support
|
|
187
225
|
|
|
188
|
-
###
|
|
226
|
+
### Basic Setup
|
|
189
227
|
|
|
190
228
|
```typescript
|
|
191
|
-
import
|
|
192
|
-
import
|
|
229
|
+
import { createServer } from "node:http";
|
|
230
|
+
import { WebSocketServer } from "ws";
|
|
193
231
|
|
|
194
|
-
const
|
|
195
|
-
const
|
|
232
|
+
const app = express();
|
|
233
|
+
const server = createServer(app);
|
|
196
234
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
235
|
+
const uploadistaServer = await createUploadistaServer({
|
|
236
|
+
dataStore,
|
|
237
|
+
flows,
|
|
238
|
+
kvStore,
|
|
239
|
+
adapter: expressAdapter({}),
|
|
240
|
+
});
|
|
201
241
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
});
|
|
242
|
+
// Create WebSocket server
|
|
243
|
+
const wss = new WebSocketServer({ server });
|
|
205
244
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
});
|
|
209
|
-
});
|
|
245
|
+
// Connect uploadista handler
|
|
246
|
+
wss.on("connection", uploadistaServer.websocketHandler);
|
|
210
247
|
|
|
211
248
|
server.listen(3000);
|
|
212
249
|
```
|
|
213
250
|
|
|
214
|
-
###
|
|
251
|
+
### Path-Based Routing
|
|
215
252
|
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
socket.on("subscribe", (channels) => {
|
|
225
|
-
channels.forEach((ch) => socket.join(ch));
|
|
226
|
-
});
|
|
264
|
+
### Authentication
|
|
227
265
|
|
|
228
|
-
|
|
229
|
-
console.log("Client disconnected");
|
|
230
|
-
});
|
|
231
|
-
});
|
|
266
|
+
WebSocket connections support both token and cookie-based authentication:
|
|
232
267
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
241
|
-
|
|
276
|
+
**Cookie-based:**
|
|
242
277
|
```typescript
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
257
|
-
app.use(express.json({ limit: "50mb" }));
|
|
258
|
-
app.use(cors());
|
|
283
|
+
## Express Version Compatibility
|
|
259
284
|
|
|
260
|
-
|
|
261
|
-
app.get("/health", (req: Request, res: Response) => {
|
|
262
|
-
res.json({ status: "ok" });
|
|
263
|
-
});
|
|
285
|
+
### Express 4
|
|
264
286
|
|
|
265
|
-
|
|
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
|
-
|
|
298
|
-
app.
|
|
299
|
-
|
|
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
|
-
|
|
304
|
-
const server = http.createServer(app);
|
|
305
|
-
const wss = new WebSocket.Server({ server });
|
|
295
|
+
### Express 5
|
|
306
296
|
|
|
307
|
-
|
|
308
|
-
adapter.websocketConnectionHandler(ws, req);
|
|
309
|
-
});
|
|
297
|
+
Use named wildcards (`*splat`):
|
|
310
298
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
##
|
|
305
|
+
## Example Project
|
|
320
306
|
|
|
321
|
-
|
|
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
|
-
|
|
324
|
-
NODE_ENV=production
|
|
325
|
-
PORT=3000
|
|
314
|
+
## API Reference
|
|
326
315
|
|
|
327
|
-
|
|
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
|
-
|
|
334
|
-
REDIS_URL=redis://localhost:6379
|
|
318
|
+
The Express adapter integrates with `@uploadista/server`:
|
|
335
319
|
|
|
336
|
-
|
|
337
|
-
|
|
320
|
+
```typescript
|
|
321
|
+
import { createUploadistaServer } from "@uploadista/server";
|
|
322
|
+
import { expressAdapter } from "@uploadista/adapters-express";
|
|
338
323
|
|
|
339
|
-
|
|
340
|
-
|
|
324
|
+
const server = await createUploadistaServer({
|
|
325
|
+
adapter: expressAdapter(/* options */),
|
|
326
|
+
// ... other config
|
|
327
|
+
});
|
|
341
328
|
```
|
|
342
329
|
|
|
343
|
-
|
|
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
|
-
|
|
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
|
-
|
|
334
|
+
If you're migrating from the legacy `createExpressUploadistaAdapter` API:
|
|
386
335
|
|
|
387
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
-
|
|
412
|
-
- `
|
|
413
|
-
-
|
|
414
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
+
The adapter is fully typed:
|
|
426
373
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
|