@igorjs/pure-ts 0.7.5 → 0.7.7
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 +39 -745
- package/dist/async/cache.d.ts +1 -6
- package/dist/async/cache.d.ts.map +1 -1
- package/dist/async/cache.js.map +1 -1
- package/dist/async/circuit-breaker.d.ts +1 -6
- package/dist/async/circuit-breaker.d.ts.map +1 -1
- package/dist/async/circuit-breaker.js.map +1 -1
- package/dist/async/index.d.ts +21 -2
- package/dist/async/index.d.ts.map +1 -1
- package/dist/async/index.js +6 -2
- package/dist/async/index.js.map +1 -1
- package/dist/async/lazy.d.ts +6 -42
- package/dist/async/lazy.d.ts.map +1 -1
- package/dist/async/lazy.js.map +1 -1
- package/dist/async/pool.d.ts +1 -5
- package/dist/async/pool.d.ts.map +1 -1
- package/dist/async/pool.js +3 -3
- package/dist/async/pool.js.map +1 -1
- package/dist/async/rate-limiter.d.ts +1 -6
- package/dist/async/rate-limiter.d.ts.map +1 -1
- package/dist/async/rate-limiter.js.map +1 -1
- package/dist/async/retry.d.ts +7 -9
- package/dist/async/retry.d.ts.map +1 -1
- package/dist/async/retry.js.map +1 -1
- package/dist/async/semaphore.d.ts +2 -7
- package/dist/async/semaphore.d.ts.map +1 -1
- package/dist/async/semaphore.js.map +1 -1
- package/dist/async/stream.d.ts +1 -5
- package/dist/async/stream.d.ts.map +1 -1
- package/dist/async/stream.js +6 -6
- package/dist/async/stream.js.map +1 -1
- package/dist/async/task-like.d.ts +30 -0
- package/dist/async/task-like.d.ts.map +1 -0
- package/dist/async/task-like.js +13 -0
- package/dist/async/task-like.js.map +1 -0
- package/dist/async/task.d.ts +8 -62
- package/dist/async/task.d.ts.map +1 -1
- package/dist/async/task.js +1 -1
- package/dist/async/task.js.map +1 -1
- package/dist/async/timer.d.ts +1 -6
- package/dist/async/timer.d.ts.map +1 -1
- package/dist/async/timer.js +4 -4
- package/dist/async/timer.js.map +1 -1
- package/dist/client.d.ts +6 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -2
- package/dist/client.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/option.d.ts +26 -68
- package/dist/core/option.d.ts.map +1 -1
- package/dist/core/option.js +7 -20
- package/dist/core/option.js.map +1 -1
- package/dist/core/pipe.d.ts +13 -0
- package/dist/core/pipe.d.ts.map +1 -1
- package/dist/core/pipe.js.map +1 -1
- package/dist/core/result.d.ts +45 -90
- package/dist/core/result.d.ts.map +1 -1
- package/dist/core/result.js +9 -65
- package/dist/core/result.js.map +1 -1
- package/dist/data/adt.d.ts +1 -2
- package/dist/data/adt.d.ts.map +1 -1
- package/dist/data/codec.d.ts +4 -3
- package/dist/data/codec.d.ts.map +1 -1
- package/dist/data/codec.js.map +1 -1
- package/dist/data/index.d.ts +12 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +3 -0
- package/dist/data/index.js.map +1 -1
- package/dist/data/internals.d.ts +2 -2
- package/dist/data/internals.d.ts.map +1 -1
- package/dist/data/list.d.ts +1 -1
- package/dist/data/non-empty-list.d.ts +1 -1
- package/dist/data/record.d.ts +1 -2
- package/dist/data/record.d.ts.map +1 -1
- package/dist/data/record.js.map +1 -1
- package/dist/data/stable-vec.d.ts +116 -0
- package/dist/data/stable-vec.d.ts.map +1 -0
- package/dist/data/stable-vec.js +184 -0
- package/dist/data/stable-vec.js.map +1 -0
- package/dist/index.d.ts +26 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/io/compression.d.ts +1 -6
- package/dist/io/compression.d.ts.map +1 -1
- package/dist/io/compression.js +3 -3
- package/dist/io/compression.js.map +1 -1
- package/dist/io/crypto.d.ts +1 -5
- package/dist/io/crypto.d.ts.map +1 -1
- package/dist/io/crypto.js +2 -2
- package/dist/io/crypto.js.map +1 -1
- package/dist/io/dns.d.ts +3 -7
- package/dist/io/dns.d.ts.map +1 -1
- package/dist/io/dns.js +3 -3
- package/dist/io/dns.js.map +1 -1
- package/dist/io/file.d.ts +1 -5
- package/dist/io/file.d.ts.map +1 -1
- package/dist/io/file.js +13 -13
- package/dist/io/file.js.map +1 -1
- package/dist/io/index.d.ts +11 -1
- package/dist/io/index.d.ts.map +1 -1
- package/dist/io/index.js +1 -0
- package/dist/io/index.js.map +1 -1
- package/dist/io/net.d.ts +1 -6
- package/dist/io/net.d.ts.map +1 -1
- package/dist/io/net.js +6 -6
- package/dist/io/net.js.map +1 -1
- package/dist/io/subprocess.d.ts +1 -6
- package/dist/io/subprocess.d.ts.map +1 -1
- package/dist/io/subprocess.js +4 -4
- package/dist/io/subprocess.js.map +1 -1
- package/dist/program.d.ts +137 -25
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +23 -5
- package/dist/program.js.map +1 -1
- package/dist/runtime/config.d.ts +4 -5
- package/dist/runtime/config.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +17 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/logger.d.ts +22 -6
- package/dist/runtime/logger.d.ts.map +1 -1
- package/dist/runtime/logger.js.map +1 -1
- package/dist/runtime/platform.d.ts +2 -2
- package/dist/runtime/platform.d.ts.map +1 -1
- package/dist/runtime/platform.js.map +1 -1
- package/dist/runtime/process.d.ts +4 -2
- package/dist/runtime/process.d.ts.map +1 -1
- package/dist/runtime/process.js.map +1 -1
- package/dist/types/error.d.ts +34 -61
- package/dist/types/error.d.ts.map +1 -1
- package/dist/types/error.js.map +1 -1
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/nominal.d.ts +1 -3
- package/dist/types/nominal.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
[](https://jsr.io/@igorjs/pure-ts)
|
|
5
5
|
[](https://jsr.io/@igorjs/pure-ts)
|
|
6
6
|
[](https://github.com/igorjs/pure-ts/blob/main/LICENSE)
|
|
7
|
-
[]()
|
|
8
8
|
[]()
|
|
9
9
|
|
|
10
10
|
Functional application framework for TypeScript. Zero dependencies.
|
|
11
11
|
|
|
12
|
-
Errors are values, not exceptions. Data is immutable, enforced at runtime. Async is lazy and composable.
|
|
12
|
+
Errors are values, not exceptions. Data is immutable, enforced at runtime. Async is lazy and composable.
|
|
13
13
|
|
|
14
14
|

|
|
15
15
|

|
|
@@ -18,27 +18,6 @@ Errors are values, not exceptions. Data is immutable, enforced at runtime. Async
|
|
|
18
18
|

|
|
19
19
|

|
|
20
20
|
|
|
21
|
-
```ts
|
|
22
|
-
import {
|
|
23
|
-
// Core
|
|
24
|
-
Result, Option, pipe, flow, Match, Eq, Ord, State,
|
|
25
|
-
Lens, LensOptional, Prism, Traversal,
|
|
26
|
-
// Data
|
|
27
|
-
Record, List, NonEmptyList, Schema, Codec,
|
|
28
|
-
// Types
|
|
29
|
-
ErrType, Duration, Cron,
|
|
30
|
-
// Async
|
|
31
|
-
Task, Stream, Lazy, Env, Timer, Retry, CircuitBreaker,
|
|
32
|
-
Semaphore, Mutex, RateLimiter, Cache, Channel,
|
|
33
|
-
// IO
|
|
34
|
-
Json, File, Crypto, Url, Encoding, Clone, Compression,
|
|
35
|
-
Client, WebSocket, Command, Dns, Net,
|
|
36
|
-
// Runtime
|
|
37
|
-
Server, Program, Logger, Config,
|
|
38
|
-
Path, Eol, Platform, Os, Process,
|
|
39
|
-
} from '@igorjs/pure-ts'
|
|
40
|
-
```
|
|
41
|
-
|
|
42
21
|
## Install
|
|
43
22
|
|
|
44
23
|
```bash
|
|
@@ -51,744 +30,59 @@ Also available on [JSR](https://jsr.io/@igorjs/pure-ts):
|
|
|
51
30
|
npx jsr add @igorjs/pure-ts
|
|
52
31
|
```
|
|
53
32
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
## What's in the box
|
|
57
|
-
|
|
58
|
-
| Layer | Primitives | Docs |
|
|
59
|
-
|-------|------------|------|
|
|
60
|
-
| **Core** | `Result`, `Option`, `pipe`, `flow`, `Match`, `Eq`, `Ord`, `State`, `Lens`, `Iso` | [core](docs/core.md) |
|
|
61
|
-
| **Data** | `Record`, `List`, `NonEmptyList`, `Schema`, `Codec`, `ADT` | [data](docs/data.md) |
|
|
62
|
-
| **Types** | `Type` (nominal branding), `ErrType`, `Duration`, `Cron` | [types](docs/types.md) |
|
|
63
|
-
| **Async** | `Task`, `Stream`, `Lazy`, `Env`, `Timer`, `Retry`, `CircuitBreaker`, `Semaphore`, `Mutex`, `RateLimiter`, `Cache`, `Channel`, `StateMachine`, `EventEmitter`, `Pool`, `Queue`, `CronRunner` | [async](docs/async.md) |
|
|
64
|
-
| **IO** | `Json`, `File`, `Crypto`, `Url`, `Encoding`, `Clone`, `Compression`, `Client`, `WebSocket`, `Command`, `Dns`, `Net` | [io](docs/io.md) |
|
|
65
|
-
| **Runtime** | `Server`, `Program`, `Logger`, `Config`, `Path`, `Eol`, `Platform`, `Os`, `Process`, adapters for Node, Deno, Bun, Lambda | [runtime](docs/runtime.md) |
|
|
66
|
-
|
|
67
|
-
See the [full documentation](docs/index.md) for API details and examples.
|
|
68
|
-
|
|
69
|
-
## Why
|
|
70
|
-
|
|
71
|
-
- **Runtime enforcement**: mutations on Records and Lists throw `TypeError`
|
|
72
|
-
- **Type-level enforcement**: `readonly` all the way down, `tsc --strict` clean
|
|
73
|
-
- **Errors as values**: `Result<T, E>` replaces try/catch, making failure explicit
|
|
74
|
-
- **Lazy async**: `Task` and `Stream` describe work without executing it
|
|
75
|
-
- **Composable optics**: `Lens`, `Prism`, `Traversal` for immutable nested updates
|
|
76
|
-
- **Boundary validation**: `Schema` parses unknown input into typed values
|
|
77
|
-
- **Resilience**: `Retry`, `CircuitBreaker`, `RateLimiter`, `Semaphore` for production systems
|
|
78
|
-
- **Safe IO**: `Json.parse`, `File.read`, `Client.get` return `Result`/`Task`, never throw
|
|
79
|
-
- **Production-ready server**: trie-based routing, typed middleware, graceful shutdown
|
|
80
|
-
- **Zero dependencies**: ships as ESM
|
|
81
|
-
|
|
82
|
-
## Quick start
|
|
83
|
-
|
|
84
|
-
### Immutable data
|
|
33
|
+
## Quick Example
|
|
85
34
|
|
|
86
35
|
```ts
|
|
87
|
-
|
|
88
|
-
user.name // 'Alice'
|
|
89
|
-
user.address.city // 'Sydney' (also a Record)
|
|
90
|
-
user.name = 'X' // TypeError
|
|
36
|
+
import { Ok, Err, pipe, Task, Schema, File, Match } from '@igorjs/pure-ts'
|
|
91
37
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
nums.uniqBy(Eq.number) // deduplicated
|
|
98
|
-
nums.groupBy(n => n > 2 ? 'big' : 'small') // { big: List[3,4], small: List[1] }
|
|
99
|
-
|
|
100
|
-
const nel = NonEmptyList.of(1, 2, 3)
|
|
101
|
-
nel.first() // 1 (not Option, guaranteed)
|
|
102
|
-
nel.reduce1((a, b) => a + b) // 6 (no init needed)
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Error handling
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
const parseAge = (input: unknown): Result<number, string> =>
|
|
109
|
-
typeof input === 'number' && input > 0 ? Ok(input) : Err('invalid age')
|
|
110
|
-
|
|
111
|
-
parseAge(25).map(n => n + 1).unwrapOr(0) // 26
|
|
112
|
-
parseAge('x').map(n => n + 1).unwrapOr(0) // 0
|
|
113
|
-
|
|
114
|
-
// traverse: map + collect in one pass
|
|
115
|
-
Result.traverse(['1', '2'], s => parseAge(Number(s))) // Ok([1, 2])
|
|
116
|
-
|
|
117
|
-
// Structured errors
|
|
118
|
-
const NotFound = ErrType('NotFound') // code: 'NOT_FOUND' (auto-derived)
|
|
119
|
-
NotFound('User not found', { id: 'u_123' })
|
|
120
|
-
.toResult() // Result<never, ErrType<'NotFound'>>
|
|
121
|
-
|
|
122
|
-
// Exhaustive pattern matching
|
|
123
|
-
Match(result)
|
|
124
|
-
.with({ tag: 'Ok' }, r => r.value)
|
|
125
|
-
.with({ tag: 'Err' }, r => r.error)
|
|
126
|
-
.exhaustive()
|
|
127
|
-
```
|
|
38
|
+
// Errors as values
|
|
39
|
+
const parse = (s: string) => {
|
|
40
|
+
const n = Number(s);
|
|
41
|
+
return Number.isNaN(n) ? Err('not a number') : Ok(n);
|
|
42
|
+
};
|
|
128
43
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
const UserSchema = Schema.object({
|
|
133
|
-
name: Schema.string,
|
|
134
|
-
age: Schema.number.refine(n => n > 0, 'positive'),
|
|
135
|
-
role: Schema.discriminatedUnion('type', {
|
|
136
|
-
admin: Schema.object({ type: Schema.literal('admin'), level: Schema.number }),
|
|
137
|
-
user: Schema.object({ type: Schema.literal('user') }),
|
|
138
|
-
}),
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
UserSchema.parse(untrustedInput) // Result<User, SchemaError>
|
|
142
|
-
|
|
143
|
-
// Bidirectional: decode + encode
|
|
144
|
-
const DateCodec = Codec.from(
|
|
145
|
-
(input: unknown) => typeof input === 'string'
|
|
146
|
-
? Ok(new Date(input)) : Err({ path: [], expected: 'ISO string', received: typeof input }),
|
|
147
|
-
(date: Date) => date.toISOString(),
|
|
148
|
-
)
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Async pipelines
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
const fetchUser = Task.fromPromise(
|
|
155
|
-
() => fetch('/api/user').then(r => r.json()),
|
|
156
|
-
e => String(e),
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
// Lazy: nothing executes until .run()
|
|
160
|
-
const pipeline = fetchUser
|
|
161
|
-
.map(user => user.name)
|
|
162
|
-
.timeout(Duration.seconds(5), () => 'timeout')
|
|
163
|
-
|
|
164
|
-
await pipeline.run() // Result<string, string>
|
|
165
|
-
|
|
166
|
-
// Retry with exponential backoff
|
|
167
|
-
const policy = Retry.policy()
|
|
168
|
-
.maxAttempts(3)
|
|
169
|
-
.exponentialBackoff(Duration.seconds(1))
|
|
170
|
-
.jitter()
|
|
171
|
-
.build()
|
|
172
|
-
|
|
173
|
-
Retry.apply(policy, fetchUser)
|
|
174
|
-
|
|
175
|
-
// Circuit breaker for cascading failure protection
|
|
176
|
-
const breaker = CircuitBreaker.create({
|
|
177
|
-
failureThreshold: 5,
|
|
178
|
-
successThreshold: 2,
|
|
179
|
-
timeout: Duration.seconds(30),
|
|
180
|
-
})
|
|
181
|
-
const protected = breaker.protect(fetchUser)
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Streams
|
|
185
|
-
|
|
186
|
-
```ts
|
|
187
|
-
// Lazy pull-based async sequences
|
|
188
|
-
const numbers = Stream.of(1, 2, 3, 4, 5)
|
|
189
|
-
const result = await numbers
|
|
190
|
-
.filter(n => n % 2 === 0)
|
|
191
|
-
.map(n => n * 10)
|
|
192
|
-
.collect()
|
|
193
|
-
.run() // Ok([20, 40])
|
|
194
|
-
|
|
195
|
-
// Sliding window, groupBy, scan
|
|
196
|
-
Stream.interval(Duration.seconds(1))
|
|
197
|
-
.take(10)
|
|
198
|
-
.window(3) // overlapping windows of 3
|
|
199
|
-
.scan((sum, w) => sum + w.length, 0) // running total
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### Optics
|
|
203
|
-
|
|
204
|
-
```ts
|
|
205
|
-
type User = { name: string; address: { city: string } }
|
|
44
|
+
pipe(parse('42'), r => r.map(n => n * 2)); // Ok(84)
|
|
206
45
|
|
|
207
|
-
|
|
208
|
-
|
|
46
|
+
// Lazy async with Result
|
|
47
|
+
const data = await Task.fromPromise(() => fetch('/api'), String)
|
|
48
|
+
.map(r => r.json())
|
|
49
|
+
.timeout(5000, () => 'timed out')
|
|
50
|
+
.run(); // Result<unknown, string>
|
|
209
51
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// Array index access (partial: returns Option)
|
|
215
|
-
const second = LensOptional.index<number>(1)
|
|
216
|
-
second.getOption([10, 20, 30]) // Some(20)
|
|
217
|
-
second.getOption([10]) // None
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### HTTP Server
|
|
221
|
-
|
|
222
|
-
```ts
|
|
223
|
-
import { bunAdapter } from '@igorjs/pure-ts/runtime/adapter/bun'
|
|
224
|
-
|
|
225
|
-
const app = Server('api')
|
|
226
|
-
.derive(req => Task.of({ requestId: crypto.randomUUID() }))
|
|
227
|
-
.get('/health', () => json({ ok: true }))
|
|
228
|
-
.get('/users/:id', ctx => json({ id: ctx.params.id, rid: ctx.requestId }))
|
|
229
|
-
.post('/users', ctx => Task(async () => {
|
|
230
|
-
const body = await ctx.req.json()
|
|
231
|
-
return Ok(json(body, { status: 201 }))
|
|
232
|
-
}))
|
|
233
|
-
.listen({ port: 3000 }, bunAdapter)
|
|
234
|
-
|
|
235
|
-
await app.run()
|
|
236
|
-
// Handles SIGINT/SIGTERM, graceful shutdown, structured logging
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Runtime adapters:
|
|
240
|
-
|
|
241
|
-
```ts
|
|
242
|
-
import { nodeAdapter } from '@igorjs/pure-ts/runtime/adapter/node'
|
|
243
|
-
import { denoAdapter } from '@igorjs/pure-ts/runtime/adapter/deno'
|
|
244
|
-
import { bunAdapter } from '@igorjs/pure-ts/runtime/adapter/bun'
|
|
245
|
-
import { lambdaAdapter } from '@igorjs/pure-ts/runtime/adapter/lambda'
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### Typed time
|
|
249
|
-
|
|
250
|
-
```ts
|
|
251
|
-
const timeout = Duration.seconds(30)
|
|
252
|
-
const backoff = Duration.multiply(timeout, 2)
|
|
253
|
-
Duration.format(backoff) // '1m'
|
|
254
|
-
Duration.toMilliseconds(backoff) // 60000
|
|
255
|
-
Duration.ord.compare(timeout, backoff) // -1
|
|
256
|
-
|
|
257
|
-
const schedule = Cron.parse('0 9 * * 1-5') // Result<CronExpression, SchemaError>
|
|
258
|
-
if (schedule.isOk) {
|
|
259
|
-
Cron.next(schedule.value) // Option<Date> (next 9am weekday)
|
|
260
|
-
Cron.matches(schedule.value, new Date()) // boolean
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### Safe IO
|
|
265
|
-
|
|
266
|
-
```ts
|
|
267
|
-
// JSON: returns Result instead of throwing
|
|
268
|
-
Json.parse('{"name":"Alice"}') // Ok({ name: 'Alice' })
|
|
269
|
-
Json.parse('not json') // Err(JsonError('...'))
|
|
270
|
-
Json.stringify(data) // Ok('{"name":"Alice"}')
|
|
271
|
-
|
|
272
|
-
// File: multi-runtime (Deno + Node + Bun), returns Task
|
|
273
|
-
const content = await File.read('./config.json').run()
|
|
274
|
-
await File.write('./out.json', data).run()
|
|
275
|
-
await File.stat('./file.txt').run() // Ok({ isFile, isDirectory, size })
|
|
276
|
-
File.copy('./a.txt', './b.txt') // Task<void, FileError>
|
|
277
|
-
|
|
278
|
-
// Crypto: web standard (all runtimes including browsers)
|
|
279
|
-
Crypto.uuid() // 'a1b2c3d4-...'
|
|
280
|
-
await Crypto.hash('SHA-256', 'hello').run() // Ok(Uint8Array)
|
|
281
|
-
Crypto.timingSafeEqual(a, b) // boolean
|
|
282
|
-
|
|
283
|
-
// URL: returns Result instead of throwing
|
|
284
|
-
Url.parse('https://example.com?q=1') // Ok(URL)
|
|
285
|
-
Url.parse('not a url') // Err(UrlError)
|
|
286
|
-
|
|
287
|
-
// Encoding: base64, hex, utf8
|
|
288
|
-
Encoding.base64.encode(bytes) // 'SGVsbG8='
|
|
289
|
-
Encoding.base64.decode('SGVsbG8=') // Ok(Uint8Array)
|
|
290
|
-
Encoding.hex.encode(bytes) // 'deadbeef'
|
|
291
|
-
|
|
292
|
-
// Compression: web standard streams
|
|
293
|
-
await Compression.gzip(data).run() // Ok(Uint8Array)
|
|
294
|
-
await Compression.gunzip(compressed).run() // roundtrip
|
|
295
|
-
|
|
296
|
-
// Clone: safe structuredClone
|
|
297
|
-
Clone.deep({ nested: [1, 2] }) // Ok(deepCopy)
|
|
298
|
-
Clone.deep({ fn: () => {} }) // Err(CloneError)
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### HTTP Client
|
|
302
|
-
|
|
303
|
-
```ts
|
|
304
|
-
const api = Client.create({
|
|
305
|
-
baseUrl: 'https://api.example.com',
|
|
306
|
-
headers: { Authorization: 'Bearer token' },
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
const result = await api.get('/users').run()
|
|
310
|
-
// Ok(ClientResponse) or Err(NetworkError | HttpError)
|
|
311
|
-
|
|
312
|
-
if (result.isOk) {
|
|
313
|
-
const users = await result.value.json() // Result<T, ParseError>
|
|
314
|
-
}
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Subprocess
|
|
318
|
-
|
|
319
|
-
```ts
|
|
320
|
-
const result = await Command.exec('git', ['status']).run()
|
|
321
|
-
// Ok({ exitCode: 0, stdout: '...', stderr: '' })
|
|
322
|
-
// Works across Node, Deno, and Bun
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### Timers
|
|
326
|
-
|
|
327
|
-
```ts
|
|
328
|
-
await Timer.sleep(Duration.seconds(1)).run() // Task<void, never>
|
|
329
|
-
Timer.interval(Duration.seconds(1)) // Stream<number, never>
|
|
330
|
-
await Timer.deadline(Duration.seconds(5), task) // Err(TimeoutError) if slow
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### DNS and TCP
|
|
334
|
-
|
|
335
|
-
```ts
|
|
336
|
-
await Dns.lookup('example.com').run() // Ok({ address, family })
|
|
337
|
-
await Dns.resolve('example.com', 'MX').run() // Ok(['mx1.example.com', ...])
|
|
52
|
+
// Validate unknown input
|
|
53
|
+
const User = Schema.object({ name: Schema.string, age: Schema.number });
|
|
54
|
+
User.parse(untrustedData); // Result<{ name: string; age: number }, SchemaError>
|
|
338
55
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
await conn.value.send('hello').run()
|
|
342
|
-
const data = await conn.value.receive().run() // Ok(Uint8Array)
|
|
343
|
-
conn.value.close()
|
|
344
|
-
}
|
|
56
|
+
// Read a file (works on Node, Deno, Bun)
|
|
57
|
+
const content = await File.read('./config.json').run();
|
|
345
58
|
```
|
|
346
59
|
|
|
347
|
-
|
|
60
|
+
## Modules
|
|
348
61
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
// Provide dependencies at the edge
|
|
358
|
-
await getUser('u_123').run({ db: realDb, logger: realLogger })
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### Resilience
|
|
362
|
-
|
|
363
|
-
```ts
|
|
364
|
-
// Semaphore: limit concurrent access
|
|
365
|
-
const sem = Semaphore.create(3)
|
|
366
|
-
const limited = sem.wrap(expensiveTask)
|
|
367
|
-
|
|
368
|
-
// Rate limiter: token bucket
|
|
369
|
-
const limiter = RateLimiter.create({
|
|
370
|
-
capacity: 100,
|
|
371
|
-
refillRate: 10,
|
|
372
|
-
refillInterval: Duration.seconds(1),
|
|
373
|
-
})
|
|
374
|
-
limiter.wrap(apiCall) // Err(RateLimited) when empty
|
|
375
|
-
|
|
376
|
-
// Cache: TTL + LRU
|
|
377
|
-
const cache = Cache.create({ ttl: Duration.minutes(5), maxSize: 1000 })
|
|
378
|
-
cache.set('key', value)
|
|
379
|
-
cache.get('key') // Option<V>
|
|
380
|
-
cache.getOrElse('key', fetchFromDb) // cache-aside pattern
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### Channels
|
|
384
|
-
|
|
385
|
-
```ts
|
|
386
|
-
const ch = Channel.bounded(10)
|
|
387
|
-
|
|
388
|
-
// Producer
|
|
389
|
-
await ch.send('hello')
|
|
390
|
-
ch.close()
|
|
391
|
-
|
|
392
|
-
// Consumer (as async iterable)
|
|
393
|
-
for await (const msg of ch.receive()) {
|
|
394
|
-
console.log(msg)
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Bridge to Stream
|
|
398
|
-
const stream = Stream.from(ch.receive())
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
### Logger and Config
|
|
402
|
-
|
|
403
|
-
```ts
|
|
404
|
-
// Structured logging
|
|
405
|
-
const log = Logger.create({ name: 'api', level: 'info' })
|
|
406
|
-
log.info('request', { method: 'GET', path: '/users' })
|
|
407
|
-
const child = log.child({ requestId: '123' })
|
|
408
|
-
|
|
409
|
-
// Type-safe config from env
|
|
410
|
-
const AppConfig = Config.from({
|
|
411
|
-
PORT: Schema.string.transform(Number).refine(n => n > 0, 'port'),
|
|
412
|
-
DATABASE_URL: Schema.string,
|
|
413
|
-
LOG_LEVEL: Schema.literal('debug').optional(),
|
|
414
|
-
})
|
|
415
|
-
const config = AppConfig.loadFrom(process.env) // Result<Config, SchemaError>
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
### Cross-platform paths
|
|
419
|
-
|
|
420
|
-
```ts
|
|
421
|
-
Path.join('src', 'core', 'result.ts') // native separator
|
|
422
|
-
Path.basename('/home/user/file.ts') // 'file.ts'
|
|
423
|
-
Path.toPosix('src\\core\\file.ts') // 'src/core/file.ts'
|
|
62
|
+
| Layer | Primitives | Docs |
|
|
63
|
+
|-------|------------|------|
|
|
64
|
+
| **Core** | `Result`, `Option`, `pipe`, `flow`, `Match`, `Eq`, `Ord`, `State`, `Lens`, `Iso` | [docs/core.md](docs/core.md) |
|
|
65
|
+
| **Data** | `Record`, `List`, `NonEmptyList`, `Schema`, `Codec`, `ADT` | [docs/data.md](docs/data.md) |
|
|
66
|
+
| **Types** | `ErrType`, `Type`, `Duration`, `Cron` | [docs/types.md](docs/types.md) |
|
|
67
|
+
| **Async** | `Task`, `Stream`, `Retry`, `CircuitBreaker`, `StateMachine`, `EventEmitter`, `Pool`, `Queue`, `CronRunner`, and more | [docs/async.md](docs/async.md) |
|
|
68
|
+
| **IO** | `File`, `Command`, `Json`, `Crypto`, `Encoding`, `Client`, `Dns`, `Net` | [docs/io.md](docs/io.md) |
|
|
69
|
+
| **Runtime** | `Server`, `Program`, `Logger`, `Config`, `Os`, `Process`, `Path` | [docs/runtime.md](docs/runtime.md) |
|
|
424
70
|
|
|
425
|
-
|
|
426
|
-
Eol.split('line1\nline2') // ['line1', 'line2']
|
|
427
|
-
```
|
|
71
|
+
[Full documentation with examples](docs/index.md)
|
|
428
72
|
|
|
429
|
-
|
|
73
|
+
## Subpath Imports
|
|
430
74
|
|
|
431
75
|
```ts
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
Program('platform', signal => Task.all([
|
|
436
|
-
publicApi.serve({ port: 3000, signal }),
|
|
437
|
-
adminApi.serve({ port: 3001, signal }),
|
|
438
|
-
])).run()
|
|
439
|
-
// Ctrl+C gracefully shuts down both
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
## API reference
|
|
443
|
-
|
|
444
|
-
### Record
|
|
445
|
-
|
|
446
|
-
| Method | Description |
|
|
447
|
-
|---|---|
|
|
448
|
-
| `Record(obj)` | Create immutable record. Freezes in-place |
|
|
449
|
-
| `Record.clone(obj)` | Deep clone then freeze |
|
|
450
|
-
| `.set(accessor, val)` | Replace nested value |
|
|
451
|
-
| `.update(accessor, fn)` | Transform nested value |
|
|
452
|
-
| `.produce(draft => ...)` | Batch mutations via draft |
|
|
453
|
-
| `.merge({ ... })` | Shallow merge |
|
|
454
|
-
| `.at(accessor)` | Safe deep access -> `Option` |
|
|
455
|
-
| `.equals(other)` | Structural deep equality |
|
|
456
|
-
|
|
457
|
-
### List
|
|
458
|
-
|
|
459
|
-
| Method | Description |
|
|
460
|
-
|---|---|
|
|
461
|
-
| `List(arr)` | Create immutable list |
|
|
462
|
-
| `.append` / `.prepend` / `.setAt` / `.updateAt` / `.removeAt` | Mutations -> new List |
|
|
463
|
-
| `.map` / `.filter` / `.reduce` / `.flatMap` | Transformations |
|
|
464
|
-
| `.find` / `.findIndex` / `.at` / `.first` / `.last` | Queries -> `Option` |
|
|
465
|
-
| `.sortBy(cmp)` / `.sortByOrd(ord)` | Sorting |
|
|
466
|
-
| `.uniqBy(eq)` / `.groupBy(fn)` | Dedup and grouping |
|
|
467
|
-
| `.equals(other)` | Structural deep equality |
|
|
468
|
-
|
|
469
|
-
### NonEmptyList
|
|
470
|
-
|
|
471
|
-
Extends List. `first()`, `last()`, `head` return `T` directly (not `Option`).
|
|
472
|
-
`reduce1(fn)` folds without initial value. `sortByOrd(ord)` and `uniqBy(eq)` preserve non-emptiness.
|
|
473
|
-
|
|
474
|
-
### Result\<T, E\>
|
|
475
|
-
|
|
476
|
-
| Method | Description |
|
|
477
|
-
|---|---|
|
|
478
|
-
| `Ok(value)` / `Err(error)` | Construct |
|
|
479
|
-
| `Result.tryCatch(fn, onError)` | Wrap throwing code |
|
|
480
|
-
| `Result.collect` / `.sequence` | All-or-nothing collection |
|
|
481
|
-
| `Result.traverse(items, fn)` | Map + collect in one pass |
|
|
482
|
-
| `.map` / `.mapErr` / `.flatMap` | Transform |
|
|
483
|
-
| `.tap` / `.tapErr` | Side effects |
|
|
484
|
-
| `.match({ Ok, Err })` | Exhaustive pattern match |
|
|
485
|
-
| `.unwrap` / `.unwrapOr` / `.unwrapOrElse` | Extract |
|
|
486
|
-
| `.zip(other)` / `.ap(fnResult)` | Combine |
|
|
487
|
-
| `.toOption()` / `.toJSON()` | Convert |
|
|
488
|
-
|
|
489
|
-
### Option\<T\>
|
|
490
|
-
|
|
491
|
-
| Method | Description |
|
|
492
|
-
|---|---|
|
|
493
|
-
| `Some(value)` / `None` | Construct |
|
|
494
|
-
| `Option.fromNullable(v)` | Null-safe wrapping |
|
|
495
|
-
| `Option.traverse(items, fn)` | Map + collect |
|
|
496
|
-
| `.map` / `.flatMap` / `.filter` | Transform |
|
|
497
|
-
| `.match({ Some, None })` | Exhaustive pattern match |
|
|
498
|
-
| `.unwrap` / `.unwrapOr` / `.unwrapOrElse` | Extract |
|
|
499
|
-
| `.zip` / `.or` / `.ap` | Combine |
|
|
500
|
-
| `.toResult(error)` | Convert |
|
|
501
|
-
|
|
502
|
-
### Schema
|
|
503
|
-
|
|
504
|
-
| Method | Description |
|
|
505
|
-
|---|---|
|
|
506
|
-
| `.string` / `.number` / `.boolean` | Primitives |
|
|
507
|
-
| `.object({ ... })` / `.array(el)` / `.tuple(...)` | Composite |
|
|
508
|
-
| `.literal(v)` / `.union(...)` / `.discriminatedUnion(key, map)` | Sum types |
|
|
509
|
-
| `.record(val)` / `.intersection(a, b)` / `.lazy(fn)` | Advanced |
|
|
510
|
-
| `.parse(unknown)` -> `Result` | Validate |
|
|
511
|
-
| `.refine(pred, label)` / `.transform(fn)` / `.optional()` / `.default(v)` | Compose |
|
|
512
|
-
|
|
513
|
-
### Codec
|
|
76
|
+
// Import everything
|
|
77
|
+
import { Ok, Task, Schema } from '@igorjs/pure-ts'
|
|
514
78
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
| `Codec.object({ ... })` / `.array(el)` / `.nullable(codec)` | Composite |
|
|
521
|
-
| `.decode(input)` -> `Result` / `.encode(value)` | Transform |
|
|
522
|
-
| `.pipe(other)` | Chain codecs |
|
|
523
|
-
|
|
524
|
-
### Task\<T, E\>
|
|
525
|
-
|
|
526
|
-
| Method | Description |
|
|
527
|
-
|---|---|
|
|
528
|
-
| `Task(async () => ...)` | Create lazy async computation |
|
|
529
|
-
| `Task.of` / `.fromResult` / `.fromPromise` | Constructors |
|
|
530
|
-
| `Task.all` / `.race` / `.allSettled` | Parallel execution |
|
|
531
|
-
| `Task.traverse(items, fn)` / `.sequence(tasks)` | Collection |
|
|
532
|
-
| `Task.ap(fnTask, argTask)` | Applicative |
|
|
533
|
-
| `.map` / `.mapErr` / `.flatMap` / `.tap` / `.tapErr` | Transform |
|
|
534
|
-
| `.timeout(ms, onTimeout)` / `.retry(n, delay?)` | Resilience |
|
|
535
|
-
| `.memoize()` | Cache result |
|
|
536
|
-
| `.run()` | Execute |
|
|
537
|
-
|
|
538
|
-
### Stream\<T, E\>
|
|
539
|
-
|
|
540
|
-
| Method | Description |
|
|
541
|
-
|---|---|
|
|
542
|
-
| `Stream.of(...)` / `.from(iterable)` / `.fromArray(arr)` | Create |
|
|
543
|
-
| `Stream.unfold(seed, fn)` / `.interval(duration)` | Generate |
|
|
544
|
-
| `.map` / `.flatMap` / `.filter` / `.take` / `.drop` / `.takeWhile` | Transform |
|
|
545
|
-
| `.chunk(size)` / `.window(size)` / `.scan(fn, init)` | Batch |
|
|
546
|
-
| `.mapErr` / `.tap` / `.concat` / `.zip` | Combine |
|
|
547
|
-
| `.collect()` / `.forEach(fn)` / `.reduce(fn, init)` / `.first()` / `.groupBy(fn)` | Collect -> Task |
|
|
548
|
-
|
|
549
|
-
### Optics
|
|
550
|
-
|
|
551
|
-
| Export | Description |
|
|
552
|
-
|---|---|
|
|
553
|
-
| `Lens.prop<S>()(key)` | Total lens for a property |
|
|
554
|
-
| `Lens.from(get, set)` | Custom lens |
|
|
555
|
-
| `LensOptional.index<T>(i)` | Array index (partial) |
|
|
556
|
-
| `LensOptional.fromNullable<S>()(key)` | Nullable field |
|
|
557
|
-
| `Prism.from(getOption, reverseGet)` | Sum type variant |
|
|
558
|
-
| `Traversal.fromArray<T>()` | All array elements |
|
|
559
|
-
| `.compose(other)` | Compose optics |
|
|
560
|
-
| `.get` / `.set(v)(s)` / `.modify(fn)(s)` | Access and update |
|
|
561
|
-
|
|
562
|
-
### Resilience
|
|
563
|
-
|
|
564
|
-
| Export | Description |
|
|
565
|
-
|---|---|
|
|
566
|
-
| `Retry.policy()` | Builder: `.maxAttempts`, `.exponentialBackoff`, `.jitter`, `.maxDelay`, `.shouldRetry` |
|
|
567
|
-
| `Retry.apply(policy, task)` | Apply policy to a Task |
|
|
568
|
-
| `CircuitBreaker.create(policy)` | Create breaker: `failureThreshold`, `successThreshold`, `timeout` |
|
|
569
|
-
| `breaker.protect(task)` | Wrap Task with circuit protection |
|
|
570
|
-
| `breaker.state()` | `'closed'` / `'open'` / `'half-open'` |
|
|
571
|
-
| `Semaphore.create(n)` | Counting semaphore: `.acquire()`, `.wrap(task)`, `.available()` |
|
|
572
|
-
| `Mutex.create()` | Mutual exclusion (semaphore with 1 permit): `.wrap(task)`, `.isLocked()` |
|
|
573
|
-
| `RateLimiter.create(policy)` | Token bucket: `capacity`, `refillRate`, `refillInterval` |
|
|
574
|
-
| `limiter.wrap(task)` | Returns `Err(RateLimited)` when bucket is empty |
|
|
575
|
-
| `Cache.create(options)` | TTL + LRU: `ttl`, `maxSize` |
|
|
576
|
-
| `cache.get(key)` / `.set(key, value)` | Returns `Option`, auto-expires |
|
|
577
|
-
| `cache.getOrElse(key, task)` | Cache-aside: returns cached or runs task |
|
|
578
|
-
| `Channel.bounded(n)` / `.unbounded()` | Async producer/consumer with backpressure |
|
|
579
|
-
| `ch.send(value)` / `ch.receive()` | Send blocks when full, receive is AsyncIterable |
|
|
580
|
-
|
|
581
|
-
### Env\<R, T, E\>
|
|
582
|
-
|
|
583
|
-
| Export | Description |
|
|
584
|
-
|---|---|
|
|
585
|
-
| `Env.of(value)` | Wrap value, ignore environment |
|
|
586
|
-
| `Env.access()` | Read the full environment |
|
|
587
|
-
| `Env.from(fn)` / `.fromSync(fn)` | Create from async/sync function |
|
|
588
|
-
| `.map` / `.mapErr` / `.flatMap` / `.tap` | Transform |
|
|
589
|
-
| `.provide(fn)` | Narrow the environment type |
|
|
590
|
-
| `.provideAll(env)` | Provide all dependencies, get Task-like |
|
|
591
|
-
|
|
592
|
-
### State\<S, A\>
|
|
593
|
-
|
|
594
|
-
| Export | Description |
|
|
595
|
-
|---|---|
|
|
596
|
-
| `State.of(value)` | Pure value, state unchanged |
|
|
597
|
-
| `State.get()` | Read state as value |
|
|
598
|
-
| `State.set(s)` / `.modify(fn)` | Replace or transform state |
|
|
599
|
-
| `.map` / `.flatMap` / `.tap` | Compose |
|
|
600
|
-
| `.run(init)` | Execute: returns `[value, finalState]` |
|
|
601
|
-
| `.eval(init)` / `.exec(init)` | Extract value or state only |
|
|
602
|
-
|
|
603
|
-
### IO
|
|
604
|
-
|
|
605
|
-
| Export | Description |
|
|
606
|
-
|---|---|
|
|
607
|
-
| `Json.parse(str)` | `Result<T, JsonError>`, never throws |
|
|
608
|
-
| `Json.stringify(value)` | `Result<string, JsonError>`, handles circular refs |
|
|
609
|
-
| `File.read` / `.write` / `.exists` / `.stat` / `.copy` / `.rename` | Multi-runtime file ops as Task |
|
|
610
|
-
| `File.makeDir` / `.remove` / `.list` / `.tempDir` | Directory operations as Task |
|
|
611
|
-
| `Crypto.uuid()` | Random UUID v4 (web standard) |
|
|
612
|
-
| `Crypto.randomBytes(n)` / `.hash(algo, data)` | Crypto ops returning Result/Task |
|
|
613
|
-
| `Crypto.timingSafeEqual(a, b)` | Constant-time byte comparison |
|
|
614
|
-
| `Url.parse(input)` | `Result<URL, UrlError>`, wraps `new URL()` |
|
|
615
|
-
| `Url.searchParams(obj)` / `.parseSearchParams(str)` | Query string utilities |
|
|
616
|
-
| `Encoding.base64` / `.hex` / `.utf8` | `.encode()` / `.decode()` with Result |
|
|
617
|
-
| `Compression.gzip` / `.gunzip` / `.deflate` / `.inflate` | Web standard CompressionStream as Task |
|
|
618
|
-
| `Clone.deep(value)` | `Result<T, CloneError>`, wraps `structuredClone` |
|
|
619
|
-
| `Client.create(options?)` | HTTP client: `baseUrl`, `headers`, custom `fetch` |
|
|
620
|
-
| `client.get` / `.post` / `.put` / `.patch` / `.delete` | Returns `Task<ClientResponse, ClientError>` |
|
|
621
|
-
| `WebSocket.router()` | WebSocket route builder: `.route(path, handler)`, `.match(path)` |
|
|
622
|
-
| `Command.exec(cmd, args?)` | Multi-runtime subprocess: `Task<CommandResult, CommandError>` |
|
|
623
|
-
| `Dns.lookup(host)` / `.resolve(host, type?)` | DNS resolution as Task |
|
|
624
|
-
| `Net.connect({ host, port })` | TCP client: `Task<TcpConnection, NetError>` |
|
|
625
|
-
| `Stream.fromReadable(stream)` | Bridge web ReadableStream to `Stream<Uint8Array>` |
|
|
626
|
-
|
|
627
|
-
### Timer
|
|
628
|
-
|
|
629
|
-
| Export | Description |
|
|
630
|
-
|---|---|
|
|
631
|
-
| `Timer.sleep(duration)` | `Task<void, never>` |
|
|
632
|
-
| `Timer.interval(period)` | `Stream<number, never>` |
|
|
633
|
-
| `Timer.delay(duration, task)` | Run task after delay |
|
|
634
|
-
| `Timer.deadline(duration, task)` | Race task against timeout |
|
|
635
|
-
| `Timer.now()` | `performance.now()` wrapper |
|
|
636
|
-
|
|
637
|
-
### Time
|
|
638
|
-
|
|
639
|
-
| Export | Description |
|
|
640
|
-
|---|---|
|
|
641
|
-
| `Duration.seconds(n)` / `.minutes` / `.hours` / `.days` / `.milliseconds` | Create |
|
|
642
|
-
| `Duration.add` / `.subtract` / `.multiply` | Arithmetic |
|
|
643
|
-
| `Duration.toMilliseconds` / `.toSeconds` / `.toMinutes` / `.toHours` | Convert |
|
|
644
|
-
| `Duration.format(d)` | Human-readable: `'2h 30m 15s'` |
|
|
645
|
-
| `Duration.eq` / `.ord` | Typeclass instances |
|
|
646
|
-
| `Cron.parse(expr)` -> `Result` | Validate 5-field cron |
|
|
647
|
-
| `Cron.next(expr, after?)` -> `Option<Date>` | Next occurrence |
|
|
648
|
-
| `Cron.matches(expr, date)` | Check match |
|
|
649
|
-
|
|
650
|
-
### Typeclasses
|
|
651
|
-
|
|
652
|
-
| Export | Description |
|
|
653
|
-
|---|---|
|
|
654
|
-
| `Eq(fn)` / `Eq.string` / `.number` / `.boolean` / `.date` | Equality |
|
|
655
|
-
| `Eq.struct({ ... })` / `Eq.contramap(eq, fn)` | Compose |
|
|
656
|
-
| `Ord(fn)` / `Ord.string` / `.number` / `.date` | Ordering |
|
|
657
|
-
| `Ord.reverse` / `.contramap` / `.min` / `.max` / `.clamp` / `.between` | Compose |
|
|
658
|
-
|
|
659
|
-
### Server
|
|
660
|
-
|
|
661
|
-
| Method | Description |
|
|
662
|
-
|---|---|
|
|
663
|
-
| `Server(name)` | Create builder |
|
|
664
|
-
| `.get` / `.post` / `.put` / `.patch` / `.delete` / `.head` / `.options` / `.all` | Routes |
|
|
665
|
-
| `.use(...middlewares)` | Untyped middleware |
|
|
666
|
-
| `.middleware(typedMw)` | Typed context-extending middleware |
|
|
667
|
-
| `.derive(resolver)` | Sequential context derivation |
|
|
668
|
-
| `.onError(handler)` | Custom error handler |
|
|
669
|
-
| `.fetch(request)` | WHATWG fetch handler |
|
|
670
|
-
| `.serve({ port, signal })` | Returns Task for composable concurrency |
|
|
671
|
-
| `.listen(options, adapter?)` | Start with Program lifecycle |
|
|
672
|
-
| `json` / `text` / `html` / `redirect` | Response helpers |
|
|
673
|
-
|
|
674
|
-
### Program
|
|
675
|
-
|
|
676
|
-
| Method | Description |
|
|
677
|
-
|---|---|
|
|
678
|
-
| `Program(name, (signal) => task)` | Create named program |
|
|
679
|
-
| `.run()` | Execute: logging, signals, exit codes |
|
|
680
|
-
| `.execute(signal?)` | Execute for testing: raw Result |
|
|
681
|
-
|
|
682
|
-
### Logger
|
|
683
|
-
|
|
684
|
-
| Export | Description |
|
|
685
|
-
|---|---|
|
|
686
|
-
| `Logger.create({ name, level?, sink? })` | Create structured logger |
|
|
687
|
-
| `Logger.json` / `.pretty` / `.silent` | Built-in sinks |
|
|
688
|
-
| `log.debug` / `.info` / `.warn` / `.error` | Log at level with optional context |
|
|
689
|
-
| `log.child(context)` | Inherit context, add fields |
|
|
690
|
-
| `log.named(name)` | Change logger name |
|
|
691
|
-
|
|
692
|
-
### Config
|
|
693
|
-
|
|
694
|
-
| Export | Description |
|
|
695
|
-
|---|---|
|
|
696
|
-
| `Config.from(shape)` | Define config schema from env |
|
|
697
|
-
| `.load()` | Read from `process.env`, returns `Result` |
|
|
698
|
-
| `.loadFrom(env)` | Read from custom record |
|
|
699
|
-
|
|
700
|
-
### Platform
|
|
701
|
-
|
|
702
|
-
| Export | Description |
|
|
703
|
-
|---|---|
|
|
704
|
-
| `Path.join` / `.normalize` / `.basename` / `.dirname` / `.extname` | OS-aware path operations |
|
|
705
|
-
| `Path.resolve` / `.relative` / `.isAbsolute` / `.parse` | Advanced path operations |
|
|
706
|
-
| `Path.toPosix(path)` | Convert to forward slashes |
|
|
707
|
-
| `Eol.normalize(text)` | Replace `\r\n` with `\n` |
|
|
708
|
-
| `Eol.split(text)` | Split on `\r?\n` |
|
|
709
|
-
| `Platform.os` / `.isWindows` | Runtime detection |
|
|
710
|
-
| `Os.hostname` / `.arch` / `.platform` / `.cpuCount` | System info as `Option` |
|
|
711
|
-
| `Os.totalMemory` / `.freeMemory` / `.tmpDir` / `.homeDir` | Resource info |
|
|
712
|
-
| `Process.cwd()` | `Result<string, ProcessError>` |
|
|
713
|
-
| `Process.pid` / `.uptime` / `.memoryUsage` | Process info as `Option` |
|
|
714
|
-
| `Process.argv()` / `.parseArgs(schema)` | Argument parsing with Schema validation |
|
|
715
|
-
|
|
716
|
-
### Utilities
|
|
717
|
-
|
|
718
|
-
| Export | Description |
|
|
719
|
-
|---|---|
|
|
720
|
-
| `pipe(val, f1, f2, ...)` | Left-to-right transformation (1-9 stages) |
|
|
721
|
-
| `flow(f1, f2, ...)` | Point-free composition (1-6 stages) |
|
|
722
|
-
| `Match(value).with(...).exhaustive()` | Exhaustive pattern matching |
|
|
723
|
-
| `match(result, { Ok, Err })` | Two-arm match for Result/Option |
|
|
724
|
-
| `ErrType(tag)` | Structured error constructor |
|
|
725
|
-
| `Type<'Name', Base>` | Nominal typing (zero runtime) |
|
|
726
|
-
| `Lazy(() => expr)` | Deferred cached computation |
|
|
727
|
-
| `isImmutable(val)` | Type guard for Records and Lists |
|
|
728
|
-
| `DeepReadonly<T>` | Recursive readonly type utility |
|
|
729
|
-
|
|
730
|
-
## Runtime compatibility
|
|
731
|
-
|
|
732
|
-
CI-tested on:      
|
|
733
|
-
|
|
734
|
-
Every module is classified by its runtime requirements. Pure modules use no runtime APIs. Web modules use standard APIs (TextEncoder, fetch, etc.). Multi-runtime modules detect Deno/Bun/Node via `globalThis` and dispatch to the appropriate API, returning `Err`/`None` in unsupported runtimes.
|
|
735
|
-
|
|
736
|
-
| Module | API | <img src="https://img.shields.io/badge/-339933?logo=nodedotjs&logoColor=white" height="14"> | <img src="https://img.shields.io/badge/-000000?logo=deno&logoColor=white" height="14"> | <img src="https://img.shields.io/badge/-000000?logo=bun&logoColor=white" height="14"> | <img src="https://img.shields.io/badge/-F38020?logo=cloudflare&logoColor=white" height="14"> | <img src="https://img.shields.io/badge/-4285F4?logo=googlechrome&logoColor=white" height="14"> |
|
|
737
|
-
|--------|-----|:---:|:---:|:---:|:---:|:---:|
|
|
738
|
-
| **Result / Option** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
739
|
-
| **pipe / flow / Match** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
740
|
-
| **Eq / Ord / State** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
741
|
-
| **Lens / Prism / Traversal** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
742
|
-
| **Record / List / NonEmptyList** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
743
|
-
| **Schema / Codec** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
744
|
-
| **ErrType / Type / Duration / Cron** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
745
|
-
| **Task / Stream / Lazy** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
746
|
-
| **Env / Channel / Cache** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
747
|
-
| **Semaphore / Mutex / RateLimiter** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
748
|
-
| **Retry / CircuitBreaker** | pure | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
749
|
-
| **Json / Clone / Url / Timer** | web | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
750
|
-
| **Crypto / Encoding / Compression** | web | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
751
|
-
| **Client** | web (fetch) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
752
|
-
| **Path / Eol / Platform** | web | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
753
|
-
| **Stream.fromReadable / WebSocket** | web | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
754
|
-
| **File / Command** | multi-runtime | ✅ | ✅ | ✅ | ⬜ | ⬜ |
|
|
755
|
-
| **Os / Process / Config** | multi-runtime | ✅ | ✅ | ✅ | ⬜ | ⬜ |
|
|
756
|
-
| **Logger** | multi-runtime | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
757
|
-
| **Server / Program** | multi-runtime | ✅ | ✅ | ✅ | ⬜ | ⬜ |
|
|
758
|
-
| **Dns / Net** | multi-runtime | ✅ | ✅ | ✅ | ⬜ | ⬜ |
|
|
759
|
-
|
|
760
|
-
**Legend:**
|
|
761
|
-
- **pure**: No runtime APIs. Pure TypeScript logic.
|
|
762
|
-
- **web**: Uses web standard APIs (`crypto.subtle`, `URL`, `TextEncoder`, `fetch`, etc.).
|
|
763
|
-
- **multi-runtime**: Detects Deno/Bun/Node via `globalThis` and dispatches to the appropriate API.
|
|
764
|
-
- ⬜ = loads safely, returns `Err`/`None` (no filesystem/subprocess in this environment).
|
|
765
|
-
|
|
766
|
-
## Performance
|
|
767
|
-
|
|
768
|
-
Class-per-shape Records eliminate Proxy from the read path:
|
|
769
|
-
|
|
770
|
-
| Operation | ops/s | vs plain JS |
|
|
771
|
-
|---|---|---|
|
|
772
|
-
| Shallow read (`user.name`) | 207M | ~0.86x (near native) |
|
|
773
|
-
| Deep read (`user.address.geo.lat`) | 19M | ~0.09x |
|
|
774
|
-
| Construction | 192K | - |
|
|
775
|
-
| `set()` shallow | 328K | - |
|
|
776
|
-
| `produce()` 3 mutations | 193K | - |
|
|
777
|
-
| `Ok(42)` | 48M | - |
|
|
778
|
-
| `Ok().map().map().map()` | 35M | - |
|
|
779
|
-
|
|
780
|
-
Memory per Record instance: ~410 bytes (vs ~376 bytes plain object = 1.09x overhead).
|
|
781
|
-
|
|
782
|
-
## Building
|
|
783
|
-
|
|
784
|
-
```bash
|
|
785
|
-
npm run check # Type check (TS7)
|
|
786
|
-
npm run build # Build
|
|
787
|
-
npm test # Test runtime
|
|
788
|
-
npm run test:types # Test types (compile-time safety suite)
|
|
789
|
-
npm run prepublishOnly # Full prepublish pipeline
|
|
79
|
+
// Or import specific modules for smaller bundles
|
|
80
|
+
import { Ok, Err, pipe } from '@igorjs/pure-ts/core'
|
|
81
|
+
import { Schema } from '@igorjs/pure-ts/data'
|
|
82
|
+
import { Task, Stream } from '@igorjs/pure-ts/async'
|
|
83
|
+
import { File, Command } from '@igorjs/pure-ts/io'
|
|
790
84
|
```
|
|
791
85
|
|
|
792
86
|
## License
|
|
793
87
|
|
|
794
|
-
Apache-2.0
|
|
88
|
+
[Apache-2.0](LICENSE)
|