brass-runtime 1.0.0 → 1.1.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # 🛠️ brass-ts — Mini runtime funcional al estilo ZIO en TypeScript
1
+ # 🛠️ brass-runtime — Mini runtime funcional al estilo ZIO en TypeScript
2
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.
3
+ **brass-runtime** 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
4
 
5
5
  El objetivo del proyecto es explorar cómo construir, desde cero, un sistema de:
6
6
 
@@ -21,7 +21,7 @@ Todo con un diseño **determinístico**, **pure FP**, y sin depender de `Promise
21
21
 
22
22
  ### 1. `Effect` sincrónico (núcleo funcional)
23
23
 
24
- En `brass-ts`, un efecto puro se modela como:
24
+ En `brass-runtime`, un efecto puro se modela como:
25
25
 
26
26
  ```ts
27
27
  type Exit<E, A> =
@@ -48,7 +48,7 @@ Este núcleo no usa `Promise` ni `async/await`. Es **100% sincrónico y determin
48
48
 
49
49
  ### 2. `Async` — efectos asincrónicos sin Promises
50
50
 
51
- Para modelar operaciones asincrónicas, `brass-ts` define un tipo de datos algebraico:
51
+ Para modelar operaciones asincrónicas, `brass-runtime` define un tipo de datos algebraico:
52
52
 
53
53
  ```ts
54
54
  type Async<R, E, A> =
@@ -147,7 +147,7 @@ si algo vive en un `Scope`, se limpia cuando el scope termina.
147
147
 
148
148
  ### 6. Acquire / Release — Resource Safety
149
149
 
150
- Al estilo `ZIO.acquireRelease`, `brass-ts` implementa:
150
+ Al estilo `ZIO.acquireRelease`, `brass-runtime` implementa:
151
151
 
152
152
  ```ts
