@uploadista/adapters-express 0.0.3

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.
Files changed (48) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-check.log +5 -0
  3. package/LICENSE +21 -0
  4. package/README.md +456 -0
  5. package/USAGE.md +164 -0
  6. package/dist/adapter-layer.d.ts +22 -0
  7. package/dist/adapter-layer.d.ts.map +1 -0
  8. package/dist/adapter-layer.js +3 -0
  9. package/dist/error-types.d.ts +24 -0
  10. package/dist/error-types.d.ts.map +1 -0
  11. package/dist/error-types.js +65 -0
  12. package/dist/flow-adapter.d.ts +19 -0
  13. package/dist/flow-adapter.d.ts.map +1 -0
  14. package/dist/flow-adapter.js +80 -0
  15. package/dist/flow-http-handlers.d.ts +9 -0
  16. package/dist/flow-http-handlers.d.ts.map +1 -0
  17. package/dist/flow-http-handlers.js +133 -0
  18. package/dist/http-handlers.d.ts +7 -0
  19. package/dist/http-handlers.d.ts.map +1 -0
  20. package/dist/http-handlers.js +78 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +2 -0
  24. package/dist/upload-http-handlers.d.ts +9 -0
  25. package/dist/upload-http-handlers.d.ts.map +1 -0
  26. package/dist/upload-http-handlers.js +113 -0
  27. package/dist/uploadista-adapter-layer.d.ts +24 -0
  28. package/dist/uploadista-adapter-layer.d.ts.map +1 -0
  29. package/dist/uploadista-adapter-layer.js +4 -0
  30. package/dist/uploadista-adapter.d.ts +78 -0
  31. package/dist/uploadista-adapter.d.ts.map +1 -0
  32. package/dist/uploadista-adapter.js +297 -0
  33. package/dist/uploadista-websocket-handler.d.ts +9 -0
  34. package/dist/uploadista-websocket-handler.d.ts.map +1 -0
  35. package/dist/uploadista-websocket-handler.js +132 -0
  36. package/dist/websocket-handler.d.ts +8 -0
  37. package/dist/websocket-handler.d.ts.map +1 -0
  38. package/dist/websocket-handler.js +82 -0
  39. package/package.json +40 -0
  40. package/src/error-types.ts +103 -0
  41. package/src/flow-http-handlers.ts +184 -0
  42. package/src/index.ts +14 -0
  43. package/src/upload-http-handlers.ts +186 -0
  44. package/src/uploadista-adapter-layer.ts +32 -0
  45. package/src/uploadista-adapter.ts +626 -0
  46. package/src/uploadista-websocket-handler.ts +209 -0
  47. package/tsconfig.json +11 -0
  48. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,626 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ import type {
3
+ EventBroadcasterService,
4
+ UploadFileDataStores,
5
+ UploadistaError,
6
+ } from "@uploadista/core";
7
+ import { type Flow, FlowProvider, FlowServer } from "@uploadista/core/flow";
8
+ import {
9
+ type BaseEventEmitterService,
10
+ type BaseKvStoreService,
11
+ createDataStoreLayer,
12
+ type DataStoreConfig,
13
+ type UploadFileDataStore,
14
+ type UploadFileKVStore,
15
+ } from "@uploadista/core/types";
16
+ import { UploadServer } from "@uploadista/core/upload";
17
+ import { type GenerateId, GenerateIdLive } from "@uploadista/core/utils";
18
+ import { memoryEventBroadcaster } from "@uploadista/event-broadcaster-memory";
19
+ import { webSocketEventEmitter } from "@uploadista/event-emitter-websocket";
20
+ import {
21
+ type MetricsService,
22
+ NodeSdkLive,
23
+ NoOpMetricsServiceLive,
24
+ } from "@uploadista/observability";
25
+ import {
26
+ type AuthCacheConfig,
27
+ AuthCacheServiceLive,
28
+ type AuthContext,
29
+ AuthContextServiceLive,
30
+ type AuthResult,
31
+ createFlowServerLayer,
32
+ createUploadServerLayer,
33
+ type FlowRequirementsOf,
34
+ } from "@uploadista/server";
35
+ import { Effect, Layer } from "effect";
36
+ import type { Request, Response } from "express";
37
+ import type { z } from "zod";
38
+ import {
39
+ handleContinueFlow,
40
+ handleFlowGet,
41
+ handleFlowPost,
42
+ handleJobStatus,
43
+ } from "./flow-http-handlers";
44
+ import {
45
+ handleUploadGet,
46
+ handleUploadPatch,
47
+ handleUploadPost,
48
+ } from "./upload-http-handlers";
49
+ import {
50
+ ExpressUploadistaAdapterService,
51
+ type ExpressUploadistaAdapterServiceShape,
52
+ type WebSocketConnection,
53
+ type WebSocketHandlers,
54
+ } from "./uploadista-adapter-layer";
55
+ import { createUploadistaWebSocketHandler } from "./uploadista-websocket-handler";
56
+
57
+ export type ExpressUploadistaAdapterOptions<
58
+ TFlows extends (
59
+ flowId: string,
60
+ clientId: string | null,
61
+ ) => Effect.Effect<
62
+ Flow<z.ZodSchema<unknown>, z.ZodSchema<unknown>, unknown>,
63
+ UploadistaError,
64
+ unknown
65
+ // biome-ignore lint/suspicious/noExplicitAny: Generic type constraint allows any flow function type
66
+ > = any,
67
+ // biome-ignore lint/suspicious/noExplicitAny: Permissive constraint allows plugin tuples, validation via PluginAssertion
68
+ TPlugins extends readonly Layer.Layer<any, never, never>[] = Layer.Layer<
69
+ any,
70
+ never,
71
+ never
72
+ >[],
73
+ > = {
74
+ // Flow configuration
75
+ flows: TFlows;
76
+ plugins?: TPlugins;
77
+
78
+ dataStore: DataStoreConfig;
79
+ bufferedDataStore?: Layer.Layer<
80
+ UploadFileDataStore,
81
+ never,
82
+ UploadFileKVStore
83
+ >;
84
+
85
+ // Shared configuration
86
+ baseUrl?: string;
87
+ kvStore: Layer.Layer<BaseKvStoreService>;
88
+ eventEmitter?: Layer.Layer<BaseEventEmitterService>;
89
+ eventBroadcaster?: Layer.Layer<EventBroadcasterService>;
90
+ generateId?: Layer.Layer<GenerateId>;
91
+ withTracing?: boolean;
92
+
93
+ // Authentication
94
+ authMiddleware?: (req: Request, res: Response) => Promise<AuthResult>;
95
+ authCacheConfig?: AuthCacheConfig;
96
+
97
+ // Metrics
98
+ metricsLayer?: Layer.Layer<MetricsService, never, never>;
99
+ };
100
+
101
+ export type InternalExpressUploadistaAdapterOptions<
102
+ TRequirements = never,
103
+ TPlugins extends readonly Layer.Layer<TRequirements, never, never>[] = [],
104
+ > = {
105
+ // Flow configuration
106
+ flowProvider: Layer.Layer<FlowProvider>;
107
+ plugins?: TPlugins;
108
+
109
+ // Upload configuration
110
+ dataStore: Layer.Layer<UploadFileDataStores, never, UploadFileKVStore>;
111
+ bufferedDataStore?: Layer.Layer<
112
+ UploadFileDataStore,
113
+ never,
114
+ UploadFileKVStore
115
+ >;
116
+ // Shared configuration
117
+ baseUrl: string;
118
+ kvStore: Layer.Layer<BaseKvStoreService>;
119
+ eventEmitter: Layer.Layer<BaseEventEmitterService>;
120
+ generateId?: Layer.Layer<GenerateId>;
121
+ withTracing?: boolean;
122
+
123
+ // Authentication
124
+ authMiddleware?: (req: Request, res: Response) => Promise<AuthResult>;
125
+ authCacheConfig?: AuthCacheConfig;
126
+
127
+ // Metrics
128
+ metricsLayer?: Layer.Layer<MetricsService, never, never>;
129
+ };
130
+
131
+ export type ExpressUploadistaAdapter = {
132
+ baseUrl: string;
133
+ handler: (
134
+ req: Request,
135
+ res: Response,
136
+ next?: (error?: Error) => void,
137
+ ) => void;
138
+ websocketHandler: (
139
+ req: IncomingMessage,
140
+ connection: WebSocketConnection,
141
+ ) => WebSocketHandlers;
142
+ websocketConnectionHandler: (ws: WebSocket, req: IncomingMessage) => void;
143
+ };
144
+
145
+ // WebSocket type from ws package
146
+ interface WebSocket {
147
+ readyState: number;
148
+ OPEN: number;
149
+ send: (data: string) => void;
150
+ close: (code?: number, reason?: string) => void;
151
+ on: (event: string, handler: (...args: any[]) => void) => void;
152
+ }
153
+
154
+ // Effect-native API
155
+ export type ExpressUploadistaServer = {
156
+ handler: (req: Request, res: Response) => Effect.Effect<void, never, never>;
157
+ uploadServer: Layer.Layer<UploadServer>;
158
+ flowServer: Layer.Layer<FlowServer>;
159
+ websocketHandler: (
160
+ req: IncomingMessage,
161
+ connection: WebSocketConnection,
162
+ ) => WebSocketHandlers;
163
+ };
164
+
165
+ // Effect-based service factory for creating the unified adapter layer
166
+ const createExpressUploadistaAdapterServiceLayer = (
167
+ baseUrl: string,
168
+ authMiddleware?: (req: Request, res: Response) => Promise<AuthResult>,
169
+ authCacheConfig?: AuthCacheConfig,
170
+ metricsLayer?: Layer.Layer<MetricsService, never, never>,
171
+ ) =>
172
+ Layer.effect(
173
+ ExpressUploadistaAdapterService,
174
+ Effect.gen(function* () {
175
+ const uploadServer = yield* UploadServer;
176
+ const flowServer = yield* FlowServer;
177
+
178
+ // Create auth cache layer (always present, even if auth is not enabled)
179
+ const authCacheLayer = AuthCacheServiceLive(authCacheConfig);
180
+
181
+ return {
182
+ handler: (req: Request, res: Response) =>
183
+ Effect.gen(function* () {
184
+ // Call auth middleware if configured and create auth context layer
185
+ let authContext: AuthContext | null = null;
186
+ if (authMiddleware) {
187
+ // Run auth middleware with timeout protection (5 seconds default)
188
+ const authMiddlewareWithTimeout = Effect.tryPromise({
189
+ try: () => authMiddleware(req, res),
190
+ catch: (error) => {
191
+ console.error("Auth middleware error:", error);
192
+ return { _tag: "AuthError" as const, error };
193
+ },
194
+ }).pipe(
195
+ Effect.timeout("5 seconds"),
196
+ Effect.catchAll((error) => {
197
+ // Check if timeout occurred
198
+ if (error && typeof error === "object" && "_tag" in error) {
199
+ if (error._tag === "TimeoutException") {
200
+ console.error(
201
+ "Auth middleware timeout exceeded (5 seconds)",
202
+ );
203
+ return Effect.succeed({
204
+ _tag: "TimeoutError" as const,
205
+ } as const);
206
+ }
207
+ }
208
+ return Effect.succeed(null);
209
+ }),
210
+ );
211
+
212
+ const authResult:
213
+ | AuthContext
214
+ | null
215
+ | { _tag: "TimeoutError" }
216
+ | { _tag: "AuthError"; error: unknown } =
217
+ yield* authMiddlewareWithTimeout;
218
+
219
+ // If auth middleware timed out, return 503 Service Unavailable
220
+ if (
221
+ authResult &&
222
+ typeof authResult === "object" &&
223
+ "_tag" in authResult &&
224
+ authResult._tag === "TimeoutError"
225
+ ) {
226
+ res.status(503).json({
227
+ error: "Authentication service unavailable",
228
+ message:
229
+ "Authentication took too long to respond. Please try again.",
230
+ });
231
+ return;
232
+ }
233
+
234
+ // If auth middleware returned null, authentication failed
235
+ if (authResult === null) {
236
+ res.status(401).json({
237
+ error: "Unauthorized",
238
+ message: "Invalid credentials",
239
+ });
240
+ return;
241
+ }
242
+
243
+ // Check for error marker (shouldn't happen after catchAll, but for type safety)
244
+ if (
245
+ authResult &&
246
+ typeof authResult === "object" &&
247
+ "_tag" in authResult &&
248
+ authResult._tag === "AuthError"
249
+ ) {
250
+ res.status(500).json({
251
+ error: "Internal Server Error",
252
+ message: "An error occurred during authentication",
253
+ });
254
+ return;
255
+ }
256
+
257
+ authContext = authResult;
258
+ }
259
+
260
+ // Create auth context layer for this request
261
+ const authContextLayer = AuthContextServiceLive(authContext);
262
+
263
+ // Combine auth context, auth cache, and metrics layers
264
+ // Always provide a metrics layer (either real or no-op) to satisfy type requirements
265
+ const authLayer = Layer.mergeAll(
266
+ authContextLayer,
267
+ authCacheLayer,
268
+ metricsLayer ?? NoOpMetricsServiceLive,
269
+ );
270
+
271
+ const url = new URL(req.url, `http://${req.get("host")}`);
272
+
273
+ // Check for uploadista/api/ prefix
274
+ if (!url.pathname.includes(`${baseUrl}/api/`)) {
275
+ res.status(404).json({ error: "Not found" });
276
+ return;
277
+ }
278
+
279
+ // Remove the prefix and get the actual route segments
280
+ const routeSegments = url.pathname
281
+ .replace(`${baseUrl}/api/`, "")
282
+ .split("/")
283
+ .filter(Boolean);
284
+
285
+ // Parse JSON body for routes that need it
286
+ const needsJsonBody =
287
+ (routeSegments.includes("upload") && req.method === "POST") ||
288
+ (routeSegments.includes("flow") && req.method === "POST") ||
289
+ (routeSegments.includes("continue") &&
290
+ req.get("Content-Type")?.includes("application/json"));
291
+
292
+ if (needsJsonBody && !req.body) {
293
+ // Manually parse JSON body
294
+ yield* Effect.tryPromise({
295
+ try: async () => {
296
+ const chunks: Buffer[] = [];
297
+ for await (const chunk of req) {
298
+ chunks.push(chunk as Buffer);
299
+ }
300
+ const body = Buffer.concat(chunks).toString();
301
+ req.body = JSON.parse(body);
302
+ },
303
+ catch: (error) => error,
304
+ });
305
+ }
306
+
307
+ // Route based on path
308
+ if (routeSegments.includes("upload")) {
309
+ // Upload API routes - these now create jobs behind the scenes
310
+ switch (req.method) {
311
+ case "POST":
312
+ return yield* handleUploadPost(req, res, uploadServer).pipe(
313
+ Effect.provide(authLayer),
314
+ );
315
+ case "GET":
316
+ return yield* handleUploadGet(req, res, uploadServer).pipe(
317
+ Effect.provide(authLayer),
318
+ );
319
+ case "PATCH":
320
+ return yield* handleUploadPatch(req, res, uploadServer).pipe(
321
+ Effect.provide(authLayer),
322
+ );
323
+ default:
324
+ res.status(405).json({ error: "Method not allowed" });
325
+ return;
326
+ }
327
+ } else if (routeSegments.includes("flow")) {
328
+ // Flow API routes
329
+ switch (req.method) {
330
+ case "GET":
331
+ return yield* handleFlowGet(req, res, flowServer).pipe(
332
+ Effect.provide(authLayer),
333
+ );
334
+ case "POST":
335
+ return yield* handleFlowPost<never>(
336
+ req,
337
+ res,
338
+ flowServer,
339
+ ).pipe(Effect.provide(authLayer));
340
+ default:
341
+ res.status(405).json({ error: "Method not allowed" });
342
+ return;
343
+ }
344
+ } else if (routeSegments.includes("jobs")) {
345
+ // Unified job status routes
346
+ if (req.method === "GET" && url.pathname.endsWith("/status")) {
347
+ return yield* handleJobStatus(req, res, flowServer).pipe(
348
+ Effect.provide(authLayer),
349
+ );
350
+ } else if (
351
+ req.method === "PATCH" &&
352
+ routeSegments.includes("continue")
353
+ ) {
354
+ return yield* handleContinueFlow<never>(
355
+ req,
356
+ res,
357
+ flowServer,
358
+ ).pipe(Effect.provide(authLayer));
359
+ }
360
+ res.status(405).json({ error: "Method not allowed" });
361
+ return;
362
+ } else {
363
+ res.status(404).json({ error: "Not found" });
364
+ return;
365
+ }
366
+ }).pipe(
367
+ Effect.catchAll(() =>
368
+ Effect.sync(() => {
369
+ res.status(500).json({ error: "Internal server error" });
370
+ }),
371
+ ),
372
+ ),
373
+
374
+ websocketHandler: createUploadistaWebSocketHandler(
375
+ baseUrl,
376
+ uploadServer,
377
+ flowServer,
378
+ ),
379
+ } satisfies ExpressUploadistaAdapterServiceShape;
380
+ }),
381
+ );
382
+
383
+ /**
384
+ * Creates an Effect-native unified Express server - combining upload and flow capabilities
385
+ */
386
+ export const createExpressUploadistaServer = <TRequirements = UploadServer>({
387
+ baseUrl,
388
+ flowProvider,
389
+ eventEmitter,
390
+ dataStore,
391
+ bufferedDataStore,
392
+ kvStore,
393
+ generateId = GenerateIdLive,
394
+ authMiddleware,
395
+ authCacheConfig,
396
+ metricsLayer,
397
+ }: InternalExpressUploadistaAdapterOptions<TRequirements>): Effect.Effect<ExpressUploadistaServer> => {
398
+ // Set up upload server dependencies using shared utility
399
+ const uploadServerLayer = createUploadServerLayer({
400
+ kvStore,
401
+ eventEmitter,
402
+ dataStore,
403
+ bufferedDataStore,
404
+ generateId,
405
+ });
406
+
407
+ // Set up flow server dependencies using shared utility
408
+ const flowServerLayer = createFlowServerLayer({
409
+ kvStore,
410
+ eventEmitter,
411
+ flowProvider,
412
+ uploadServer: uploadServerLayer,
413
+ });
414
+
415
+ // Set up adapter
416
+ const adapterLayer = Layer.provide(
417
+ createExpressUploadistaAdapterServiceLayer(
418
+ baseUrl,
419
+ authMiddleware,
420
+ authCacheConfig,
421
+ metricsLayer,
422
+ ),
423
+ Layer.mergeAll(uploadServerLayer, flowServerLayer),
424
+ );
425
+
426
+ return Effect.gen(function* () {
427
+ const adapterService = yield* ExpressUploadistaAdapterService;
428
+
429
+ return {
430
+ handler: (req: Request, res: Response) =>
431
+ adapterService.handler(req, res),
432
+ websocketHandler: (
433
+ req: IncomingMessage,
434
+ connection: WebSocketConnection,
435
+ ) => adapterService.websocketHandler(req, connection),
436
+ uploadServer: uploadServerLayer,
437
+ flowServer: flowServerLayer,
438
+ } satisfies ExpressUploadistaServer;
439
+ }).pipe(Effect.provide(adapterLayer));
440
+ };
441
+
442
+ const runProgram = <A, E>(
443
+ effect: Effect.Effect<A, E>,
444
+ withTracing: boolean,
445
+ ) => {
446
+ if (withTracing) {
447
+ return Effect.runPromise(effect.pipe(Effect.provide(NodeSdkLive)));
448
+ }
449
+ return Effect.runPromise(effect);
450
+ };
451
+
452
+ /**
453
+ * Creates a Promise-based Express adapter for compatibility with existing Express applications
454
+ * This wraps the Effect-native version with Promise conversion and caches the server instance
455
+ */
456
+ export const createInternalExpressUploadistaAdapter = async <
457
+ TRequirements = UploadServer,
458
+ TPlugins extends readonly Layer.Layer<TRequirements, never, never>[] = [],
459
+ >({
460
+ baseUrl,
461
+ flowProvider,
462
+ plugins,
463
+ eventEmitter,
464
+ dataStore,
465
+ bufferedDataStore,
466
+ kvStore,
467
+ withTracing = false,
468
+ authMiddleware,
469
+ authCacheConfig,
470
+ metricsLayer,
471
+ }: InternalExpressUploadistaAdapterOptions<
472
+ TRequirements,
473
+ TPlugins
474
+ >): Promise<ExpressUploadistaAdapter> => {
475
+ // Create and cache the Effect server instance
476
+ const uploadistaServer = await Effect.runPromise(
477
+ createExpressUploadistaServer<TRequirements>({
478
+ baseUrl,
479
+ flowProvider,
480
+ eventEmitter,
481
+ dataStore,
482
+ bufferedDataStore,
483
+ kvStore,
484
+ authMiddleware,
485
+ authCacheConfig,
486
+ metricsLayer,
487
+ }),
488
+ );
489
+
490
+ // Merge all plugin layers so we can provide them when running handlers
491
+ const pluginLayers = Layer.mergeAll(
492
+ uploadistaServer.uploadServer,
493
+ ...(plugins ?? []),
494
+ ) as Layer.Layer<UploadServer | TRequirements, never, never>;
495
+
496
+ return {
497
+ baseUrl,
498
+ handler: (req: Request, res: Response, next) => {
499
+ runProgram(
500
+ uploadistaServer.handler(req, res).pipe(Effect.provide(pluginLayers)),
501
+ withTracing,
502
+ ).catch((error) => {
503
+ console.error("Express adapter error:", error);
504
+ if (next) next(error);
505
+ });
506
+ },
507
+ websocketHandler: (req: IncomingMessage, connection: WebSocketConnection) =>
508
+ uploadistaServer.websocketHandler(req, connection),
509
+ websocketConnectionHandler: (ws: WebSocket, req: IncomingMessage) => {
510
+ // Filter to only handle uploadista WebSocket paths
511
+ if (!req.url?.startsWith(`/${baseUrl}/ws/`)) {
512
+ ws.close(1008, "Invalid WebSocket path");
513
+ return;
514
+ }
515
+
516
+ console.log(`📡 WebSocket connected: ${req.url}`);
517
+
518
+ const connection: WebSocketConnection = {
519
+ id: `conn_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
520
+ send: (data: string) => {
521
+ if (ws.readyState === ws.OPEN) {
522
+ ws.send(data);
523
+ }
524
+ },
525
+ close: (code?: number, reason?: string) => ws.close(code, reason),
526
+ readyState: ws.readyState,
527
+ };
528
+
529
+ // Initialize WebSocket handler with Uploadista and get event handlers
530
+ const handlers = uploadistaServer.websocketHandler(req, connection);
531
+
532
+ // Attach event handlers
533
+ ws.on("message", (message: Buffer) => {
534
+ handlers.onMessage(message.toString());
535
+ });
536
+
537
+ ws.on("close", () => {
538
+ handlers.onClose();
539
+ });
540
+
541
+ ws.on("error", (error: Error) => {
542
+ handlers.onError(error);
543
+ });
544
+ },
545
+ } satisfies ExpressUploadistaAdapter;
546
+ };
547
+
548
+ /**
549
+ * Creates a Promise-based Uploadista Express adapter for compatibility
550
+ *
551
+ * Note: Ensure that the plugins array provides all services required by your flows.
552
+ * Missing plugin services will result in runtime errors during flow execution.
553
+ */
554
+ export const createExpressUploadistaAdapter = async <
555
+ TFlows extends (
556
+ flowId: string,
557
+ clientId: string | null,
558
+ ) => Effect.Effect<
559
+ Flow<z.ZodSchema<unknown>, z.ZodSchema<unknown>, unknown>,
560
+ UploadistaError,
561
+ unknown
562
+ // biome-ignore lint/suspicious/noExplicitAny: Generic type constraint allows any flow function type
563
+ > = any,
564
+ // biome-ignore lint/suspicious/noExplicitAny: Permissive constraint allows plugin tuples, validation done at runtime
565
+ TPlugins extends readonly Layer.Layer<any, never, never>[] = Layer.Layer<
566
+ any,
567
+ never,
568
+ never
569
+ >[],
570
+ >({
571
+ baseUrl = "uploadista",
572
+ flows,
573
+ // Default to an empty plugin list while preserving the generic type
574
+ plugins = [] as unknown as TPlugins,
575
+ eventEmitter,
576
+ eventBroadcaster = memoryEventBroadcaster,
577
+ dataStore,
578
+ bufferedDataStore,
579
+ kvStore,
580
+ generateId = GenerateIdLive,
581
+ authMiddleware,
582
+ authCacheConfig,
583
+ metricsLayer,
584
+ }: ExpressUploadistaAdapterOptions<
585
+ TFlows,
586
+ TPlugins
587
+ >): Promise<ExpressUploadistaAdapter> => {
588
+ type FlowReq = FlowRequirementsOf<TFlows>;
589
+ // Create a simplified flow provider that uses the flows function directly
590
+ const createFlowProvider = Effect.succeed({
591
+ getFlow: (flowId: string, clientId: string | null) => {
592
+ // The flows function returns an Effect with TRequirements context,
593
+ // but the FlowProvider interface expects no context.
594
+ // We cast this to match the interface - the requirements will be provided
595
+ // at the layer level when the flow adapter is created.
596
+ return flows(flowId, clientId) as Effect.Effect<
597
+ Flow<z.ZodSchema<unknown>, z.ZodSchema<unknown>, FlowReq>,
598
+ UploadistaError
599
+ >;
600
+ },
601
+ });
602
+
603
+ // Create the flow provider layer that provides the requirements
604
+ const flowProvider = Layer.effect(FlowProvider, createFlowProvider);
605
+
606
+ // Default eventEmitter to webSocketEventEmitter with the provided eventBroadcaster
607
+ const finalEventEmitter =
608
+ eventEmitter ?? webSocketEventEmitter(eventBroadcaster);
609
+
610
+ // Convert DataStoreConfig to Layer
611
+ const dataStoreLayer = await createDataStoreLayer(dataStore);
612
+
613
+ return createInternalExpressUploadistaAdapter<FlowReq, TPlugins>({
614
+ baseUrl,
615
+ flowProvider,
616
+ plugins,
617
+ eventEmitter: finalEventEmitter,
618
+ dataStore: dataStoreLayer,
619
+ bufferedDataStore,
620
+ kvStore,
621
+ generateId,
622
+ authMiddleware,
623
+ authCacheConfig,
624
+ metricsLayer,
625
+ });
626
+ };