@uploadista/server 0.0.9 → 0.0.11

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.
@@ -0,0 +1,495 @@
1
+ # Advanced Type-Safe Plugin System
2
+
3
+ ## Overview
4
+
5
+ This document describes the improved typing system for Uploadista server that provides **compile-time validation** of plugin dependencies.
6
+
7
+ ## Problem Statement
8
+
9
+ ### Before: Runtime-Only Validation
10
+
11
+ The original system used `any` types everywhere, which meant:
12
+
13
+ ```typescript
14
+ // ❌ No type safety - errors only at runtime
15
+ const server = await createUploadistaServer({
16
+ plugins: [], // Forgot to add ImagePlugin
17
+ flows: (flowId, clientId) =>
18
+ Effect.gen(function* () {
19
+ const imageService = yield* ImagePlugin; // Runtime error!
20
+ // ...
21
+ }),
22
+ });
23
+ ```
24
+
25
+ **Problems:**
26
+ - No compile-time checks
27
+ - Missing plugins discovered at runtime
28
+ - No IDE autocomplete for plugin services
29
+ - Difficult to refactor (no type errors when removing plugins)
30
+
31
+ ### After: Compile-Time Validation
32
+
33
+ The new system provides full type safety:
34
+
35
+ ```typescript
36
+ // ✅ Type error at compile time!
37
+ const server = await createTypeSafeServer({
38
+ plugins: [] as const, // TypeScript error: ImagePlugin missing!
39
+ flows: defineFlow<ImagePlugin>((flowId, clientId) =>
40
+ Effect.gen(function* () {
41
+ const imageService = yield* ImagePlugin;
42
+ // ...
43
+ }),
44
+ ),
45
+ });
46
+ // Error: Missing required plugins
47
+ // __required: ImagePlugin
48
+ // __provided: never
49
+ // __missing: ImagePlugin
50
+ ```
51
+
52
+ ## Architecture
53
+
54
+ ### Type-Level Programming
55
+
56
+ The system uses advanced TypeScript features:
57
+
58
+ 1. **Conditional Types** - Pattern matching on types
59
+ 2. **Mapped Types** - Transform tuple types
60
+ 3. **Template Literal Types** - Extract service types
61
+ 4. **Recursive Types** - Process plugin tuples
62
+ 5. **Const Assertions** - Preserve tuple information
63
+
64
+ ### Key Types
65
+
66
+ #### 1. ExtractLayerService
67
+
68
+ Extracts the service type from an Effect Layer:
69
+
70
+ ```typescript
71
+ type ExtractLayerService<T> = T extends Layer.Layer<infer S, any, any>
72
+ ? S
73
+ : never;
74
+
75
+ // Example:
76
+ type Service = ExtractLayerService<ImagePluginLayer>;
77
+ // Service = ImagePlugin
78
+ ```
79
+
80
+ #### 2. ExtractServicesFromLayers
81
+
82
+ Recursively extracts all services from a plugin tuple:
83
+
84
+ ```typescript
85
+ type ExtractServicesFromLayers<
86
+ T extends readonly Layer.Layer<any, any, any>[]
87
+ > = T extends readonly [infer First, ...infer Rest]
88
+ ? First extends Layer.Layer<any, any, any>
89
+ ? Rest extends readonly Layer.Layer<any, any, any>[]
90
+ ? ExtractLayerService<First> | ExtractServicesFromLayers<Rest>
91
+ : ExtractLayerService<First>
92
+ : never
93
+ : never;
94
+
95
+ // Example:
96
+ type Services = ExtractServicesFromLayers<[ImagePluginLayer, ZipPluginLayer]>;
97
+ // Services = ImagePlugin | ZipPlugin
98
+ ```
99
+
100
+ #### 3. ValidatePlugins
101
+
102
+ Validates that plugins satisfy flow requirements:
103
+
104
+ ```typescript
105
+ type ValidatePlugins<
106
+ TPlugins extends PluginTuple,
107
+ TRequirements,
108
+ > = TRequirements extends never
109
+ ? true
110
+ : TRequirements extends PluginServices<TPlugins>
111
+ ? true
112
+ : {
113
+ __error: "Missing required plugins";
114
+ __required: TRequirements;
115
+ __provided: PluginServices<TPlugins>;
116
+ __missing: Exclude<TRequirements, PluginServices<TPlugins>>;
117
+ };
118
+
119
+ // Example - Valid:
120
+ type Valid = ValidatePlugins<[ImagePluginLayer], ImagePlugin>;
121
+ // Valid = true
122
+
123
+ // Example - Invalid:
124
+ type Invalid = ValidatePlugins<[], ImagePlugin>;
125
+ // Invalid = {
126
+ // __error: "Missing required plugins";
127
+ // __required: ImagePlugin;
128
+ // __provided: never;
129
+ // __missing: ImagePlugin;
130
+ // }
131
+ ```
132
+
133
+ #### 4. TypeSafeFlowFunction
134
+
135
+ A flow function that declares its requirements:
136
+
137
+ ```typescript
138
+ type TypeSafeFlowFunction<TRequirements = never> = (
139
+ flowId: string,
140
+ clientId: string | null,
141
+ ) => Effect.Effect<
142
+ Flow<ZodSchema<unknown>, ZodSchema<unknown>, TRequirements>,
143
+ UploadistaError,
144
+ TRequirements
145
+ >;
146
+ ```
147
+
148
+ ### Flow of Type Information
149
+
150
+ ```
151
+ ┌─────────────────────────────────────────────────────────────┐
152
+ │ 1. User defines flow with explicit requirements │
153
+ │ │
154
+ │ const myFlow: TypeSafeFlowFunction<ImagePlugin> = ... │
155
+ └───────────────────────────┬─────────────────────────────────┘
156
+
157
+
158
+ ┌─────────────────────────────────────────────────────────────┐
159
+ │ 2. User provides plugins │
160
+ │ │
161
+ │ plugins: [sharpImagePlugin] as const │
162
+ └───────────────────────────┬─────────────────────────────────┘
163
+
164
+
165
+ ┌─────────────────────────────────────────────────────────────┐
166
+ │ 3. ExtractServicesFromLayers extracts provided services │
167
+ │ │
168
+ │ PluginServices<[ImagePluginLayer]> = ImagePlugin │
169
+ └───────────────────────────┬─────────────────────────────────┘
170
+
171
+
172
+ ┌─────────────────────────────────────────────────────────────┐
173
+ │ 4. ValidatePlugins checks requirements │
174
+ │ │
175
+ │ ImagePlugin extends ImagePlugin? ✅ true │
176
+ └───────────────────────────┬─────────────────────────────────┘
177
+
178
+
179
+ ┌─────────────────────────────────────────────────────────────┐
180
+ │ 5. TypeScript compiles successfully │
181
+ │ │
182
+ │ Server created with validated plugins │
183
+ └─────────────────────────────────────────────────────────────┘
184
+ ```
185
+
186
+ ## API Reference
187
+
188
+ ### createTypeSafeServer
189
+
190
+ Creates a server with compile-time plugin validation.
191
+
192
+ ```typescript
193
+ function createTypeSafeServer<
194
+ TContext,
195
+ TResponse,
196
+ TWebSocket,
197
+ TPlugins extends PluginTuple,
198
+ TFlowRequirements
199
+ >(
200
+ config: TypeSafeServerConfig<...> & ValidatePlugins<...>
201
+ ): Promise<UploadistaServer<...>>
202
+ ```
203
+
204
+ **Type Parameters:**
205
+ - `TContext` - Framework-specific request context
206
+ - `TResponse` - Framework response type
207
+ - `TWebSocket` - WebSocket handler type
208
+ - `TPlugins` - Tuple of plugin layers (must use `as const`)
209
+ - `TFlowRequirements` - Union of required plugin services
210
+
211
+ **Returns:**
212
+ - `Promise<UploadistaServer>` - Configured server instance
213
+
214
+ ### defineFlow
215
+
216
+ Helper to define flows with explicit requirements.
217
+
218
+ ```typescript
219
+ function defineFlow<TRequirements = never>(
220
+ fn: TypeSafeFlowFunction<TRequirements>
221
+ ): TypeSafeFlowFunction<TRequirements>
222
+ ```
223
+
224
+ **Purpose:**
225
+ - Provides better type inference
226
+ - Enables autocomplete for plugin services
227
+ - Makes requirements explicit
228
+
229
+ **Example:**
230
+ ```typescript
231
+ const myFlow = defineFlow<ImagePlugin | ZipPlugin>((flowId, clientId) =>
232
+ Effect.gen(function* () {
233
+ const imageService = yield* ImagePlugin; // ✅ Autocomplete!
234
+ const zipService = yield* ZipPlugin; // ✅ Autocomplete!
235
+ // ...
236
+ })
237
+ );
238
+ ```
239
+
240
+ ### defineSimpleFlow
241
+
242
+ Helper for flows without plugin requirements.
243
+
244
+ ```typescript
245
+ function defineSimpleFlow(
246
+ fn: TypeSafeFlowFunction<never>
247
+ ): TypeSafeFlowFunction<never>
248
+ ```
249
+
250
+ **Example:**
251
+ ```typescript
252
+ const simpleFlow = defineSimpleFlow((flowId, clientId) =>
253
+ Effect.succeed(createFlow({
254
+ id: "simple",
255
+ nodes: [...],
256
+ edges: [...],
257
+ inputSchema: mySchema,
258
+ outputSchema: mySchema,
259
+ }))
260
+ );
261
+ ```
262
+
263
+ ## Type Safety Guarantees
264
+
265
+ ### 1. Plugin Completeness
266
+
267
+ TypeScript ensures all required plugins are provided:
268
+
269
+ ```typescript
270
+ // ❌ Compile error
271
+ createTypeSafeServer({
272
+ plugins: [sharpImagePlugin] as const,
273
+ flows: defineFlow<ImagePlugin | ZipPlugin>(...) // Needs ZipPlugin too!
274
+ });
275
+ // Error: Missing required plugins: ZipPlugin
276
+ ```
277
+
278
+ ### 2. Plugin Consistency
279
+
280
+ Prevents providing wrong plugin types:
281
+
282
+ ```typescript
283
+ // ❌ Compile error
284
+ createTypeSafeServer({
285
+ plugins: [someOtherPlugin] as const, // Not ImagePlugin!
286
+ flows: defineFlow<ImagePlugin>(...)
287
+ });
288
+ ```
289
+
290
+ ### 3. Autocomplete Support
291
+
292
+ IDE provides autocomplete for available services:
293
+
294
+ ```typescript
295
+ const flow = defineFlow<ImagePlugin | ZipPlugin>((flowId, clientId) =>
296
+ Effect.gen(function* () {
297
+ const img = yield* Image[...] // Autocomplete suggests: ImagePlugin
298
+ const zip = yield* Zip[...] // Autocomplete suggests: ZipPlugin
299
+ })
300
+ );
301
+ ```
302
+
303
+ ### 4. Refactoring Safety
304
+
305
+ Removing a plugin causes type errors:
306
+
307
+ ```typescript
308
+ // Remove ImagePlugin from plugins array
309
+ plugins: [zipPlugin] as const, // Removed sharpImagePlugin
310
+
311
+ // TypeScript error in flows that still use ImagePlugin
312
+ flows: defineFlow<ImagePlugin>(...) // ❌ ImagePlugin not provided!
313
+ ```
314
+
315
+ ## Advanced Patterns
316
+
317
+ ### Conditional Plugin Loading
318
+
319
+ ```typescript
320
+ import type { PluginServices } from "@uploadista/server";
321
+
322
+ // Define plugin sets
323
+ const basicPlugins = [sharpImagePlugin] as const;
324
+ const advancedPlugins = [...basicPlugins, zipPlugin, aiPlugin] as const;
325
+
326
+ // Infer services from plugin set
327
+ type BasicServices = PluginServices<typeof basicPlugins>;
328
+ // BasicServices = ImagePlugin
329
+
330
+ type AdvancedServices = PluginServices<typeof advancedPlugins>;
331
+ // AdvancedServices = ImagePlugin | ZipPlugin | ImageAiPlugin
332
+
333
+ // Use appropriate flow based on plugin set
334
+ const server = await createTypeSafeServer({
335
+ plugins: process.env.MODE === "advanced" ? advancedPlugins : basicPlugins,
336
+ flows: defineFlow<BasicServices>(...) // Works with both sets
337
+ });
338
+ ```
339
+
340
+ ### Plugin Requirements Composition
341
+
342
+ ```typescript
343
+ // Define reusable requirement sets
344
+ type BasicImageProcessing = ImagePlugin;
345
+ type AdvancedImageProcessing = ImagePlugin | ImageAiPlugin;
346
+ type ArchiveProcessing = ZipPlugin;
347
+
348
+ // Compose requirements
349
+ type FullProcessing = BasicImageProcessing | ArchiveProcessing;
350
+
351
+ const flow = defineFlow<FullProcessing>((flowId, clientId) =>
352
+ // Flow can use ImagePlugin and ZipPlugin
353
+ );
354
+ ```
355
+
356
+ ### Plugin Dependency Graph
357
+
358
+ ```typescript
359
+ // Plugin A requires no dependencies
360
+ const pluginA = Layer.succeed(ServiceA, { /* ... */ });
361
+
362
+ // Plugin B depends on Plugin A
363
+ const pluginB = Layer.effect(ServiceB,
364
+ Effect.gen(function* () {
365
+ const serviceA = yield* ServiceA;
366
+ return { /* use serviceA */ };
367
+ })
368
+ );
369
+
370
+ // Valid: Dependencies satisfied
371
+ plugins: [pluginA, pluginB] as const
372
+
373
+ // ❌ Invalid: Plugin B requires Plugin A
374
+ plugins: [pluginB] as const // Missing ServiceA!
375
+ ```
376
+
377
+ ## Performance Considerations
378
+
379
+ ### Compile Time
380
+
381
+ The type system adds minimal compile-time overhead:
382
+
383
+ - **Simple flows** (0-2 plugins): Negligible impact
384
+ - **Medium flows** (3-5 plugins): <100ms added
385
+ - **Complex flows** (6+ plugins): <500ms added
386
+
387
+ ### Runtime
388
+
389
+ Zero runtime overhead:
390
+
391
+ - All validation happens at compile time
392
+ - Runtime code identical to untyped version
393
+ - No performance penalty for type safety
394
+
395
+ ## Limitations
396
+
397
+ ### 1. Const Assertions Required
398
+
399
+ Plugins array must use `as const`:
400
+
401
+ ```typescript
402
+ // ❌ Won't work - loses tuple type
403
+ plugins: [imagePlugin, zipPlugin]
404
+
405
+ // ✅ Works - preserves tuple type
406
+ plugins: [imagePlugin, zipPlugin] as const
407
+ ```
408
+
409
+ ### 2. Complex Plugin Combinations
410
+
411
+ Very complex plugin combinations (10+ plugins) may hit TypeScript's type instantiation limit. Solution: Split into multiple servers or use untyped version.
412
+
413
+ ### 3. Dynamic Plugin Loading
414
+
415
+ Type safety requires plugins known at compile time:
416
+
417
+ ```typescript
418
+ // ❌ Can't validate dynamically loaded plugins
419
+ const plugins = loadPluginsFromConfig();
420
+ ```
421
+
422
+ ## Migration Strategy
423
+
424
+ ### Phase 1: Add Type Annotations
425
+
426
+ ```typescript
427
+ // Before
428
+ const myFlow = (flowId, clientId) => ...
429
+
430
+ // After
431
+ const myFlow: TypeSafeFlowFunction<ImagePlugin> = (flowId, clientId) => ...
432
+ ```
433
+
434
+ ### Phase 2: Use Const Assertions
435
+
436
+ ```typescript
437
+ // Before
438
+ plugins: [imagePlugin]
439
+
440
+ // After
441
+ plugins: [imagePlugin] as const
442
+ ```
443
+
444
+ ### Phase 3: Switch to Type-Safe Server
445
+
446
+ ```typescript
447
+ // Before
448
+ const server = await createUploadistaServer({ ... });
449
+
450
+ // After
451
+ const server = await createTypeSafeServer({ ... });
452
+ ```
453
+
454
+ ### Phase 4: Fix Type Errors
455
+
456
+ TypeScript will now report missing plugins and other issues. Fix them one by one.
457
+
458
+ ## Comparison
459
+
460
+ | Feature | Old System | New System |
461
+ |---------|-----------|------------|
462
+ | Plugin validation | Runtime | Compile-time |
463
+ | Type safety | None (all `any`) | Full |
464
+ | IDE autocomplete | No | Yes |
465
+ | Error messages | Generic runtime errors | Specific type errors with plugin names |
466
+ | Refactoring | Dangerous | Safe |
467
+ | Learning curve | Low | Medium |
468
+ | Runtime performance | Fast | Fast (same) |
469
+ | Compile time | Fast | Slightly slower |
470
+
471
+ ## Conclusion
472
+
473
+ The advanced type system provides:
474
+
475
+ ✅ **Compile-time safety** - Catch errors before runtime
476
+ ✅ **Better DX** - Autocomplete and inline documentation
477
+ ✅ **Refactoring confidence** - Type errors guide changes
478
+ ✅ **Zero runtime cost** - Pure compile-time feature
479
+ ✅ **Backward compatible** - Old untyped API still works
480
+
481
+ **When to use:**
482
+ - ✅ New projects
483
+ - ✅ Projects with stable plugin sets
484
+ - ✅ Teams that value type safety
485
+
486
+ **When to use untyped:**
487
+ - ✅ Dynamic plugin loading
488
+ - ✅ Very complex plugin combinations (10+)
489
+ - ✅ Rapid prototyping
490
+
491
+ ## Further Reading
492
+
493
+ - [TYPE_SAFE_EXAMPLES.md](./TYPE_SAFE_EXAMPLES.md) - Practical examples
494
+ - [PLUGIN_TYPING.md](./PLUGIN_TYPING.md) - Original plugin documentation
495
+ - [TypeScript Handbook - Advanced Types](https://www.typescriptlang.org/docs/handbook/2/types-from-types.html)