@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 CHANGED
@@ -1,323 +1,226 @@
1
1
  # @uploadista/server
2
2
 
3
- Core server utilities and authentication for Uploadista file upload and flow processing.
3
+ Core server for Uploadista file upload and flow processing.
4
4
 
5
- This package provides framework-agnostic server components including authentication context management, caching utilities, and layer composition helpers. Use this with adapter packages (`@uploadista/adapters-hono`, `@uploadista/adapters-express`, `@uploadista/adapters-fastify`) to set up complete upload servers.
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
- - **Authentication Context** - User identity and metadata management
10
- - **Auth Caching** - LRU cache for auth contexts with TTL support
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
- - **Effect Layers** - Dependency injection for upload and flow servers
13
- - **Error Handling** - Standardized error responses with HTTP status codes
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 @uploadista/core
20
+ npm install @uploadista/server
21
21
  # or
22
- pnpm add @uploadista/server @uploadista/core
22
+ pnpm add @uploadista/server
23
23
  ```
24
24
 
25
- ## Requirements
25
+ Also install an adapter for your framework:
26
26
 
27
- - Node.js 18+
28
- - TypeScript 5.0+ (optional but recommended)
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
- // Provide auth context to your effects
48
- const effect = Effect.service(AuthContextService).pipe(
49
- Effect.andThen((authService) => authService.getClientId()),
50
- );
31
+ # Express
32
+ pnpm add @uploadista/adapters-express express
51
33
 
52
- const result = await Effect.runPromise(
53
- effect.pipe(Effect.provide(AuthContextServiceLive(authContext))),
54
- );
55
- console.log(result); // "user-123"
34
+ # Fastify
35
+ pnpm add @uploadista/adapters-fastify fastify
56
36
  ```
57
37
 
58
- ### 2. Get JWT Credentials
59
-
60
- ```typescript
61
- import { getAuthCredentials } from "@uploadista/server";
38
+ ## Requirements
62
39
 
63
- // Exchange credentials for JWT token
64
- const response = await getAuthCredentials({
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
- if (response.isValid) {
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
- ### 3. Create Upload Server Layer
45
+ ### Hono Example
78
46
 
79
47
  ```typescript
80
- import { createUploadServerLayer } from "@uploadista/server";
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
- ```typescript
98
- import { createFlowServerLayer } from "@uploadista/server";
99
-
100
- const flowServerLayer = createFlowServerLayer({
101
- kvStore: redisKvStore,
102
- eventEmitter: webSocketEventEmitter,
103
- flowProvider: createFlowsEffect,
104
- uploadServer: uploadServerLayer,
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
- // Use in your framework adapter...
108
- ```
73
+ app.all("/uploadista/api/*", (c) => uploadistaServer.handler(c));
109
74
 
