functype 0.8.67 → 0.8.69
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 +136 -6
- package/dist/Either-BfXNbTHo.d.ts +533 -0
- package/dist/Map-vivbm5n0.d.ts +65 -0
- package/dist/{Tuple-DfdXAbL_.d.ts → Valuable-CtuVEKTZ.d.ts} +17 -10
- package/dist/chunk-5DWCHDSA.mjs +39 -0
- package/dist/chunk-5DWCHDSA.mjs.map +1 -0
- package/dist/chunk-7PQA3W7W.mjs +2 -0
- package/dist/chunk-7PQA3W7W.mjs.map +1 -0
- package/dist/either/index.d.ts +2 -3
- package/dist/either/index.mjs +1 -1
- package/dist/fpromise/index.d.ts +373 -3
- package/dist/fpromise/index.mjs +1 -1
- package/dist/index.d.ts +533 -2
- package/dist/index.mjs +1 -1
- package/dist/list/index.d.ts +2 -3
- package/dist/list/index.mjs +1 -1
- package/dist/map/index.d.ts +4 -3
- package/dist/map/index.mjs +1 -1
- package/dist/option/index.d.ts +2 -987
- package/dist/option/index.mjs +1 -1
- package/dist/set/index.d.ts +2 -3
- package/dist/set/index.mjs +1 -1
- package/dist/try/index.d.ts +59 -3
- package/dist/try/index.mjs +1 -1
- package/dist/tuple/index.d.ts +12 -1
- package/dist/tuple/index.mjs +1 -1
- package/package.json +17 -16
- package/readme/BUNDLE_OPTIMIZATION.md +74 -0
- package/readme/FPromise-Assessment.md +43 -0
- package/readme/HKT.md +110 -0
- package/readme/ROADMAP.md +113 -0
- package/readme/TASK-IMPLEMENTATION.md +290 -0
- package/readme/TASK-TODO.md +40 -0
- package/readme/TASK-UPDATES.md +64 -0
- package/readme/TUPLE-EXAMPLES.md +79 -0
- package/readme/TaskMigration.md +129 -0
- package/readme/ai-guide.md +406 -0
- package/readme/examples.md +2093 -0
- package/readme/quick-reference.md +514 -0
- package/readme/task-cancellation-progress.md +258 -0
- package/readme/task-error-handling.md +128 -0
- package/readme/task-quick-reference.md +157 -0
- package/readme/tasks.md +205 -0
- package/readme/type-index.md +238 -0
- package/dist/chunk-NTL4HYMA.mjs +0 -18
- package/dist/chunk-NTL4HYMA.mjs.map +0 -1
- package/dist/chunk-PXFJPCM7.mjs +0 -2
- package/dist/chunk-PXFJPCM7.mjs.map +0 -1
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Task Implementation Analysis and Best Practices
|
|
2
|
+
|
|
3
|
+
This document provides a deep analysis of the Task implementation in functype, discussing its design considerations, trade-offs, and recommendations for effective usage.
|
|
4
|
+
|
|
5
|
+
## Overview of Task Implementation
|
|
6
|
+
|
|
7
|
+
The Task module provides a robust bridge between functional programming patterns and asynchronous operations. It offers a structured way to handle errors, cancellation, and progress tracking while maintaining type safety.
|
|
8
|
+
|
|
9
|
+
Key features include:
|
|
10
|
+
|
|
11
|
+
- Named task contexts for richer error information
|
|
12
|
+
- Explicit try/catch/finally semantics
|
|
13
|
+
- Interoperability with Promise-based code
|
|
14
|
+
- Cancellation support
|
|
15
|
+
- Progress tracking for long-running operations
|
|
16
|
+
|
|
17
|
+
## Advanced Design Considerations
|
|
18
|
+
|
|
19
|
+
### Error Context Propagation
|
|
20
|
+
|
|
21
|
+
**Current Behavior:**
|
|
22
|
+
When tasks are nested, the outer task's context (name, description) overwrites the inner task's context in error propagation:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// If an error occurs in the inner task:
|
|
26
|
+
await Task({ name: "OuterTask" }).Async(async () => {
|
|
27
|
+
return Task({ name: "InnerTask" }).Async(async () => {
|
|
28
|
+
throw new Error("Inner error")
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// The error will have:
|
|
33
|
+
// - message: "Inner error"
|
|
34
|
+
// - name: "OuterTask" (not "InnerTask")
|
|
35
|
+
// - taskInfo: { name: "OuterTask", ... }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This happens because when the inner task's promise rejects, it's caught by the outer task, which wraps it in its own error context. This is technically correct from a runtime perspective but might be counterintuitive when debugging.
|
|
39
|
+
|
|
40
|
+
**Recommendation:**
|
|
41
|
+
Consider implementing an option to preserve the innermost error context:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// Possible enhancement
|
|
45
|
+
const preserveErrorContext = true
|
|
46
|
+
await Task({ name: "OuterTask", preserveErrorContext }).Async(...)
|
|
47
|
+
|
|
48
|
+
// Which would yield an error with:
|
|
49
|
+
// - Error context: "InnerTask" (preserved from inner task)
|
|
50
|
+
// - Original context chain: ["OuterTask", "InnerTask"] (for tracing)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Asynchronous Edge Cases
|
|
54
|
+
|
|
55
|
+
Some asynchronous patterns can create complexity in error handling, particularly around cancellation and finally blocks:
|
|
56
|
+
|
|
57
|
+
**Potential Issues:**
|
|
58
|
+
|
|
59
|
+
1. Race conditions between cancellation signals and operation completion
|
|
60
|
+
2. Callback execution order with finally blocks and cancellation
|
|
61
|
+
3. Resource cleanup timing in complex async flows
|
|
62
|
+
|
|
63
|
+
**Recommendations:**
|
|
64
|
+
|
|
65
|
+
- Use explicit state machines for complex task flows
|
|
66
|
+
- Implement deterministic callback ordering guarantees
|
|
67
|
+
- Add explicit resource tracking for critical operations
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Structured resource pattern example
|
|
71
|
+
Task.withResource(
|
|
72
|
+
() => openResource(),
|
|
73
|
+
(resource) => useResource(resource),
|
|
74
|
+
(resource) => closeResource(resource),
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Performance Considerations
|
|
79
|
+
|
|
80
|
+
Task operations add some overhead compared to native Promises:
|
|
81
|
+
|
|
82
|
+
1. **Extra Promise wrapping** - Each Task operation creates additional Promise layers
|
|
83
|
+
2. **Error conversion** - Converting to Throwable objects has a small cost
|
|
84
|
+
3. **Context maintenance** - Task metadata is carried throughout the operation
|
|
85
|
+
4. **Cancellation checks** - Additional checks during execution
|
|
86
|
+
|
|
87
|
+
**Recommendations:**
|
|
88
|
+
|
|
89
|
+
- Use the Task API selectively for operations where the benefits outweigh costs
|
|
90
|
+
- Consider a lightweight mode for performance-critical paths:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Hypothetical lightweight mode
|
|
94
|
+
const lightTask = Task.light()
|
|
95
|
+
lightTask.Async(...) // Minimal overhead, fewer features
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- Group multiple small tasks into larger task units to amortize overhead
|
|
99
|
+
|
|
100
|
+
## Cancellation Best Practices
|
|
101
|
+
|
|
102
|
+
The cancellation system is cooperative rather than preemptive, meaning tasks must actively check for cancellation:
|
|
103
|
+
|
|
104
|
+
### Effective Cancellation Patterns
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// 1. Regular checking pattern
|
|
108
|
+
Task.cancellable(async (token) => {
|
|
109
|
+
for (const item of largeDataset) {
|
|
110
|
+
// Check cancellation periodically
|
|
111
|
+
if (token.isCancelled) {
|
|
112
|
+
return // Exit early
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Process item
|
|
116
|
+
await processItem(item)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// 2. Integration with fetch and DOM APIs
|
|
121
|
+
Task.cancellable(async (token) => {
|
|
122
|
+
const response = await fetch(url, {
|
|
123
|
+
signal: token.signal, // AbortSignal integration
|
|
124
|
+
})
|
|
125
|
+
return await response.json()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// 3. Custom cancellation behavior
|
|
129
|
+
Task.cancellable(async (token) => {
|
|
130
|
+
let resources = []
|
|
131
|
+
|
|
132
|
+
token.onCancel(() => {
|
|
133
|
+
// Clean up resources when cancelled
|
|
134
|
+
resources.forEach((r) => r.dispose())
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Main task work...
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Cancellation Caveats
|
|
142
|
+
|
|
143
|
+
1. **Cancellation is not guaranteed to be immediate** - Tasks may continue briefly after cancellation
|
|
144
|
+
2. **External resources may need explicit cleanup** - Database connections, file handles, etc.
|
|
145
|
+
3. **Third-party APIs may not support cancellation** - Wrapping them requires careful handling
|
|
146
|
+
|
|
147
|
+
## Progress Tracking
|
|
148
|
+
|
|
149
|
+
For long-running operations, progress tracking provides visibility:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// Basic progress usage
|
|
153
|
+
const { task, currentProgress } = Task.withProgress(
|
|
154
|
+
async (updateProgress) => {
|
|
155
|
+
updateProgress(0) // Start
|
|
156
|
+
|
|
157
|
+
// Do work in chunks
|
|
158
|
+
for (let i = 0; i < 10; i++) {
|
|
159
|
+
await doChunk(i)
|
|
160
|
+
updateProgress((i + 1) * 10) // 10%, 20%, etc.
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return result
|
|
164
|
+
},
|
|
165
|
+
(percent) => {
|
|
166
|
+
// Update UI with progress
|
|
167
|
+
updateProgressBar(percent)
|
|
168
|
+
},
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
// Combined with cancellation
|
|
172
|
+
const { task, cancel, currentProgress } = Task.withProgress(async (updateProgress, token) => {
|
|
173
|
+
// Both progress tracking and cancellation
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Progress Implementation Considerations
|
|
178
|
+
|
|
179
|
+
1. **Progress granularity** - Too frequent updates can impact performance
|
|
180
|
+
2. **Cancellation interaction** - Progress should stop updating when cancelled
|
|
181
|
+
3. **Indeterminate phases** - Some operations can't report precise progress
|
|
182
|
+
|
|
183
|
+
## Advanced Task Composition
|
|
184
|
+
|
|
185
|
+
### Task Racing
|
|
186
|
+
|
|
187
|
+
The `Task.race` functionality allows racing multiple tasks against each other with optional timeout:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Race with timeout
|
|
191
|
+
const raceResult = await Task.race([slowOperation(), fastOperation()], 1000) // 1 second timeout
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Potential Future Composition Enhancements
|
|
195
|
+
|
|
196
|
+
1. **Structured Concurrency**
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// Hypothetical API
|
|
200
|
+
const { results, errors } = await Task.scope(async (scope) => {
|
|
201
|
+
// All tasks in scope have linked lifecycle
|
|
202
|
+
const task1 = scope.spawn(operation1())
|
|
203
|
+
const task2 = scope.spawn(operation2())
|
|
204
|
+
|
|
205
|
+
return await scope.join() // Wait for all to complete or fail
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
2. **Resource Management**
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
// Hypothetical API for guaranteed resource cleanup
|
|
213
|
+
await Task.using(
|
|
214
|
+
() => openDatabase(), // Acquire
|
|
215
|
+
async (db) => {
|
|
216
|
+
// Use resource
|
|
217
|
+
return await db.query()
|
|
218
|
+
},
|
|
219
|
+
(db) => db.close(), // Always called
|
|
220
|
+
)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Testing Considerations
|
|
224
|
+
|
|
225
|
+
Testing Task-based code requires special attention:
|
|
226
|
+
|
|
227
|
+
1. **Time and async flow control** - Use timeouts and explicit resolutions
|
|
228
|
+
2. **Cancellation testing** - Test both normal and cancelled paths
|
|
229
|
+
3. **Progress testing** - Verify progress reporting accuracy
|
|
230
|
+
4. **Error context verification** - Check error details are preserved
|
|
231
|
+
|
|
232
|
+
Example property tests:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// Task Async should satisfy monad laws
|
|
236
|
+
test("Right identity: t.Async(f) >>= id = t.Async(f)", () => {
|
|
237
|
+
fc.assert(
|
|
238
|
+
fc.property(fc.string(), async (value) => {
|
|
239
|
+
const f = async () => value
|
|
240
|
+
const taskResult = await Task().Async(f)
|
|
241
|
+
const directResult = await f()
|
|
242
|
+
return taskResult === directResult
|
|
243
|
+
}),
|
|
244
|
+
)
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Integration with Other Patterns
|
|
249
|
+
|
|
250
|
+
Tasks work especially well when combined with other functional patterns:
|
|
251
|
+
|
|
252
|
+
### With Option
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// Convert nullable API response to Option
|
|
256
|
+
const getUserOption = async (id: string) => {
|
|
257
|
+
const response = await Task().Async(async () => {
|
|
258
|
+
return fetch(`/api/users/${id}`)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
return Option.fromNullable(response)
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### With Either
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// Type-specific error handling
|
|
269
|
+
const result = await Task().Async(async () => {
|
|
270
|
+
// Operation returning Either<ApiError, UserData>
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// Handle specific error types
|
|
274
|
+
result.match({
|
|
275
|
+
left: (error) => handleApiError(error),
|
|
276
|
+
right: (data) => useData(data),
|
|
277
|
+
})
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Conclusion
|
|
281
|
+
|
|
282
|
+
The Task implementation offers a powerful approach to functional asynchronous programming. By understanding its design trade-offs and following best practices, developers can create more robust, maintainable code with better error handling.
|
|
283
|
+
|
|
284
|
+
Remember that Task is not a replacement for all Promise usage - evaluate when the additional features justify the slightly increased complexity and overhead.
|
|
285
|
+
|
|
286
|
+
## See Also
|
|
287
|
+
|
|
288
|
+
- [Task Error Handling](../docs/task-error-handling.md)
|
|
289
|
+
- [Task Migration Guide](../docs/TaskMigration.md)
|
|
290
|
+
- [Task TODO](../docs/TASK-TODO.md)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Task TODO
|
|
2
|
+
|
|
3
|
+
## Goals
|
|
4
|
+
|
|
5
|
+
- Enhance the Task module as an adapter between promise-based code and functional patterns
|
|
6
|
+
- Improve interoperability with existing JavaScript/TypeScript codebases
|
|
7
|
+
- Allow gradual migration to functional patterns without complete rewrites
|
|
8
|
+
|
|
9
|
+
## Implementation Tasks
|
|
10
|
+
|
|
11
|
+
- [x] Review current Task implementation for completeness of promise integration
|
|
12
|
+
- [x] Ensure robust error handling in sync/async conversions
|
|
13
|
+
- [x] Document explicit try/catch/finally semantics
|
|
14
|
+
- [x] Add examples showing migration from promise-based to functional patterns
|
|
15
|
+
- [x] Add utilities to simplify Task composition with promise-returning functions
|
|
16
|
+
- [x] Create migration guide for converting promise chains to Task operations
|
|
17
|
+
- [x] Create Task.race for racing multiple tasks with timeout support
|
|
18
|
+
- [x] Implement Task.fromNodeCallback for Node.js style callbacks
|
|
19
|
+
- [x] Implement cancellation support for Task operations
|
|
20
|
+
- [x] Add progress tracking for long-running Task operations
|
|
21
|
+
|
|
22
|
+
## Design Considerations
|
|
23
|
+
|
|
24
|
+
- Maintain clear separation between synchronous and promise-based operations
|
|
25
|
+
- Preserve functional error handling patterns while supporting promise interop
|
|
26
|
+
- Keep API consistent with the rest of the library's functional approach
|
|
27
|
+
|
|
28
|
+
## Completed Enhancements
|
|
29
|
+
|
|
30
|
+
- Added `fromPromise` adapter to convert promise-returning functions to Task-compatible functions
|
|
31
|
+
- Added `toPromise` converter to transform Task results back to promises
|
|
32
|
+
- Enhanced documentation with clearer descriptions of functionality
|
|
33
|
+
- Created TaskMigration.md guide showing how to migrate from promises to functional Task patterns
|
|
34
|
+
- Added comprehensive tests for the new adapter methods
|
|
35
|
+
- Implemented `Task.race` for racing multiple tasks with timeout support
|
|
36
|
+
- Added `Task.fromNodeCallback` for Node.js-style callback integration
|
|
37
|
+
- Implemented cancellation support with CancellationToken pattern
|
|
38
|
+
- Added progress tracking for long-running operations
|
|
39
|
+
- Created property-based tests verifying monadic laws and edge cases
|
|
40
|
+
- Added comprehensive documentation for all new functionality
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Task Module Updates Summary
|
|
2
|
+
|
|
3
|
+
## Overview of Enhancements
|
|
4
|
+
|
|
5
|
+
We've made the following significant enhancements to the Task module:
|
|
6
|
+
|
|
7
|
+
1. **Implemented Task.race** - For racing multiple tasks with timeout support
|
|
8
|
+
2. **Added Task.fromNodeCallback** - For Node.js-style callback integration
|
|
9
|
+
3. **Implemented Cancellation Support** - For stopping long-running operations
|
|
10
|
+
4. **Added Progress Tracking** - For monitoring operation completion status
|
|
11
|
+
5. **Comprehensive Tests** - Including property-based tests and edge case handling
|
|
12
|
+
|
|
13
|
+
## Documentation Updates
|
|
14
|
+
|
|
15
|
+
The following new documentation has been created:
|
|
16
|
+
|
|
17
|
+
1. **[TASK-IMPLEMENTATION.md](../docs/TASK-IMPLEMENTATION.md)** - Deep analysis of Task design considerations and trade-offs
|
|
18
|
+
2. **[task-cancellation-progress.md](task-cancellation-progress.md)** - Guide to using cancellation and progress features
|
|
19
|
+
3. **[task-quick-reference.md](task-quick-reference.md)** - Compact reference for all Task functionality
|
|
20
|
+
|
|
21
|
+
## Current Status
|
|
22
|
+
|
|
23
|
+
The Task implementation now:
|
|
24
|
+
|
|
25
|
+
- Provides a robust bridge between functional patterns and async operations
|
|
26
|
+
- Seamlessly integrates with existing Promise-based and Node.js callback APIs
|
|
27
|
+
- Supports cancellation with proper resource cleanup
|
|
28
|
+
- Tracks progress for long-running operations
|
|
29
|
+
- Maintains rich error context throughout the operation lifecycle
|
|
30
|
+
- Follows functional programming principles
|
|
31
|
+
|
|
32
|
+
## Recommendations for Integration
|
|
33
|
+
|
|
34
|
+
### Documentation Recommendations
|
|
35
|
+
|
|
36
|
+
1. **Update quick-reference.md**: Add Task section based on task-quick-reference.md
|
|
37
|
+
2. **Update TASK-TODO.md**: Mark newly implemented items as completed
|
|
38
|
+
3. **Update tasks.md**: Mark cancellation and Node.js callback items as completed
|
|
39
|
+
|
|
40
|
+
### Implementation Recommendations
|
|
41
|
+
|
|
42
|
+
1. **Error Context Preservation**: Consider an option to preserve innermost task context
|
|
43
|
+
2. **Timeout Support**: Add global timeout support for any task operation
|
|
44
|
+
3. **Structured Resource Management**: Implement a `Task.using` pattern for guaranteed cleanup
|
|
45
|
+
4. **Lightweight Mode**: Consider a performance-optimized Task variant for hot paths
|
|
46
|
+
|
|
47
|
+
## Next Steps
|
|
48
|
+
|
|
49
|
+
Based on the existing roadmap and our implementation, these items could be prioritized:
|
|
50
|
+
|
|
51
|
+
1. **Error Aggregation for Parallel Operations**: For collecting multiple errors when running tasks in parallel
|
|
52
|
+
2. **Task Scope API**: For structured concurrency patterns with linked task lifecycles
|
|
53
|
+
3. **Framework Integration**: Create React/Vue hooks for Task management
|
|
54
|
+
4. **Performance Monitoring**: Add performance tracking capabilities to tasks
|
|
55
|
+
|
|
56
|
+
## Testing Observations
|
|
57
|
+
|
|
58
|
+
During the implementation and testing process, we discovered:
|
|
59
|
+
|
|
60
|
+
1. **Task Nesting Behavior**: Outer task context overwrites inner task context in errors
|
|
61
|
+
2. **Cancellation Edge Cases**: Some race conditions exist in rapidly cancelled tasks
|
|
62
|
+
3. **Testing Challenges**: Timing-dependent tests require careful structuring
|
|
63
|
+
|
|
64
|
+
Overall, the Task implementation is now significantly more robust and feature-complete, providing a strong foundation for asynchronous functional programming in TypeScript.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Tuple Enhanced with Modern TypeScript Features
|
|
2
|
+
|
|
3
|
+
## What's Improved
|
|
4
|
+
|
|
5
|
+
Our enhanced `Tuple` implementation leverages modern TypeScript features:
|
|
6
|
+
|
|
7
|
+
1. **Const type parameters** (TypeScript 5.0+)
|
|
8
|
+
2. **Variadic tuple types** (TypeScript 4.0+)
|
|
9
|
+
3. **Stronger type inference** for tuple elements
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
### Basic Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Tuple } from "@/tuple"
|
|
17
|
+
|
|
18
|
+
// Create a tuple with mixed types
|
|
19
|
+
const personTuple = Tuple(["John Doe", 42, true])
|
|
20
|
+
|
|
21
|
+
// Access values with type safety
|
|
22
|
+
const name = personTuple.get(0) // Type is string
|
|
23
|
+
const age = personTuple.get(1) // Type is number
|
|
24
|
+
const active = personTuple.get(2) // Type is boolean
|
|
25
|
+
|
|
26
|
+
// Transform the tuple
|
|
27
|
+
const mapped = personTuple.map((values) => values.map((x) => (typeof x === "number" ? x * 2 : x)))
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Preserving Literal Types
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// Using 'as const' with the enhanced implementation
|
|
34
|
+
const literalTuple = Tuple([1, "hello", true] as const)
|
|
35
|
+
|
|
36
|
+
// TypeScript now knows the exact types:
|
|
37
|
+
const first = literalTuple.get(0) // Type is exactly 1 (not just number)
|
|
38
|
+
const second = literalTuple.get(1) // Type is exactly 'hello' (not just string)
|
|
39
|
+
const third = literalTuple.get(2) // Type is exactly true (not just boolean)
|
|
40
|
+
|
|
41
|
+
// Benefits:
|
|
42
|
+
// - Better type checking
|
|
43
|
+
// - Autocomplete shows exact values
|
|
44
|
+
// - Prevents invalid index access
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Type-Level Utilities (for future improvements)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Example of potential future type utilities
|
|
51
|
+
type FirstElement<T extends Tuple<unknown[]>> = T extends Tuple<[infer F, ...unknown[]]> ? F : never
|
|
52
|
+
|
|
53
|
+
// Extract first element's type
|
|
54
|
+
type First = FirstElement<typeof literalTuple> // Would be 1
|
|
55
|
+
|
|
56
|
+
// Could expand to other utilities like:
|
|
57
|
+
// - LastElement
|
|
58
|
+
// - RemoveFirst
|
|
59
|
+
// - Prepend<T, Item>
|
|
60
|
+
// - etc.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## When Is This Useful?
|
|
64
|
+
|
|
65
|
+
1. **Strong typing for heterogeneous collections**:
|
|
66
|
+
|
|
67
|
+
- When you need to store different types in a fixed structure
|
|
68
|
+
- When you want type safety beyond what arrays provide
|
|
69
|
+
|
|
70
|
+
2. **APIs returning fixed-length, mixed-type results**:
|
|
71
|
+
|
|
72
|
+
- When a function returns multiple values of different types
|
|
73
|
+
|
|
74
|
+
3. **Data transformation pipelines**:
|
|
75
|
+
|
|
76
|
+
- When you need to transform data while preserving type information
|
|
77
|
+
|
|
78
|
+
4. **Configuration objects with fixed format**:
|
|
79
|
+
- When config must follow a specific format with specific types at each position
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Task Migration Guide
|
|
2
|
+
|
|
3
|
+
This guide demonstrates how to migrate traditional Promise-based code to the functional `Task` pattern in the functype library.
|
|
4
|
+
|
|
5
|
+
## The Problem with Traditional Promises
|
|
6
|
+
|
|
7
|
+
Traditional JavaScript/TypeScript Promise-based code often suffers from:
|
|
8
|
+
|
|
9
|
+
1. Implicit error handling (errors silently propagate)
|
|
10
|
+
2. Hard-to-trace error stacks
|
|
11
|
+
3. Mixed error types without type safety
|
|
12
|
+
4. Verbose try/catch blocks
|
|
13
|
+
5. Side effects spread throughout the codebase
|
|
14
|
+
|
|
15
|
+
## Migration Path: Traditional Promises → Task
|
|
16
|
+
|
|
17
|
+
### Step 1: Identify Promise-Based Code
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// Traditional promise-based API call
|
|
21
|
+
function fetchUserData(userId: string): Promise<UserData> {
|
|
22
|
+
return fetch(`/api/users/${userId}`).then((response) => {
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`Failed to fetch user: ${response.statusText}`)
|
|
25
|
+
}
|
|
26
|
+
return response.json()
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Usage with promise chains
|
|
31
|
+
function displayUserProfile(userId: string): Promise<void> {
|
|
32
|
+
return fetchUserData(userId)
|
|
33
|
+
.then((userData) => {
|
|
34
|
+
renderProfile(userData)
|
|
35
|
+
})
|
|
36
|
+
.catch((error) => {
|
|
37
|
+
showErrorMessage(error)
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Step 2: Convert to Task with fromPromise adapter
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { Task } from "@/core/task/Task"
|
|
46
|
+
|
|
47
|
+
// Step 1: Create a Task wrapper
|
|
48
|
+
const userTask = Task<UserData>({ name: "UserOperations" })
|
|
49
|
+
|
|
50
|
+
// Step 2: Convert promise function to Task function
|
|
51
|
+
const fetchUserData = (userId: string): FPromise<UserData> => {
|
|
52
|
+
return userTask.fromPromise((id: string) =>
|
|
53
|
+
fetch(`/api/users/${id}`).then((response) => {
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error(`Failed to fetch user: ${response.statusText}`)
|
|
56
|
+
}
|
|
57
|
+
return response.json()
|
|
58
|
+
}),
|
|
59
|
+
)(userId)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Step 3: Use in Task-based workflow
|
|
63
|
+
const displayUserProfile = (userId: string): FPromise<void> => {
|
|
64
|
+
return fetchUserData(userId)
|
|
65
|
+
.then((userData) => {
|
|
66
|
+
renderProfile(userData)
|
|
67
|
+
return undefined
|
|
68
|
+
})
|
|
69
|
+
.catch((error) => {
|
|
70
|
+
showErrorMessage(error)
|
|
71
|
+
return undefined
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Step 3: Fully Embrace the Functional Pattern
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { Task, TaskResult, TaskException } from "@/core/task/Task"
|
|
80
|
+
import { Either } from "@/either/Either"
|
|
81
|
+
import { Throwable } from "@/core/throwable/Throwable"
|
|
82
|
+
|
|
83
|
+
// Create a domain-specific Task
|
|
84
|
+
const userTask = Task<UserData>({ name: "UserOperations" })
|
|
85
|
+
|
|
86
|
+
// Fully functional API
|
|
87
|
+
const fetchUserData = (userId: string): FPromise<Either<Throwable, UserData>> => {
|
|
88
|
+
return FPromise<Either<Throwable, UserData>>(async (resolve) => {
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetch(`/api/users/${userId}`)
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
resolve(userTask.fail(new Error(`Failed to fetch user: ${response.statusText}`)))
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
const data = await response.json()
|
|
96
|
+
resolve(userTask.success(data))
|
|
97
|
+
} catch (error) {
|
|
98
|
+
resolve(userTask.fail(error))
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Functional composition with proper error handling
|
|
104
|
+
const displayUserProfile = (userId: string): FPromise<void> => {
|
|
105
|
+
return fetchUserData(userId).then((result) => {
|
|
106
|
+
if (result.isRight()) {
|
|
107
|
+
renderProfile(result.get())
|
|
108
|
+
} else {
|
|
109
|
+
showErrorMessage(result.get())
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Benefits of Task-Based Approach
|
|
116
|
+
|
|
117
|
+
1. **Explicit Error Handling**: Errors are represented as values in the Either type
|
|
118
|
+
2. **Type Safety**: Error and success paths are fully typed
|
|
119
|
+
3. **Composition**: Tasks can be composed with other functional patterns
|
|
120
|
+
4. **Interoperability**: Can work with existing Promise-based code
|
|
121
|
+
5. **Testability**: Pure functions are easier to test
|
|
122
|
+
|
|
123
|
+
## Best Practices
|
|
124
|
+
|
|
125
|
+
1. Use descriptive names for Tasks that reflect their domain purpose
|
|
126
|
+
2. Leverage the `fromPromise` adapter for gradual migration
|
|
127
|
+
3. Combine with Either for full type safety in error handling
|
|
128
|
+
4. Use `toPromise` when integrating back with traditional Promise-based APIs
|
|
129
|
+
5. Keep Task operations pure when possible
|