@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.
Files changed (142) hide show
  1. package/README.md +39 -745
  2. package/dist/async/cache.d.ts +1 -6
  3. package/dist/async/cache.d.ts.map +1 -1
  4. package/dist/async/cache.js.map +1 -1
  5. package/dist/async/circuit-breaker.d.ts +1 -6
  6. package/dist/async/circuit-breaker.d.ts.map +1 -1
  7. package/dist/async/circuit-breaker.js.map +1 -1
  8. package/dist/async/index.d.ts +21 -2
  9. package/dist/async/index.d.ts.map +1 -1
  10. package/dist/async/index.js +6 -2
  11. package/dist/async/index.js.map +1 -1
  12. package/dist/async/lazy.d.ts +6 -42
  13. package/dist/async/lazy.d.ts.map +1 -1
  14. package/dist/async/lazy.js.map +1 -1
  15. package/dist/async/pool.d.ts +1 -5
  16. package/dist/async/pool.d.ts.map +1 -1
  17. package/dist/async/pool.js +3 -3
  18. package/dist/async/pool.js.map +1 -1
  19. package/dist/async/rate-limiter.d.ts +1 -6
  20. package/dist/async/rate-limiter.d.ts.map +1 -1
  21. package/dist/async/rate-limiter.js.map +1 -1
  22. package/dist/async/retry.d.ts +7 -9
  23. package/dist/async/retry.d.ts.map +1 -1
  24. package/dist/async/retry.js.map +1 -1
  25. package/dist/async/semaphore.d.ts +2 -7
  26. package/dist/async/semaphore.d.ts.map +1 -1
  27. package/dist/async/semaphore.js.map +1 -1
  28. package/dist/async/stream.d.ts +1 -5
  29. package/dist/async/stream.d.ts.map +1 -1
  30. package/dist/async/stream.js +6 -6
  31. package/dist/async/stream.js.map +1 -1
  32. package/dist/async/task-like.d.ts +30 -0
  33. package/dist/async/task-like.d.ts.map +1 -0
  34. package/dist/async/task-like.js +13 -0
  35. package/dist/async/task-like.js.map +1 -0
  36. package/dist/async/task.d.ts +8 -62
  37. package/dist/async/task.d.ts.map +1 -1
  38. package/dist/async/task.js +1 -1
  39. package/dist/async/task.js.map +1 -1
  40. package/dist/async/timer.d.ts +1 -6
  41. package/dist/async/timer.d.ts.map +1 -1
  42. package/dist/async/timer.js +4 -4
  43. package/dist/async/timer.js.map +1 -1
  44. package/dist/client.d.ts +6 -7
  45. package/dist/client.d.ts.map +1 -1
  46. package/dist/client.js +2 -2
  47. package/dist/client.js.map +1 -1
  48. package/dist/core/index.d.ts +1 -1
  49. package/dist/core/index.d.ts.map +1 -1
  50. package/dist/core/index.js +1 -1
  51. package/dist/core/index.js.map +1 -1
  52. package/dist/core/option.d.ts +26 -68
  53. package/dist/core/option.d.ts.map +1 -1
  54. package/dist/core/option.js +7 -20
  55. package/dist/core/option.js.map +1 -1
  56. package/dist/core/pipe.d.ts +13 -0
  57. package/dist/core/pipe.d.ts.map +1 -1
  58. package/dist/core/pipe.js.map +1 -1
  59. package/dist/core/result.d.ts +45 -90
  60. package/dist/core/result.d.ts.map +1 -1
  61. package/dist/core/result.js +9 -65
  62. package/dist/core/result.js.map +1 -1
  63. package/dist/data/adt.d.ts +1 -2
  64. package/dist/data/adt.d.ts.map +1 -1
  65. package/dist/data/codec.d.ts +4 -3
  66. package/dist/data/codec.d.ts.map +1 -1
  67. package/dist/data/codec.js.map +1 -1
  68. package/dist/data/index.d.ts +12 -0
  69. package/dist/data/index.d.ts.map +1 -1
  70. package/dist/data/index.js +3 -0
  71. package/dist/data/index.js.map +1 -1
  72. package/dist/data/internals.d.ts +2 -2
  73. package/dist/data/internals.d.ts.map +1 -1
  74. package/dist/data/list.d.ts +1 -1
  75. package/dist/data/non-empty-list.d.ts +1 -1
  76. package/dist/data/record.d.ts +1 -2
  77. package/dist/data/record.d.ts.map +1 -1
  78. package/dist/data/record.js.map +1 -1
  79. package/dist/data/stable-vec.d.ts +116 -0
  80. package/dist/data/stable-vec.d.ts.map +1 -0
  81. package/dist/data/stable-vec.js +184 -0
  82. package/dist/data/stable-vec.js.map +1 -0
  83. package/dist/index.d.ts +26 -9
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +6 -4
  86. package/dist/index.js.map +1 -1
  87. package/dist/io/compression.d.ts +1 -6
  88. package/dist/io/compression.d.ts.map +1 -1
  89. package/dist/io/compression.js +3 -3
  90. package/dist/io/compression.js.map +1 -1
  91. package/dist/io/crypto.d.ts +1 -5
  92. package/dist/io/crypto.d.ts.map +1 -1
  93. package/dist/io/crypto.js +2 -2
  94. package/dist/io/crypto.js.map +1 -1
  95. package/dist/io/dns.d.ts +3 -7
  96. package/dist/io/dns.d.ts.map +1 -1
  97. package/dist/io/dns.js +3 -3
  98. package/dist/io/dns.js.map +1 -1
  99. package/dist/io/file.d.ts +1 -5
  100. package/dist/io/file.d.ts.map +1 -1
  101. package/dist/io/file.js +13 -13
  102. package/dist/io/file.js.map +1 -1
  103. package/dist/io/index.d.ts +11 -1
  104. package/dist/io/index.d.ts.map +1 -1
  105. package/dist/io/index.js +1 -0
  106. package/dist/io/index.js.map +1 -1
  107. package/dist/io/net.d.ts +1 -6
  108. package/dist/io/net.d.ts.map +1 -1
  109. package/dist/io/net.js +6 -6
  110. package/dist/io/net.js.map +1 -1
  111. package/dist/io/subprocess.d.ts +1 -6
  112. package/dist/io/subprocess.d.ts.map +1 -1
  113. package/dist/io/subprocess.js +4 -4
  114. package/dist/io/subprocess.js.map +1 -1
  115. package/dist/program.d.ts +137 -25
  116. package/dist/program.d.ts.map +1 -1
  117. package/dist/program.js +23 -5
  118. package/dist/program.js.map +1 -1
  119. package/dist/runtime/config.d.ts +4 -5
  120. package/dist/runtime/config.d.ts.map +1 -1
  121. package/dist/runtime/index.d.ts +17 -0
  122. package/dist/runtime/index.d.ts.map +1 -1
  123. package/dist/runtime/index.js +1 -0
  124. package/dist/runtime/index.js.map +1 -1
  125. package/dist/runtime/logger.d.ts +22 -6
  126. package/dist/runtime/logger.d.ts.map +1 -1
  127. package/dist/runtime/logger.js.map +1 -1
  128. package/dist/runtime/platform.d.ts +2 -2
  129. package/dist/runtime/platform.d.ts.map +1 -1
  130. package/dist/runtime/platform.js.map +1 -1
  131. package/dist/runtime/process.d.ts +4 -2
  132. package/dist/runtime/process.d.ts.map +1 -1
  133. package/dist/runtime/process.js.map +1 -1
  134. package/dist/types/error.d.ts +34 -61
  135. package/dist/types/error.d.ts.map +1 -1
  136. package/dist/types/error.js.map +1 -1
  137. package/dist/types/index.d.ts +11 -0
  138. package/dist/types/index.d.ts.map +1 -1
  139. package/dist/types/index.js.map +1 -1
  140. package/dist/types/nominal.d.ts +1 -3
  141. package/dist/types/nominal.d.ts.map +1 -1
  142. package/package.json +1 -1