110
- ## API Reference
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
- **Methods**:
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 { AuthContextServiceLive } from "@uploadista/server";
159
-
160
- const authLayer = AuthContextServiceLive({
161
- clientId: "user-123",
162
- permissions: ["upload:create"],
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
- Exchange client credentials for JWT token.
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
- if (response.isValid) {
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
- ### Caching
188
-
189
- #### `AuthCacheConfig`
190
-
191
- Configuration for auth context caching.
104
+ ### Fastify Example
192
105
 
193
106
  ```typescript
194
- type AuthCacheConfig = {
195
- maxSize?: number; // Default: 10000
196
- ttl?: number; // Default: 3600000 (1 hour)
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
- #### `AuthCacheService`
113
+ const fastify = Fastify({ logger: true });
201
114
 
202
- Effect service for storing and retrieving cached auth contexts.
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
- ```typescript
205
- export class AuthCacheService extends Context.Tag("AuthCacheService")<
206
- AuthCacheService,
207
- {
208
- readonly set: (
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
- **Methods**:
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
- #### `AuthCacheServiceLive(config?)`
131
+ ### `createUploadistaServer(config)`
228
132
 
229
- Create in-memory auth cache layer.
133
+ The main function to create an Uploadista server instance.
230
134
 
231
135
  ```typescript
232
- import { AuthCacheServiceLive } from "@uploadista/server";
233
-
234
- const cacheLayer = AuthCacheServiceLive({
235
- maxSize: 5000,
236
- ttl: 1800000, // 30 minutes
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
- ### Layer Composition
241
-
242
- #### `UploadServerLayerConfig`
243
-
244
- Configuration for creating upload server layer.
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 UploadServerLayerConfig {
248
- kvStore: Layer.Layer<BaseKvStoreService>;
249
- eventEmitter: Layer.Layer<BaseEventEmitterService>;
250
- dataStore: Layer.Layer<UploadFileDataStores, never, UploadFileKVStore>;
251
- bufferedDataStore?: Layer.Layer<UploadFileDataStore>;
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
- #### `createUploadServerLayer(config)`
183
+ ### Authentication
257
184
 
258
- Compose upload server with all dependencies.
185
+ Authentication is handled via the adapter's `authMiddleware`:
259
186
 
260
187
  ```typescript
261
- import { createUploadServerLayer } from "@uploadista/server";
262
-
263
- const uploadLayer = createUploadServerLayer({
264
- kvStore: redisKvStore,
265
- eventEmitter: webSocketEventEmitter,
266
- dataStore: s3DataStore,
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
- #### `FlowServerLayerConfig`
271
-
272
- Configuration for creating flow server layer.
202
+ #### `AuthContext`
273
203
 
274
204
  ```typescript
275
- interface FlowServerLayerConfig {
276
- kvStore: Layer.Layer<BaseKvStoreService>;
277
- eventEmitter: Layer.Layer<BaseEventEmitterService>;
278
- flowProvider: Layer.Layer<FlowProvider>;
279
- uploadServer: Layer.Layer<UploadServer>;
280
- }
205
+ type AuthContext = {
206
+ clientId: string;
207
+ permissions?: string[];
208
+ metadata?: Record<string, unknown>;
209
+ };
281
210
  ```
282
211
 
283
- #### `createFlowServerLayer(config)`
284
-
285
- Compose flow server with all dependencies.
212
+ #### `AuthCacheConfig`
286
213
 
287
214
  ```typescript
288
- import { createFlowServerLayer } from "@uploadista/server";
289
-
290
- const flowLayer = createFlowServerLayer({
291
- kvStore: redisKvStore,
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
- #### `AdapterError`
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 Integration
324
+ ## Framework Adapters
478
325
 
479
- This package is used by framework adapters:
326
+ Three official adapters are available:
480
327
 
481
- - **[@uploadista/adapters-hono](../adapters-hono/)** - For Cloudflare Workers
482
- - **[@uploadista/adapters-express](../adapters-express/)** - For Node.js Express
483
- - **[@uploadista/adapters-fastify](../adapters-fastify/)** - For Node.js Fastify
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 Server Example
332
+ ## Complete Example with Plugins
486
333
 
487
334
  ```typescript
488
- import { Effect, Layer } from "effect";
489
- import {
490
- createUploadServerLayer,
491
- createFlowServerLayer,
492
- AuthContextServiceLive,
493
- } from "@uploadista/server";
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 { s3DataStore } from "@uploadista/data-store-s3";
496
- import { webSocketEventEmitter } from "@uploadista/event-emitter-websocket";
497
-
498
- // Configure servers
499
- const uploadLayer = createUploadServerLayer({
500
- kvStore: redisKvStore,
501
- eventEmitter: webSocketEventEmitter,
502
- dataStore: s3DataStore,
503
- });
504
-
505
- const flowLayer = createFlowServerLayer({
506
- kvStore: redisKvStore,
507
- eventEmitter: webSocketEventEmitter,
508
- flowProvider: createFlowsEffect,
509
- uploadServer: uploadLayer,
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
- // Set up authentication for a request
513
- const authContext = {
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
- // Compose all layers
519
- const appLayer = Layer.provide(flowLayer, uploadLayer).pipe(
520
- Layer.provide(AuthContextServiceLive(authContext)),
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
- // Run effects
524
- const myEffect = Effect.gen(function* () {
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
- Effect.runPromise(myEffect.pipe(Effect.provide(appLayer)));
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
- UploadServerLayerConfig,
543
- FlowServerLayerConfig,
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