brass-runtime 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Augusto Vivaldelli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,361 @@
1
+ # 🛠️ brass-ts — Mini runtime funcional al estilo ZIO en TypeScript
2
+
3
+ **brass-ts** es un runtime funcional inspirado en **ZIO 2**, escrito en **TypeScript vanilla** y **sin usar Promises ni async/await** como primitiva principal de modelado.
4
+
5
+ El objetivo del proyecto es explorar cómo construir, desde cero, un sistema de:
6
+
7
+ - **Efectos puros** (sincrónicos y asincrónicos)
8
+ - **Concurrencia estructurada**
9
+ - **Fibras** (fibers)
10
+ - **Scheduler cooperativo**
11
+ - **Limpieza segura de recursos (acquire / release)**
12
+ - **Scopes estructurados**
13
+ - **Finalizers (a nivel scope y fibra)**
14
+ - **Streams estructurados (ZStream-like) con backpressure**
15
+
16
+ Todo con un diseño **determinístico**, **pure FP**, y sin depender de `Promise` ni `async/await` para la semántica del modelo.
17
+
18
+ ---
19
+
20
+ ## ✨ Características principales
21
+
22
+ ### 1. `Effect` sincrónico (núcleo funcional)
23
+
24
+ En `brass-ts`, un efecto puro se modela como:
25
+
26
+ ```ts
27
+ type Exit<E, A> =
28
+ | { _tag: "Success"; value: A }
29
+ | { _tag: "Failure"; error: E };
30
+
31
+ type Effect<R, E, A> = (env: R) => Exit<E, A>;
32
+ ```
33
+
34
+ Con combinadores típicos de un sistema de efectos:
35
+
36
+ - `map`
37
+ - `flatMap`
38
+ - `mapError`
39
+ - `catchAll`
40
+ - `zip`
41
+ - `foreach`
42
+ - `collectAll`
43
+ - `as`, `asUnit`, `tap`
44
+
45
+ Este núcleo no usa `Promise` ni `async/await`. Es **100% sincrónico y determinista**.
46
+
47
+ ---
48
+
49
+ ### 2. `Async` — efectos asincrónicos sin Promises
50
+
51
+ Para modelar operaciones asincrónicas, `brass-ts` define un tipo de datos algebraico:
52
+
53
+ ```ts
54
+ type Async<R, E, A> =
55
+ | { _tag: "Succeed"; value: A }
56
+ | { _tag: "Fail"; error: E }
57
+ | { _tag: "Sync"; thunk: (env: R) => A }
58
+ | { _tag: "Async"; register: (env: R, cb: (exit: Exit<E, A>) => void) => void }
59
+ | { _tag: "FlatMap"; first: Async<R, E, any>; andThen: (a: any) => Async<R, E, A> };
60
+ ```
61
+
62
+ Con constructores como:
63
+
64
+ - `asyncSucceed`
65
+ - `asyncFail`
66
+ - `asyncSync`
67
+ - `asyncTotal`
68
+ - `async` (primitive para integrar APIs callback-based como `setTimeout`, `fs`, etc.)
69
+ - `asyncMap`
70
+ - `asyncFlatMap`
71
+
72
+ Y un runtime que ejecuta `Async` mediante un **intérprete explícito**, sin usar `Promise`.
73
+
74
+ ---
75
+
76
+ ### 3. Scheduler cooperativo
77
+
78
+ El sistema usa un **scheduler cooperativo** con una cola de tareas:
79
+
80
+ ```ts
81
+ class Scheduler {
82
+ schedule(task: () => void): void;
83
+ }
84
+ ```
85
+
86
+ El `Scheduler` controla:
87
+
88
+ - el orden en que se ejecutan los pasos de cada fibra,
89
+ - la equidad (fairness),
90
+ - posibles políticas de prioridad.
91
+
92
+ Esto permite testear y razonar sobre la concurrencia sin depender del azar del event loop.
93
+
94
+ ---
95
+
96
+ ### 4. Fibers (fibras)
97
+
98
+ Cada programa `Async` corre dentro de una **fibra**:
99
+
100
+ ```ts
101
+ type Fiber<E, A> = {
102
+ id: number;
103
+ status: () => "Running" | "Done" | "Interrupted";
104
+ join: (cb: (exit: Exit<E | Interrupted, A>) => void) => void;
105
+ interrupt: () => void;
106
+ addFinalizer: (f: (exit: Exit<E | Interrupted, A>) => Async<any, any, any>) => void;
107
+ };
108
+ ```
109
+
110
+ Las fibras proveen:
111
+
112
+ - concurrencia liviana (miles de fibers),
113
+ - cancelación cooperativa (`interrupt`),
114
+ - `join` para esperar resultados,
115
+ - **finalizers de fibra** (LIFO) que se ejecutan siempre: éxito, fallo o interrupción.
116
+
117
+ ---
118
+
119
+ ### 5. Scopes — Concurrencia estructurada
120
+
121
+ Un **Scope** modela una unidad de concurrencia estructurada:
122
+
123
+ ```ts
124
+ class Scope<R> {
125
+ fork<E, A>(eff: Async<R, E, A>, env: R): Fiber<E, A>;
126
+ subScope(): Scope<R>;
127
+ addFinalizer(f: (exit: Exit<any, any>) => Async<R, any, any>): void;
128
+ close(exit?: Exit<any, any>): void;
129
+ isClosed(): boolean;
130
+ }
131
+ ```
132
+
133
+ Un scope:
134
+
135
+ - rastrea las fibras hijas,
136
+ - rastrea sub-scopes,
137
+ - mantiene una pila de finalizers (LIFO),
138
+ - al cerrarse:
139
+ - interrumpe fibras hijas,
140
+ - cierra sub-scopes,
141
+ - ejecuta finalizers registrados.
142
+
143
+ Esto da **concurrencia estructurada** al estilo ZIO:
144
+ si algo vive en un `Scope`, se limpia cuando el scope termina.
145
+
146
+ ---
147
+
148
+ ### 6. Acquire / Release — Resource Safety
149
+
150
+ Al estilo `ZIO.acquireRelease`, `brass-ts` implementa:
151
+
152
+ ```ts
153
+ acquireRelease(
154
+ acquire: Async<R, E, A>,
155
+ release: (res: A, exit: Exit<any, any>) => Async<R, any, any>,
156
+ scope: Scope<R>
157
+ ): Async<R, E, A>;
158
+ ```
159
+
160
+ Semántica:
161
+
162
+ - `acquire` corre dentro del scope,
163
+ - si tiene éxito, registra un finalizer que hace `release(res, exitFinalDelScope)`,
164
+ - el finalizer se ejecuta:
165
+ - si el scope cierra con éxito,
166
+ - si hay error,
167
+ - si hay interrupción/cancelación.
168
+
169
+ **Garantiza cleanup de recursos** (archivos, sockets, conexiones, etc.) de forma estructurada.
170
+
171
+ ---
172
+
173
+ ### 7. Structured Concurrency: `race`, `zipPar`, `collectAllPar`
174
+
175
+ Sobre fibras, scopes y `Async`, se construyen combinadores de **concurrencia estructurada**:
176
+
177
+ #### `race(left, right, scope)`
178
+
179
+ - ejecuta `left` y `right` en paralelo dentro de un scope,
180
+ - el primero que termina “gana”,
181
+ - la fibra perdedora se interrumpe,
182
+ - se propaga el resultado del ganador.
183
+
184
+ #### `zipPar(left, right, scope)`
185
+
186
+ - ejecuta ambos efectos en paralelo,
187
+ - si alguno falla → se cancela el otro,
188
+ - si ambos tienen éxito → devuelve `[A, B]`.
189
+
190
+ #### `collectAllPar(effects, scope)`
191
+
192
+ - ejecuta una lista de efectos en paralelo,
193
+ - si alguno falla → cancela el resto,
194
+ - si todos completan → devuelve la lista de resultados.
195
+
196
+ Esto replica la semántica de **ZIO 2 structured concurrency**.
197
+
198
+ ---
199
+
200
+ ### 8. ZStream-like — Streams estructurados con backpressure
201
+
202
+ `brass-ts` incluye una base de **streams estructurados** inspirados en `ZStream`:
203
+
204
+ ```ts
205
+ type Pull<R, E, A> = Async<R, Option<E>, A>;
206
+
207
+ type ZStream<R, E, A> = {
208
+ open: (scope: Scope<R>) => Pull<R, E, A>;
209
+ };
210
+ ```
211
+
212
+ Donde:
213
+
214
+ - `Success(a)` → el stream produjo un valor,
215
+ - `Failure(Some(e))` → error,
216
+ - `Failure(None)` → fin del stream.
217
+
218
+ Constructores básicos:
219
+
220
+ - `empty`
221
+ - `streamOf`
222
+ - `fromArray`
223
+
224
+ Transformaciones:
225
+
226
+ - `map`
227
+ - `filter`
228
+ - `fromResource` (integra acquire/release con streams)
229
+
230
+ Consumo:
231
+
232
+ ```ts
233
+ runCollect(stream, env): Async<R, E, A[]>;
234
+ ```
235
+
236
+ El consumo se hace respetando backpressure: cada `pull` produce como mucho un valor,
237
+ y el scope del stream garantiza que todos los recursos/finalizers se limpien al terminar
238
+ (el stream o el consumidor).
239
+
240
+ ---
241
+
242
+ ## 📁 Estructura sugerida del proyecto
243
+
244
+ Una posible organización de archivos para tu repo de **brass-ts**:
245
+
246
+ ```bash
247
+ src/
248
+ fibers/
249
+ scheduler/
250
+ stream/
251
+ types/
252
+
253
+
254
+ examples/
255
+ demo.ts
256
+ fiberFinalizer.ts
257
+ resourceExample.ts
258
+ ```
259
+
260
+ ---
261
+
262
+ ## 🚀 Ejemplo rápido
263
+
264
+ ```ts
265
+ import {
266
+ asyncTotal,
267
+ asyncFlatMap,
268
+ asyncSucceed,
269
+ } from "./asyncEffect";
270
+ import { sleep } from "./std";
271
+ import { Scope } from "./scope";
272
+ import { race } from "./concurrency";
273
+
274
+ type Env = {};
275
+
276
+ function task(name: string, ms: number) {
277
+ return asyncFlatMap(sleep(ms), () =>
278
+ asyncSucceed(`Terminé ${name}`)
279
+ );
280
+ }
281
+
282
+ function main() {
283
+ const env: Env = {};
284
+ const scope = new Scope<Env>();
285
+
286
+ const fast = task("rápida", 200);
287
+ const slow = task("lenta", 1000);
288
+
289
+ race(fast, slow, scope)(env, exit => {
290
+ console.log("Resultado race:", exit);
291
+ scope.close(exit);
292
+ });
293
+ }
294
+
295
+ main();
296
+ ```
297
+
298
+ ---
299
+
300
+ ## 🧪 Objetivos del proyecto
301
+
302
+ - Explorar el diseño de runtimes funcionales modernos (tipo ZIO) en TypeScript.
303
+ - Entender y practicar:
304
+ - Efectos tipados (`R`, `E`, `A`),
305
+ - Concurrencia estructurada,
306
+ - Fibras,
307
+ - Scopes y finalizers,
308
+ - Streams con recursos seguros y backpressure.
309
+ - Servir como base educativa y potencialmente como **runtime experimental**
310
+ para proyectos de ejemplo, demos y pruebas de conceptos FP en TS.
311
+
312
+ ---
313
+
314
+ ## 📝 Estado actual
315
+
316
+ - [x] Núcleo de efectos sincrónicos (`Effect`)
317
+ - [x] Núcleo de efectos asincrónicos (`Async`) sin Promises
318
+ - [x] Scheduler cooperativo
319
+ - [x] Fibers con finalizers
320
+ - [x] Scopes con finalizers (LIFO)
321
+ - [x] Acquire / Release
322
+ - [x] Concurrencia estructurada (`race`, `zipPar`, `collectAllPar`)
323
+ - [x] Streams básicos (`ZStream`-like) con backpressure y scopes
324
+ - [ ] Buffering en streams
325
+ - [ ] Merge / zipPar de streams
326
+ - [ ] Hubs / Broadcast / Multicast
327
+ - [ ] Pipelines (tipo `ZPipeline`)
328
+ - [ ] Channels / Sinks avanzado
329
+
330
+ ---
331
+
332
+ ## 📜 Licencia
333
+
334
+ Este proyecto está pensado como laboratorio de ideas FP.
335
+ Se recomienda usar licencia MIT:
336
+
337
+ ```text
338
+ MIT License
339
+ Copyright (c) 2025
340
+ ```
341
+
342
+ ---
343
+
344
+ ## 🤝 Contribuciones
345
+
346
+ Ideas de mejora, PRs y discusiones de diseño son más que bienvenidas.
347
+
348
+ Algunas direcciones interesantes para futuro:
349
+
350
+ - Integrar fs / net de Node de forma segura vía `Async`,
351
+ - Agregar tests deterministas de concurrencia,
352
+ - Implementar Hubs y Queues al estilo ZIO,
353
+ - Extender ZStream con merges, buffers y pipelines,
354
+ - Explorar integración con TypeScript decorators para “endpoints” basados en efectos.
355
+
356
+ ---
357
+
358
+ Hecho con ❤️ en TypeScript, para aprender y jugar con runtimes funcionales.
359
+
360
+ **Nombre del proyecto:** `brass-ts`
361
+ **Objetivo:** construir un mini ZIO-like runtime en el ecosistema JS/TS, pero manteniendo el control total sobre la semántica de los efectos desde el código de usuario.
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sleep = sleep;
4
+ const effect_1 = require("../types/effect");
5
+ const stream_1 = require("../stream/stream");
6
+ const asyncEffect_1 = require("../types/asyncEffect");
7
+ const fiber_1 = require("../fibers/fiber");
8
+ const withScope_1 = require("../scheduler/withScope");
9
+ function main() {
10
+ const env = {};
11
+ const fiberA = (0, fiber_1.fork)(task("A", 1000), env);
12
+ const fiberB = (0, fiber_1.fork)(task("B", 500), env);
13
+ fiberA.join((exit) => {
14
+ console.log("Fiber A:", exit);
15
+ });
16
+ fiberB.join((exit) => {
17
+ console.log("Fiber B:", exit);
18
+ });
19
+ const eff1 = (0, effect_1.succeed)(10);
20
+ const eff2 = (0, effect_1.succeed)(20);
21
+ const sumEff = (0, effect_1.zip)(eff1, eff2);
22
+ const sumExit = sumEff({});
23
+ console.log("sumExit:", sumExit);
24
+ const numsEff = (0, effect_1.foreach)([1, 2, 3], (n) => (0, effect_1.succeed)(n * 2));
25
+ console.log("foreach:", numsEff({}));
26
+ const s = (0, stream_1.fromArray)([1, 2, 3, 4]);
27
+ const sMapped = (0, stream_1.mapStream)(s, (n) => n * 10);
28
+ const collected = (0, stream_1.collectStream)(sMapped, env);
29
+ console.log("Stream mapeado:", collected);
30
+ (0, withScope_1.withScope)(scope => {
31
+ const f1 = scope.fork(task("A", 1000), env);
32
+ const f2 = scope.fork(task("B", 1500), env);
33
+ const f3 = scope.fork(task("C", 2000), env);
34
+ console.log("Tareas lanzadas dentro del scope");
35
+ // Si quiero, cancelo todo luego de 1.2s
36
+ setTimeout(() => {
37
+ console.log("CANCELANDO TODO EL SCOPE...");
38
+ scope.close();
39
+ }, 1200);
40
+ f1.join(console.log);
41
+ f2.join(console.log);
42
+ f3.join(console.log);
43
+ });
44
+ }
45
+ function sleep(ms) {
46
+ return (0, asyncEffect_1.async)((_, cb) => {
47
+ setTimeout(() => {
48
+ cb({ _tag: "Success", value: undefined });
49
+ }, ms);
50
+ });
51
+ }
52
+ function task(name, ms) {
53
+ return (0, asyncEffect_1.asyncFlatMap)(sleep(ms), () => (0, asyncEffect_1.asyncSucceed)(`Terminé ${name} después de ${ms}ms`));
54
+ }
55
+ main();
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ // fiberFinalizer.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const asyncEffect_1 = require("../types/asyncEffect");
5
+ const fiber_1 = require("../fibers/fiber");
6
+ const demo_1 = require("./demo");
7
+ function main() {
8
+ const env = {};
9
+ const eff = (0, asyncEffect_1.asyncFlatMap)((0, asyncEffect_1.asyncTotal)(() => console.log("Start")), () => (0, asyncEffect_1.asyncFlatMap)((0, demo_1.sleep)(1000), () => (0, asyncEffect_1.asyncTotal)(() => "done")));
10
+ const fiber = (0, fiber_1.fork)(eff, env);
11
+ fiber.addFinalizer(exit => (0, asyncEffect_1.asyncTotal)(() => {
12
+ console.log("RUNNING FINALIZER → exit =", exit._tag);
13
+ }));
14
+ // cancelamos antes de terminar para ver que el finalizer corre
15
+ setTimeout(() => {
16
+ console.log("Interrupting fiber...");
17
+ fiber.interrupt();
18
+ }, 500);
19
+ fiber.join((exit) => {
20
+ console.log("Fiber completed:", exit);
21
+ });
22
+ }
23
+ main();
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ // src/resourceExample.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const acquireRelease_1 = require("../scheduler/acquireRelease");
5
+ const asyncEffect_1 = require("../types/asyncEffect");
6
+ const withScope_1 = require("../scheduler/withScope");
7
+ function openFile(name) {
8
+ console.log("OPEN FILE:", name);
9
+ return {
10
+ name,
11
+ closed: false,
12
+ close() {
13
+ console.log("CLOSE FILE:", name);
14
+ this.closed = true;
15
+ }
16
+ };
17
+ }
18
+ function main() {
19
+ (0, withScope_1.withScope)(scope => {
20
+ const env = {};
21
+ const program = (0, acquireRelease_1.acquireRelease)((0, asyncEffect_1.asyncTotal)(() => openFile("data.txt")), (file, exit) => (0, asyncEffect_1.asyncTotal)(() => {
22
+ console.log("Finalizer running due to:", exit._tag);
23
+ file.close();
24
+ }), scope);
25
+ // use the resource
26
+ scope.fork((0, asyncEffect_1.asyncFlatMap)(program, (fh) => (0, asyncEffect_1.asyncFlatMap)(sleep(1000), () => (0, asyncEffect_1.asyncSucceed)(`Using ${fh.name}`))), env);
27
+ // cancel whole scope early (to force cleanup)
28
+ setTimeout(() => {
29
+ console.log("Cancelling scope manually...");
30
+ scope.close();
31
+ }, 500);
32
+ });
33
+ }
34
+ function sleep(ms) {
35
+ return (0, asyncEffect_1.async)((_, cb) => {
36
+ setTimeout(() => {
37
+ cb({ _tag: "Success", value: undefined });
38
+ }, ms);
39
+ });
40
+ }
41
+ main();
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fork = fork;
4
+ exports.unsafeRunAsync = unsafeRunAsync;
5
+ const scheduler_1 = require("../scheduler/scheduler");
6
+ let nextId = 1;
7
+ class RuntimeFiber {
8
+ constructor(effect, env, scheduler) {
9
+ this.scheduler = scheduler;
10
+ this.statusValue = "Running";
11
+ this.interrupted = false;
12
+ this.result = null;
13
+ this.joiners = [];
14
+ this.stack = [];
15
+ this.fiberFinalizers = [];
16
+ this.id = nextId++;
17
+ this.current = effect;
18
+ this.env = env;
19
+ }
20
+ addFinalizer(f) {
21
+ this.fiberFinalizers.push(f);
22
+ }
23
+ status() {
24
+ return this.statusValue;
25
+ }
26
+ join(cb) {
27
+ if (this.result != null) {
28
+ cb(this.result);
29
+ }
30
+ else {
31
+ this.joiners.push(cb);
32
+ }
33
+ }
34
+ interrupt() {
35
+ this.interrupted = true;
36
+ // cancelación cooperativa: el código async puede chequear flags externos
37
+ }
38
+ runFiberFinalizers(exit) {
39
+ while (this.fiberFinalizers.length > 0) {
40
+ const fin = this.fiberFinalizers.pop();
41
+ fin(exit); // fire-and-forget (igual que ZIO)
42
+ }
43
+ }
44
+ /** Programa un paso de la fibra en el scheduler */
45
+ schedule() {
46
+ this.scheduler.schedule(() => this.step());
47
+ }
48
+ notify(exit) {
49
+ if (this.result != null)
50
+ return;
51
+ // ejecutar finalizers de fibra
52
+ this.runFiberFinalizers(exit);
53
+ // marcar estado final
54
+ this.statusValue = this.interrupted ? "Interrupted" : "Done";
55
+ this.result = exit;
56
+ // notificar joiners
57
+ for (const j of this.joiners)
58
+ j(exit);
59
+ this.joiners.length = 0;
60
+ }
61
+ onSuccess(value) {
62
+ const cont = this.stack.pop();
63
+ if (!cont) {
64
+ // terminamos con éxito
65
+ this.notify({ _tag: "Success", value });
66
+ return;
67
+ }
68
+ this.current = cont(value);
69
+ this.schedule(); // siguiente paso
70
+ }
71
+ onFailure(error) {
72
+ this.notify({ _tag: "Failure", error });
73
+ }
74
+ /** Un *paso* de evaluación de la fibra */
75
+ step() {
76
+ if (this.result != null)
77
+ return; // ya terminó
78
+ const current = this.current;
79
+ switch (current._tag) {
80
+ case "Succeed":
81
+ this.onSuccess(current.value);
82
+ return;
83
+ case "Fail":
84
+ this.onFailure(current.error);
85
+ return;
86
+ case "Sync":
87
+ try {
88
+ const v = current.thunk(this.env);
89
+ this.onSuccess(v);
90
+ }
91
+ catch (e) {
92
+ this.onFailure(e);
93
+ }
94
+ return;
95
+ case "FlatMap":
96
+ this.stack.push(current.andThen);
97
+ this.current = current.first;
98
+ this.schedule(); // reducimos el first en otro paso
99
+ return;
100
+ case "Async":
101
+ current.register(this.env, (exit) => {
102
+ if (this.interrupted && exit._tag === "Success") {
103
+ this.onFailure({ _tag: "Interrupted" });
104
+ }
105
+ else if (exit._tag === "Success") {
106
+ this.onSuccess(exit.value);
107
+ }
108
+ else {
109
+ this.onFailure(exit.error);
110
+ }
111
+ });
112
+ return;
113
+ }
114
+ }
115
+ }
116
+ // API pública: fork + helper unsafeRunAsync
117
+ function fork(effect, env, scheduler = scheduler_1.globalScheduler) {
118
+ const fiber = new RuntimeFiber(effect, env, scheduler);
119
+ fiber.schedule(); // arrancamos la primera reducción
120
+ return fiber;
121
+ }
122
+ // “correr” un Async como antes, pero apoyado en fibras + scheduler
123
+ function unsafeRunAsync(effect, env, cb) {
124
+ const fiber = fork(effect, env);
125
+ fiber.join(cb);
126
+ }
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types/effect"), exports);
18
+ __exportStar(require("./types/asyncEffect"), exports);
19
+ __exportStar(require("./stream/stream"), exports);
20
+ __exportStar(require("./types/option"), exports);
21
+ __exportStar(require("./types/effect"), exports);
22
+ __exportStar(require("./scheduler/withScope"), exports);
23
+ __exportStar(require("./scheduler/acquireRelease"), exports);
24
+ __exportStar(require("./scheduler/scheduler"), exports);
25
+ __exportStar(require("./types/cancel"), exports);
26
+ __exportStar(require("./scheduler/scope"), exports);
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.acquireRelease = acquireRelease;
4
+ const asyncEffect_1 = require("../types/asyncEffect");
5
+ /**
6
+ * acquireRelease:
7
+ * acquire: Async<R, E, A>
8
+ * release: (A, Exit) => Async<R, never, void>
9
+ */
10
+ function acquireRelease(acquire, release, scope) {
11
+ return (0, asyncEffect_1.asyncFlatMap)(acquire, (resource) => {
12
+ // registrar finalizer
13
+ scope.addFinalizer((exit) => release(resource, exit));
14
+ return (0, asyncEffect_1.asyncSucceed)(resource);
15
+ });
16
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.globalScheduler = exports.Scheduler = void 0;
4
+ class Scheduler {
5
+ constructor() {
6
+ this.queue = [];
7
+ this.flushing = false;
8
+ }
9
+ schedule(task) {
10
+ this.queue.push(task);
11
+ if (!this.flushing) {
12
+ this.flush();
13
+ }
14
+ }
15
+ flush() {
16
+ this.flushing = true;
17
+ // Versión simple: drena todo de una
18
+ while (this.queue.length > 0) {
19
+ const t = this.queue.shift();
20
+ t();
21
+ }
22
+ this.flushing = false;
23
+ }
24
+ }
25
+ exports.Scheduler = Scheduler;
26
+ // Un scheduler global para todo el runtime
27
+ exports.globalScheduler = new Scheduler();
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scope = void 0;
4
+ // src/scope.ts
5
+ const fiber_1 = require("../fibers/fiber");
6
+ let nextScopeId = 1;
7
+ class Scope {
8
+ constructor() {
9
+ this.closed = false;
10
+ this.children = new Set();
11
+ this.subScopes = new Set();
12
+ this.finalizers = [];
13
+ this.id = nextScopeId++;
14
+ }
15
+ /** registra un finalizer (LIFO) */
16
+ addFinalizer(f) {
17
+ if (this.closed) {
18
+ throw new Error("Trying to add finalizer to closed scope");
19
+ }
20
+ this.finalizers.push(f);
21
+ }
22
+ /** crea un sub scope */
23
+ subScope() {
24
+ if (this.closed)
25
+ throw new Error("Scope closed");
26
+ const s = new Scope();
27
+ this.subScopes.add(s);
28
+ return s;
29
+ }
30
+ /** fork en este scope */
31
+ fork(eff, env) {
32
+ if (this.closed)
33
+ throw new Error("Scope closed");
34
+ const f = (0, fiber_1.fork)(eff, env);
35
+ this.children.add(f);
36
+ f.join(() => {
37
+ this.children.delete(f);
38
+ });
39
+ return f;
40
+ }
41
+ /** Cierre estructurado */
42
+ close(exit = { _tag: "Success", value: undefined }) {
43
+ if (this.closed)
44
+ return;
45
+ this.closed = true;
46
+ // 1) cancelar hijos
47
+ for (const f of this.children) {
48
+ f.interrupt();
49
+ }
50
+ // 2) cerrar sub scopes
51
+ for (const s of this.subScopes) {
52
+ s.close(exit);
53
+ }
54
+ // 3) ejecutar finalizers en orden LIFO
55
+ while (this.finalizers.length > 0) {
56
+ const fin = this.finalizers.pop();
57
+ fin(exit); // se ejecuta como Async, pero no esperamos
58
+ }
59
+ this.children.clear();
60
+ this.subScopes.clear();
61
+ }
62
+ isClosed() {
63
+ return this.closed;
64
+ }
65
+ }
66
+ exports.Scope = Scope;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ // src/withScope.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.withScope = withScope;
5
+ const scope_1 = require("./scope");
6
+ /**
7
+ * Ejecuta una función dentro de un scope estructurado.
8
+ * Al final (éxito o error), se cierra el scope garantizando cleanup.
9
+ */
10
+ function withScope(body) {
11
+ const scope = new scope_1.Scope();
12
+ try {
13
+ const result = body(scope);
14
+ return result;
15
+ }
16
+ finally {
17
+ scope.close(); // cleanup garantizado
18
+ }
19
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ // ----- ADT de Stream -----
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.flattenStream = exports.concatStream = exports.emitStream = exports.emptyStream = void 0;
5
+ exports.uncons = uncons;
6
+ exports.mapStream = mapStream;
7
+ exports.fromArray = fromArray;
8
+ exports.collectStream = collectStream;
9
+ const effect_js_1 = require("../types/effect.js");
10
+ const option_js_1 = require("../types/option.js");
11
+ // ----- Constructores helpers -----
12
+ const emptyStream = () => ({
13
+ _tag: "Empty",
14
+ });
15
+ exports.emptyStream = emptyStream;
16
+ const emitStream = (value) => ({
17
+ _tag: "Emit",
18
+ value,
19
+ });
20
+ exports.emitStream = emitStream;
21
+ const concatStream = (left, right) => ({
22
+ _tag: "Concat",
23
+ left,
24
+ right,
25
+ });
26
+ exports.concatStream = concatStream;
27
+ const flattenStream = (stream) => ({
28
+ _tag: "Flatten",
29
+ stream,
30
+ });
31
+ exports.flattenStream = flattenStream;
32
+ // ----- uncons: ZIO[R, Option[E], (A, ZStream)] -----
33
+ function uncons(self) {
34
+ switch (self._tag) {
35
+ case "Empty":
36
+ // fin de stream => Failure(None)
37
+ return (0, effect_js_1.fail)(option_js_1.none);
38
+ case "Emit":
39
+ // value.mapError(Some(_)).map(a => (a, Empty))
40
+ return (0, effect_js_1.map)((0, effect_js_1.mapError)(self.value, (e) => (0, option_js_1.some)(e)), (a) => [a, (0, exports.emptyStream)()]);
41
+ case "Concat":
42
+ // left.uncons.map(...).orElseOptional(right.uncons)
43
+ return (0, effect_js_1.orElseOptional)((0, effect_js_1.map)(uncons(self.left), ([a, tail]) => [
44
+ a,
45
+ (0, exports.concatStream)(tail, self.right),
46
+ ]), () => uncons(self.right));
47
+ case "Flatten":
48
+ // stream.uncons.flatMap { case (head, tail) =>
49
+ // head.uncons
50
+ // .map { case (a, as) => (a, as ++ Flatten(tail)) }
51
+ // .orElseOptional(Flatten(tail).uncons)
52
+ // }
53
+ return (0, effect_js_1.flatMap)(uncons(self.stream), ([head, tail]) => (0, effect_js_1.orElseOptional)((0, effect_js_1.map)(uncons(head), ([a, as]) => [
54
+ a,
55
+ (0, exports.concatStream)(as, (0, exports.flattenStream)(tail)),
56
+ ]), () => uncons((0, exports.flattenStream)(tail))));
57
+ }
58
+ }
59
+ // ---------- combinadores extra opcionales ----------
60
+ function mapStream(self, f) {
61
+ switch (self._tag) {
62
+ case "Empty":
63
+ return (0, exports.emptyStream)();
64
+ case "Emit":
65
+ return (0, exports.emitStream)((0, effect_js_1.map)(self.value, f));
66
+ case "Concat":
67
+ return (0, exports.concatStream)(mapStream(self.left, f), mapStream(self.right, f));
68
+ case "Flatten": {
69
+ const mappedOuter = mapStream(self.stream, (inner) => mapStream(inner, f));
70
+ return (0, exports.flattenStream)(mappedOuter);
71
+ }
72
+ }
73
+ }
74
+ // Stream finito desde un array
75
+ function fromArray(values) {
76
+ let s = (0, exports.emptyStream)();
77
+ for (let i = values.length - 1; i >= 0; i--) {
78
+ const head = (0, exports.emitStream)((0, effect_js_1.succeed)(values[i]));
79
+ s = (0, exports.concatStream)(head, s);
80
+ }
81
+ return s;
82
+ }
83
+ // Consumidor síncrono del stream
84
+ function collectStream(stream, env) {
85
+ const result = [];
86
+ let current = stream;
87
+ while (true) {
88
+ const exit = uncons(current)(env);
89
+ if (exit._tag === "Failure") {
90
+ const optErr = exit.error;
91
+ if (optErr._tag === "None") {
92
+ // fin del stream
93
+ return result;
94
+ }
95
+ else {
96
+ // error real
97
+ throw optErr.value;
98
+ }
99
+ }
100
+ else {
101
+ const [head, tail] = exit.value;
102
+ result.push(head);
103
+ current = tail;
104
+ }
105
+ }
106
+ }
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ // src/structuredConcurrency.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.race = race;
5
+ exports.zipPar = zipPar;
6
+ exports.collectAllPar = collectAllPar;
7
+ const asyncEffect_1 = require("../types/asyncEffect");
8
+ /**
9
+ * race(A, B):
10
+ * - corre A y B en paralelo
11
+ * - el primero que termine gana
12
+ * - el otro es cancelado
13
+ */
14
+ function race(left, right, parentScope) {
15
+ return (0, asyncEffect_1.async)((env, cb) => {
16
+ // cada carrera tiene su propio scope interno
17
+ const scope = parentScope.subScope();
18
+ let done = false;
19
+ const onResult = (exit) => {
20
+ if (done)
21
+ return;
22
+ done = true;
23
+ // cerrar todo (esto cancela la otra fibra)
24
+ scope.close(exit);
25
+ cb(exit);
26
+ };
27
+ const fiberLeft = scope.fork(left, env);
28
+ const fiberRight = scope.fork(right, env);
29
+ fiberLeft.join(onResult);
30
+ fiberRight.join(onResult);
31
+ });
32
+ }
33
+ /**
34
+ * zipPar(A, B):
35
+ * - corre A y B en paralelo
36
+ * - si ambas terminan bien → éxito con (A, B)
37
+ * - si una falla → cancelar todo y devolver fallo
38
+ */
39
+ function zipPar(left, right, parentScope) {
40
+ return (0, asyncEffect_1.async)((env, cb) => {
41
+ const scope = parentScope.subScope();
42
+ let leftExit = null;
43
+ let rightExit = null;
44
+ let done = false;
45
+ const checkDone = () => {
46
+ // si todavía no tenemos ambos resultados, no hacemos nada
47
+ if (!leftExit || !rightExit || done)
48
+ return;
49
+ done = true;
50
+ if (leftExit._tag === "Success" && rightExit._tag === "Success") {
51
+ // ambos ok
52
+ scope.close({ _tag: "Success", value: undefined });
53
+ cb({
54
+ _tag: "Success",
55
+ value: [leftExit.value, rightExit.value],
56
+ });
57
+ return;
58
+ }
59
+ // algún error, cancelar todo
60
+ let cause;
61
+ if (leftExit._tag === "Failure") {
62
+ cause = leftExit.error;
63
+ }
64
+ else if (rightExit._tag === "Failure") {
65
+ cause = rightExit.error;
66
+ }
67
+ else {
68
+ // Esto es lógicamente imposible, pero lo ponemos
69
+ // para mantener feliz a TypeScript.
70
+ throw new Error("zipPar: unreachable state (no Failure exit)");
71
+ }
72
+ const errExit = {
73
+ _tag: "Failure",
74
+ error: cause,
75
+ };
76
+ scope.close(errExit);
77
+ cb(errExit);
78
+ };
79
+ const f1 = scope.fork(left, env);
80
+ const f2 = scope.fork(right, env);
81
+ f1.join((exit) => {
82
+ leftExit = exit;
83
+ checkDone();
84
+ });
85
+ f2.join((exit) => {
86
+ rightExit = exit;
87
+ checkDone();
88
+ });
89
+ });
90
+ }
91
+ /**
92
+ * collectAllPar:
93
+ * - corre todos en paralelo
94
+ * - si uno falla → cancela todos
95
+ * - si todos terminan bien → devuelve array de resultados
96
+ */
97
+ function collectAllPar(effects, parentScope) {
98
+ return (0, asyncEffect_1.async)((env, cb) => {
99
+ const scope = parentScope.subScope();
100
+ const results = new Array(effects.length);
101
+ let completed = 0;
102
+ let done = false;
103
+ effects.forEach((eff, i) => {
104
+ const f = scope.fork(eff, env);
105
+ f.join((exit) => {
106
+ if (done)
107
+ return;
108
+ if (exit._tag === "Failure") {
109
+ done = true;
110
+ const errExit = {
111
+ _tag: "Failure",
112
+ error: exit.error,
113
+ };
114
+ scope.close(errExit);
115
+ cb(errExit);
116
+ return;
117
+ }
118
+ results[i] = exit.value;
119
+ completed++;
120
+ if (completed === effects.length) {
121
+ done = true;
122
+ const successExit = {
123
+ _tag: "Success",
124
+ value: results,
125
+ };
126
+ scope.close({ _tag: "Success", value: undefined });
127
+ cb(successExit);
128
+ }
129
+ });
130
+ });
131
+ });
132
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.async = exports.asyncTotal = exports.asyncSync = exports.asyncFail = exports.asyncSucceed = void 0;
4
+ exports.asyncMap = asyncMap;
5
+ exports.asyncFlatMap = asyncFlatMap;
6
+ exports.fromPromise = fromPromise;
7
+ exports.tryPromiseAbortable = tryPromiseAbortable;
8
+ exports.fromPromiseAbortable = fromPromiseAbortable;
9
+ const asyncSucceed = (value) => ({
10
+ _tag: "Succeed",
11
+ value,
12
+ });
13
+ exports.asyncSucceed = asyncSucceed;
14
+ const asyncFail = (error) => ({
15
+ _tag: "Fail",
16
+ error,
17
+ });
18
+ exports.asyncFail = asyncFail;
19
+ const asyncSync = (thunk) => ({
20
+ _tag: "Sync",
21
+ thunk,
22
+ });
23
+ exports.asyncSync = asyncSync;
24
+ const asyncTotal = (thunk) => (0, exports.asyncSync)(() => thunk());
25
+ exports.asyncTotal = asyncTotal;
26
+ const async = (register) => ({
27
+ _tag: "Async",
28
+ register,
29
+ });
30
+ exports.async = async;
31
+ function asyncMap(fa, f) {
32
+ return asyncFlatMap(fa, (a) => (0, exports.asyncSucceed)(f(a)));
33
+ }
34
+ function asyncFlatMap(fa, f) {
35
+ return {
36
+ _tag: "FlatMap",
37
+ first: fa,
38
+ andThen: f,
39
+ };
40
+ }
41
+ function fromPromise(thunk, onError) {
42
+ return (0, exports.async)((env, cb) => {
43
+ thunk(env)
44
+ .then((value) => cb({ _tag: "Success", value }))
45
+ .catch((err) => cb({ _tag: "Failure", error: onError(err) }));
46
+ });
47
+ }
48
+ //TODO: Esto lo hago porque me interesa saber el nombre explicito de lo que falla, no solo que falle sino mas bien un detalle
49
+ const isAbortError = (e) => typeof e === "object" &&
50
+ e !== null &&
51
+ "name" in e &&
52
+ e.name === "AbortError";
53
+ function tryPromiseAbortable(thunk) {
54
+ return fromPromiseAbortable(thunk, (e) => isAbortError(e)
55
+ ? { _tag: "Abort" }
56
+ : { _tag: "PromiseRejected", reason: e });
57
+ }
58
+ function fromPromiseAbortable(thunk, onError) {
59
+ return (0, exports.async)((env, cb) => {
60
+ const ac = new AbortController();
61
+ let done = false;
62
+ const safeCb = (exit) => {
63
+ if (done)
64
+ return;
65
+ done = true;
66
+ cb(exit);
67
+ };
68
+ try {
69
+ const p = thunk(env, ac.signal);
70
+ p.then((value) => safeCb({ _tag: "Success", value }))
71
+ .catch((err) => safeCb({ _tag: "Failure", error: onError(err) }));
72
+ }
73
+ catch (e) {
74
+ safeCb({ _tag: "Failure", error: onError(e) });
75
+ }
76
+ return () => {
77
+ done = true;
78
+ ac.abort();
79
+ };
80
+ });
81
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeCancelToken = makeCancelToken;
4
+ exports.linkAbortController = linkAbortController;
5
+ /** Implementación simple de CancelToken */
6
+ function makeCancelToken() {
7
+ let cancelled = false;
8
+ const listeners = new Set();
9
+ const cancel = () => {
10
+ if (cancelled)
11
+ return;
12
+ cancelled = true;
13
+ // ejecuta y limpia
14
+ for (const f of listeners) {
15
+ try {
16
+ f();
17
+ }
18
+ catch { /* opcional: log */ }
19
+ }
20
+ listeners.clear();
21
+ };
22
+ return {
23
+ isCancelled: () => cancelled,
24
+ onCancel: (f) => {
25
+ if (cancelled) {
26
+ // si ya está cancelado, ejecuta inmediatamente
27
+ try {
28
+ f();
29
+ }
30
+ catch { /* opcional */ }
31
+ return () => { };
32
+ }
33
+ listeners.add(f);
34
+ return () => { listeners.delete(f); };
35
+ },
36
+ cancel,
37
+ };
38
+ }
39
+ /**
40
+ * Helper: conecta un AbortController a un CancelToken.
41
+ * Devuelve una función para desenganchar (unsubscribe).
42
+ */
43
+ function linkAbortController(token, ac) {
44
+ return token.onCancel(() => ac.abort());
45
+ }
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ // src/effect.ts
3
+ // Resultado de ejecutar un efecto
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.fromThunk = exports.sync = exports.fail = exports.succeed = void 0;
6
+ exports.map = map;
7
+ exports.flatMap = flatMap;
8
+ exports.mapError = mapError;
9
+ exports.catchAll = catchAll;
10
+ exports.orElseOptional = orElseOptional;
11
+ exports.zip = zip;
12
+ exports.tap = tap;
13
+ exports.as = as;
14
+ exports.asUnit = asUnit;
15
+ exports.foreach = foreach;
16
+ exports.collectAll = collectAll;
17
+ // ---------- Constructores básicos ----------
18
+ const succeed = (value) => (_) => ({ _tag: "Success", value });
19
+ exports.succeed = succeed;
20
+ const fail = (error) => () => ({ _tag: "Failure", error });
21
+ exports.fail = fail;
22
+ const sync = (thunk) => (env) => {
23
+ try {
24
+ return { _tag: "Success", value: thunk(env) };
25
+ }
26
+ catch (e) {
27
+ return { _tag: "Failure", error: e };
28
+ }
29
+ };
30
+ exports.sync = sync;
31
+ const fromThunk = (thunk) => (0, exports.sync)(() => thunk());
32
+ exports.fromThunk = fromThunk;
33
+ // ---------- Combinadores de valor ----------
34
+ function map(eff, f) {
35
+ return (env) => {
36
+ const exit = eff(env);
37
+ if (exit._tag === "Success") {
38
+ return { _tag: "Success", value: f(exit.value) };
39
+ }
40
+ return exit;
41
+ };
42
+ }
43
+ function flatMap(eff, f) {
44
+ return (env) => {
45
+ const exit1 = eff(env);
46
+ if (exit1._tag === "Failure") {
47
+ return exit1;
48
+ }
49
+ return f(exit1.value)(env);
50
+ };
51
+ }
52
+ function mapError(eff, f) {
53
+ return (env) => {
54
+ const exit = eff(env);
55
+ if (exit._tag === "Failure") {
56
+ return { _tag: "Failure", error: f(exit.error) };
57
+ }
58
+ return exit;
59
+ };
60
+ }
61
+ function catchAll(eff, handler) {
62
+ return (env) => {
63
+ const exit = eff(env);
64
+ if (exit._tag === "Failure") {
65
+ return handler(exit.error)(env);
66
+ }
67
+ return exit;
68
+ };
69
+ }
70
+ /**
71
+ * Versión estilo ZIO.orElseOptional:
72
+ * eff: Effect<R, Option<E>, A>
73
+ * - Failure(Some(e)) => se propaga
74
+ * - Failure(None) => se ejecuta `that`
75
+ */
76
+ function orElseOptional(eff, that) {
77
+ return (env) => {
78
+ const exit1 = eff(env);
79
+ if (exit1._tag === "Success")
80
+ return exit1;
81
+ const opt = exit1.error;
82
+ if (opt._tag === "Some")
83
+ return exit1; // error real
84
+ return that()(env); // None => probamos alternativa
85
+ };
86
+ }
87
+ // ---------- Más combinadores ----------
88
+ function zip(fa, fb) {
89
+ return (env) => {
90
+ const ea = fa(env);
91
+ if (ea._tag === "Failure")
92
+ return ea;
93
+ const eb = fb(env);
94
+ if (eb._tag === "Failure")
95
+ return eb;
96
+ return { _tag: "Success", value: [ea.value, eb.value] };
97
+ };
98
+ }
99
+ function tap(eff, f) {
100
+ return (env) => {
101
+ const ea = eff(env);
102
+ if (ea._tag === "Failure")
103
+ return ea;
104
+ const eb = f(ea.value)(env);
105
+ if (eb._tag === "Failure")
106
+ return eb;
107
+ return ea;
108
+ };
109
+ }
110
+ function as(eff, value) {
111
+ return map(eff, () => value);
112
+ }
113
+ function asUnit(eff) {
114
+ return as(eff, undefined);
115
+ }
116
+ function foreach(items, f) {
117
+ return (env) => {
118
+ const out = [];
119
+ for (const a of items) {
120
+ const exit = f(a)(env);
121
+ if (exit._tag === "Failure")
122
+ return exit;
123
+ out.push(exit.value);
124
+ }
125
+ return { _tag: "Success", value: out };
126
+ };
127
+ }
128
+ function collectAll(effects) {
129
+ return foreach(effects, (e) => e);
130
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.some = exports.none = void 0;
4
+ exports.none = { _tag: "None" };
5
+ const some = (value) => ({ _tag: "Some", value });
6
+ exports.some = some;
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "brass-runtime",
3
+ "version": "1.0.0",
4
+ "description": "Effect runtime utilities for TypeScript",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "commonjs",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "dev": "ts-node-dev src/examples/demo.ts",
17
+ "build": "tsc",
18
+ "test": "node -e \"console.log('no tests yet')\"",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "devDependencies": {
22
+ "@semantic-release/changelog": "^6.0.0",
23
+ "@semantic-release/commit-analyzer": "^13.0.0",
24
+ "@semantic-release/git": "^10.0.0",
25
+ "@semantic-release/github": "^11.0.0",
26
+ "@semantic-release/npm": "^13.1.3",
27
+ "@semantic-release/release-notes-generator": "^14.0.0",
28
+ "semantic-release": "^25.0.2",
29
+ "ts-node-dev": "^2.0.0",
30
+ "typescript": "^5.9.3"
31
+ }
32
+ }