brass-runtime 1.0.1 → 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/dist/examples/demo.js +3 -3
- package/dist/examples/fiberFinalizer.js +27 -5
- package/dist/examples/index.js +6 -0
- package/dist/examples/resourceExample.js +3 -4
- package/dist/examples/test-canceler.js +29 -0
- package/dist/fibers/fiber.js +86 -29
- package/dist/index.js +1 -3
- package/dist/scheduler/scheduler.js +35 -11
- package/dist/scheduler/scope.js +19 -3
- package/dist/stream/buffer.js +50 -0
- package/dist/types/asyncEffect.js +25 -0
- package/package.json +3 -2
- package/dist/scheduler/acquireRelease.js +0 -16
- package/dist/scheduler/withScope.js +0 -19
package/dist/examples/demo.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
12
|
-
|
|
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(
|
|
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(
|
|
42
|
+
console.log(`\n[+${Date.now() - t0}ms] Fiber completed:`, exit);
|
|
21
43
|
});
|
|
22
44
|
}
|
|
23
45
|
main();
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// src/resourceExample.ts
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
const
|
|
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,
|
|
18
|
+
(0, scope_1.withScope)(scope => {
|
|
20
19
|
const env = {};
|
|
21
|
-
const program = (0,
|
|
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);
|
package/dist/fibers/fiber.js
CHANGED
|
@@ -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
|
-
|
|
43
|
+
this.blockedOnAsync = false;
|
|
44
|
+
this.schedule("interrupt-step");
|
|
37
45
|
}
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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();
|
|
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;
|
|
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();
|
|
126
|
+
//this.schedule("flatMap-step");
|
|
99
127
|
return;
|
|
100
|
-
case "Async":
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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();
|
|
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/
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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();
|
package/dist/scheduler/scope.js
CHANGED
|
@@ -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);
|
|
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
|
|
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": "
|
|
18
|
+
"test": "tsx src/examples/index.ts",
|
|
19
19
|
"prepublishOnly": "npm run build"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"conventional-changelog-conventionalcommits": "^7.x.x",
|
|
29
29
|
"semantic-release": "^25.0.2",
|
|
30
30
|
"ts-node-dev": "^2.0.0",
|
|
31
|
+
"tsx": "^4.21.0",
|
|
31
32
|
"typescript": "^5.9.3"
|
|
32
33
|
}
|
|
33
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
|
-
}
|