@zambit/elevate-ts 0.1.2

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 (170) hide show
  1. package/CLA-CORPORATE.md +72 -0
  2. package/CLA-INDIVIDUAL.md +71 -0
  3. package/DUAL-PUBLISHING-STRATEGY.md +571 -0
  4. package/LICENSE +610 -0
  5. package/README.md +77 -0
  6. package/dist/cjs/Either.d.ts +67 -0
  7. package/dist/cjs/Either.d.ts.map +1 -0
  8. package/dist/cjs/Either.js +147 -0
  9. package/dist/cjs/Either.js.map +1 -0
  10. package/dist/cjs/EitherAsync.d.ts +139 -0
  11. package/dist/cjs/EitherAsync.d.ts.map +1 -0
  12. package/dist/cjs/EitherAsync.js +171 -0
  13. package/dist/cjs/EitherAsync.js.map +1 -0
  14. package/dist/cjs/Function.d.ts +153 -0
  15. package/dist/cjs/Function.d.ts.map +1 -0
  16. package/dist/cjs/Function.js +110 -0
  17. package/dist/cjs/Function.js.map +1 -0
  18. package/dist/cjs/List.d.ts +134 -0
  19. package/dist/cjs/List.d.ts.map +1 -0
  20. package/dist/cjs/List.js +243 -0
  21. package/dist/cjs/List.js.map +1 -0
  22. package/dist/cjs/Maybe.d.ts +64 -0
  23. package/dist/cjs/Maybe.d.ts.map +1 -0
  24. package/dist/cjs/Maybe.js +122 -0
  25. package/dist/cjs/Maybe.js.map +1 -0
  26. package/dist/cjs/MaybeAsync.d.ts +115 -0
  27. package/dist/cjs/MaybeAsync.d.ts.map +1 -0
  28. package/dist/cjs/MaybeAsync.js +151 -0
  29. package/dist/cjs/MaybeAsync.js.map +1 -0
  30. package/dist/cjs/NonEmptyList.d.ts +86 -0
  31. package/dist/cjs/NonEmptyList.d.ts.map +1 -0
  32. package/dist/cjs/NonEmptyList.js +128 -0
  33. package/dist/cjs/NonEmptyList.js.map +1 -0
  34. package/dist/cjs/Reader.d.ts +53 -0
  35. package/dist/cjs/Reader.d.ts.map +1 -0
  36. package/dist/cjs/Reader.js +60 -0
  37. package/dist/cjs/Reader.js.map +1 -0
  38. package/dist/cjs/State.d.ts +71 -0
  39. package/dist/cjs/State.d.ts.map +1 -0
  40. package/dist/cjs/State.js +94 -0
  41. package/dist/cjs/State.js.map +1 -0
  42. package/dist/cjs/Tuple.d.ts +69 -0
  43. package/dist/cjs/Tuple.d.ts.map +1 -0
  44. package/dist/cjs/Tuple.js +73 -0
  45. package/dist/cjs/Tuple.js.map +1 -0
  46. package/dist/cjs/Validation.d.ts +53 -0
  47. package/dist/cjs/Validation.d.ts.map +1 -0
  48. package/dist/cjs/Validation.js +77 -0
  49. package/dist/cjs/Validation.js.map +1 -0
  50. package/dist/cjs/index.d.ts +12 -0
  51. package/dist/cjs/index.d.ts.map +1 -0
  52. package/dist/cjs/index.js +25 -0
  53. package/dist/cjs/index.js.map +1 -0
  54. package/dist/cjs/tokens/config.d.ts +43 -0
  55. package/dist/cjs/tokens/config.d.ts.map +1 -0
  56. package/dist/cjs/tokens/config.js +162 -0
  57. package/dist/cjs/tokens/config.js.map +1 -0
  58. package/dist/cjs/tokens/cssGen.d.ts +18 -0
  59. package/dist/cjs/tokens/cssGen.d.ts.map +1 -0
  60. package/dist/cjs/tokens/cssGen.js +144 -0
  61. package/dist/cjs/tokens/cssGen.js.map +1 -0
  62. package/dist/cjs/tokens/generateCSS.d.ts +18 -0
  63. package/dist/cjs/tokens/generateCSS.d.ts.map +1 -0
  64. package/dist/cjs/tokens/generateCSS.js +83 -0
  65. package/dist/cjs/tokens/generateCSS.js.map +1 -0
  66. package/dist/cjs/tokens/index.d.ts +13 -0
  67. package/dist/cjs/tokens/index.d.ts.map +1 -0
  68. package/dist/cjs/tokens/index.js +12 -0
  69. package/dist/cjs/tokens/index.js.map +1 -0
  70. package/dist/cjs/tsconfig.tsbuildinfo +1 -0
  71. package/dist/esm/Either.d.ts +67 -0
  72. package/dist/esm/Either.d.ts.map +1 -0
  73. package/dist/esm/Either.js +136 -0
  74. package/dist/esm/Either.js.map +1 -0
  75. package/dist/esm/EitherAsync.d.ts +139 -0
  76. package/dist/esm/EitherAsync.d.ts.map +1 -0
  77. package/dist/esm/EitherAsync.js +173 -0
  78. package/dist/esm/EitherAsync.js.map +1 -0
  79. package/dist/esm/Function.d.ts +153 -0
  80. package/dist/esm/Function.d.ts.map +1 -0
  81. package/dist/esm/Function.js +109 -0
  82. package/dist/esm/Function.js.map +1 -0
  83. package/dist/esm/List.d.ts +134 -0
  84. package/dist/esm/List.d.ts.map +1 -0
  85. package/dist/esm/List.js +243 -0
  86. package/dist/esm/List.js.map +1 -0
  87. package/dist/esm/Maybe.d.ts +64 -0
  88. package/dist/esm/Maybe.d.ts.map +1 -0
  89. package/dist/esm/Maybe.js +113 -0
  90. package/dist/esm/Maybe.js.map +1 -0
  91. package/dist/esm/MaybeAsync.d.ts +115 -0
  92. package/dist/esm/MaybeAsync.d.ts.map +1 -0
  93. package/dist/esm/MaybeAsync.js +150 -0
  94. package/dist/esm/MaybeAsync.js.map +1 -0
  95. package/dist/esm/NonEmptyList.d.ts +86 -0
  96. package/dist/esm/NonEmptyList.d.ts.map +1 -0
  97. package/dist/esm/NonEmptyList.js +128 -0
  98. package/dist/esm/NonEmptyList.js.map +1 -0
  99. package/dist/esm/Reader.d.ts +53 -0
  100. package/dist/esm/Reader.d.ts.map +1 -0
  101. package/dist/esm/Reader.js +60 -0
  102. package/dist/esm/Reader.js.map +1 -0
  103. package/dist/esm/State.d.ts +71 -0
  104. package/dist/esm/State.d.ts.map +1 -0
  105. package/dist/esm/State.js +94 -0
  106. package/dist/esm/State.js.map +1 -0
  107. package/dist/esm/Tuple.d.ts +69 -0
  108. package/dist/esm/Tuple.d.ts.map +1 -0
  109. package/dist/esm/Tuple.js +73 -0
  110. package/dist/esm/Tuple.js.map +1 -0
  111. package/dist/esm/Validation.d.ts +53 -0
  112. package/dist/esm/Validation.d.ts.map +1 -0
  113. package/dist/esm/Validation.js +79 -0
  114. package/dist/esm/Validation.js.map +1 -0
  115. package/dist/esm/index.d.ts +12 -0
  116. package/dist/esm/index.d.ts.map +1 -0
  117. package/dist/esm/index.js +22 -0
  118. package/dist/esm/index.js.map +1 -0
  119. package/dist/esm/src/Either.d.ts +67 -0
  120. package/dist/esm/src/Either.d.ts.map +1 -0
  121. package/dist/esm/src/Either.js +147 -0
  122. package/dist/esm/src/Either.js.map +1 -0
  123. package/dist/esm/src/EitherAsync.d.ts +139 -0
  124. package/dist/esm/src/EitherAsync.d.ts.map +1 -0
  125. package/dist/esm/src/EitherAsync.js +171 -0
  126. package/dist/esm/src/EitherAsync.js.map +1 -0
  127. package/dist/esm/src/Function.d.ts +153 -0
  128. package/dist/esm/src/Function.d.ts.map +1 -0
  129. package/dist/esm/src/Function.js +110 -0
  130. package/dist/esm/src/Function.js.map +1 -0
  131. package/dist/esm/src/List.d.ts +134 -0
  132. package/dist/esm/src/List.d.ts.map +1 -0
  133. package/dist/esm/src/List.js +243 -0
  134. package/dist/esm/src/List.js.map +1 -0
  135. package/dist/esm/src/Maybe.d.ts +64 -0
  136. package/dist/esm/src/Maybe.d.ts.map +1 -0
  137. package/dist/esm/src/Maybe.js +122 -0
  138. package/dist/esm/src/Maybe.js.map +1 -0
  139. package/dist/esm/src/MaybeAsync.d.ts +115 -0
  140. package/dist/esm/src/MaybeAsync.d.ts.map +1 -0
  141. package/dist/esm/src/MaybeAsync.js +151 -0
  142. package/dist/esm/src/MaybeAsync.js.map +1 -0
  143. package/dist/esm/src/NonEmptyList.d.ts +86 -0
  144. package/dist/esm/src/NonEmptyList.d.ts.map +1 -0
  145. package/dist/esm/src/NonEmptyList.js +128 -0
  146. package/dist/esm/src/NonEmptyList.js.map +1 -0
  147. package/dist/esm/src/Reader.d.ts +53 -0
  148. package/dist/esm/src/Reader.d.ts.map +1 -0
  149. package/dist/esm/src/Reader.js +60 -0
  150. package/dist/esm/src/Reader.js.map +1 -0
  151. package/dist/esm/src/State.d.ts +71 -0
  152. package/dist/esm/src/State.d.ts.map +1 -0
  153. package/dist/esm/src/State.js +94 -0
  154. package/dist/esm/src/State.js.map +1 -0
  155. package/dist/esm/src/Tuple.d.ts +69 -0
  156. package/dist/esm/src/Tuple.d.ts.map +1 -0
  157. package/dist/esm/src/Tuple.js +73 -0
  158. package/dist/esm/src/Tuple.js.map +1 -0
  159. package/dist/esm/src/Validation.d.ts +53 -0
  160. package/dist/esm/src/Validation.d.ts.map +1 -0
  161. package/dist/esm/src/Validation.js +77 -0
  162. package/dist/esm/src/Validation.js.map +1 -0
  163. package/dist/esm/src/index.d.ts +12 -0
  164. package/dist/esm/src/index.d.ts.map +1 -0
  165. package/dist/esm/src/index.js +25 -0
  166. package/dist/esm/src/index.js.map +1 -0
  167. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
  168. package/elevate-ts-vs-effect-critique.md +806 -0
  169. package/eslint.config.js +104 -0
  170. package/package.json +139 -0
