@uploadista/server 0.0.20-beta.7 → 0.0.20-beta.9
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 +226 -375
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +31 -31
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +31 -31
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -12
- package/src/adapter/types.ts +3 -3
- package/src/core/http-handlers/flow-http-handlers.ts +13 -13
- package/src/core/http-handlers/upload-http-handlers.ts +16 -9
- package/src/core/plugin-types.ts +2 -2
- package/src/core/server.ts +6 -6
- package/src/core/websocket-handlers/flow-websocket-handlers.ts +5 -5
- package/src/core/websocket-handlers/upload-websocket-handlers.ts +5 -5
- package/src/core/websocket-handlers/websocket-handlers.ts +10 -10
- package/src/error-types.ts +1 -1
- package/src/layer-utils.ts +24 -24
- package/src/plugins-typing.ts +4 -4
package/README.md
CHANGED
|
@@ -1,323 +1,226 @@
|
|
|
1
1
|
# @uploadista/server
|
|
2
2
|
|
|
3
|
-
Core server
|
|
3
|
+
Core server for Uploadista file upload and flow processing.
|
|
4
4
|
|
|
5
|
-
This package provides
|
|
5
|
+
This package provides the unified `createUploadistaServer` function that combines upload handling, flow execution, and authentication into a single server instance. Use it with adapter packages (`@uploadista/adapters-hono`, `@uploadista/adapters-express`, `@uploadista/adapters-fastify`) to set up complete upload servers.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
9
|
+
- **Unified Server** - Single `createUploadistaServer` function for all frameworks
|
|
10
|
+
- **Plugin System** - Extend with image processing, AI, and custom plugins
|
|
11
|
+
- **Authentication** - Built-in auth middleware support via adapters
|
|
11
12
|
- **Health Checks** - Kubernetes-compatible liveness/readiness probes
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **HTTP Utilities** - Route parsing and error mapping helpers
|
|
13
|
+
- **Observability** - OpenTelemetry tracing support
|
|
14
|
+
- **Circuit Breakers** - Built-in resilience patterns
|
|
15
15
|
- **TypeScript** - Full type safety with comprehensive JSDoc
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install @uploadista/server
|
|
20
|
+
npm install @uploadista/server
|
|
21
21
|
# or
|
|
22
|
-
pnpm add @uploadista/server
|
|
22
|
+
pnpm add @uploadista/server
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Also install an adapter for your framework:
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
## Quick Start
|
|
31
|
-
|
|
32
|
-
### 1. Set Up Authentication
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
import { AuthContextServiceLive } from "@uploadista/server";
|
|
36
|
-
import { Effect } from "effect";
|
|
37
|
-
|
|
38
|
-
// Create auth context for a request
|
|
39
|
-
const authContext = {
|
|
40
|
-
clientId: "user-123",
|
|
41
|
-
metadata: {
|
|
42
|
-
permissions: ["upload:create", "flow:execute"],
|
|
43
|
-
quota: { storage: 1000000000 }, // 1GB
|
|
44
|
-
},
|
|
45
|
-
};
|
|
27
|
+
```bash
|
|
28
|
+
# Hono (recommended for Cloudflare Workers)
|
|
29
|
+
pnpm add @uploadista/adapters-hono hono
|
|
46
30
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Effect.andThen((authService) => authService.getClientId()),
|
|
50
|
-
);
|
|
31
|
+
# Express
|
|
32
|
+
pnpm add @uploadista/adapters-express express
|
|
51
33
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
);
|
|
55
|
-
console.log(result); // "user-123"
|
|
34
|
+
# Fastify
|
|
35
|
+
pnpm add @uploadista/adapters-fastify fastify
|
|
56
36
|
```
|
|
57
37
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import { getAuthCredentials } from "@uploadista/server";
|
|
38
|
+
## Requirements
|
|
62
39
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
uploadistaClientId: process.env.UPLOADISTA_CLIENT_ID,
|
|
66
|
-
uploadistaApiKey: process.env.UPLOADISTA_API_KEY,
|
|
67
|
-
});
|
|
40
|
+
- Node.js 18+
|
|
41
|
+
- TypeScript 5.0+ (optional but recommended)
|
|
68
42
|
|
|
69
|
-
|
|
70
|
-
console.log(`Token: ${response.data.token}`);
|
|
71
|
-
console.log(`Expires in: ${response.data.expiresIn}s`);
|
|
72
|
-
} else {
|
|
73
|
-
console.error(`Auth failed: ${response.error}`);
|
|
74
|
-
}
|
|
75
|
-
```
|
|
43
|
+
## Quick Start
|
|
76
44
|
|
|
77
|
-
###
|
|
45
|
+
### Hono Example
|
|
78
46
|
|
|
79
47
|
```typescript
|
|
80
|
-
import {
|
|
48
|
+
import { Hono } from "hono";
|
|
49
|
+
import { createUploadistaServer } from "@uploadista/server";
|
|
50
|
+
import { honoAdapter } from "@uploadista/adapters-hono";
|
|
51
|
+
import { s3Store } from "@uploadista/data-store-s3";
|
|
81
52
|
import { redisKvStore } from "@uploadista/kv-store-redis";
|
|
82
|
-
import { s3DataStore } from "@uploadista/data-store-s3";
|
|
83
|
-
import { webSocketEventEmitter } from "@uploadista/event-emitter-websocket";
|
|
84
|
-
import { memoryEventBroadcaster } from "@uploadista/event-broadcaster-memory";
|
|
85
|
-
|
|
86
|
-
const uploadServerLayer = createUploadServerLayer({
|
|
87
|
-
kvStore: redisKvStore,
|
|
88
|
-
eventEmitter: webSocketEventEmitter,
|
|
89
|
-
dataStore: s3DataStore,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Use in your framework adapter...
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### 4. Create Flow Server Layer
|
|
96
53
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
54
|
+
const app = new Hono();
|
|
55
|
+
|
|
56
|
+
const uploadistaServer = await createUploadistaServer({
|
|
57
|
+
dataStore: s3Store({
|
|
58
|
+
deliveryUrl: process.env.CDN_URL!,
|
|
59
|
+
s3ClientConfig: {
|
|
60
|
+
bucket: process.env.S3_BUCKET!,
|
|
61
|
+
region: process.env.AWS_REGION!,
|
|
62
|
+
credentials: {
|
|
63
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
64
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
kvStore: redisKvStore({ redis: redisClient }),
|
|
69
|
+
flows: (flowId, clientId) => createFlowEffect(flowId, clientId),
|
|
70
|
+
adapter: honoAdapter(),
|
|
105
71
|
});
|
|
106
72
|
|
|
107
|
-
|
|
108
|
-
```
|
|
73
|
+
app.all("/uploadista/api/*", (c) => uploadistaServer.handler(c));
|
|
109
74
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
### Authentication
|
|
113
|
-
|
|
114
|
-
#### `AuthContext`
|
|
115
|
-
|
|
116
|
-
User identity and authorization metadata.
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
type AuthContext = {
|
|
120
|
-
clientId: string;
|
|
121
|
-
metadata?: Record<string, unknown>;
|
|
122
|
-
permissions?: string[];
|
|
123
|
-
};
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
**Properties**:
|
|
127
|
-
- `clientId` - Unique user identifier
|
|
128
|
-
- `metadata` - Custom authorization metadata (permissions, quotas, etc.)
|
|
129
|
-
- `permissions` - Array of permission strings for authorization
|
|
130
|
-
|
|
131
|
-
#### `AuthContextService`
|
|
132
|
-
|
|
133
|
-
Effect service for accessing auth context throughout request processing.
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
export class AuthContextService extends Context.Tag("AuthContextService")<
|
|
137
|
-
AuthContextService,
|
|
138
|
-
{
|
|
139
|
-
readonly getClientId: () => Effect.Effect<string | null>;
|
|
140
|
-
readonly getMetadata: () => Effect.Effect<Record<string, unknown>>;
|
|
141
|
-
readonly hasPermission: (permission: string) => Effect.Effect<boolean>;
|
|
142
|
-
readonly getAuthContext: () => Effect.Effect<AuthContext | null>;
|
|
143
|
-
}
|
|
144
|
-
>() {}
|
|
75
|
+
export default app;
|
|
145
76
|
```
|
|
146
77
|
|
|
147
|
-
|
|
148
|
-
- `getClientId()` - Get current client ID
|
|
149
|
-
- `getMetadata()` - Get auth metadata object
|
|
150
|
-
- `hasPermission(permission)` - Check if user has permission
|
|
151
|
-
- `getAuthContext()` - Get full auth context
|
|
152
|
-
|
|
153
|
-
#### `AuthContextServiceLive(authContext)`
|
|
154
|
-
|
|
155
|
-
Factory for creating AuthContextService layer.
|
|
78
|
+
### Express Example
|
|
156
79
|
|
|
157
80
|
```typescript
|
|
158
|
-
import
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
81
|
+
import express from "express";
|
|
82
|
+
import { createUploadistaServer } from "@uploadista/server";
|
|
83
|
+
import { expressAdapter } from "@uploadista/adapters-express";
|
|
84
|
+
import { fileStore } from "@uploadista/data-store-filesystem";
|
|
85
|
+
import { fileKvStore } from "@uploadista/kv-store-filesystem";
|
|
86
|
+
|
|
87
|
+
const app = express();
|
|
88
|
+
app.use(express.json({ limit: "50mb" }));
|
|
89
|
+
|
|
90
|
+
const uploadistaServer = await createUploadistaServer({
|
|
91
|
+
dataStore: fileStore({ directory: "./uploads" }),
|
|
92
|
+
kvStore: fileKvStore({ directory: "./uploads" }),
|
|
93
|
+
flows: (flowId, clientId) => createFlowEffect(flowId, clientId),
|
|
94
|
+
adapter: expressAdapter(),
|
|
163
95
|
});
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
#### `getAuthCredentials(params)`
|
|
167
96
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
import { getAuthCredentials } from "@uploadista/server";
|
|
172
|
-
|
|
173
|
-
const response = await getAuthCredentials({
|
|
174
|
-
uploadistaClientId: "my-client",
|
|
175
|
-
uploadistaApiKey: "sk_...",
|
|
176
|
-
baseUrl: "https://api.uploadista.com", // optional
|
|
97
|
+
app.all("/uploadista/api/*splat", (request, response, next) => {
|
|
98
|
+
uploadistaServer.handler({ request, response, next });
|
|
177
99
|
});
|
|
178
100
|
|
|
179
|
-
|
|
180
|
-
// response.data.token - JWT token
|
|
181
|
-
// response.data.expiresIn - Seconds until expiration
|
|
182
|
-
} else {
|
|
183
|
-
// response.error - Error message
|
|
184
|
-
}
|
|
101
|
+
app.listen(3000);
|
|
185
102
|
```
|
|
186
103
|
|
|
187
|
-
###
|
|
188
|
-
|
|
189
|
-
#### `AuthCacheConfig`
|
|
190
|
-
|
|
191
|
-
Configuration for auth context caching.
|
|
104
|
+
### Fastify Example
|
|
192
105
|
|
|
193
106
|
```typescript
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
};
|
|
198
|
-
|
|
107
|
+
import Fastify from "fastify";
|
|
108
|
+
import { createUploadistaServer } from "@uploadista/server";
|
|
109
|
+
import { fastifyAdapter } from "@uploadista/adapters-fastify";
|
|
110
|
+
import { fileStore } from "@uploadista/data-store-filesystem";
|
|
111
|
+
import { fileKvStore } from "@uploadista/kv-store-filesystem";
|
|
199
112
|
|
|
200
|
-
|
|
113
|
+
const fastify = Fastify({ logger: true });
|
|
201
114
|
|
|
202
|
-
|
|
115
|
+
const uploadistaServer = await createUploadistaServer({
|
|
116
|
+
dataStore: fileStore({ directory: "./uploads" }),
|
|
117
|
+
kvStore: fileKvStore({ directory: "./uploads" }),
|
|
118
|
+
flows: (flowId, clientId) => createFlowEffect(flowId, clientId),
|
|
119
|
+
adapter: fastifyAdapter(),
|
|
120
|
+
});
|
|
203
121
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
jobId: string,
|
|
210
|
-
authContext: AuthContext,
|
|
211
|
-
) => Effect.Effect<void>;
|
|
212
|
-
readonly get: (jobId: string) => Effect.Effect<AuthContext | null>;
|
|
213
|
-
readonly delete: (jobId: string) => Effect.Effect<void>;
|
|
214
|
-
readonly clear: () => Effect.Effect<void>;
|
|
215
|
-
readonly size: () => Effect.Effect<number>;
|
|
216
|
-
}
|
|
217
|
-
>() {}
|
|
122
|
+
fastify.all("/uploadista/api/*", async (request, reply) => {
|
|
123
|
+
return uploadistaServer.handler({ request, reply });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await fastify.listen({ port: 3000 });
|
|
218
127
|
```
|
|
219
128
|
|
|
220
|
-
|
|
221
|
-
- `set(jobId, authContext)` - Cache auth for a job
|
|
222
|
-
- `get(jobId)` - Retrieve cached auth
|
|
223
|
-
- `delete(jobId)` - Remove specific cache entry
|
|
224
|
-
- `clear()` - Clear all cached entries
|
|
225
|
-
- `size()` - Get number of cached entries
|
|
129
|
+
## API Reference
|
|
226
130
|
|
|
227
|
-
|
|
131
|
+
### `createUploadistaServer(config)`
|
|
228
132
|
|
|
229
|
-
|
|
133
|
+
The main function to create an Uploadista server instance.
|
|
230
134
|
|
|
231
135
|
```typescript
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
136
|
+
const uploadistaServer = await createUploadistaServer({
|
|
137
|
+
// Required
|
|
138
|
+
dataStore: s3Store({ /* config */ }),
|
|
139
|
+
kvStore: redisKvStore({ /* config */ }),
|
|
140
|
+
flows: (flowId, clientId) => createFlowEffect(flowId, clientId),
|
|
141
|
+
adapter: honoAdapter(),
|
|
142
|
+
|
|
143
|
+
// Optional
|
|
144
|
+
plugins: [imagePlugin],
|
|
145
|
+
eventBroadcaster: redisEventBroadcaster({ /* config */ }),
|
|
146
|
+
withTracing: true,
|
|
147
|
+
baseUrl: "uploadista",
|
|
148
|
+
circuitBreaker: true,
|
|
149
|
+
healthCheck: { version: "1.0.0" },
|
|
150
|
+
authCacheConfig: { maxSize: 10000, ttl: 3600000 },
|
|
237
151
|
});
|
|
238
152
|
```
|
|
239
153
|
|
|
240
|
-
###
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
154
|
+
### Configuration Options
|
|
155
|
+
|
|
156
|
+
| Option | Type | Required | Default | Description |
|
|
157
|
+
|--------|------|----------|---------|-------------|
|
|
158
|
+
| `dataStore` | `DataStoreConfig` | Yes | - | File storage backend (S3, Azure, GCS, filesystem) |
|
|
159
|
+
| `kvStore` | `Layer<BaseKvStoreService>` | Yes | - | Metadata storage (Redis, memory, filesystem) |
|
|
160
|
+
| `flows` | `(flowId, clientId) => Effect<Flow>` | Yes | - | Flow provider function |
|
|
161
|
+
| `adapter` | `ServerAdapter` | Yes | - | Framework adapter (Hono, Express, Fastify) |
|
|
162
|
+
| `plugins` | `PluginLayer[]` | No | `[]` | Processing plugins |
|
|
163
|
+
| `eventBroadcaster` | `Layer<EventBroadcasterService>` | No | Memory | Cross-instance event broadcasting |
|
|
164
|
+
| `withTracing` | `boolean` | No | `false` | Enable OpenTelemetry tracing |
|
|
165
|
+
| `observabilityLayer` | `Layer` | No | - | Custom observability layer |
|
|
166
|
+
| `baseUrl` | `string` | No | `"uploadista"` | API base path |
|
|
167
|
+
| `circuitBreaker` | `boolean` | No | `true` | Enable circuit breakers |
|
|
168
|
+
| `deadLetterQueue` | `boolean` | No | `false` | Enable DLQ for failed jobs |
|
|
169
|
+
| `healthCheck` | `HealthCheckConfig` | No | - | Health endpoint configuration |
|
|
170
|
+
| `authCacheConfig` | `AuthCacheConfig` | No | - | Auth caching configuration |
|
|
171
|
+
|
|
172
|
+
### Return Type
|
|
245
173
|
|
|
246
174
|
```typescript
|
|
247
|
-
interface
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
generateId?: Layer.Layer<GenerateId>;
|
|
175
|
+
interface UploadistaServer<TContext, TResponse, TWebSocketHandler> {
|
|
176
|
+
handler: (req: TContext) => Promise<TResponse>;
|
|
177
|
+
websocketHandler: TWebSocketHandler;
|
|
178
|
+
baseUrl: string;
|
|
179
|
+
dispose: () => Promise<void>;
|
|
253
180
|
}
|
|
254
181
|
```
|
|
255
182
|
|
|
256
|
-
|
|
183
|
+
### Authentication
|
|
257
184
|
|
|
258
|
-
|
|
185
|
+
Authentication is handled via the adapter's `authMiddleware`:
|
|
259
186
|
|
|
260
187
|
```typescript
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
188
|
+
adapter: honoAdapter({
|
|
189
|
+
authMiddleware: async (c) => {
|
|
190
|
+
const token = c.req.header("Authorization")?.split(" ")[1];
|
|
191
|
+
if (!token) return null;
|
|
192
|
+
|
|
193
|
+
const payload = await verifyJWT(token);
|
|
194
|
+
return {
|
|
195
|
+
clientId: payload.sub,
|
|
196
|
+
permissions: payload.permissions || [],
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
})
|
|
268
200
|
```
|
|
269
201
|
|
|
270
|
-
#### `
|
|
271
|
-
|
|
272
|
-
Configuration for creating flow server layer.
|
|
202
|
+
#### `AuthContext`
|
|
273
203
|
|
|
274
204
|
```typescript
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
205
|
+
type AuthContext = {
|
|
206
|
+
clientId: string;
|
|
207
|
+
permissions?: string[];
|
|
208
|
+
metadata?: Record<string, unknown>;
|
|
209
|
+
};
|
|
281
210
|
```
|
|
282
211
|
|
|
283
|
-
#### `
|
|
284
|
-
|
|
285
|
-
Compose flow server with all dependencies.
|
|
212
|
+
#### `AuthCacheConfig`
|
|
286
213
|
|
|
287
214
|
```typescript
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
eventEmitter: webSocketEventEmitter,
|
|
293
|
-
flowProvider: createFlowsEffect,
|
|
294
|
-
uploadServer: uploadLayer,
|
|
295
|
-
});
|
|
215
|
+
type AuthCacheConfig = {
|
|
216
|
+
maxSize?: number; // Default: 10000
|
|
217
|
+
ttl?: number; // Default: 3600000 (1 hour)
|
|
218
|
+
};
|
|
296
219
|
```
|
|
297
220
|
|
|
298
221
|
### Error Handling
|
|
299
222
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
Base error class for adapter errors.
|
|
303
|
-
|
|
304
|
-
```typescript
|
|
305
|
-
class AdapterError extends Error {
|
|
306
|
-
constructor(
|
|
307
|
-
message: string,
|
|
308
|
-
statusCode?: number,
|
|
309
|
-
errorCode?: string,
|
|
310
|
-
) {}
|
|
311
|
-
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
**Properties**:
|
|
315
|
-
- `statusCode` - HTTP status code (default: 500)
|
|
316
|
-
- `errorCode` - Machine-readable error code
|
|
317
|
-
|
|
318
|
-
#### `ValidationError`, `NotFoundError`, `BadRequestError`
|
|
319
|
-
|
|
320
|
-
Pre-configured error classes:
|
|
223
|
+
The server automatically handles errors and returns appropriate HTTP responses:
|
|
321
224
|
|
|
322
225
|
```typescript
|
|
323
226
|
import {
|
|
@@ -336,62 +239,6 @@ throw new NotFoundError("Upload");
|
|
|
336
239
|
throw new BadRequestError("Invalid JSON body");
|
|
337
240
|
```
|
|
338
241
|
|
|
339
|
-
#### Error Response Factories
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
|
-
import {
|
|
343
|
-
createErrorResponseBody,
|
|
344
|
-
createUploadistaErrorResponseBody,
|
|
345
|
-
createGenericErrorResponseBody,
|
|
346
|
-
} from "@uploadista/server";
|
|
347
|
-
|
|
348
|
-
// For adapter errors
|
|
349
|
-
const errorResponse = createErrorResponseBody(
|
|
350
|
-
new ValidationError("Invalid data"),
|
|
351
|
-
);
|
|
352
|
-
// => { error: "Invalid data", code: "VALIDATION_ERROR", timestamp: "..." }
|
|
353
|
-
|
|
354
|
-
// For core library errors
|
|
355
|
-
const uploadistaErrorResponse = createUploadistaErrorResponseBody(error);
|
|
356
|
-
|
|
357
|
-
// For unknown errors
|
|
358
|
-
const genericErrorResponse = createGenericErrorResponseBody("Something went wrong");
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### HTTP Utilities
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
import {
|
|
365
|
-
parseUrlSegments,
|
|
366
|
-
getLastSegment,
|
|
367
|
-
hasBasePath,
|
|
368
|
-
getRouteSegments,
|
|
369
|
-
handleFlowError,
|
|
370
|
-
extractJobIdFromStatus,
|
|
371
|
-
extractJobAndNodeId,
|
|
372
|
-
extractFlowAndStorageId,
|
|
373
|
-
} from "@uploadista/server";
|
|
374
|
-
|
|
375
|
-
// Parse route
|
|
376
|
-
const segments = parseUrlSegments("/uploadista/api/upload/abc");
|
|
377
|
-
// => ["uploadista", "api", "upload", "abc"]
|
|
378
|
-
|
|
379
|
-
// Check if request is for uploadista
|
|
380
|
-
const isUploadistaRequest = hasBasePath("/uploadista/api/upload", "uploadista");
|
|
381
|
-
// => true
|
|
382
|
-
|
|
383
|
-
// Extract parameters from URL
|
|
384
|
-
const jobId = extractJobIdFromStatus(["jobs", "job-123", "status"]);
|
|
385
|
-
// => "job-123"
|
|
386
|
-
|
|
387
|
-
// Handle errors consistently
|
|
388
|
-
const errorInfo = handleFlowError({
|
|
389
|
-
code: "FILE_NOT_FOUND",
|
|
390
|
-
message: "File not found",
|
|
391
|
-
});
|
|
392
|
-
// => { status: 404, code: "FILE_NOT_FOUND", message: "File not found" }
|
|
393
|
-
```
|
|
394
|
-
|
|
395
242
|
## Health Check Endpoints
|
|
396
243
|
|
|
397
244
|
The server provides Kubernetes-compatible health check endpoints for production deployments.
|
|
@@ -474,60 +321,93 @@ readinessProbe:
|
|
|
474
321
|
|
|
475
322
|
For complete documentation, see [docs/HEALTH_CHECKS.md](./docs/HEALTH_CHECKS.md).
|
|
476
323
|
|
|
477
|
-
## Framework
|
|
324
|
+
## Framework Adapters
|
|
478
325
|
|
|
479
|
-
|
|
326
|
+
Three official adapters are available:
|
|
480
327
|
|
|
481
|
-
- **[@uploadista/adapters-hono](../adapters-hono/)** - For Cloudflare Workers
|
|
482
|
-
- **[@uploadista/adapters-express](../adapters-express/)** - For
|
|
483
|
-
- **[@uploadista/adapters-fastify](../adapters-fastify/)** - For
|
|
328
|
+
- **[@uploadista/adapters-hono](../adapters-hono/)** - For Hono and Cloudflare Workers
|
|
329
|
+
- **[@uploadista/adapters-express](../adapters-express/)** - For Express.js
|
|
330
|
+
- **[@uploadista/adapters-fastify](../adapters-fastify/)** - For Fastify
|
|
484
331
|
|
|
485
|
-
## Complete
|
|
332
|
+
## Complete Example with Plugins
|
|
486
333
|
|
|
487
334
|
```typescript
|
|
488
|
-
import {
|
|
489
|
-
import {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
} from "@uploadista/
|
|
335
|
+
import { Hono } from "hono";
|
|
336
|
+
import { serve } from "@hono/node-server";
|
|
337
|
+
import { createNodeWebSocket } from "@hono/node-ws";
|
|
338
|
+
import { createClient } from "@redis/client";
|
|
339
|
+
import { createUploadistaServer } from "@uploadista/server";
|
|
340
|
+
import { honoAdapter } from "@uploadista/adapters-hono";
|
|
341
|
+
import { s3Store } from "@uploadista/data-store-s3";
|
|
494
342
|
import { redisKvStore } from "@uploadista/kv-store-redis";
|
|
495
|
-
import {
|
|
496
|
-
import {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
343
|
+
import { redisEventBroadcaster } from "@uploadista/event-broadcaster-redis";
|
|
344
|
+
import { imagePlugin } from "@uploadista/flow-images-sharp";
|
|
345
|
+
|
|
346
|
+
const app = new Hono();
|
|
347
|
+
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });
|
|
348
|
+
|
|
349
|
+
// Initialize Redis
|
|
350
|
+
const redisClient = createClient({ url: process.env.REDIS_URL });
|
|
351
|
+
await redisClient.connect();
|
|
352
|
+
|
|
353
|
+
const redisSubscriberClient = createClient({ url: process.env.REDIS_URL });
|
|
354
|
+
await redisSubscriberClient.connect();
|
|
355
|
+
|
|
356
|
+
// Create server with all features
|
|
357
|
+
const uploadistaServer = await createUploadistaServer({
|
|
358
|
+
dataStore: s3Store({
|
|
359
|
+
deliveryUrl: process.env.CDN_URL!,
|
|
360
|
+
s3ClientConfig: {
|
|
361
|
+
bucket: process.env.S3_BUCKET!,
|
|
362
|
+
region: process.env.AWS_REGION!,
|
|
363
|
+
credentials: {
|
|
364
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
365
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
}),
|
|
369
|
+
kvStore: redisKvStore({ redis: redisClient }),
|
|
370
|
+
eventBroadcaster: redisEventBroadcaster({
|
|
371
|
+
redis: redisClient,
|
|
372
|
+
subscriberRedis: redisSubscriberClient,
|
|
373
|
+
}),
|
|
374
|
+
flows: (flowId, clientId) => createFlowEffect(flowId, clientId),
|
|
375
|
+
plugins: [imagePlugin],
|
|
376
|
+
adapter: honoAdapter({
|
|
377
|
+
authMiddleware: async (c) => {
|
|
378
|
+
const token = c.req.header("Authorization")?.split(" ")[1];
|
|
379
|
+
if (!token) return null;
|
|
380
|
+
const payload = await verifyJWT(token);
|
|
381
|
+
return { clientId: payload.sub };
|
|
382
|
+
},
|
|
383
|
+
}),
|
|
384
|
+
withTracing: Boolean(process.env.OTEL_EXPORTER_OTLP_ENDPOINT),
|
|
510
385
|
});
|
|
511
386
|
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
clientId: "user-123",
|
|
515
|
-
permissions: ["upload:create", "flow:execute"],
|
|
516
|
-
};
|
|
387
|
+
// HTTP routes
|
|
388
|
+
app.all("/uploadista/api/*", (c) => uploadistaServer.handler(c));
|
|
517
389
|
|
|
518
|
-
//
|
|
519
|
-
|
|
520
|
-
|
|
390
|
+
// WebSocket routes for real-time progress
|
|
391
|
+
app.get(
|
|
392
|
+
"/uploadista/ws/upload/:uploadId",
|
|
393
|
+
upgradeWebSocket(uploadistaServer.websocketHandler)
|
|
394
|
+
);
|
|
395
|
+
app.get(
|
|
396
|
+
"/uploadista/ws/flow/:jobId",
|
|
397
|
+
upgradeWebSocket(uploadistaServer.websocketHandler)
|
|
521
398
|
);
|
|
522
399
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const uploadServer = yield* UploadServer;
|
|
526
|
-
const flowServer = yield* FlowServer;
|
|
527
|
-
// ... use uploadServer and flowServer
|
|
528
|
-
});
|
|
400
|
+
const server = serve({ port: 3000, fetch: app.fetch });
|
|
401
|
+
injectWebSocket(server);
|
|
529
402
|
|
|
530
|
-
|
|
403
|
+
// Graceful shutdown
|
|
404
|
+
process.on("SIGTERM", async () => {
|
|
405
|
+
server.close();
|
|
406
|
+
await uploadistaServer.dispose();
|
|
407
|
+
await redisClient.quit();
|
|
408
|
+
await redisSubscriberClient.quit();
|
|
409
|
+
process.exit(0);
|
|
410
|
+
});
|
|
531
411
|
```
|
|
532
412
|
|
|
533
413
|
## TypeScript Support
|
|
@@ -537,41 +417,12 @@ Full TypeScript support with comprehensive types:
|
|
|
537
417
|
```typescript
|
|
538
418
|
import type {
|
|
539
419
|
AuthContext,
|
|
540
|
-
AuthResult,
|
|
541
420
|
AuthCacheConfig,
|
|
542
|
-
|
|
543
|
-
|
|
421
|
+
UploadistaServerConfig,
|
|
422
|
+
UploadistaServer,
|
|
544
423
|
} from "@uploadista/server";
|
|
545
|
-
import type { UploadServer, FlowServer } from "@uploadista/core";
|
|
546
424
|
```
|
|
547
425
|
|
|
548
|
-
## Architecture Notes
|
|
549
|
-
|
|
550
|
-
### Authentication Flow
|
|
551
|
-
|
|
552
|
-
1. Client authenticates with credentials (ID + API key)
|
|
553
|
-
2. Server validates and issues JWT token
|
|
554
|
-
3. Token includes user identity and permissions
|
|
555
|
-
4. Subsequent requests include token in Authorization header
|
|
556
|
-
5. Auth context created from token claims
|
|
557
|
-
6. Auth context passed through Effect layers to handlers
|
|
558
|
-
7. Handlers check permissions before processing
|
|
559
|
-
|
|
560
|
-
### Effect Layer Pattern
|
|
561
|
-
|
|
562
|
-
- Use `Layer.provide()` to compose dependencies
|
|
563
|
-
- Each layer provides one service (UploadServer, FlowServer, etc.)
|
|
564
|
-
- Auth context automatically available via AuthContextService
|
|
565
|
-
- Cache automatically handles auth context persistence across requests
|
|
566
|
-
|
|
567
|
-
### Error Handling Strategy
|
|
568
|
-
|
|
569
|
-
1. Catch domain errors in handlers
|
|
570
|
-
2. Map to AdapterError with appropriate HTTP status
|
|
571
|
-
3. Format using createErrorResponseBody
|
|
572
|
-
4. Return JSON error response with timestamp
|
|
573
|
-
5. Log errors for monitoring
|
|
574
|
-
|
|
575
426
|
## Related Packages
|
|
576
427
|
|
|
577
428
|
- **[@uploadista/core](../../core/)** - Core upload and flow engine
|