package/README.md CHANGED
@@ -4,12 +4,12 @@
4
4
  [![JSR](https://jsr.io/badges/@igorjs/pure-ts)](https://jsr.io/@igorjs/pure-ts)
5
5
  [![JSR Score](https://jsr.io/badges/@igorjs/pure-ts/score)](https://jsr.io/@igorjs/pure-ts)
6
6
  [![License](https://img.shields.io/npm/l/@igorjs/pure-ts)](https://github.com/igorjs/pure-ts/blob/main/LICENSE)
7
- [![Tests](https://img.shields.io/badge/tests-1016_passing-brightgreen)]()
7
+ [![Tests](https://img.shields.io/badge/tests-1035_passing-brightgreen)]()
8
8
  [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)]()
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. The type system carries everything.
12
+ Errors are values, not exceptions. Data is immutable, enforced at runtime. Async is lazy and composable.
13
13
 
14
14
  ![Node.js](https://img.shields.io/badge/Node.js_22+-339933?logo=nodedotjs&logoColor=white)
15
15
  ![Deno](https://img.shields.io/badge/Deno_2+-000000?logo=deno&logoColor=white)
@@ -18,27 +18,6 @@ Errors are values, not exceptions. Data is immutable, enforced at runtime. Async
18
18
  ![Browser](https://img.shields.io/badge/Chromium-4285F4?logo=googlechrome&logoColor=white)
19
19
  ![TypeScript](https://img.shields.io/badge/TypeScript_5.5+-3178C6?logo=typescript&logoColor=white)
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
- Requires Node >= 22 (LTS). Compatible with TypeScript >= 5.5 and TypeScript 7 (`tsgo`).
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
- const user = Record({ name: 'Alice', address: { city: 'Sydney' } })
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
- user.set(u => u.address.city, 'Melbourne') // new Record
93
- user.produce(d => { d.name = 'Bob' }) // batch mutations via draft
94
-
95
- const nums = List([3, 1, 4])
96
- nums.sortByOrd(Ord.number) // List [1, 3, 4]
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
- ### Validation
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
- const city = Lens.prop<User>()('address')
208
- .compose(Lens.prop<{ city: string }>()('city'))
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
- city.get(user) // 'Sydney'
211
- pipe(user, city.set('Melbourne')) // new object, city changed
212
- pipe(user, city.modify(s => s.toUpperCase())) // new object, city uppercased
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
- const conn = await Net.connect({ host: '127.0.0.1', port: 8080 }).run()
340
- if (conn.isOk) {
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
- ### Dependency injection
60
+ ## Modules
348
61
 
349
- ```ts
350
- type Deps = { db: Database; logger: Logger }
351
-
352
- const getUser = (id: string) =>
353
- Env.access<Deps>().flatMap(({ db }) =>
354
- Env.fromSync(() => db.query(id))
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
- Eol.normalize('line1\r\nline2') // 'line1\nline2'
426
- Eol.split('line1\nline2') // ['line1', 'line2']
427
- ```
71
+ [Full documentation with examples](docs/index.md)
428
72
 
429
- ### Concurrent servers
73
+ ## Subpath Imports
430
74
 
431
75
  ```ts
432
- const publicApi = Server('public').get('/health', () => json({ ok: true }))
433
- const adminApi = Server('admin').get('/metrics', () => json({ uptime: 0 }))
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
- | Method | Description |
516
- |---|---|
517
- | `Codec.from(decode, encode)` | Custom bidirectional codec |
518
- | `Codec.fromSchema(schema, encode)` | Bridge from Schema |
519
- | `Codec.string` / `.number` / `.boolean` | Primitives |
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: &nbsp; ![Node.js 22](https://img.shields.io/badge/Node.js_22-339933?logo=nodedotjs&logoColor=white) &nbsp; ![Node.js 24](https://img.shields.io/badge/Node.js_24-339933?logo=nodedotjs&logoColor=white) &nbsp; ![Deno](https://img.shields.io/badge/Deno_2+-000000?logo=deno&logoColor=white) &nbsp; ![Bun](https://img.shields.io/badge/Bun-000000?logo=bun&logoColor=white) &nbsp; ![CF Workers](https://img.shields.io/badge/CF_Workers-F38020?logo=cloudflare&logoColor=white) &nbsp; ![Browser](https://img.shields.io/badge/Chromium-4285F4?logo=googlechrome&logoColor=white)
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)