@@ -0,0 +1,806 @@
1
+ # elevate-ts vs Effect-TS: Technical Comparison & Critique
2
+
3
+ **Date:** 2026-04-10
4
+ **Scope:** Runtime-agnostic functional programming libraries for TypeScript
5
+ **Excluded:** Node.js-specific concerns (elevate-ts does not target Node.js)
6
+
7
+ ---
8
+
9
+ ## 1. Core Type Design
10
+
11
+ ### elevate-ts: Simple, Focused Monads
12
+
13
+ ```typescript
14
+ // Maybe<A>
15
+ type Maybe<A> = Just<A> | Nothing;
16
+
17
+ // Either<L, R>
18
+ type Either<L, R> = Left<L> | Right<R>;
19
+
20
+ // Reader<R, A> — simple environment threading
21
+ type Reader<R, A> = { readonly tag: 'Reader'; readonly run: (env: R) => A };
22
+ ```
23
+
24
+ **Characteristics:**
25
+
26
+ - Single-value result types; no intrinsic dependency context
27
+ - Reader is just an alias for `(env: R) => A`, wrapped for branded clarity
28
+ - All three core monads are **discriminated unions** with tagged structures
29
+ - No concept of a unifying effect type; composition is modular but not hierarchical
30
+ - ~50 lines per monad implementation
31
+
32
+ **Strengths:**
33
+
34
+ - Dead simple to understand and teach
35
+ - Zero learning curve for JavaScript developers
36
+ - Minimal surface area means fewer bugs in the library itself
37
+ - Extremely fast—no hidden allocations or fiber overhead
38
+
39
+ **Weaknesses:**
40
+
41
+ - No way to encode "this computation requires dependency X" _in the type itself_
42
+ - Splitting error handling concerns (expected errors vs unexpected failures) requires separate conventions
43
+ - No first-class way to model concurrent or cancellable computations
44
+ - Reader is lazy but stateless; cannot model resource acquisition/release
45
+ - Composing independent effects (fan-out) requires manual lifting into arrays or tuples
46
+
47
+ ---
48
+
49
+ ### Effect-TS: Unified Effect Type
50
+
51
+ ```typescript
52
+ // Effect<A, E, R> — three-dimensional computation
53
+ type Effect<out A, out E = never, out R = never> = ...
54
+
55
+ // Exit<A, E> — the actual result of running an Effect
56
+ type Exit<A, E = never> = Success<A> | Failure<Cause<E>>
57
+
58
+ // Cause<E> — lossless error structure
59
+ type Cause<E> = Fail<E> | Die | Interrupt | Sequential<E> | Parallel<E>
60
+ ```
61
+
62
+ **Characteristics:**
63
+
64
+ - Single unified effect type; three type parameters encode **success** (A), **error** (E), and **environment/dependencies** (R)
65
+ - Exit/Cause model captures complete failure history: typed errors, unexpected defects, interruptions, and compositions
66
+ - All effects are **lazy** (not executed until run); enables precise control over resource lifecycle
67
+ - Environment (R) is threaded through the type system; used to encode dependencies and resource scopes
68
+ - ~1000+ lines across Effect, Exit, Cause, Context, Layer modules (feature-complete)
69
+
70
+ **Strengths:**
71
+
72
+ - **R parameter is mandatory in the type.** Can never accidentally run an effect without providing required dependencies
73
+ - **Cause model is lossless.** If your effect throws, fails, or gets cancelled—all of that is captured. Debugging concurrent systems becomes tractable
74
+ - **Single effect type simplifies composition.** `Effect.all`, `Effect.race`, etc. work on the same type
75
+ - **Resource safety by default.** Layer handles acquisition, cleanup, and error recovery automatically
76
+ - Type system prevents incorrect sequencing (e.g., you cannot run an effect that requires a service without providing it)
77
+
78
+ **Weaknesses:**
79
+
80
+ - Steep learning curve; three type parameters require understanding of variance and multiple monadic laws
81
+ - Cause model adds abstraction overhead; debugging simple, non-concurrent code requires understanding why your error is wrapped in a Cause
82
+ - Library size is massive (~150+ modules); tree-shaking is essential for smaller bundles
83
+ - Effect execution requires understanding of Fiber, Runtime, and Executor concepts for troubleshooting
84
+ - Generator syntax (`Effect.gen`) is syntactic sugar that hides the underlying monadic structure
85
+
86
+ ---
87
+
88
+ ## 2. Error Handling
89
+
90
+ ### elevate-ts: Binary Simplicity
91
+
92
+ ```typescript
93
+ // Either<L, R> — Left is error, Right is success
94
+ const attempt = (f: () => Value): Either<string, Value> => Either.tryCatch(f, (e) => String(e));
95
+
96
+ // Flat error type: no distinction between expected errors and unexpected exceptions
97
+ const result = pipe(
98
+ attempt(() => JSON.parse(input)),
99
+ Either.chain((parsed) => validate(parsed))
100
+ );
101
+
102
+ // If JSON.parse throws: Left("SyntaxError: ...")
103
+ // If validate returns Left: Left("Validation failed")
104
+ // Both Left—no structural difference
105
+ ```
106
+
107
+ **Design Philosophy:**
108
+
109
+ - Errors are **first-class values** but **not deeply modeled**
110
+ - No distinction between recoverable (expected) and unrecoverable (unexpected) failures
111
+ - Stack traces are converted to strings; context is lost
112
+ - Parallel errors are modeled as an array of Lefts; sequential errors require manual sequencing
113
+
114
+ **Strengths:**
115
+
116
+ - Dead simple for single-threaded, synchronous error handling
117
+ - Fast to implement and reason about
118
+ - Suitable for small scripts and form validation (classical use case)
119
+
120
+ **Weaknesses:**
121
+
122
+ - **Cannot distinguish exception types programmatically.** If you need "retry on timeout, fail on auth error," you must encode both in the error type manually
123
+ - **No execution trace.** If an effect is interrupted or times out, you lose that information
124
+ - **Parallel/concurrent errors require convention.** No native way to collect multiple independent failures
125
+ - **Stack traces are lost.** Converting exceptions to strings destroys the original context
126
+ - **Defects (unexpected exceptions) blend with expected errors.** No way to say "this error was supposed to be caught; this one wasn't"
127
+
128
+ ---
129
+
130
+ ### Effect-TS: Lossless Cause Model
131
+
132
+ ```typescript
133
+ // Cause<E> — models failure in complete detail
134
+ type Cause<E> =
135
+ | Fail<E> // Expected, typed error
136
+ | Die // Unexpected exception (preserves stack trace)
137
+ | Interrupt // Effect was cancelled
138
+ | Sequential<E> // Chain failed; cleanup also failed
139
+ | Parallel<E>; // Multiple concurrent failures
140
+
141
+ // Exit<A, E>
142
+ type Exit<A, E> = Success<A> | Failure<Cause<E>>;
143
+
144
+ // Example: concurrent errors are captured structurally
145
+ const results = Effect.allPar([effect1, effect2, effect3]);
146
+ // If effect1 fails with Fail("error1") and effect2 throws new Error("bug"),
147
+ // Cause captures both:
148
+ // Parallel([Fail("error1"), Die(Error("bug"))])
149
+ ```
150
+
151
+ **Design Philosophy:**
152
+
153
+ - Errors are **completely modeled** in the type system
154
+ - **Expected errors** (Fail) vs **unexpected exceptions** (Die) are structurally distinct
155
+ - **Interruption/cancellation is a first-class failure mode**
156
+ - **Sequential failures preserve causality** (main error + cleanup error are both retained)
157
+
158
+ **Strengths:**
159
+
160
+ - **Precise error discrimination.** Can match on error type without string parsing
161
+ - **Full execution traces preserved.** Debugging concurrent failures shows exactly which fiber failed and why
162
+ - **Cleanup errors don't hide main errors.** If a finalizer throws while handling another error, both are captured
163
+ - **Timeout/cancellation is explicit.** A cancelled effect looks different from a failed one
164
+ - **Observability ready.** Structured error information integrates with logging/tracing systems
165
+
166
+ **Weaknesses:**
167
+
168
+ - **Cause is abstract.** End users cannot directly pattern-match on it; must use helper functions (Exit.match, etc.)
169
+ - **Causes are verbose for simple cases.** A single validation error is still wrapped in `Cause<ValidationError>`; no "unwrapping" helper for the common case
170
+ - **Serialization complexity.** Cause structures with circular references (common in concurrent scenarios) are harder to log
171
+ - **Die (exception wrapper) still loses information.** If the exception is not designed to be meaningful (e.g., `throw {}` in user code), Cause doesn't recover that
172
+
173
+ ---
174
+
175
+ ## 3. Dependency Injection & Service Management
176
+
177
+ ### elevate-ts: Reader Monad + Manual Threading
178
+
179
+ ```typescript
180
+ // Reader<R, A> — essentially (env: R) => A, branded
181
+ type Reader<R, A> = { tag: 'Reader'; run: (env: R) => A }
182
+
183
+ // Define a service
184
+ type Database = { query: (sql: string) => Promise<any[]> }
185
+
186
+ // Create a Reader that depends on Database
187
+ const getUser = (userId: string): Reader<Database, Promise<User>> =>
188
+ Reader((db) => db.query(`SELECT * FROM users WHERE id = ${userId}`))
189
+
190
+ // Compose readers with chaining
191
+ const getUserWithPosts = (userId: string): Reader<Database, Promise<{ user: User; posts: Post[] }>> =>
192
+ pipe(
193
+ Reader.ask<Database>(),
194
+ Reader.chain((db) =>
195
+ Reader((db2) => ({
196
+ user: db.query('SELECT * FROM users WHERE id = ?', [userId]),
197
+ posts: db2.query('SELECT * FROM posts WHERE user_id = ?', [userId]),
198
+ }))
199
+ )
200
+ )
201
+
202
+ // Run with a concrete database
203
+ const env: Database = { query: (...) => ... }
204
+ const result = getUser('123').run(env)
205
+ ```
206
+
207
+ **Characteristics:**
208
+
209
+ - Reader is **just a lazy function** with environment threading
210
+ - No built-in memoization; if you ask for the same service twice, it's computed twice
211
+ - No resource lifecycle management; acquiring a database connection requires manual wrapping
212
+ - Composing multiple services requires manually threading the environment through each Reader
213
+
214
+ **Strengths:**
215
+
216
+ - **Minimal overhead.** Reader is literally just a function; no runtime indirection
217
+ - **Transparent.** Easy to see what dependencies flow through the computation
218
+ - **Works fine for static, compile-time-known dependencies**
219
+
220
+ **Weaknesses:**
221
+
222
+ - **No resource safety.** If a Reader acquires a resource (database, file handle, HTTP connection), cleanup is manual and easy to forget
223
+ - **No memoization.** Multiple calls to `Reader.ask()` don't reuse a cached service; each call recreates it
224
+ - **Composing independent services is awkward.** No native way to say "I need service A and service B in parallel; fail if either setup fails"
225
+ - **No typed access.** Environment is just `R`; nothing prevents accidentally threading the wrong environment into a sub-Reader
226
+ - **No dependency graph.** Cannot inspect what services are required without running the Reader
227
+
228
+ ---
229
+
230
+ ### Effect-TS: Layer + Context/Tag System
231
+
232
+ ```typescript
233
+ // Tag<Id, Service> — a typed key for a service
234
+ class UserRepository extends Context.Tag("UserRepository")<UserRepository, {
235
+ getUser: (id: string) => Effect<User>
236
+ }>() {}
237
+
238
+ // Layer<ROut, E, RIn> — a recipe for building services
239
+ // ROut: what services this layer provides
240
+ // E: errors that can occur during setup
241
+ // RIn: dependencies required to build these services
242
+ const UserRepositoryLive: Layer<UserRepository, Error, never> =
243
+ Layer.sync(() => ({
244
+ getUser: (id: string) => Effect.promise(() => db.query(...)),
245
+ }))
246
+
247
+ // Dependency: a UserRepository requires a Database
248
+ const DatabaseLive: Layer<Database, Error, never> =
249
+ Layer.succeed(() => createDatabaseConnection())
250
+
251
+ // Compose layers; Effect enforces that dependencies are provided
252
+ const AppLive: Layer<UserRepository | Database, Error, never> =
253
+ Layer.compose(UserRepositoryLive, DatabaseLive)
254
+
255
+ // Use the service in an effect
256
+ const getUser = (id: string): Effect<User, Error, UserRepository> =>
257
+ Effect.serviceWith(UserRepository, (repo) => repo.getUser(id))
258
+
259
+ // Run the effect; layers are automatically acquired, shared, and cleaned up
260
+ const result = pipe(
261
+ getUser('123'),
262
+ Effect.provide(AppLive),
263
+ Effect.runPromise
264
+ )
265
+ ```
266
+
267
+ **Characteristics:**
268
+
269
+ - **Tag** is a **strongly-typed service identifier** with a unique key
270
+ - **Layer** is a **composable service factory** with automatic resource management
271
+ - **Memoization by default.** Identical layers are executed once; results are shared across the effect
272
+ - **Typed dependency graph.** The R parameter in Effect explicitly lists required services
273
+ - **Sequential composition.** Layers are built in order; if A requires B, and B requires C, the system handles the sequencing
274
+
275
+ **Strengths:**
276
+
277
+ - **Type-safe dependency injection.** Cannot accidentally provide the wrong service; the type system prevents it
278
+ - **Resource safety guaranteed.** Layer handles acquisition, cleanup, and error recovery automatically
279
+ - **Memoization built-in.** Service creation happens once per scope; no accidental duplication
280
+ - **Composable dependency graphs.** Can declare that service A requires B requires C, and the system builds them in the right order
281
+ - **Testability.** Easy to provide a test layer that overrides production services
282
+ - **Observability hooks.** Layer supports spans and logging for each service lifecycle event
283
+
284
+ **Weaknesses:**
285
+
286
+ - **Verbose for simple cases.** A single service requires defining a Tag, creating a Layer, and providing it—boilerplate for Hello World
287
+ - **Implicit ordering.** If Layer A depends on B which depends on C, this is not explicit in the type; you find out at runtime if it fails
288
+ - **Learning curve.** Understanding variance, covariance in the R parameter is non-trivial
289
+ - **No dynamic dependency lookup.** All dependencies must be known at compile time; cannot say "use whatever service matches this interface"
290
+ - **Error during layer setup hides original effect.** If a dependency fails to initialize, the original effect error is lost (though Cause captures this)
291
+
292
+ ---
293
+
294
+ ## 4. Composability: Pipe vs Pipe vs Gen
295
+
296
+ ### elevate-ts: `pipe()`
297
+
298
+ ```typescript
299
+ import { pipe } from '@zambit/elevate-ts/Function';
300
+
301
+ const result = pipe(initialValue, step1, step2, step3);
302
+ // Explicitly chains operations left-to-right
303
+ // Each step is a simple function: (A) => B
304
+ ```
305
+
306
+ **Characteristics:**
307
+
308
+ - **Function composition** via pipe
309
+ - **Data flows left-to-right** in a visually clear manner
310
+ - **Works with any function** that takes one argument
311
+ - **No special syntax** required
312
+
313
+ **Strengths:**
314
+
315
+ - **Visually clear.** Easy to follow the data flow
316
+ - **Minimal indirection.** No operators, no operator precedence surprises
317
+ - **Works with plain functions.** Can mix monadic and non-monadic operations
318
+
319
+ **Weaknesses:**
320
+
321
+ - **Requires explicit wrapping.** `map`, `chain`, etc. must be curried and passed explicitly
322
+ - **Verbose for deeply nested operations.** 10+ steps = hard to read
323
+ - **No compile-time error checking.** Typos in function names appear at runtime
324
+
325
+ ---
326
+
327
+ ### Effect-TS: `pipe()` + `Effect.gen()`
328
+
329
+ ```typescript
330
+ import { pipe, Effect } from 'effect';
331
+
332
+ // 1. Using pipe (similar to elevate-ts)
333
+ const result1 = pipe(
334
+ Effect.succeed(5),
335
+ Effect.map((x) => x * 2),
336
+ Effect.flatMap((y) => Effect.succeed(y + 1))
337
+ );
338
+
339
+ // 2. Using Effect.gen (generator-based, looks imperative)
340
+ const result2 = Effect.gen(function* () {
341
+ const x = yield* Effect.succeed(5);
342
+ const y = x * 2;
343
+ const z = yield* Effect.succeed(y + 1);
344
+ return z;
345
+ });
346
+
347
+ // Both are equivalent; gen is syntactic sugar
348
+ ```
349
+
350
+ **Characteristics:**
351
+
352
+ - **pipe() works like elevate-ts** but with Effect-specific operators
353
+ - **gen() uses JavaScript generators** to hide the monadic structure
354
+ - **Generator syntax looks imperative** but executes as a monad under the hood
355
+
356
+ **Strengths (pipe):**
357
+
358
+ - **Same as elevate-ts** plus Effect-specific helpers
359
+ - **Forced explicit sequencing** (easier to debug)
360
+
361
+ **Strengths (gen):**
362
+
363
+ - **Reads like imperative code** (familiar to non-FP developers)
364
+ - **Automatic monadic binding** (no explicit `flatMap` calls)
365
+ - **Easier for developers coming from async/await**
366
+
367
+ **Weaknesses (pipe):**
368
+
369
+ - **More verbose than gen for complex effects**
370
+ - **Operator precedence must be understood** (e.g., `map` before `flatMap`)
371
+
372
+ **Weaknesses (gen):**
373
+
374
+ - **Hides the monadic structure.** Beginners don't learn what's actually happening
375
+ - **Magical.** JavaScript generators are notoriously hard to debug; stack traces are confusing
376
+ - **Not idiomatic FP.** Experts find it unreadable; looks like old-school imperative code
377
+ - **Cannot inspect the generator** to understand dependencies without running it
378
+
379
+ ---
380
+
381
+ ## 5. Runtime Agnosticism
382
+
383
+ ### elevate-ts: Explicit Cloudflare Workers Target
384
+
385
+ **Design choices:**
386
+
387
+ - No Node.js built-ins (no `fs`, `path`, `stream`, etc.)
388
+ - No DOM APIs (no `window`, `document`)
389
+ - **Works on:** Node.js, Cloudflare Workers, browsers, Bun
390
+ - **Design goal:** Runtime-agnostic by avoiding platform-specific APIs altogether
391
+ - Zero dependencies; no reliance on platform-specific libraries
392
+
393
+ **Trade-off:**
394
+
395
+ - **Simpler:** Smaller bundle, no platform-specific code paths
396
+ - **Limited:** Cannot access filesystem, process APIs, or Node-specific packages
397
+
398
+ ---
399
+
400
+ ### Effect-TS: Multi-Platform Facade
401
+
402
+ **Design choices:**
403
+
404
+ - Core `effect` package is runtime-agnostic
405
+ - Platform-specific implementations in separate packages:
406
+ - `@effect/platform-node` — Node.js APIs (fs, path, child_process, etc.)
407
+ - `@effect/platform-bun` — Bun-specific APIs
408
+ - `@effect/platform-browser` — Browser APIs (fetch, localStorage, etc.)
409
+ - `@effect/platform` — Abstract interfaces (e.g., FileSystem trait)
410
+ - Each platform provides concrete Layer implementations
411
+
412
+ **Example:**
413
+
414
+ ```typescript
415
+ // Abstract: works on any platform with a FileSystem
416
+ const readFile = (path: string): Effect<string, Error, FileSystem> =>
417
+ Effect.serviceWith(FileSystem, (fs) => fs.readFileUtf8(path))
418
+
419
+ // Provide platform-specific implementation
420
+ import { FileSystemLive } from '@effect/platform-node'
421
+ const result = pipe(
422
+ readFile('/etc/hosts'),
423
+ Effect.provide(FileSystemLive)
424
+ Effect.runPromise
425
+ )
426
+ ```
427
+
428
+ **Strengths:**
429
+
430
+ - **Single effect type across platforms** (no `NodeEffect<A,E>` vs `BrowserEffect<A,E>`)
431
+ - **Swappable implementations.** Test with mock FileSystem, deploy with node FileSystem
432
+ - **Type-safe platform features.** Cannot call Node-only APIs in browser code if properly layered
433
+
434
+ **Weaknesses:**
435
+
436
+ - **Bloated for Cloudflare Workers.** Must tree-shake platform-node, platform-bun to keep workers small
437
+ - **Abstraction overhead.** FileSystem is abstract; concrete implementations add a layer of indirection
438
+ - **Not designed for Workers.** The platform ecosystem is Node-centric; Workers support is secondary
439
+
440
+ ---
441
+
442
+ ## 6. Schema & Validation
443
+
444
+ ### elevate-ts: None Built-in
445
+
446
+ ```typescript
447
+ // Validation monad exists but is for applicative-style error collection, not schema validation
448
+ import { Validation } from '@zambit/elevate-ts';
449
+
450
+ // Manual validation
451
+ const validateUser = (data: unknown): Either<string[], User> => {
452
+ if (typeof data !== 'object' || data === null) return Left(['Not an object']);
453
+ const obj = data as Record<string, unknown>;
454
+ if (typeof obj.id !== 'string') return Left(['id must be a string']);
455
+ if (typeof obj.name !== 'string') return Left(['name must be a string']);
456
+ return Right({ id: obj.id, name: obj.name });
457
+ };
458
+ ```
459
+
460
+ **Status:**
461
+
462
+ - Validation monad exists for **collecting errors during applicative operations** (not for schema)
463
+ - No schema language; validation is hand-written
464
+ - No type derivation; types and validators are separate
465
+
466
+ **Suitable for:**
467
+
468
+ - Small, hand-rolled validators
469
+ - Form validation (classical use case)
470
+ - Exploratory prototyping
471
+
472
+ **Not suitable for:**
473
+
474
+ - API contract validation
475
+ - Serialization/deserialization
476
+ - Type-driven code generation
477
+
478
+ ---
479
+
480
+ ### Effect-TS: First-Class Schema
481
+
482
+ ```typescript
483
+ import { Schema, Effect } from 'effect';
484
+
485
+ // Define schema once; used for validation, serialization, docs
486
+ const UserSchema = Schema.Struct({
487
+ id: Schema.String,
488
+ name: Schema.String,
489
+ email: Schema.String,
490
+ age: Schema.optional(Schema.Int)
491
+ });
492
+
493
+ // Automatic validation
494
+ const parseUser = (data: unknown): Effect<User, ParseError> => Schema.decode(UserSchema)(data);
495
+
496
+ // Automatic serialization
497
+ const encodeUser = (user: User): Effect<unknown, ParseError> => Schema.encode(UserSchema)(user);
498
+
499
+ // Type is derived automatically
500
+ type User = Schema.Type<typeof UserSchema>;
501
+ // User = { id: string; name: string; email: string; age?: number }
502
+
503
+ // Used throughout the ecosystem
504
+ // - HTTP API endpoint definitions (Schema for request/response)
505
+ // - RPC serialization (Schema for protocol messages)
506
+ // - Database mappings (Schema for row types)
507
+ // - OpenAPI/Swagger generation (Schema for documentation)
508
+ ```
509
+
510
+ **Characteristics:**
511
+
512
+ - **Single source of truth** for types and validation
513
+ - **Composable.** Schemas nest and combine declaratively
514
+ - **Errorless.** Schema errors are captured in the Effect type, not thrown
515
+ - **Type-safe serialization.** Encoding/decoding are type-checked
516
+
517
+ **Strengths:**
518
+
519
+ - **No duplication.** Type and schema are one
520
+ - **Ecosystem integration.** HttpApi, RPC, SQL all use Schema
521
+ - **Automatic code generation.** OpenAPI docs, client libraries derived from Schema
522
+ - **Round-trip safety.** Can encode then decode; result is equivalent to original
523
+
524
+ **Weaknesses:**
525
+
526
+ - **Learning curve.** Schema combinators are numerous; composition is not obvious
527
+ - **Performance cost.** Validation during decode is slower than a hand-written check
528
+ - **Strict by default.** Schema rejects unknown fields; must explicitly allow them
529
+ - **Serialization semantics differ.** Decoding integers from JSON strings requires explicit `.pipe(Schema.parseJson)`
530
+
531
+ ---
532
+
533
+ ## 7. Ecosystem & Feature Breadth
534
+
535
+ ### elevate-ts: Minimal, Focused
536
+
537
+ | Feature | Status |
538
+ | ----------------- | ------------------------------------ |
539
+ | Core monads | [YES] Complete (Maybe, Either, etc.) |
540
+ | Async support | [YES] (EitherAsync, MaybeAsync) |
541
+ | DI / Environment | [YES] Basic (Reader) |
542
+ | Schema validation | [NO] Not built-in |
543
+ | HTTP server | [NO] Not included |
544
+ | Database | [NO] Not included |
545
+ | CLI tools | [NO] Not included |
546
+ | Testing utilities | [NO] Not included |
547
+ | Observability | [NO] Not included |
548
+
549
+ **Philosophy:**
550
+
551
+ - Minimal, batteries-not-included
552
+ - Users assemble their own stack
553
+ - ~500 lines of source code
554
+
555
+ **Suitable for:**
556
+
557
+ - Microlibraries
558
+ - Cloudflare Worker projects (form processing, data transformation)
559
+ - Educational purposes
560
+
561
+ ---
562
+
563
+ ### Effect-TS: Comprehensive Ecosystem
564
+
565
+ | Feature | Package | Status |
566
+ | ----------------- | --------------------- | -------------------------------------------------- |
567
+ | Core effects | effect | [YES] Complete |
568
+ | Schema validation | effect | [YES] Built-in |
569
+ | HTTP server | @effect/platform | [YES] Declarative HttpApi |
570
+ | Database | @effect/sql | [YES] 8+ implementations (PG, MySQL, SQLite, etc.) |
571
+ | CLI | @effect/cli | [YES] Command parsing, help generation |
572
+ | Testing | @effect/vitest | [YES] Vitest integration |
573
+ | Observability | @effect/opentelemetry | [YES] Tracing, metrics, logs |
574
+ | AI integration | @effect/ai | [YES] OpenAI, Anthropic, Bedrock, Google |
575
+ | RPC | @effect/rpc | [YES] Type-safe RPC |
576
+ | Cluster | @effect/cluster | [YES] Distributed computing |
577
+ | Durable workflows | @effect/workflow | [YES] Temporal-like workflows |
578
+
579
+ **Philosophy:**
580
+
581
+ - Comprehensive; most production concerns are addressed
582
+ - Layered ecosystem; use only what you need
583
+ - ~200K lines across all packages
584
+
585
+ **Suitable for:**
586
+
587
+ - Full-stack applications
588
+ - Microservices
589
+ - Cloud deployments (any runtime)
590
+ - Production grade requirements
591
+
592
+ ---
593
+
594
+ ## 8. Specific Strengths & Weaknesses
595
+
596
+ ### Where elevate-ts Wins
597
+
598
+ 1. **Simplicity.** Learning curve is hours, not weeks. No magical generators, no three-type-parameter monads.
599
+ 2. **Bundle size.** [YES] ~3KB minified for all core modules. Perfect for Cloudflare Workers.
600
+ 3. **Performance.** No fiber scheduling, no runtime allocations for trivial effects.
601
+ 4. **Clarity.** Code intent is immediately obvious; no hidden abstractions.
602
+ 5. **Cloudflare Workers.** Explicitly designed for this; no Node.js baggage.
603
+ 6. **Teaching.** Perfect for learning functional programming without getting lost in production complexity.
604
+
605
+ ### Where elevate-ts Struggles
606
+
607
+ 1. **Dependency injection.** Reader is fine for simple cases but lacks resource safety; no memoization.
608
+ 2. **Concurrent error handling.** Multiple independent failures must be manually coordinated.
609
+ 3. **Error diagnostics.** No structured error model; stack traces are lost; timeout/cancellation look the same as normal failures.
610
+ 4. **Observability.** No built-in logging, tracing, or metrics integration.
611
+ 5. **Ecosystem.** No HTTP server, database, or CLI support; users must roll their own or import unrelated libraries.
612
+ 6. **Scaling.** Not designed for applications with 20+ services; the manual dependency threading gets unwieldy.
613
+
614
+ ---
615
+
616
+ ### Where Effect-TS Wins
617
+
618
+ 1. **Type safety in dependencies.** R parameter in Effect prevents accidentally running code without required services.
619
+ 2. **Resource management.** Layer handles acquisition, cleanup, error recovery automatically; nearly impossible to leak resources.
620
+ 3. **Error diagnostics.** Cause model preserves stack traces, distinguishes failures types, captures cancellation.
621
+ 4. **Concurrency.** First-class structured concurrency; `Effect.all`, `Effect.race` handle multiple failures correctly.
622
+ 5. **Observability.** OpenTelemetry integration, structured logging, built-in tracing.
623
+ 6. **Ecosystem.** SQL, HTTP, CLI, RPC, workflows all use the same effect type; no impedance mismatch between libraries.
624
+ 7. **Scaling.** Designed for applications with dozens of services; the type system scales with complexity.
625
+ 8. **Schema-driven development.** Type and validator are one; eliminates a large class of bugs.
626
+
627
+ ### Where Effect-TS Struggles
628
+
629
+ 1. **Learning curve.** Three-month ramp-up for a team unfamiliar with effect systems. Generators confuse beginners.
630
+ 2. **Bundle size.** Even with tree-shaking, [NOTE] ~50KB for core + Schema + Platform abstractions.
631
+ 3. **Cloudflare Workers.** Runtime overhead and bundle size make it suboptimal for edge workers with strict latency/size constraints.
632
+ 4. **Simple cases.** Writing "hello world" or a small form validator requires more boilerplate than plain TypeScript.
633
+ 5. **Operational overhead.** Fiber scheduling, runtime management, executor configuration—teams need expertise to debug.
634
+ 6. **Library size.** 200+ modules means maintenance burden; new major versions can break many downstream projects.
635
+
636
+ ---
637
+
638
+ ## 9. Runtime Agnosticism in Practice
639
+
640
+ ### elevate-ts
641
+
642
+ ```typescript
643
+ // Works everywhere: browser, Worker, Bun
644
+ import { pipe, Either, Just } from '@zambit/elevate-ts';
645
+
646
+ const validate = (input: string): Either<string, number> => {
647
+ try {
648
+ return Either.Right(parseInt(input, 10));
649
+ } catch (e) {
650
+ return Either.Left('Invalid number');
651
+ }
652
+ };
653
+
654
+ const result = pipe(
655
+ Just('42'),
656
+ Maybe.chain((s) => Either.toMaybe(validate(s)))
657
+ );
658
+ ```
659
+
660
+ **Reality:** Works because it doesn't rely on ANY platform APIs. It's pure computation.
661
+
662
+ ---
663
+
664
+ ### Effect-TS
665
+
666
+ ```typescript
667
+ // Browser: works with @effect/platform-browser
668
+ import { Effect } from 'effect';
669
+ import { HttpClient } from '@effect/platform-browser';
670
+
671
+ const fetchUser = (id: string): Effect<User, Error, HttpClient> =>
672
+ Effect.gen(function* () {
673
+ const client = yield* HttpClient.HttpClient;
674
+ const response = yield* client.get(`/api/users/${id}`);
675
+ return response.json;
676
+ });
677
+
678
+ // Cloudflare Worker: would need @effect/platform-worker (doesn't exist; would use @effect/platform-browser)
679
+ // Node.js: would use @effect/platform-node
680
+ ```
681
+
682
+ **Reality:** Core Effect is runtime-agnostic, but platform-specific modules are required for I/O. Developers must know which platform they're targeting and provide the correct Layer.
683
+
684
+ ---
685
+
686
+ ## 10. Verdict: When to Choose Each
687
+
688
+ ### Choose elevate-ts
689
+
690
+ 1. **Targeting Cloudflare Workers** and you need FP without bundle bloat
691
+ 2. **Teaching FP** to JavaScript developers
692
+ 3. **Simple data transformation** (parsing, validation, filtering arrays)
693
+ 4. **Single developer or small team** with limited FP experience
694
+ 5. **No dependency injection** or simple environment threading suffices
695
+ 6. **Offline-first or client-side** app with no backend integration
696
+ 7. **Bundle size is critical** ([NOTE] <10KB constraint)
697
+
698
+ **Example Use Case:** [NOTE] Cloudflare Worker: receive JSON, parse with Either, transform with pipe, return JSON. No dependencies, no concurrent operations, no resource cleanup.
699
+
700
+ ---
701
+
702
+ ### Choose Effect-TS
703
+
704
+ 1. **Full-stack application** with database, HTTP, authentication
705
+ 2. **Microservices** with strong dependency management requirements
706
+ 3. **Concurrent operations** (parallel jobs, pub/sub, worker pools)
707
+ 4. **Production observability** is non-negotiable (tracing, metrics, logs)
708
+ 5. **Large codebase** (20+ services, 50+ effects) where type safety prevents bugs
709
+ 6. **Team is FP-experienced** or willing to invest in training
710
+ 7. **Long-term maintenance** is a priority (the type system catches breakages)
711
+
712
+ **Example Use Case:** [NOTE] Full-stack app: Effect handles HTTP server, DB queries, authentication, logging, error recovery all in one type system. A backend refactoring automatically identifies
713
+ where services need to be provided; the compiler fails fast.
714
+
715
+ ---
716
+
717
+ ### Hybrid Approach
718
+
719
+ Some projects use **both**:
720
+
721
+ ```typescript
722
+ // Cloudflare Worker (elevate-ts): receives request, validates input
723
+ import { pipe, Either } from '@zambit/elevate-ts'
724
+
725
+ const validateRequest = (req: Request): Either<string, Payload> => { ... }
726
+
727
+ // Calls backend (Effect-TS): handles orchestration, observability
728
+ // Backend is a full-stack Effect app; Worker is a lightweight gatekeeper
729
+ ```
730
+
731
+ **Rationale:**
732
+
733
+ - Worker stays small and fast (elevate-ts)
734
+ - Backend handles complexity (Effect-TS)
735
+ - Clear separation of concerns
736
+
737
+ ---
738
+
739
+ ## 11. Critical Design Flaws & Observations
740
+
741
+ ### elevate-ts Flaws
742
+
743
+ 1. **Reader has no resource semantics.** If a Reader acquires a resource (database, connection pool), cleanup is manual. This is fundamentally unsafe for production code.
744
+
745
+ 2. **No error context preservation.** Converting exceptions to strings (`onError: (e) => String(e)`) loses the original error type. Downstream handlers cannot discriminate the error.
746
+
747
+ 3. **Validation monad is not a schema.** It collects errors, but there's no automatic type derivation, no serialization support, no ecosystem integration. It's a solution looking for a problem.
748
+
749
+ 4. **No way to model dependencies in the type.** A Reader<{ db: Database; cache: Cache }, Result> has no compile-time check that the dependencies are provided. This is a type safety gap.
750
+
751
+ ---
752
+
753
+ ### Effect-TS Flaws
754
+
755
+ 1. **Cause is too complex for simple cases.** A single validation error is still `Failure(Cause<ValidationError>)`. The mental model overhead is unjustified for "is this number valid?"
756
+
757
+ 2. **Generator syntax is a footgun.** `Effect.gen` looks imperative but is actually monadic; the evaluation order is non-obvious. Stack traces from generators are useless for debugging.
758
+
759
+ 3. **Layer composition errors are opaque.** Errors from nested dependency chains lack clear traces.
760
+
761
+ 4. **No "async/await"-style Layer syntax.** Writing layers requires understanding monad laws; `Layer.fromEffect`, `Layer.provide`, `Layer.compose` are terse but require deep knowledge.
762
+
763
+ 5. **Platform abstractions leak.** FileSystem is abstract; concrete implementations have different semantics (e.g., browsers don't support absolute paths).
764
+
765
+ ---
766
+
767
+ ## 12. Overall Assessment
768
+
769
+ ### elevate-ts Niche, Excellent Tool
770
+
771
+ - Solves **one problem exceptionally well:** lightweight FP for Cloudflare Workers and browsers
772
+ - **Not intended as general-purpose framework** (design explicitly optimizes for edge workers)
773
+ - **Risk:** Backend development will hit resource safety and dependency management ceiling
774
+ - **Sweet spot:** Projects <100 effects, no backend, or client-side validation layer
775
+
776
+ [YES] A+ for intended scope; [NO] C- if used beyond it
777
+
778
+ ---
779
+
780
+ ### Effect-TS Comprehensive, but Heavyweight
781
+
782
+ - Solves **the complete stack:** dependencies, concurrency, errors, observability, serialization
783
+ - **Uncompromising about correctness:** Type system forces proper error/service/resource handling
784
+ - **Cost:** Learning curve, bundle size, operational complexity
785
+ - **Risk:** Easy to over-engineer simple problems; teams often cargo-cult patterns
786
+ - **Sweet spot:** Distributed systems, long-lived services, FP-experienced teams
787
+
788
+ [YES] A- for production systems; [NO] D for "Hello World"
789
+
790
+ ---
791
+
792
+ ## 13. Final Thoughts
793
+
794
+ **elevate-ts is not trying to be Effect-TS, and that's okay.**
795
+
796
+ elevate-ts is **intentionally minimalist.** It provides core monads, assumes vanilla TypeScript for I/O, and optimizes for bundle size. It's a **library**, not a framework.
797
+
798
+ Effect-TS is **intentionally maximalist.** It provides everything: scheduling, resources, DI, observability, schema validation. It's a **framework** designed as an application foundation.
799
+
800
+ **When in doubt:**
801
+
802
+ - If building a **Cloudflare Worker** or **client-side validation**, choose elevate-ts.
803
+ - If building a **backend service or distributed system**, choose Effect-TS.
804
+ - If building **both**, use both—they complement each other.
805
+
806
+ The real gap in the TypeScript ecosystem is **middle ground:** something lighter than Effect-TS but with better resource safety than elevate-ts' Reader. As of 2026-04, that gap remains unfilled.