153
153
  acquireRelease(
@@ -199,7 +199,7 @@ Esto replica la semántica de **ZIO 2 structured concurrency**.
199
199
 
200
200
  ### 8. ZStream-like — Streams estructurados con backpressure
201
201
 
202
- `brass-ts` incluye una base de **streams estructurados** inspirados en `ZStream`:
202
+ `brass-runtime` incluye una base de **streams estructurados** inspirados en `ZStream`:
203
203
 
204
204
  ```ts
205
205
  type Pull<R, E, A> = Async<R, Option<E>, A>;
@@ -241,7 +241,7 @@ y el scope del stream garantiza que todos los recursos/finalizers se limpien al
241
241
 
242
242
  ## 📁 Estructura sugerida del proyecto
243
243
 
244
- Una posible organización de archivos para tu repo de **brass-ts**:
244
+ Una posible organización de archivos para tu repo de **brass-runtime**:
245
245
 
246
246
  ```bash
247
247
  src/
@@ -357,5 +357,5 @@ Algunas direcciones interesantes para futuro:
357
357
 
358
358
  Hecho con ❤️ en TypeScript, para aprender y jugar con runtimes funcionales.
359
359
 
360
- **Nombre del proyecto:** `brass-ts`
360
+ **Nombre del proyecto:** `brass-runtime`
361
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.
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sleep = sleep;
4
+ const fiber_1 = require("../fibers/fiber");
4
5
  const effect_1 = require("../types/effect");
5
6
  const stream_1 = require("../stream/stream");
7
+ const scope_1 = require("../scheduler/scope");
6
8
  const asyncEffect_1 = require("../types/asyncEffect");
7
- const fiber_1 = require("../fibers/fiber");
8
- const withScope_1 = require("../scheduler/withScope");
9
9
  function main() {
10
10
  const env = {};
11
11
  const fiberA = (0, fiber_1.fork)(task("A", 1000), env);
@@ -27,7 +27,7 @@ function main() {
27
27
  const sMapped = (0, stream_1.mapStream)(s, (n) => n * 10);
28
28
  const collected = (0, stream_1.collectStream)(sMapped, env);
29
29
  console.log("Stream mapeado:", collected);
30
- (0, withScope_1.withScope)(scope => {
30
+ (0, scope_1.withScope)(scope => {
31
31
  const f1 = scope.fork(task("A", 1000), env);
32
32
  const f2 = scope.fork(task("B", 1500), env);
33
33
  const f3 = scope.fork(task("C", 2000), env);
@@ -1,23 +1,45 @@
1
1
  "use strict";
2
- // fiberFinalizer.ts
3
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ // fiberFinalizer.ts
4
4
  const asyncEffect_1 = require("../types/asyncEffect");
5
5
  const fiber_1 = require("../fibers/fiber");
6
6
  const demo_1 = require("./demo");
7
+ function formatExit(exit) {
8
+ if (!exit || typeof exit !== "object")
9
+ return String(exit);
10
+ if (exit._tag === "Success")
11
+ return `Success(value=${JSON.stringify(exit.value)})`;
12
+ if (exit._tag === "Failure")
13
+ return `Failure(error=${JSON.stringify(exit.error)})`;
14
+ return JSON.stringify(exit);
15
+ }
7
16
  function main() {
8
17
  const env = {};
18
+ const t0 = Date.now();
9
19
  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
20
  const fiber = (0, fiber_1.fork)(eff, env);
11
- fiber.addFinalizer(exit => (0, asyncEffect_1.asyncTotal)(() => {
12
- console.log("RUNNING FINALIZER → exit =", exit._tag);
21
+ // 👇 Instrumentación del finalizer
22
+ let finCount = 0;
23
+ fiber.addFinalizer((exit) => (0, asyncEffect_1.asyncTotal)(() => {
24
+ var _a;
25
+ finCount += 1;
26
+ const ms = Date.now() - t0;
27
+ const stack = (_a = new Error().stack) === null || _a === void 0 ? void 0 : _a.split("\n").slice(1, 8).join("\n");
28
+ console.log("\n================ FINALIZER START ================");
29
+ console.log(`time: +${ms}ms`);
30
+ console.log(`finalizer call #: ${finCount}`);
31
+ console.log(`exit: ${formatExit(exit)}`);
32
+ console.log("raw exit object:", exit);
33
+ console.log("stack (where finalizer ran):\n" + stack);
34
+ console.log("================= FINALIZER END =================\n");
13
35
  }));
14
36
  // cancelamos antes de terminar para ver que el finalizer corre
15
37
  setTimeout(() => {
16
- console.log("Interrupting fiber...");
38
+ console.log(`\n[+${Date.now() - t0}ms] Interrupting fiber...`);
17
39
  fiber.interrupt();
18
40
  }, 500);
19
41
  fiber.join((exit) => {
20
- console.log("Fiber completed:", exit);
42
+ console.log(`\n[+${Date.now() - t0}ms] Fiber completed:`, exit);
21
43
  });
22
44
  }
23
45
  main();
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./demo");
4
+ require("./fiberFinalizer");
5
+ require("./resourceExample");
6
+ require("./test-canceler");
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  // src/resourceExample.ts
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- const acquireRelease_1 = require("../scheduler/acquireRelease");
4
+ const scope_1 = require("../scheduler/scope");
5
5
  const asyncEffect_1 = require("../types/asyncEffect");
6
- const withScope_1 = require("../scheduler/withScope");
7
6
  function openFile(name) {
8
7
  console.log("OPEN FILE:", name);
9
8
  return {
@@ -16,9 +15,9 @@ function openFile(name) {
16
15
  };
17
16
  }
18
17
  function main() {
19
- (0, withScope_1.withScope)(scope => {
18
+ (0, scope_1.withScope)(scope => {
20
19
  const env = {};
21
- const program = (0, acquireRelease_1.acquireRelease)((0, asyncEffect_1.asyncTotal)(() => openFile("data.txt")), (file, exit) => (0, asyncEffect_1.asyncTotal)(() => {
20
+ const program = (0, asyncEffect_1.acquireRelease)((0, asyncEffect_1.asyncTotal)(() => openFile("data.txt")), (file, exit) => (0, asyncEffect_1.asyncTotal)(() => {
22
21
  console.log("Finalizer running due to:", exit._tag);
23
22
  file.close();
24
23
  }), scope);
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fiber_1 = require("../fibers/fiber");
4
+ const asyncEffect_1 = require("../types/asyncEffect");
5
+ let ticks = 0;
6
+ const effect = (0, asyncEffect_1.async)((_env, cb) => {
7
+ const id = setInterval(() => {
8
+ ticks++;
9
+ }, 10);
10
+ cb({ _tag: "Success", value: undefined });
11
+ return () => clearInterval(id);
12
+ });
13
+ (0, fiber_1.unsafeRunAsync)(effect, undefined, (exit) => {
14
+ console.log("Fiber exit:", exit);
15
+ });
16
+ setTimeout(() => {
17
+ const t1 = ticks;
18
+ console.log("ticks @50ms:", t1);
19
+ setTimeout(() => {
20
+ const t2 = ticks;
21
+ console.log("ticks @150ms:", t2);
22
+ if (t2 > t1 + 1) {
23
+ console.log("❌ LEAK: el interval siguió vivo después de terminar la fiber");
24
+ }
25
+ else {
26
+ console.log("✅ OK: el interval se limpió (canceler ejecutado)");
27
+ }
28
+ }, 100);
29
+ }, 50);
@@ -7,12 +7,17 @@ let nextId = 1;
7
7
  class RuntimeFiber {
8
8
  constructor(effect, env, scheduler) {
9
9
  this.scheduler = scheduler;
10
+ this.closing = null;
11
+ this.finishing = false;
10
12
  this.statusValue = "Running";
11
13
  this.interrupted = false;
12
14
  this.result = null;
13
15
  this.joiners = [];
14
16
  this.stack = [];
15
17
  this.fiberFinalizers = [];
18
+ this.scheduled = false;
19
+ this.finalizersDrained = false;
20
+ this.blockedOnAsync = false;
16
21
  this.id = nextId++;
17
22
  this.current = effect;
18
23
  this.env = env;
@@ -24,36 +29,55 @@ class RuntimeFiber {
24
29
  return this.statusValue;
25
30
  }
26
31
  join(cb) {
27
- if (this.result != null) {
32
+ if (this.result != null)
28
33
  cb(this.result);
29
- }
30
- else {
34
+ else
31
35
  this.joiners.push(cb);
32
- }
33
36
  }
34
37
  interrupt() {
38
+ if (this.result != null)
39
+ return;
40
+ if (this.interrupted)
41
+ return;
35
42
  this.interrupted = true;
36
- // cancelación cooperativa: el código async puede chequear flags externos
43
+ this.blockedOnAsync = false;
44
+ this.schedule("interrupt-step");
37
45
  }
38
- runFiberFinalizers(exit) {
46
+ schedule(tag = "step") {
47
+ if (this.result != null)
48
+ return;
49
+ if (this.scheduled)
50
+ return;
51
+ this.scheduled = true;
52
+ this.scheduler.schedule(() => {
53
+ this.scheduled = false;
54
+ this.step();
55
+ }, `fiber#${this.id}.${tag}`);
56
+ }
57
+ runFinalizersOnce(exit) {
58
+ if (this.finalizersDrained)
59
+ return;
60
+ this.finalizersDrained = true;
39
61
  while (this.fiberFinalizers.length > 0) {
40
62
  const fin = this.fiberFinalizers.pop();
41
- fin(exit); // fire-and-forget (igual que ZIO)
63
+ try {
64
+ fin(exit);
65
+ }
66
+ catch { }
42
67
  }
43
68
  }
44
- /** Programa un paso de la fibra en el scheduler */
45
- schedule() {
46
- this.scheduler.schedule(() => this.step());
47
- }
48
69
  notify(exit) {
49
70
  if (this.result != null)
50
71
  return;
51
- // ejecutar finalizers de fibra
52
- this.runFiberFinalizers(exit);
53
- // marcar estado final
72
+ if (this.closing != null)
73
+ return;
74
+ this.finishing = true;
75
+ this.closing = exit;
76
+ // ✅ ejecutar finalizers YA (garantiza clearInterval)
77
+ this.runFinalizersOnce(exit);
78
+ // completar
54
79
  this.statusValue = this.interrupted ? "Interrupted" : "Done";
55
80
  this.result = exit;
56
- // notificar joiners
57
81
  for (const j of this.joiners)
58
82
  j(exit);
59
83
  this.joiners.length = 0;
@@ -61,20 +85,24 @@ class RuntimeFiber {
61
85
  onSuccess(value) {
62
86
  const cont = this.stack.pop();
63
87
  if (!cont) {
64
- // terminamos con éxito
65
88
  this.notify({ _tag: "Success", value });
66
89
  return;
67
90
  }
68
91
  this.current = cont(value);
69
- this.schedule(); // siguiente paso
92
+ //this.schedule("onSuccess-step");
70
93
  }
71
94
  onFailure(error) {
72
95
  this.notify({ _tag: "Failure", error });
73
96
  }
74
- /** Un *paso* de evaluación de la fibra */
75
97
  step() {
76
98
  if (this.result != null)
77
- return; // ya terminó
99
+ return;
100
+ if (this.blockedOnAsync)
101
+ return;
102
+ if (this.interrupted && this.closing == null) {
103
+ this.notify({ _tag: "Failure", error: { _tag: "Interrupted" } });
104
+ return;
105
+ }
78
106
  const current = this.current;
79
107
  switch (current._tag) {
80
108
  case "Succeed":
@@ -95,28 +123,57 @@ class RuntimeFiber {
95
123
  case "FlatMap":
96
124
  this.stack.push(current.andThen);
97
125
  this.current = current.first;
98
- this.schedule(); // reducimos el first en otro paso
126
+ //this.schedule("flatMap-step");
99
127
  return;
100
- case "Async":
101
- current.register(this.env, (exit) => {
102
- if (this.interrupted && exit._tag === "Success") {
128
+ case "Async": {
129
+ if (this.finishing)
130
+ return;
131
+ this.blockedOnAsync = true;
132
+ let pending = null;
133
+ let completedSync = false;
134
+ const resume = () => {
135
+ if (!pending)
136
+ return;
137
+ const exit = pending;
138
+ pending = null;
139
+ this.blockedOnAsync = false;
140
+ if (this.result != null || this.closing != null)
141
+ return;
142
+ if (this.interrupted) {
103
143
  this.onFailure({ _tag: "Interrupted" });
144
+ return;
104
145
  }
105
- else if (exit._tag === "Success") {
146
+ if (exit._tag === "Success")
106
147
  this.onSuccess(exit.value);
107
- }
108
- else {
148
+ else
109
149
  this.onFailure(exit.error);
110
- }
150
+ this.schedule("async-resume");
151
+ };
152
+ const canceler = current.register(this.env, (exit) => {
153
+ // guardamos el resultado, pero NO ejecutamos todavía
154
+ pending = exit;
155
+ completedSync = true;
156
+ // no llamamos resume acá
111
157
  });
158
+ if (typeof canceler === "function") {
159
+ this.addFinalizer((_exit) => {
160
+ try {
161
+ canceler();
162
+ }
163
+ catch { }
164
+ });
165
+ }
166
+ if (completedSync) {
167
+ this.scheduler.schedule(resume, `fiber#${this.id}.async-sync-resume`);
168
+ }
112
169
  return;
170
+ }
113
171
  }
114
172
  }
115
173
  }
116
- // API pública: fork + helper unsafeRunAsync
117
174
  function fork(effect, env, scheduler = scheduler_1.globalScheduler) {
118
175
  const fiber = new RuntimeFiber(effect, env, scheduler);
119
- fiber.schedule(); // arrancamos la primera reducción
176
+ fiber.schedule("initial-step");
120
177
  return fiber;
121
178
  }
122
179
  // “correr” un Async como antes, pero apoyado en fibras + scheduler
package/dist/index.js CHANGED
@@ -19,8 +19,6 @@ __exportStar(require("./types/asyncEffect"), exports);
19
19
  __exportStar(require("./stream/stream"), exports);
20
20
  __exportStar(require("./types/option"), exports);
21
21
  __exportStar(require("./types/effect"), exports);
22
- __exportStar(require("./scheduler/withScope"), exports);
23
- __exportStar(require("./scheduler/acquireRelease"), exports);
22
+ __exportStar(require("./scheduler/scope"), exports);
24
23
  __exportStar(require("./scheduler/scheduler"), exports);
25
24
  __exportStar(require("./types/cancel"), exports);
26
- __exportStar(require("./scheduler/scope"), exports);
@@ -5,23 +5,47 @@ class Scheduler {
5
5
  constructor() {
6
6
  this.queue = [];
7
7
  this.flushing = false;
8
+ this.requested = false;
8
9
  }
9
- schedule(task) {
10
- this.queue.push(task);
11
- if (!this.flushing) {
12
- this.flush();
13
- }
10
+ // ✅ tag opcional
11
+ schedule(task, tag = "anonymous") {
12
+ var _a;
13
+ this.queue.push({ tag, task });
14
+ this.requestFlush();
15
+ // log: tamaño + próximos tags (head)
16
+ console.log("SCHEDULER", {
17
+ flushing: this.flushing,
18
+ q: this.queue.length,
19
+ next: (_a = this.queue[0]) === null || _a === void 0 ? void 0 : _a.tag,
20
+ head: this.queue.slice(0, 5).map(t => t.tag),
21
+ });
22
+ }
23
+ requestFlush() {
24
+ if (this.requested)
25
+ return;
26
+ this.requested = true;
27
+ queueMicrotask(() => this.flush());
14
28
  }
15
29
  flush() {
30
+ if (this.flushing)
31
+ return;
16
32
  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();
33
+ this.requested = false;
34
+ try {
35
+ while (this.queue.length > 0) {
36
+ const { task } = this.queue.shift();
37
+ try {
38
+ task();
39
+ }
40
+ catch { }
41
+ }
42
+ }
43
+ finally {
44
+ this.flushing = false;
45
+ if (this.queue.length > 0)
46
+ this.requestFlush();
21
47
  }
22
- this.flushing = false;
23
48
  }
24
49
  }
25
50
  exports.Scheduler = Scheduler;
26
- // Un scheduler global para todo el runtime
27
51
  exports.globalScheduler = new Scheduler();
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Scope = void 0;
4
+ exports.withScope = withScope;
4
5
  // src/scope.ts
5
6
  const fiber_1 = require("../fibers/fiber");
6
7
  let nextScopeId = 1;
7
8
  class Scope {
8
- constructor() {
9
+ constructor(env) {
10
+ this.env = env;
9
11
  this.closed = false;
10
12
  this.children = new Set();
11
13
  this.subScopes = new Set();
@@ -23,7 +25,7 @@ class Scope {
23
25
  subScope() {
24
26
  if (this.closed)
25
27
  throw new Error("Scope closed");
26
- const s = new Scope();
28
+ const s = new Scope(this.env);
27
29
  this.subScopes.add(s);
28
30
  return s;
29
31
  }
@@ -54,7 +56,8 @@ class Scope {
54
56
  // 3) ejecutar finalizers en orden LIFO
55
57
  while (this.finalizers.length > 0) {
56
58
  const fin = this.finalizers.pop();
57
- fin(exit); // se ejecuta como Async, pero no esperamos
59
+ const eff = fin(exit);
60
+ (0, fiber_1.fork)(eff, this.env); // <-- esto hace que se ejecute el Async
58
61
  }
59
62
  this.children.clear();
60
63
  this.subScopes.clear();
@@ -64,3 +67,16 @@ class Scope {
64
67
  }
65
68
  }
66
69
  exports.Scope = Scope;
70
+ /**
71
+ * Ejecuta una función dentro de un scope estructurado.
72
+ * Al final (éxito o error), se cierra el scope garantizando cleanup.
73
+ */
74
+ function withScope(body) {
75
+ const scope = new Scope({});
76
+ try {
77
+ return body(scope);
78
+ }
79
+ finally {
80
+ scope.close();
81
+ }
82
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /*
3
+ import {ZStream} from "./stream";
4
+
5
+ type Signal<E, A> =
6
+ | { _tag: "Elem"; value: A }
7
+ | { _tag: "End" }
8
+ | { _tag: "Fail"; error: E }
9
+ function buffer<R, E, A>(
10
+ stream: ZStream<R, E, A>,
11
+ capacity: number,
12
+ strategy: "backpressure" | "dropping" | "sliding" = "backpressure",
13
+ ): ZStream<R, E, A> {
14
+ return new ZStream((scope) =>
15
+ Async.gen(function* (_) {
16
+ const pullUp = yield* _(stream.open(scope))
17
+ const q = yield* _(Queue.bounded<Signal<E, A>>(capacity, strategy))
18
+
19
+ // Producer fiber: llena la cola
20
+ const producer = yield* _(Async.forkScoped(scope, // importante: scoped para que se interrumpa al cerrar
21
+ Async.forever(
22
+ pullUp.foldCauseAsync(
23
+ // upstream terminó/falló
24
+ (cause) =>
25
+ cause.match({
26
+ end: () => q.offer({ _tag: "End" }).unit(),
27
+ fail: (e) => q.offer({ _tag: "Fail", error: e }).unit(),
28
+ }),
29
+ // got elem
30
+ (a) => q.offer({ _tag: "Elem", value: a }).unit(),
31
+ )
32
+ )
33
+ ))
34
+
35
+ // Downstream Pull
36
+ const pullDown: Pull<R, E, A> = q.take().flatMap((sig) => {
37
+ switch (sig._tag) {
38
+ case "Elem": return Async.succeed(sig.value)
39
+ case "End": return Async.fail(None) // fin del stream
40
+ case "Fail": return Async.fail(Some(sig.error))
41
+ }
42
+ })
43
+
44
+ // Importante: si el consumidor termina antes,
45
+ // el scope debería interrumpir producer automáticamente.
46
+ return pullDown
47
+ })
48
+ )
49
+ }
50
+ */
@@ -1,11 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.async = exports.asyncTotal = exports.asyncSync = exports.asyncFail = exports.asyncSucceed = void 0;
4
+ exports.unit = unit;
4
5
  exports.asyncMap = asyncMap;
5
6
  exports.asyncFlatMap = asyncFlatMap;
6
7
  exports.fromPromise = fromPromise;
7
8
  exports.tryPromiseAbortable = tryPromiseAbortable;
8
9
  exports.fromPromiseAbortable = fromPromiseAbortable;
10
+ exports.acquireRelease = acquireRelease;
11
+ function unit() {
12
+ return (0, exports.asyncSync)(() => undefined);
13
+ }
9
14
  const asyncSucceed = (value) => ({
10
15
  _tag: "Succeed",
11
16
  value,
@@ -79,3 +84,23 @@ function fromPromiseAbortable(thunk, onError) {
79
84
  };
80
85
  });
81
86
  }
87
+ function acquireRelease(acquire, release, scope) {
88
+ return asyncFlatMap(acquire, (resource) => {
89
+ // registrar finalizer
90
+ scope.addFinalizer((exit) => release(resource, exit));
91
+ return (0, exports.asyncSucceed)(resource);
92
+ });
93
+ }
94
+ function registerInterruptible(register) {
95
+ return (0, exports.async)((env, cb) => {
96
+ const canceler = register(env, cb);
97
+ return typeof canceler === "function"
98
+ ? (0, exports.asyncSync)((_env) => {
99
+ try {
100
+ canceler();
101
+ }
102
+ catch { }
103
+ })
104
+ : unit();
105
+ });
106
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brass-runtime",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Effect runtime utilities for TypeScript",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -15,7 +15,7 @@
15
15
  "scripts": {
16
16
  "dev": "ts-node-dev src/examples/demo.ts",
17
17
  "build": "tsc",
18
- "test": "node -e \"console.log('no tests yet')\"",
18
+ "test": "tsx src/examples/index.ts",
19
19
  "prepublishOnly": "npm run build"
20
20
  },
21
21
  "devDependencies": {
@@ -25,8 +25,10 @@
25
25
  "@semantic-release/github": "^11.0.0",
26
26
  "@semantic-release/npm": "^13.1.3",
27
27
  "@semantic-release/release-notes-generator": "^14.0.0",
28
+ "conventional-changelog-conventionalcommits": "^7.x.x",
28
29
  "semantic-release": "^25.0.2",
29
30
  "ts-node-dev": "^2.0.0",
31
+ "tsx": "^4.21.0",
30
32
  "typescript": "^5.9.3"
31
33
  }
32
34
  }
@@ -1,16 +0,0 @@
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
- }
@@ -1,19 +0,0 @@
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
- }