brass-runtime 1.13.7 → 1.14.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 +231 -55
- package/dist/agent/cli/main.cjs +43 -43
- package/dist/agent/cli/main.js +2 -2
- package/dist/agent/cli/main.mjs +2 -2
- package/dist/agent/index.cjs +3 -3
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +2 -2
- package/dist/agent/index.mjs +2 -2
- package/dist/chunk-4N2JEK4H.mjs +3897 -0
- package/dist/chunk-BKBFSOGT.cjs +3897 -0
- package/dist/{chunk-XNOTJSMZ.mjs → chunk-BMRF4FN6.js} +268 -8
- package/dist/chunk-JT7D6M5H.js +3897 -0
- package/dist/{chunk-3R7ZYRK2.mjs → chunk-MQF7HZ7Y.mjs} +1 -1
- package/dist/chunk-SKVY72E5.cjs +667 -0
- package/dist/{chunk-ATHSSDUF.js → chunk-UWMMYKVK.mjs} +268 -8
- package/dist/{chunk-INZBKOHY.js → chunk-WJESVBWN.js} +1 -1
- package/dist/{chunk-XDINDYNA.cjs → chunk-XTMZTVIT.cjs} +134 -134
- package/dist/{effect-ISvXPLgc.d.ts → effect-DM56H743.d.ts} +191 -21
- package/dist/http/index.cjs +808 -140
- package/dist/http/index.d.ts +181 -8
- package/dist/http/index.js +793 -125
- package/dist/http/index.mjs +793 -125
- package/dist/index.cjs +1785 -137
- package/dist/index.d.ts +979 -36
- package/dist/index.js +1675 -27
- package/dist/index.mjs +1675 -27
- package/dist/stream-Oqe6WeLE.d.ts +173 -0
- package/package.json +1 -1
- package/wasm/pkg/brass_runtime_wasm_engine.d.ts +95 -16
- package/wasm/pkg/brass_runtime_wasm_engine.js +715 -15
- package/wasm/pkg/brass_runtime_wasm_engine_bg.wasm +0 -0
- package/wasm/pkg/brass_runtime_wasm_engine_bg.wasm.d.ts +78 -7
- package/dist/chunk-2P4PD6D7.cjs +0 -2557
- package/dist/chunk-7F2R7A2V.mjs +0 -2557
- package/dist/chunk-L6KKKM66.js +0 -2557
- package/dist/chunk-ZTDK2DLG.cjs +0 -407
- package/dist/stream-BvukHxCv.d.ts +0 -66
|
@@ -0,0 +1,3897 @@
|
|
|
1
|
+
// src/core/types/asyncEffect.ts
|
|
2
|
+
var Async = {
|
|
3
|
+
succeed: (value) => ({ _tag: "Succeed", value }),
|
|
4
|
+
fail: (error) => ({ _tag: "Fail", error }),
|
|
5
|
+
sync: (thunk) => ({ _tag: "Sync", thunk }),
|
|
6
|
+
async: (register) => ({ _tag: "Async", register })
|
|
7
|
+
};
|
|
8
|
+
function asyncFold(fa, onFailure, onSuccess) {
|
|
9
|
+
return { _tag: "Fold", first: fa, onFailure, onSuccess };
|
|
10
|
+
}
|
|
11
|
+
function asyncCatchAll(fa, handler) {
|
|
12
|
+
return asyncFold(
|
|
13
|
+
fa,
|
|
14
|
+
(e) => handler(e),
|
|
15
|
+
(a) => asyncSucceed(a)
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
function asyncMapError(fa, f) {
|
|
19
|
+
return asyncFold(
|
|
20
|
+
fa,
|
|
21
|
+
(e) => asyncFail(f(e)),
|
|
22
|
+
(a) => asyncSucceed(a)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
var SUCCEED_UNDEFINED = { _tag: "Succeed", value: void 0 };
|
|
26
|
+
var SUCCEED_TRUE = { _tag: "Succeed", value: true };
|
|
27
|
+
var SUCCEED_FALSE = { _tag: "Succeed", value: false };
|
|
28
|
+
var SUCCEED_NULL = { _tag: "Succeed", value: null };
|
|
29
|
+
var unit = () => SUCCEED_UNDEFINED;
|
|
30
|
+
var asyncSucceed = (value) => {
|
|
31
|
+
if (value === void 0) return SUCCEED_UNDEFINED;
|
|
32
|
+
if (value === true) return SUCCEED_TRUE;
|
|
33
|
+
if (value === false) return SUCCEED_FALSE;
|
|
34
|
+
if (value === null) return SUCCEED_NULL;
|
|
35
|
+
return { _tag: "Succeed", value };
|
|
36
|
+
};
|
|
37
|
+
var asyncFail = (error) => ({
|
|
38
|
+
_tag: "Fail",
|
|
39
|
+
error
|
|
40
|
+
});
|
|
41
|
+
var asyncSync = (thunk) => ({
|
|
42
|
+
_tag: "Sync",
|
|
43
|
+
thunk
|
|
44
|
+
});
|
|
45
|
+
var asyncTotal = (thunk) => asyncSync(() => thunk());
|
|
46
|
+
var async = (register) => ({
|
|
47
|
+
_tag: "Async",
|
|
48
|
+
register
|
|
49
|
+
});
|
|
50
|
+
function asyncMap(fa, f) {
|
|
51
|
+
return asyncFlatMap(fa, (a) => asyncSucceed(f(a)));
|
|
52
|
+
}
|
|
53
|
+
function asyncFlatMap(fa, f) {
|
|
54
|
+
return {
|
|
55
|
+
_tag: "FlatMap",
|
|
56
|
+
first: fa,
|
|
57
|
+
andThen: f
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function acquireRelease(acquire, release, scope) {
|
|
61
|
+
return asyncFlatMap(acquire, (resource) => {
|
|
62
|
+
scope.addFinalizer((exit) => release(resource, exit));
|
|
63
|
+
return asyncSucceed(resource);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function asyncInterruptible(register) {
|
|
67
|
+
return async(register);
|
|
68
|
+
}
|
|
69
|
+
var withAsyncPromise = (run) => (eff) => {
|
|
70
|
+
const anyEff = eff;
|
|
71
|
+
if (!anyEff.toPromise) {
|
|
72
|
+
anyEff.toPromise = (env) => run(eff, env);
|
|
73
|
+
anyEff.unsafeRunPromise = () => run(eff, {});
|
|
74
|
+
}
|
|
75
|
+
return anyEff;
|
|
76
|
+
};
|
|
77
|
+
var mapAsync = (fa, f) => asyncFlatMap(fa, (a) => asyncSucceed(f(a)));
|
|
78
|
+
var mapTryAsync = (fa, f) => asyncFlatMap(fa, (a) => asyncSync(() => f(a)));
|
|
79
|
+
|
|
80
|
+
// src/core/types/option.ts
|
|
81
|
+
var none = { _tag: "None" };
|
|
82
|
+
var some = (value) => ({ _tag: "Some", value });
|
|
83
|
+
|
|
84
|
+
// src/core/types/effect.ts
|
|
85
|
+
var Cause = {
|
|
86
|
+
fail: (error) => ({ _tag: "Fail", error }),
|
|
87
|
+
interrupt: () => ({ _tag: "Interrupt" }),
|
|
88
|
+
die: (defect) => ({ _tag: "Die", defect })
|
|
89
|
+
};
|
|
90
|
+
var Exit = {
|
|
91
|
+
succeed: (value) => ({
|
|
92
|
+
_tag: "Success",
|
|
93
|
+
value
|
|
94
|
+
}),
|
|
95
|
+
failCause: (cause) => ({
|
|
96
|
+
_tag: "Failure",
|
|
97
|
+
cause
|
|
98
|
+
})
|
|
99
|
+
};
|
|
100
|
+
var succeed = (value) => asyncSucceed(value);
|
|
101
|
+
var fail = (error) => asyncFail(error);
|
|
102
|
+
var sync = (thunk) => asyncSync((env) => thunk(env));
|
|
103
|
+
var map = (fa, f) => asyncMap(fa, f);
|
|
104
|
+
var flatMap = (fa, f) => asyncFlatMap(fa, (a) => f(a));
|
|
105
|
+
var mapError = (fa, f) => asyncMapError(fa, f);
|
|
106
|
+
var catchAll = (fa, handler) => asyncFold(
|
|
107
|
+
fa,
|
|
108
|
+
(e) => handler(e),
|
|
109
|
+
(a) => asyncSucceed(a)
|
|
110
|
+
);
|
|
111
|
+
function orElseOptional(fa, that) {
|
|
112
|
+
return asyncFold(
|
|
113
|
+
fa,
|
|
114
|
+
(opt) => opt._tag === "Some" ? asyncFail(opt) : that(),
|
|
115
|
+
(a) => asyncSucceed(a)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
var end = () => fail(none);
|
|
119
|
+
|
|
120
|
+
// src/core/runtime/ringBuffer.ts
|
|
121
|
+
var PushStatus = /* @__PURE__ */ ((PushStatus3) => {
|
|
122
|
+
PushStatus3[PushStatus3["Ok"] = 0] = "Ok";
|
|
123
|
+
PushStatus3[PushStatus3["Grew"] = 1] = "Grew";
|
|
124
|
+
PushStatus3[PushStatus3["Dropped"] = 2] = "Dropped";
|
|
125
|
+
return PushStatus3;
|
|
126
|
+
})(PushStatus || {});
|
|
127
|
+
var RingBuffer = class {
|
|
128
|
+
buf;
|
|
129
|
+
head = 0;
|
|
130
|
+
tail = 0;
|
|
131
|
+
size_ = 0;
|
|
132
|
+
// nuevo
|
|
133
|
+
maxCap;
|
|
134
|
+
constructor(initialCapacity = 1024, maxCapacity = initialCapacity) {
|
|
135
|
+
let initPow = Math.max(2, initialCapacity);
|
|
136
|
+
initPow--;
|
|
137
|
+
initPow |= initPow >>> 1;
|
|
138
|
+
initPow |= initPow >>> 2;
|
|
139
|
+
initPow |= initPow >>> 4;
|
|
140
|
+
initPow |= initPow >>> 8;
|
|
141
|
+
initPow |= initPow >>> 16;
|
|
142
|
+
initPow++;
|
|
143
|
+
let maxPow = Math.max(initPow, maxCapacity);
|
|
144
|
+
maxPow--;
|
|
145
|
+
maxPow |= maxPow >>> 1;
|
|
146
|
+
maxPow |= maxPow >>> 2;
|
|
147
|
+
maxPow |= maxPow >>> 4;
|
|
148
|
+
maxPow |= maxPow >>> 8;
|
|
149
|
+
maxPow |= maxPow >>> 16;
|
|
150
|
+
maxPow++;
|
|
151
|
+
this.buf = new Array(initPow);
|
|
152
|
+
this.maxCap = maxPow;
|
|
153
|
+
}
|
|
154
|
+
get length() {
|
|
155
|
+
return this.size_;
|
|
156
|
+
}
|
|
157
|
+
get capacity() {
|
|
158
|
+
return this.buf.length;
|
|
159
|
+
}
|
|
160
|
+
isEmpty() {
|
|
161
|
+
return this.size_ === 0;
|
|
162
|
+
}
|
|
163
|
+
push(value) {
|
|
164
|
+
if (this.size_ === this.buf.length) {
|
|
165
|
+
if (this.buf.length >= this.maxCap) {
|
|
166
|
+
return 2 /* Dropped */;
|
|
167
|
+
}
|
|
168
|
+
this.grow();
|
|
169
|
+
this.buf[this.tail] = value;
|
|
170
|
+
this.tail = this.tail + 1 & this.buf.length - 1;
|
|
171
|
+
this.size_++;
|
|
172
|
+
return 0 /* Ok */ | 1 /* Grew */;
|
|
173
|
+
}
|
|
174
|
+
this.buf[this.tail] = value;
|
|
175
|
+
this.tail = this.tail + 1 & this.buf.length - 1;
|
|
176
|
+
this.size_++;
|
|
177
|
+
return 0 /* Ok */;
|
|
178
|
+
}
|
|
179
|
+
shift() {
|
|
180
|
+
if (this.size_ === 0) return void 0;
|
|
181
|
+
const value = this.buf[this.head];
|
|
182
|
+
this.buf[this.head] = void 0;
|
|
183
|
+
this.head = this.head + 1 & this.buf.length - 1;
|
|
184
|
+
this.size_--;
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
clear() {
|
|
188
|
+
this.head = 0;
|
|
189
|
+
this.tail = 0;
|
|
190
|
+
this.size_ = 0;
|
|
191
|
+
}
|
|
192
|
+
grow() {
|
|
193
|
+
const old = this.buf;
|
|
194
|
+
const nextLen = Math.min(old.length * 2, this.maxCap);
|
|
195
|
+
const newBuf = new Array(nextLen);
|
|
196
|
+
for (let i = 0; i < this.size_; i++) {
|
|
197
|
+
newBuf[i] = old[this.head + i & old.length - 1];
|
|
198
|
+
}
|
|
199
|
+
this.buf = newBuf;
|
|
200
|
+
this.head = 0;
|
|
201
|
+
this.tail = this.size_;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// src/core/runtime/wasmModule.ts
|
|
206
|
+
import { createRequire } from "module";
|
|
207
|
+
var cachedWasmModule;
|
|
208
|
+
var cachedWasmModuleErrors = [];
|
|
209
|
+
function resolveWasmModule(options = {}) {
|
|
210
|
+
if (!options.fresh && options.modulePath == null && cachedWasmModule !== void 0) {
|
|
211
|
+
return cachedWasmModule;
|
|
212
|
+
}
|
|
213
|
+
const req = getBestRequire();
|
|
214
|
+
const candidates = wasmModuleCandidates(options.modulePath);
|
|
215
|
+
const errors = [];
|
|
216
|
+
if (!req) {
|
|
217
|
+
errors.push("no CommonJS-compatible require/createRequire was available");
|
|
218
|
+
return remember(options, null, errors);
|
|
219
|
+
}
|
|
220
|
+
for (const candidate of candidates) {
|
|
221
|
+
try {
|
|
222
|
+
return remember(options, req(candidate), errors);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
errors.push(`${candidate}: ${formatError(error)}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return remember(options, null, errors);
|
|
228
|
+
}
|
|
229
|
+
function wasmModuleResolutionErrors() {
|
|
230
|
+
return cachedWasmModuleErrors.slice();
|
|
231
|
+
}
|
|
232
|
+
function wasmModuleCandidates(modulePath) {
|
|
233
|
+
if (modulePath) return [modulePath];
|
|
234
|
+
return [
|
|
235
|
+
"brass-runtime/wasm/pkg/brass_runtime_wasm_engine.js",
|
|
236
|
+
"../wasm/pkg/brass_runtime_wasm_engine.js",
|
|
237
|
+
"../../../wasm/pkg/brass_runtime_wasm_engine.js",
|
|
238
|
+
"../../../../../wasm/pkg/brass_runtime_wasm_engine.js",
|
|
239
|
+
`${getCwd()}/wasm/pkg/brass_runtime_wasm_engine.js`
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
function remember(options, value, errors) {
|
|
243
|
+
if (!options.fresh && options.modulePath == null) {
|
|
244
|
+
cachedWasmModule = value;
|
|
245
|
+
cachedWasmModuleErrors = errors;
|
|
246
|
+
}
|
|
247
|
+
return value;
|
|
248
|
+
}
|
|
249
|
+
function getBestRequire() {
|
|
250
|
+
return getNonWebpackRequire() ?? getRuntimeRequire() ?? getCreateRequire();
|
|
251
|
+
}
|
|
252
|
+
function getNonWebpackRequire() {
|
|
253
|
+
try {
|
|
254
|
+
return (0, eval)(
|
|
255
|
+
"typeof __non_webpack_require__ === 'function' ? __non_webpack_require__ : undefined"
|
|
256
|
+
);
|
|
257
|
+
} catch {
|
|
258
|
+
return void 0;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function getRuntimeRequire() {
|
|
262
|
+
try {
|
|
263
|
+
return (0, eval)(
|
|
264
|
+
"typeof require === 'function' ? require : undefined"
|
|
265
|
+
);
|
|
266
|
+
} catch {
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function getCreateRequire() {
|
|
271
|
+
try {
|
|
272
|
+
return createRequire(`${getCwd()}/package.json`);
|
|
273
|
+
} catch {
|
|
274
|
+
return void 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function getCwd() {
|
|
278
|
+
try {
|
|
279
|
+
return (0, eval)(
|
|
280
|
+
"typeof process !== 'undefined' ? process.cwd() : ''"
|
|
281
|
+
);
|
|
282
|
+
} catch {
|
|
283
|
+
return "";
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function formatError(error) {
|
|
287
|
+
return error instanceof Error ? error.message : String(error);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/core/runtime/boundedRingBuffer.ts
|
|
291
|
+
var cachedWasmCtor;
|
|
292
|
+
function resolveWasmRingBuffer() {
|
|
293
|
+
if (cachedWasmCtor !== void 0) return cachedWasmCtor;
|
|
294
|
+
const mod = resolveWasmModule();
|
|
295
|
+
cachedWasmCtor = mod?.BrassWasmRingBuffer ?? null;
|
|
296
|
+
return cachedWasmCtor;
|
|
297
|
+
}
|
|
298
|
+
var WasmRingBuffer = class {
|
|
299
|
+
engine = "wasm";
|
|
300
|
+
fallbackUsed = false;
|
|
301
|
+
inner;
|
|
302
|
+
pushes = 0;
|
|
303
|
+
shifts = 0;
|
|
304
|
+
clears = 0;
|
|
305
|
+
dropped = 0;
|
|
306
|
+
constructor(initialCapacity, maxCapacity) {
|
|
307
|
+
const Ctor = resolveWasmRingBuffer();
|
|
308
|
+
if (!Ctor) {
|
|
309
|
+
throw new Error("brass-runtime wasm ring buffer is not available. Run npm run build:wasm first.");
|
|
310
|
+
}
|
|
311
|
+
this.inner = new Ctor(initialCapacity, maxCapacity);
|
|
312
|
+
}
|
|
313
|
+
get length() {
|
|
314
|
+
return this.inner.len();
|
|
315
|
+
}
|
|
316
|
+
get capacity() {
|
|
317
|
+
return this.inner.capacity();
|
|
318
|
+
}
|
|
319
|
+
isEmpty() {
|
|
320
|
+
return this.inner.is_empty();
|
|
321
|
+
}
|
|
322
|
+
push(value) {
|
|
323
|
+
this.pushes++;
|
|
324
|
+
const status = this.inner.push(value);
|
|
325
|
+
if ((status & 2) !== 0) this.dropped++;
|
|
326
|
+
return status;
|
|
327
|
+
}
|
|
328
|
+
shift() {
|
|
329
|
+
const value = this.inner.shift();
|
|
330
|
+
if (value !== void 0) this.shifts++;
|
|
331
|
+
return value === void 0 ? void 0 : value;
|
|
332
|
+
}
|
|
333
|
+
clear() {
|
|
334
|
+
this.clears++;
|
|
335
|
+
this.inner.clear();
|
|
336
|
+
}
|
|
337
|
+
stats() {
|
|
338
|
+
return {
|
|
339
|
+
engine: "wasm",
|
|
340
|
+
fallbackUsed: false,
|
|
341
|
+
data: {
|
|
342
|
+
len: this.length,
|
|
343
|
+
capacity: this.capacity,
|
|
344
|
+
pushes: this.pushes,
|
|
345
|
+
shifts: this.shifts,
|
|
346
|
+
clears: this.clears,
|
|
347
|
+
dropped: this.dropped
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
var TsBoundedRingBuffer = class {
|
|
353
|
+
constructor(inner) {
|
|
354
|
+
this.inner = inner;
|
|
355
|
+
}
|
|
356
|
+
inner;
|
|
357
|
+
engine = "ts";
|
|
358
|
+
fallbackUsed = false;
|
|
359
|
+
pushes = 0;
|
|
360
|
+
shifts = 0;
|
|
361
|
+
clears = 0;
|
|
362
|
+
dropped = 0;
|
|
363
|
+
get length() {
|
|
364
|
+
return this.inner.length;
|
|
365
|
+
}
|
|
366
|
+
get capacity() {
|
|
367
|
+
return this.inner.capacity;
|
|
368
|
+
}
|
|
369
|
+
isEmpty() {
|
|
370
|
+
return this.inner.isEmpty();
|
|
371
|
+
}
|
|
372
|
+
push(value) {
|
|
373
|
+
this.pushes++;
|
|
374
|
+
const status = this.inner.push(value);
|
|
375
|
+
if ((status & 2) !== 0) this.dropped++;
|
|
376
|
+
return status;
|
|
377
|
+
}
|
|
378
|
+
shift() {
|
|
379
|
+
const value = this.inner.shift();
|
|
380
|
+
if (value !== void 0) this.shifts++;
|
|
381
|
+
return value;
|
|
382
|
+
}
|
|
383
|
+
clear() {
|
|
384
|
+
this.clears++;
|
|
385
|
+
this.inner.clear();
|
|
386
|
+
}
|
|
387
|
+
stats() {
|
|
388
|
+
return {
|
|
389
|
+
engine: "ts",
|
|
390
|
+
fallbackUsed: false,
|
|
391
|
+
data: {
|
|
392
|
+
len: this.length,
|
|
393
|
+
capacity: this.capacity,
|
|
394
|
+
pushes: this.pushes,
|
|
395
|
+
shifts: this.shifts,
|
|
396
|
+
clears: this.clears,
|
|
397
|
+
dropped: this.dropped
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
function makeBoundedRingBuffer(initialCapacity, maxCapacity = initialCapacity, options = {}) {
|
|
403
|
+
const engine = options.engine ?? "ts";
|
|
404
|
+
if (engine === "ts") {
|
|
405
|
+
return new TsBoundedRingBuffer(new RingBuffer(initialCapacity, maxCapacity));
|
|
406
|
+
}
|
|
407
|
+
if (engine === "wasm") {
|
|
408
|
+
return new WasmRingBuffer(initialCapacity, maxCapacity);
|
|
409
|
+
}
|
|
410
|
+
throw new Error(`brass-runtime ring buffer engine must be 'ts' or 'wasm'; received '${String(engine)}'`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/core/runtime/scheduler.ts
|
|
414
|
+
var FLUSH_BUDGET = 2048;
|
|
415
|
+
var MICRO_THRESHOLD = 4096;
|
|
416
|
+
var SCHEDULER_QUEUE_CAPACITY = 8192;
|
|
417
|
+
var DEFAULT_LANE_CAPACITY = 1024;
|
|
418
|
+
var DEFAULT_LANE_BUDGET = 64;
|
|
419
|
+
var DEFAULT_MAX_LANES = 256;
|
|
420
|
+
var scheduleMacro = (() => {
|
|
421
|
+
if (typeof globalThis.setImmediate === "function") return (f) => globalThis.setImmediate(f);
|
|
422
|
+
if (typeof globalThis.MessageChannel === "function") {
|
|
423
|
+
const ch = new globalThis.MessageChannel();
|
|
424
|
+
let cb = null;
|
|
425
|
+
ch.port1.onmessage = () => {
|
|
426
|
+
const f = cb;
|
|
427
|
+
cb = null;
|
|
428
|
+
f?.();
|
|
429
|
+
};
|
|
430
|
+
return (f) => {
|
|
431
|
+
cb = f;
|
|
432
|
+
ch.port2.postMessage(0);
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
return (f) => setTimeout(f, 0);
|
|
436
|
+
})();
|
|
437
|
+
function resolveWasmScheduler() {
|
|
438
|
+
const mod = resolveWasmModule();
|
|
439
|
+
return mod?.BrassWasmSchedulerStateMachine ?? null;
|
|
440
|
+
}
|
|
441
|
+
var LaneState = class {
|
|
442
|
+
constructor(key, initial, max) {
|
|
443
|
+
this.key = key;
|
|
444
|
+
this.queue = makeBoundedRingBuffer(initial, max, { engine: "ts" });
|
|
445
|
+
}
|
|
446
|
+
key;
|
|
447
|
+
queue;
|
|
448
|
+
enqueuedTasks = 0;
|
|
449
|
+
executedTasks = 0;
|
|
450
|
+
droppedTasks = 0;
|
|
451
|
+
};
|
|
452
|
+
var JsSchedulerState = class {
|
|
453
|
+
constructor(options) {
|
|
454
|
+
this.options = options;
|
|
455
|
+
}
|
|
456
|
+
options;
|
|
457
|
+
lanes = /* @__PURE__ */ new Map();
|
|
458
|
+
laneOrder = [];
|
|
459
|
+
rrIndex = 0;
|
|
460
|
+
rrRemaining = 0;
|
|
461
|
+
flushing = false;
|
|
462
|
+
scheduled = false;
|
|
463
|
+
scheduledFlushes = 0;
|
|
464
|
+
completedFlushes = 0;
|
|
465
|
+
enqueuedTasks = 0;
|
|
466
|
+
executedTasks = 0;
|
|
467
|
+
droppedTasks = 0;
|
|
468
|
+
yieldedByBudget = 0;
|
|
469
|
+
totalLength = 0;
|
|
470
|
+
get totalCapacity() {
|
|
471
|
+
let n = 0;
|
|
472
|
+
for (const lane of this.lanes.values()) n += lane.queue.capacity;
|
|
473
|
+
return n;
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
var WasmSchedulerState = class {
|
|
477
|
+
machine;
|
|
478
|
+
nextRef = 1;
|
|
479
|
+
tasks = /* @__PURE__ */ new Map();
|
|
480
|
+
tags = /* @__PURE__ */ new Map();
|
|
481
|
+
constructor(options) {
|
|
482
|
+
const Ctor = resolveWasmScheduler();
|
|
483
|
+
if (!Ctor) throw new Error("brass-runtime wasm scheduler is not available. Run npm run build:wasm first.");
|
|
484
|
+
this.machine = new Ctor(
|
|
485
|
+
options.initialCapacity ?? 1024,
|
|
486
|
+
options.maxCapacity ?? SCHEDULER_QUEUE_CAPACITY,
|
|
487
|
+
options.flushBudget ?? FLUSH_BUDGET,
|
|
488
|
+
options.microThreshold ?? MICRO_THRESHOLD,
|
|
489
|
+
options.laneCapacity ?? DEFAULT_LANE_CAPACITY,
|
|
490
|
+
options.laneBudget ?? DEFAULT_LANE_BUDGET,
|
|
491
|
+
options.maxLanes ?? DEFAULT_MAX_LANES
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
enqueue(task, tag) {
|
|
495
|
+
const ref = this.nextRef++;
|
|
496
|
+
this.tasks.set(ref, task);
|
|
497
|
+
this.tags.set(ref, tag);
|
|
498
|
+
const policy = this.machine.enqueue(ref, tag);
|
|
499
|
+
if (policy === 3) {
|
|
500
|
+
this.tasks.delete(ref);
|
|
501
|
+
this.tags.delete(ref);
|
|
502
|
+
}
|
|
503
|
+
return policy;
|
|
504
|
+
}
|
|
505
|
+
enqueueBatch(tasks) {
|
|
506
|
+
const refs = [];
|
|
507
|
+
const tags = [];
|
|
508
|
+
for (const { task, tag } of tasks) {
|
|
509
|
+
const ref = this.nextRef++;
|
|
510
|
+
this.tasks.set(ref, task);
|
|
511
|
+
this.tags.set(ref, tag);
|
|
512
|
+
refs.push(ref);
|
|
513
|
+
tags.push(tag);
|
|
514
|
+
}
|
|
515
|
+
const policies = this.machine.enqueue_batch(new Uint32Array(refs), tags);
|
|
516
|
+
for (let i = 0; i < policies.length; i++) {
|
|
517
|
+
if (policies[i] === 3) {
|
|
518
|
+
this.tasks.delete(refs[i]);
|
|
519
|
+
this.tags.delete(refs[i]);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return Array.from(policies);
|
|
523
|
+
}
|
|
524
|
+
beginFlush() {
|
|
525
|
+
return this.machine.begin_flush();
|
|
526
|
+
}
|
|
527
|
+
shift() {
|
|
528
|
+
const ref = this.machine.shift();
|
|
529
|
+
if (ref === 0) return void 0;
|
|
530
|
+
const task = this.tasks.get(ref);
|
|
531
|
+
const tag = this.tags.get(ref) ?? "anonymous";
|
|
532
|
+
this.tasks.delete(ref);
|
|
533
|
+
this.tags.delete(ref);
|
|
534
|
+
return task ? { task, tag } : void 0;
|
|
535
|
+
}
|
|
536
|
+
endFlush(ran) {
|
|
537
|
+
return this.machine.end_flush(ran);
|
|
538
|
+
}
|
|
539
|
+
stats() {
|
|
540
|
+
return { engine: "wasm", fallbackUsed: false, data: JSON.parse(this.machine.stats_json()) };
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
function sanitizeLaneKey(value) {
|
|
544
|
+
let key = "";
|
|
545
|
+
let previousWasColon = false;
|
|
546
|
+
for (const ch of value.trim()) {
|
|
547
|
+
if (isLaneWhitespace(ch)) {
|
|
548
|
+
if (!previousWasColon && key.length > 0) key += ":";
|
|
549
|
+
previousWasColon = true;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (isAsciiAlphaNumeric(ch) || ch === "_" || ch === "." || ch === ":" || ch === "/" || ch === "#" || ch === "-") {
|
|
553
|
+
key += ch;
|
|
554
|
+
previousWasColon = ch === ":";
|
|
555
|
+
} else {
|
|
556
|
+
key += "_";
|
|
557
|
+
previousWasColon = false;
|
|
558
|
+
}
|
|
559
|
+
if (key.length >= 160) break;
|
|
560
|
+
}
|
|
561
|
+
return key.length > 0 ? key : "anonymous";
|
|
562
|
+
}
|
|
563
|
+
function isLaneWhitespace(ch) {
|
|
564
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
|
|
565
|
+
}
|
|
566
|
+
function isAsciiAlphaNumeric(ch) {
|
|
567
|
+
if (ch.length !== 1) return false;
|
|
568
|
+
const code = ch.charCodeAt(0);
|
|
569
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
|
|
570
|
+
}
|
|
571
|
+
function laneTag(lane, tag = "task") {
|
|
572
|
+
return `lane:${sanitizeLaneKey(lane)}|${tag}`;
|
|
573
|
+
}
|
|
574
|
+
function extractStackLocation(line) {
|
|
575
|
+
const raw = extractParenthesizedLocation(line) ?? extractAtLocation(line);
|
|
576
|
+
if (!raw) return void 0;
|
|
577
|
+
return stripLineAndColumn(raw);
|
|
578
|
+
}
|
|
579
|
+
function extractParenthesizedLocation(line) {
|
|
580
|
+
const closeIdx = line.lastIndexOf(")");
|
|
581
|
+
if (closeIdx <= 0) return void 0;
|
|
582
|
+
const openIdx = line.lastIndexOf("(", closeIdx - 1);
|
|
583
|
+
if (openIdx < 0 || openIdx + 1 >= closeIdx) return void 0;
|
|
584
|
+
return line.slice(openIdx + 1, closeIdx).trim();
|
|
585
|
+
}
|
|
586
|
+
function extractAtLocation(line) {
|
|
587
|
+
const trimmed = line.trim();
|
|
588
|
+
if (!trimmed.startsWith("at ")) return void 0;
|
|
589
|
+
const afterAt = trimmed.slice(3).trimStart();
|
|
590
|
+
if (!afterAt) return void 0;
|
|
591
|
+
const lastSpace = afterAt.lastIndexOf(" ");
|
|
592
|
+
return (lastSpace >= 0 ? afterAt.slice(lastSpace + 1) : afterAt).trim();
|
|
593
|
+
}
|
|
594
|
+
function stripLineAndColumn(location) {
|
|
595
|
+
let out = location.trim();
|
|
596
|
+
out = stripTrailingNumericSegment(out);
|
|
597
|
+
out = stripTrailingNumericSegment(out);
|
|
598
|
+
return out;
|
|
599
|
+
}
|
|
600
|
+
function stripTrailingNumericSegment(value) {
|
|
601
|
+
const colon = value.lastIndexOf(":");
|
|
602
|
+
if (colon < 0 || colon + 1 >= value.length) return value;
|
|
603
|
+
for (let i = colon + 1; i < value.length; i++) {
|
|
604
|
+
const code = value.charCodeAt(i);
|
|
605
|
+
if (code < 48 || code > 57) return value;
|
|
606
|
+
}
|
|
607
|
+
return value.slice(0, colon);
|
|
608
|
+
}
|
|
609
|
+
function normalizeSlashes(value) {
|
|
610
|
+
return value.replaceAll("\\", "/").trim();
|
|
611
|
+
}
|
|
612
|
+
function isPseudoStackLocation(location) {
|
|
613
|
+
const normalized = normalizeSlashes(location);
|
|
614
|
+
return normalized === "<anonymous>" || normalized === "anonymous" || normalized.startsWith("node:") || normalized.includes("/node_modules/") || normalized.startsWith("node_modules/");
|
|
615
|
+
}
|
|
616
|
+
function isInternalRuntimeFrame(location, line) {
|
|
617
|
+
const normalized = normalizeSlashes(location);
|
|
618
|
+
const frame = normalizeSlashes(line);
|
|
619
|
+
return isRuntimeSourcePath(normalized) || isRuntimeDistFile(normalized) || normalized.includes("/node_modules/brass-runtime/") || normalized.endsWith("/node_modules/brass-runtime") || INTERNAL_FRAME_TOKENS.some((token) => frame.includes(token));
|
|
620
|
+
}
|
|
621
|
+
var INTERNAL_FRAME_TOKENS = [
|
|
622
|
+
"inferCallerLaneFromStack",
|
|
623
|
+
"Runtime.",
|
|
624
|
+
"RuntimeFiber.",
|
|
625
|
+
"EngineFiberHandle.",
|
|
626
|
+
"WasmFiberEngine.",
|
|
627
|
+
"JsFiberEngine.",
|
|
628
|
+
"Scheduler."
|
|
629
|
+
];
|
|
630
|
+
function isRuntimeSourcePath(path) {
|
|
631
|
+
const marker = "src/core/runtime/";
|
|
632
|
+
const idx = path.startsWith(marker) ? 0 : path.indexOf(`/${marker}`);
|
|
633
|
+
if (idx < 0) return false;
|
|
634
|
+
const offset = idx === 0 ? marker.length : idx + marker.length + 1;
|
|
635
|
+
const rest = path.slice(offset);
|
|
636
|
+
return rest !== "__tests__" && !rest.startsWith("__tests__/");
|
|
637
|
+
}
|
|
638
|
+
function isRuntimeDistFile(path) {
|
|
639
|
+
if (!hasPathSegment(path, "dist")) return false;
|
|
640
|
+
const file = basename(path);
|
|
641
|
+
const stem = dropKnownExtension(file);
|
|
642
|
+
if (stem === file) return false;
|
|
643
|
+
return stem === "index" || stem.includes("runtime");
|
|
644
|
+
}
|
|
645
|
+
function hasPathSegment(path, segment) {
|
|
646
|
+
return normalizeSlashes(path).split("/").includes(segment);
|
|
647
|
+
}
|
|
648
|
+
function basename(path) {
|
|
649
|
+
const normalized = normalizeSlashes(path);
|
|
650
|
+
const slash = normalized.lastIndexOf("/");
|
|
651
|
+
return slash < 0 ? normalized : normalized.slice(slash + 1);
|
|
652
|
+
}
|
|
653
|
+
function normalizeCallerLocation(location) {
|
|
654
|
+
let out = normalizeSlashes(location);
|
|
655
|
+
if (out.startsWith("file://")) out = out.slice("file://".length);
|
|
656
|
+
const srcIdx = out.lastIndexOf("/src/");
|
|
657
|
+
if (srcIdx >= 0) {
|
|
658
|
+
out = out.slice(srcIdx + 5);
|
|
659
|
+
} else {
|
|
660
|
+
const cwd = typeof process !== "undefined" ? normalizeSlashes(process.cwd()) : "";
|
|
661
|
+
if (cwd && out.startsWith(cwd + "/")) out = out.slice(cwd.length + 1);
|
|
662
|
+
out = lastPathSegments(out, 2);
|
|
663
|
+
}
|
|
664
|
+
return dropKnownExtension(out);
|
|
665
|
+
}
|
|
666
|
+
function lastPathSegments(path, count) {
|
|
667
|
+
const parts = normalizeSlashes(path).split("/").filter((part) => part.length > 0);
|
|
668
|
+
if (parts.length <= count) return path;
|
|
669
|
+
return parts.slice(parts.length - count).join("/");
|
|
670
|
+
}
|
|
671
|
+
function dropKnownExtension(path) {
|
|
672
|
+
const extensions = [".tsx", ".mts", ".cts", ".jsx", ".mjs", ".cjs", ".ts", ".js"];
|
|
673
|
+
for (const ext of extensions) {
|
|
674
|
+
if (path.endsWith(ext)) return path.slice(0, -ext.length);
|
|
675
|
+
}
|
|
676
|
+
return path;
|
|
677
|
+
}
|
|
678
|
+
function inferCallerLaneFromStack(stack = new Error().stack, fallback = "anonymous") {
|
|
679
|
+
const lines = (stack ?? "").split("\n").slice(1);
|
|
680
|
+
for (const line of lines) {
|
|
681
|
+
const location = extractStackLocation(line);
|
|
682
|
+
if (!location) continue;
|
|
683
|
+
if (isPseudoStackLocation(location)) continue;
|
|
684
|
+
if (isInternalRuntimeFrame(location, line)) continue;
|
|
685
|
+
return sanitizeLaneKey(normalizeCallerLocation(location));
|
|
686
|
+
}
|
|
687
|
+
return sanitizeLaneKey(fallback);
|
|
688
|
+
}
|
|
689
|
+
var laneKeyCache = /* @__PURE__ */ new Map();
|
|
690
|
+
var LANE_CACHE_MAX = 1024;
|
|
691
|
+
function inferLane(tag) {
|
|
692
|
+
let cached = laneKeyCache.get(tag);
|
|
693
|
+
if (cached !== void 0) return cached;
|
|
694
|
+
const explicit = extractTaggedLane(tag, "lane:");
|
|
695
|
+
if (explicit) {
|
|
696
|
+
cached = sanitizeLaneKey(explicit);
|
|
697
|
+
} else {
|
|
698
|
+
const caller = extractTaggedLane(tag, "caller:");
|
|
699
|
+
if (caller) {
|
|
700
|
+
cached = sanitizeLaneKey(caller);
|
|
701
|
+
} else {
|
|
702
|
+
const firstSep = firstSeparatorIndex(tag);
|
|
703
|
+
const first = firstSep < 0 ? tag : tag.slice(0, firstSep);
|
|
704
|
+
cached = sanitizeLaneKey(first || "anonymous");
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (laneKeyCache.size >= LANE_CACHE_MAX) laneKeyCache.clear();
|
|
708
|
+
laneKeyCache.set(tag, cached);
|
|
709
|
+
return cached;
|
|
710
|
+
}
|
|
711
|
+
function extractTaggedLane(tag, prefix) {
|
|
712
|
+
if (!tag.startsWith(prefix)) return void 0;
|
|
713
|
+
const end2 = tag.indexOf("|", prefix.length);
|
|
714
|
+
if (end2 < 0) return void 0;
|
|
715
|
+
const value = tag.slice(prefix.length, end2);
|
|
716
|
+
return value.length > 0 ? value : void 0;
|
|
717
|
+
}
|
|
718
|
+
function firstSeparatorIndex(value) {
|
|
719
|
+
let best = -1;
|
|
720
|
+
for (const sep of [".", "#", "/"]) {
|
|
721
|
+
const idx = value.indexOf(sep);
|
|
722
|
+
if (idx >= 0 && (best < 0 || idx < best)) best = idx;
|
|
723
|
+
}
|
|
724
|
+
return best;
|
|
725
|
+
}
|
|
726
|
+
var Scheduler = class {
|
|
727
|
+
engine;
|
|
728
|
+
js;
|
|
729
|
+
wasm;
|
|
730
|
+
flushBudget;
|
|
731
|
+
microThreshold;
|
|
732
|
+
laneCapacity;
|
|
733
|
+
laneBudget;
|
|
734
|
+
maxLanes;
|
|
735
|
+
fallbackUsed;
|
|
736
|
+
boundFlush = () => this.flush();
|
|
737
|
+
constructor(options = {}) {
|
|
738
|
+
this.flushBudget = options.flushBudget ?? FLUSH_BUDGET;
|
|
739
|
+
this.microThreshold = options.microThreshold ?? MICRO_THRESHOLD;
|
|
740
|
+
this.laneCapacity = options.laneCapacity ?? DEFAULT_LANE_CAPACITY;
|
|
741
|
+
this.laneBudget = options.laneBudget ?? DEFAULT_LANE_BUDGET;
|
|
742
|
+
this.maxLanes = options.maxLanes ?? DEFAULT_MAX_LANES;
|
|
743
|
+
const requested = options.engine ?? "ts";
|
|
744
|
+
if (requested === "wasm") {
|
|
745
|
+
this.wasm = new WasmSchedulerState(options);
|
|
746
|
+
this.engine = "wasm";
|
|
747
|
+
this.fallbackUsed = false;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (requested === "ts") {
|
|
751
|
+
this.js = new JsSchedulerState({ ...options, engine: "ts" });
|
|
752
|
+
this.engine = "ts";
|
|
753
|
+
this.fallbackUsed = false;
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
throw new Error(`brass-runtime scheduler engine must be 'ts' or 'wasm'; received '${String(requested)}'`);
|
|
757
|
+
}
|
|
758
|
+
schedule(task, tag = "anonymous") {
|
|
759
|
+
if (typeof task !== "function") return "dropped";
|
|
760
|
+
if (this.wasm) return this.scheduleWasm(task, tag);
|
|
761
|
+
return this.scheduleJs(task, tag);
|
|
762
|
+
}
|
|
763
|
+
scheduleBatch(tasks) {
|
|
764
|
+
if (this.wasm) return this.scheduleBatchWasm(tasks);
|
|
765
|
+
return tasks.map(({ fn, tag }) => this.schedule(fn, tag));
|
|
766
|
+
}
|
|
767
|
+
stats() {
|
|
768
|
+
if (this.wasm) return this.wasm.stats();
|
|
769
|
+
const js = this.js;
|
|
770
|
+
const lanes = Array.from(js.lanes.values()).map((lane) => ({ key: lane.key, len: lane.queue.length, capacity: lane.queue.capacity, enqueuedTasks: lane.enqueuedTasks, executedTasks: lane.executedTasks, droppedTasks: lane.droppedTasks }));
|
|
771
|
+
return { engine: "ts", fallbackUsed: false, data: { len: js.totalLength, capacity: js.totalCapacity, phase: js.flushing ? "flushing" : js.scheduled ? "scheduled" : "idle", scheduledFlushes: js.scheduledFlushes, completedFlushes: js.completedFlushes, enqueuedTasks: js.enqueuedTasks, executedTasks: js.executedTasks, droppedTasks: js.droppedTasks, yieldedByBudget: js.yieldedByBudget, lanes } };
|
|
772
|
+
}
|
|
773
|
+
scheduleWasm(task, tag) {
|
|
774
|
+
const policy = this.wasm.enqueue(task, tag);
|
|
775
|
+
if (policy === 3) return "dropped";
|
|
776
|
+
if (policy === 0) this.requestFlush("micro");
|
|
777
|
+
else if (policy === 1) this.requestFlush("macro");
|
|
778
|
+
return "accepted";
|
|
779
|
+
}
|
|
780
|
+
scheduleBatchWasm(tasks) {
|
|
781
|
+
const validTasks = [];
|
|
782
|
+
const results = [];
|
|
783
|
+
const indexMap = [];
|
|
784
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
785
|
+
const { fn, tag } = tasks[i];
|
|
786
|
+
if (typeof fn !== "function") {
|
|
787
|
+
results.push("dropped");
|
|
788
|
+
} else {
|
|
789
|
+
results.push("accepted");
|
|
790
|
+
indexMap.push(i);
|
|
791
|
+
validTasks.push({ task: fn, tag });
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (validTasks.length === 0) return results;
|
|
795
|
+
const policies = this.wasm.enqueueBatch(validTasks);
|
|
796
|
+
let needsMicro = false;
|
|
797
|
+
let needsMacro = false;
|
|
798
|
+
for (let j = 0; j < policies.length; j++) {
|
|
799
|
+
const policy = policies[j];
|
|
800
|
+
const originalIdx = indexMap[j];
|
|
801
|
+
if (policy === 3) {
|
|
802
|
+
results[originalIdx] = "dropped";
|
|
803
|
+
} else if (policy === 0) {
|
|
804
|
+
needsMicro = true;
|
|
805
|
+
} else if (policy === 1) {
|
|
806
|
+
needsMacro = true;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (needsMicro) this.requestFlush("micro");
|
|
810
|
+
else if (needsMacro) this.requestFlush("macro");
|
|
811
|
+
return results;
|
|
812
|
+
}
|
|
813
|
+
getOrCreateLane(key) {
|
|
814
|
+
const js = this.js;
|
|
815
|
+
const existing = js.lanes.get(key);
|
|
816
|
+
if (existing) return existing;
|
|
817
|
+
if (js.lanes.size >= this.maxLanes) return js.lanes.get("overflow") ?? this.createLane("overflow");
|
|
818
|
+
return this.createLane(key);
|
|
819
|
+
}
|
|
820
|
+
createLane(key) {
|
|
821
|
+
const js = this.js;
|
|
822
|
+
const lane = new LaneState(key, this.laneCapacity, this.laneCapacity);
|
|
823
|
+
js.lanes.set(key, lane);
|
|
824
|
+
js.laneOrder.push(key);
|
|
825
|
+
return lane;
|
|
826
|
+
}
|
|
827
|
+
scheduleJs(task, tag) {
|
|
828
|
+
const js = this.js;
|
|
829
|
+
const lane = this.getOrCreateLane(inferLane(tag));
|
|
830
|
+
const status = lane.queue.push({ task, tag });
|
|
831
|
+
lane.enqueuedTasks++;
|
|
832
|
+
js.enqueuedTasks++;
|
|
833
|
+
if ((status & 2) !== 0) {
|
|
834
|
+
lane.droppedTasks++;
|
|
835
|
+
js.droppedTasks++;
|
|
836
|
+
if (!js.flushing && !js.scheduled && js.totalLength > 0) {
|
|
837
|
+
js.scheduled = true;
|
|
838
|
+
js.scheduledFlushes++;
|
|
839
|
+
this.requestFlush(js.totalLength > this.microThreshold ? "macro" : "micro");
|
|
840
|
+
}
|
|
841
|
+
return "dropped";
|
|
842
|
+
}
|
|
843
|
+
js.totalLength++;
|
|
844
|
+
if (js.flushing) return "accepted";
|
|
845
|
+
if (!js.scheduled) {
|
|
846
|
+
js.scheduled = true;
|
|
847
|
+
js.scheduledFlushes++;
|
|
848
|
+
this.requestFlush(js.totalLength > this.microThreshold ? "macro" : "micro");
|
|
849
|
+
}
|
|
850
|
+
return "accepted";
|
|
851
|
+
}
|
|
852
|
+
requestFlush(kind) {
|
|
853
|
+
if (kind === "micro") queueMicrotask(this.boundFlush);
|
|
854
|
+
else scheduleMacro(this.boundFlush);
|
|
855
|
+
}
|
|
856
|
+
flush() {
|
|
857
|
+
if (this.wasm) return this.flushWasm();
|
|
858
|
+
this.flushJs();
|
|
859
|
+
}
|
|
860
|
+
flushWasm() {
|
|
861
|
+
const wasm = this.wasm;
|
|
862
|
+
const budget = wasm.beginFlush();
|
|
863
|
+
if (budget <= 0) return;
|
|
864
|
+
let ran = 0;
|
|
865
|
+
try {
|
|
866
|
+
while (ran < budget) {
|
|
867
|
+
const next = wasm.shift();
|
|
868
|
+
if (!next) break;
|
|
869
|
+
ran++;
|
|
870
|
+
try {
|
|
871
|
+
next.task();
|
|
872
|
+
} catch (e) {
|
|
873
|
+
console.error(`[Scheduler] task threw (tag=${next.tag})`, e);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
} finally {
|
|
877
|
+
const policy = wasm.endFlush(ran);
|
|
878
|
+
if (policy === 0) this.requestFlush("micro");
|
|
879
|
+
else if (policy === 1) this.requestFlush("macro");
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
shiftFromNextLane() {
|
|
883
|
+
const js = this.js;
|
|
884
|
+
const n = js.laneOrder.length;
|
|
885
|
+
if (n === 0) return void 0;
|
|
886
|
+
if (js.rrRemaining > 0) {
|
|
887
|
+
const currentIdx = (js.rrIndex + n - 1) % n;
|
|
888
|
+
const currentLane = js.lanes.get(js.laneOrder[currentIdx]);
|
|
889
|
+
const next = currentLane?.queue.shift();
|
|
890
|
+
if (next) {
|
|
891
|
+
js.rrRemaining--;
|
|
892
|
+
currentLane.executedTasks++;
|
|
893
|
+
js.totalLength--;
|
|
894
|
+
return next;
|
|
895
|
+
}
|
|
896
|
+
js.rrRemaining = 0;
|
|
897
|
+
}
|
|
898
|
+
for (let scanned = 0; scanned < n; scanned++) {
|
|
899
|
+
const idx = js.rrIndex % n;
|
|
900
|
+
const key = js.laneOrder[idx];
|
|
901
|
+
js.rrIndex = (idx + 1) % n;
|
|
902
|
+
const lane = js.lanes.get(key);
|
|
903
|
+
if (!lane || lane.queue.length === 0) continue;
|
|
904
|
+
js.rrRemaining = Math.max(0, this.laneBudget - 1);
|
|
905
|
+
lane.executedTasks++;
|
|
906
|
+
js.totalLength--;
|
|
907
|
+
return lane.queue.shift();
|
|
908
|
+
}
|
|
909
|
+
return void 0;
|
|
910
|
+
}
|
|
911
|
+
flushJs() {
|
|
912
|
+
const js = this.js;
|
|
913
|
+
if (js.flushing) return;
|
|
914
|
+
js.flushing = true;
|
|
915
|
+
js.scheduled = false;
|
|
916
|
+
let ran = 0;
|
|
917
|
+
try {
|
|
918
|
+
while (ran < this.flushBudget) {
|
|
919
|
+
const next = this.shiftFromNextLane();
|
|
920
|
+
if (!next) break;
|
|
921
|
+
ran++;
|
|
922
|
+
js.executedTasks++;
|
|
923
|
+
try {
|
|
924
|
+
next.task();
|
|
925
|
+
} catch (e) {
|
|
926
|
+
console.error(`[Scheduler] task threw (tag=${next.tag})`, e);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
} finally {
|
|
930
|
+
js.flushing = false;
|
|
931
|
+
js.completedFlushes++;
|
|
932
|
+
if (js.totalLength > 0 && !js.scheduled) {
|
|
933
|
+
js.scheduled = true;
|
|
934
|
+
js.scheduledFlushes++;
|
|
935
|
+
const kind = ran >= this.flushBudget || js.totalLength > this.microThreshold ? "macro" : "micro";
|
|
936
|
+
if (ran >= this.flushBudget) js.yieldedByBudget++;
|
|
937
|
+
this.requestFlush(kind);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
var globalScheduler = new Scheduler();
|
|
943
|
+
|
|
944
|
+
// src/core/runtime/hostAction.ts
|
|
945
|
+
var DefaultHostExecutor = {
|
|
946
|
+
async execute(action) {
|
|
947
|
+
return {
|
|
948
|
+
kind: "error",
|
|
949
|
+
actionId: action.actionId,
|
|
950
|
+
error: new Error(`No HostExecutor configured for HostAction kind=${action.kind} target=${action.target}`)
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
// src/core/runtime/contex.ts
|
|
956
|
+
var emptyContext = { parent: null, patch: /* @__PURE__ */ Object.create(null) };
|
|
957
|
+
|
|
958
|
+
// src/core/runtime/forkPolicy.ts
|
|
959
|
+
function makeForkPolicy(env, hooks) {
|
|
960
|
+
const svc = resolveForkServices(env);
|
|
961
|
+
return {
|
|
962
|
+
initChild(fiber, parent, scopeId) {
|
|
963
|
+
const parentCtx = parent?.fiberContext;
|
|
964
|
+
const trace = forkTrace(svc, parentCtx?.trace ?? null);
|
|
965
|
+
fiber.fiberContext = {
|
|
966
|
+
log: parentCtx?.log ?? emptyContext,
|
|
967
|
+
trace
|
|
968
|
+
};
|
|
969
|
+
fiber.parentFiberId = parent?.id;
|
|
970
|
+
fiber.name = svc.childName(parent?.name);
|
|
971
|
+
if (scopeId !== void 0) fiber.scopeId = scopeId;
|
|
972
|
+
hooks.emit(
|
|
973
|
+
{
|
|
974
|
+
type: "fiber.start",
|
|
975
|
+
fiberId: fiber.id,
|
|
976
|
+
parentFiberId: parent?.id,
|
|
977
|
+
scopeId: fiber.scopeId,
|
|
978
|
+
// ✅ ahora viaja
|
|
979
|
+
name: fiber.name
|
|
980
|
+
},
|
|
981
|
+
{ fiberId: parent?.id, traceId: parentCtx?.trace?.traceId, spanId: parentCtx?.trace?.spanId }
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
function resolveForkServices(env) {
|
|
987
|
+
const defaultTracer = {
|
|
988
|
+
newTraceId: () => randomRuntimeId("trace"),
|
|
989
|
+
newSpanId: () => randomRuntimeId("span")
|
|
990
|
+
};
|
|
991
|
+
const brass = env?.brass;
|
|
992
|
+
const tracer = brass?.tracer ?? defaultTracer;
|
|
993
|
+
const seed = brass?.traceSeed;
|
|
994
|
+
const childName = brass?.childName ?? ((p) => p ? `${p}/child` : void 0);
|
|
995
|
+
return { tracer, seed, childName };
|
|
996
|
+
}
|
|
997
|
+
function randomRuntimeId(prefix) {
|
|
998
|
+
const cryptoLike = globalThis.crypto;
|
|
999
|
+
if (typeof cryptoLike?.randomUUID === "function") {
|
|
1000
|
+
return cryptoLike.randomUUID();
|
|
1001
|
+
}
|
|
1002
|
+
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
1003
|
+
}
|
|
1004
|
+
function forkTrace(svc, parentTrace) {
|
|
1005
|
+
if (parentTrace) {
|
|
1006
|
+
return {
|
|
1007
|
+
traceId: parentTrace.traceId,
|
|
1008
|
+
spanId: svc.tracer.newSpanId(),
|
|
1009
|
+
parentSpanId: parentTrace.spanId,
|
|
1010
|
+
sampled: parentTrace.sampled
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
if (svc.seed) return { ...svc.seed };
|
|
1014
|
+
return { traceId: svc.tracer.newTraceId(), spanId: svc.tracer.newSpanId(), sampled: true };
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// src/core/runtime/engine/opcodes.ts
|
|
1018
|
+
var REF_INDEX_BITS = 20;
|
|
1019
|
+
var REF_INDEX_MASK = (1 << REF_INDEX_BITS) - 1;
|
|
1020
|
+
var REF_GENERATION_SHIFT = REF_INDEX_BITS;
|
|
1021
|
+
var REF_GENERATION_MASK = (1 << 32 - REF_INDEX_BITS) - 1;
|
|
1022
|
+
function encodeRef(index, generation) {
|
|
1023
|
+
return ((generation & REF_GENERATION_MASK) << REF_GENERATION_SHIFT | index & REF_INDEX_MASK) >>> 0;
|
|
1024
|
+
}
|
|
1025
|
+
function decodeRef(ref) {
|
|
1026
|
+
return {
|
|
1027
|
+
index: ref & REF_INDEX_MASK,
|
|
1028
|
+
generation: ref >>> REF_GENERATION_SHIFT & REF_GENERATION_MASK
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
var HostRegistry = class {
|
|
1032
|
+
slots = [{ value: void 0, generation: 0, occupied: false }];
|
|
1033
|
+
free = [];
|
|
1034
|
+
live = 0;
|
|
1035
|
+
allocated = 0;
|
|
1036
|
+
released = 0;
|
|
1037
|
+
reused = 0;
|
|
1038
|
+
staleReads = 0;
|
|
1039
|
+
register(value) {
|
|
1040
|
+
const index = this.free.pop() ?? this.slots.length;
|
|
1041
|
+
let slot = this.slots[index];
|
|
1042
|
+
if (slot) {
|
|
1043
|
+
this.reused += 1;
|
|
1044
|
+
slot.value = value;
|
|
1045
|
+
slot.occupied = true;
|
|
1046
|
+
slot.generation = slot.generation + 1 & REF_GENERATION_MASK || 1;
|
|
1047
|
+
} else {
|
|
1048
|
+
slot = { value, generation: 1, occupied: true };
|
|
1049
|
+
this.slots[index] = slot;
|
|
1050
|
+
}
|
|
1051
|
+
this.live += 1;
|
|
1052
|
+
this.allocated += 1;
|
|
1053
|
+
return encodeRef(index, slot.generation);
|
|
1054
|
+
}
|
|
1055
|
+
get(ref) {
|
|
1056
|
+
const { index, generation } = decodeRef(ref);
|
|
1057
|
+
const slot = this.slots[index];
|
|
1058
|
+
if (!slot || !slot.occupied || slot.generation !== generation) {
|
|
1059
|
+
this.staleReads += 1;
|
|
1060
|
+
throw new Error(`Missing or stale host registry ref ${ref}`);
|
|
1061
|
+
}
|
|
1062
|
+
return slot.value;
|
|
1063
|
+
}
|
|
1064
|
+
set(ref, value) {
|
|
1065
|
+
const { index, generation } = decodeRef(ref);
|
|
1066
|
+
const slot = this.slots[index];
|
|
1067
|
+
if (!slot || !slot.occupied || slot.generation !== generation) {
|
|
1068
|
+
this.staleReads += 1;
|
|
1069
|
+
throw new Error(`Missing or stale host registry ref ${ref}`);
|
|
1070
|
+
}
|
|
1071
|
+
slot.value = value;
|
|
1072
|
+
}
|
|
1073
|
+
delete(ref) {
|
|
1074
|
+
const { index, generation } = decodeRef(ref);
|
|
1075
|
+
const slot = this.slots[index];
|
|
1076
|
+
if (!slot || !slot.occupied || slot.generation !== generation) return;
|
|
1077
|
+
slot.value = void 0;
|
|
1078
|
+
slot.occupied = false;
|
|
1079
|
+
this.live = Math.max(0, this.live - 1);
|
|
1080
|
+
this.released += 1;
|
|
1081
|
+
this.free.push(index);
|
|
1082
|
+
}
|
|
1083
|
+
clear() {
|
|
1084
|
+
for (let index = 1; index < this.slots.length; index++) {
|
|
1085
|
+
const slot = this.slots[index];
|
|
1086
|
+
if (!slot?.occupied) continue;
|
|
1087
|
+
slot.value = void 0;
|
|
1088
|
+
slot.occupied = false;
|
|
1089
|
+
this.free.push(index);
|
|
1090
|
+
this.released += 1;
|
|
1091
|
+
}
|
|
1092
|
+
this.live = 0;
|
|
1093
|
+
}
|
|
1094
|
+
size() {
|
|
1095
|
+
return this.live;
|
|
1096
|
+
}
|
|
1097
|
+
stats() {
|
|
1098
|
+
return {
|
|
1099
|
+
live: this.live,
|
|
1100
|
+
capacity: Math.max(0, this.slots.length - 1),
|
|
1101
|
+
allocated: this.allocated,
|
|
1102
|
+
released: this.released,
|
|
1103
|
+
reused: this.reused,
|
|
1104
|
+
staleReads: this.staleReads
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
var ProgramBuilder = class {
|
|
1109
|
+
nodes = [];
|
|
1110
|
+
registry = new HostRegistry();
|
|
1111
|
+
compile(effect) {
|
|
1112
|
+
const root = this.visit(effect);
|
|
1113
|
+
return {
|
|
1114
|
+
program: {
|
|
1115
|
+
version: 1,
|
|
1116
|
+
root,
|
|
1117
|
+
nodes: this.nodes
|
|
1118
|
+
},
|
|
1119
|
+
registry: this.registry
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
append(effect) {
|
|
1123
|
+
const previousNodes = this.nodes.length;
|
|
1124
|
+
const root = this.visit(effect);
|
|
1125
|
+
return {
|
|
1126
|
+
root,
|
|
1127
|
+
nodes: this.nodes.slice(previousNodes)
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
add(node) {
|
|
1131
|
+
const id = this.nodes.length;
|
|
1132
|
+
this.nodes.push(node);
|
|
1133
|
+
return id;
|
|
1134
|
+
}
|
|
1135
|
+
visit(effect) {
|
|
1136
|
+
const current = effect;
|
|
1137
|
+
switch (current._tag) {
|
|
1138
|
+
case "Succeed":
|
|
1139
|
+
return this.add({ tag: "Succeed", valueRef: this.registry.register(current.value) });
|
|
1140
|
+
case "Fail":
|
|
1141
|
+
return this.add({ tag: "Fail", errorRef: this.registry.register(current.error) });
|
|
1142
|
+
case "Sync":
|
|
1143
|
+
return this.add({ tag: "Sync", fnRef: this.registry.register(current.thunk) });
|
|
1144
|
+
case "Async":
|
|
1145
|
+
return this.add({ tag: "Async", registerRef: this.registry.register(current.register) });
|
|
1146
|
+
case "FlatMap":
|
|
1147
|
+
return this.add({
|
|
1148
|
+
tag: "FlatMap",
|
|
1149
|
+
first: this.visit(current.first),
|
|
1150
|
+
fnRef: this.registry.register(current.andThen)
|
|
1151
|
+
});
|
|
1152
|
+
case "Fold":
|
|
1153
|
+
return this.add({
|
|
1154
|
+
tag: "Fold",
|
|
1155
|
+
first: this.visit(current.first),
|
|
1156
|
+
onFailureRef: this.registry.register(current.onFailure),
|
|
1157
|
+
onSuccessRef: this.registry.register(current.onSuccess)
|
|
1158
|
+
});
|
|
1159
|
+
case "Fork": {
|
|
1160
|
+
const base = {
|
|
1161
|
+
tag: "Fork",
|
|
1162
|
+
effectRef: this.registry.register(current.effect)
|
|
1163
|
+
};
|
|
1164
|
+
return this.add(current.scopeId === void 0 ? base : { ...base, scopeId: current.scopeId });
|
|
1165
|
+
}
|
|
1166
|
+
case "HostAction": {
|
|
1167
|
+
const base = {
|
|
1168
|
+
tag: "HostAction",
|
|
1169
|
+
actionRef: this.registry.register(current.action)
|
|
1170
|
+
};
|
|
1171
|
+
return this.add(current.decode === void 0 ? base : { ...base, decodeRef: this.registry.register(current.decode) });
|
|
1172
|
+
}
|
|
1173
|
+
default:
|
|
1174
|
+
return this.add({ tag: "Fail", errorRef: this.registry.register(new Error(`Unknown Async opcode: ${current?._tag}`)) });
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
// src/core/runtime/engine/FiberHandleImpl.ts
|
|
1180
|
+
var EngineFiberHandle = class {
|
|
1181
|
+
constructor(id, runtime, onScheduledStep, onInterrupt, onJoiner, onQueued, onScheduleDropped, onScheduleRequest) {
|
|
1182
|
+
this.onScheduledStep = onScheduledStep;
|
|
1183
|
+
this.onInterrupt = onInterrupt;
|
|
1184
|
+
this.onJoiner = onJoiner;
|
|
1185
|
+
this.onQueued = onQueued;
|
|
1186
|
+
this.onScheduleDropped = onScheduleDropped;
|
|
1187
|
+
this.onScheduleRequest = onScheduleRequest;
|
|
1188
|
+
this.id = id;
|
|
1189
|
+
this.runtime = runtime;
|
|
1190
|
+
}
|
|
1191
|
+
onScheduledStep;
|
|
1192
|
+
onInterrupt;
|
|
1193
|
+
onJoiner;
|
|
1194
|
+
onQueued;
|
|
1195
|
+
onScheduleDropped;
|
|
1196
|
+
onScheduleRequest;
|
|
1197
|
+
id;
|
|
1198
|
+
runtime;
|
|
1199
|
+
fiberContext;
|
|
1200
|
+
name;
|
|
1201
|
+
scopeId;
|
|
1202
|
+
parentFiberId;
|
|
1203
|
+
lane;
|
|
1204
|
+
result = null;
|
|
1205
|
+
joiners = [];
|
|
1206
|
+
finalizers = [];
|
|
1207
|
+
finalizersDrained = false;
|
|
1208
|
+
internalStatus = "running";
|
|
1209
|
+
queued = false;
|
|
1210
|
+
status() {
|
|
1211
|
+
if (this.result == null) return "Running";
|
|
1212
|
+
if (this.result._tag === "Failure" && this.result.cause._tag === "Interrupt") return "Interrupted";
|
|
1213
|
+
return "Done";
|
|
1214
|
+
}
|
|
1215
|
+
engineStatus() {
|
|
1216
|
+
return this.internalStatus;
|
|
1217
|
+
}
|
|
1218
|
+
setEngineStatus(status) {
|
|
1219
|
+
this.internalStatus = status;
|
|
1220
|
+
}
|
|
1221
|
+
markDequeued() {
|
|
1222
|
+
this.queued = false;
|
|
1223
|
+
if (this.result == null && this.internalStatus === "queued") {
|
|
1224
|
+
this.internalStatus = "running";
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
join(cb) {
|
|
1228
|
+
if (this.result != null) cb(this.result);
|
|
1229
|
+
else {
|
|
1230
|
+
this.onJoiner?.(this.id);
|
|
1231
|
+
this.joiners.push(cb);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
interrupt() {
|
|
1235
|
+
if (this.result != null) return;
|
|
1236
|
+
this.onInterrupt(this.id, Cause.interrupt());
|
|
1237
|
+
}
|
|
1238
|
+
addFinalizer(f) {
|
|
1239
|
+
this.finalizers.push(f);
|
|
1240
|
+
}
|
|
1241
|
+
schedule(tag = "step") {
|
|
1242
|
+
if (this.result != null || this.queued) return;
|
|
1243
|
+
this.queued = true;
|
|
1244
|
+
this.internalStatus = "queued";
|
|
1245
|
+
this.onQueued?.(this.id);
|
|
1246
|
+
const label = `wasm-fiber#${this.id}.${tag}`;
|
|
1247
|
+
const result = this.onScheduleRequest ? this.onScheduleRequest(this.id, label) : this.scheduleWithRuntime(label);
|
|
1248
|
+
if (result === "dropped") {
|
|
1249
|
+
this.queued = false;
|
|
1250
|
+
this.onScheduleDropped?.(this.id, label);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
scheduleWithRuntime(label) {
|
|
1254
|
+
const lane = this.lane ?? this.runtime.lane;
|
|
1255
|
+
return this.runtime.scheduler.schedule(() => {
|
|
1256
|
+
this.markDequeued();
|
|
1257
|
+
if (this.result != null) return;
|
|
1258
|
+
this.onScheduledStep(this.id);
|
|
1259
|
+
}, lane ? laneTag(lane, label) : label);
|
|
1260
|
+
}
|
|
1261
|
+
emit(ev) {
|
|
1262
|
+
this.runtime.hooks.emit(ev, {
|
|
1263
|
+
fiberId: this.id,
|
|
1264
|
+
scopeId: this.scopeId,
|
|
1265
|
+
traceId: this.fiberContext?.trace?.traceId,
|
|
1266
|
+
spanId: this.fiberContext?.trace?.spanId
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
succeed(value) {
|
|
1270
|
+
this.complete(Exit.succeed(value));
|
|
1271
|
+
}
|
|
1272
|
+
fail(error) {
|
|
1273
|
+
this.complete(Exit.failCause(Cause.fail(error)));
|
|
1274
|
+
}
|
|
1275
|
+
die(defect) {
|
|
1276
|
+
this.complete(Exit.failCause(Cause.die(defect)));
|
|
1277
|
+
}
|
|
1278
|
+
interrupted() {
|
|
1279
|
+
this.complete(Exit.failCause(Cause.interrupt()));
|
|
1280
|
+
}
|
|
1281
|
+
complete(exit) {
|
|
1282
|
+
if (this.result != null) return;
|
|
1283
|
+
this.runFinalizersOnce(exit);
|
|
1284
|
+
this.result = exit;
|
|
1285
|
+
this.internalStatus = exit._tag === "Success" ? "done" : exit.cause._tag === "Interrupt" ? "interrupted" : "failed";
|
|
1286
|
+
const status = exit._tag === "Success" ? "success" : exit.cause._tag === "Interrupt" ? "interrupted" : "failure";
|
|
1287
|
+
this.emit({
|
|
1288
|
+
type: "fiber.end",
|
|
1289
|
+
fiberId: this.id,
|
|
1290
|
+
status,
|
|
1291
|
+
error: exit._tag === "Failure" ? exit.cause : void 0
|
|
1292
|
+
});
|
|
1293
|
+
for (const joiner of this.joiners) joiner(exit);
|
|
1294
|
+
this.joiners.length = 0;
|
|
1295
|
+
}
|
|
1296
|
+
runFinalizersOnce(exit) {
|
|
1297
|
+
if (this.finalizersDrained) return;
|
|
1298
|
+
this.finalizersDrained = true;
|
|
1299
|
+
while (this.finalizers.length > 0) {
|
|
1300
|
+
const finalizer = this.finalizers.pop();
|
|
1301
|
+
try {
|
|
1302
|
+
const eff = finalizer(exit);
|
|
1303
|
+
if (eff && typeof eff === "object" && "_tag" in eff) {
|
|
1304
|
+
this.runtime.fork(eff);
|
|
1305
|
+
}
|
|
1306
|
+
} catch {
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
|
|
1312
|
+
// src/core/runtime/engine/binaryAbi.ts
|
|
1313
|
+
var ABI_VERSION = 1;
|
|
1314
|
+
var EVENT_WORDS = 5;
|
|
1315
|
+
var NONE_U32 = 4294967295;
|
|
1316
|
+
var OpcodeTagCode = /* @__PURE__ */ ((OpcodeTagCode2) => {
|
|
1317
|
+
OpcodeTagCode2[OpcodeTagCode2["Succeed"] = 0] = "Succeed";
|
|
1318
|
+
OpcodeTagCode2[OpcodeTagCode2["Fail"] = 1] = "Fail";
|
|
1319
|
+
OpcodeTagCode2[OpcodeTagCode2["Sync"] = 2] = "Sync";
|
|
1320
|
+
OpcodeTagCode2[OpcodeTagCode2["Async"] = 3] = "Async";
|
|
1321
|
+
OpcodeTagCode2[OpcodeTagCode2["FlatMap"] = 4] = "FlatMap";
|
|
1322
|
+
OpcodeTagCode2[OpcodeTagCode2["Fold"] = 5] = "Fold";
|
|
1323
|
+
OpcodeTagCode2[OpcodeTagCode2["Fork"] = 6] = "Fork";
|
|
1324
|
+
OpcodeTagCode2[OpcodeTagCode2["HostAction"] = 7] = "HostAction";
|
|
1325
|
+
return OpcodeTagCode2;
|
|
1326
|
+
})(OpcodeTagCode || {});
|
|
1327
|
+
var EventKindCode = /* @__PURE__ */ ((EventKindCode2) => {
|
|
1328
|
+
EventKindCode2[EventKindCode2["Continue"] = 0] = "Continue";
|
|
1329
|
+
EventKindCode2[EventKindCode2["Done"] = 1] = "Done";
|
|
1330
|
+
EventKindCode2[EventKindCode2["Failed"] = 2] = "Failed";
|
|
1331
|
+
EventKindCode2[EventKindCode2["Interrupted"] = 3] = "Interrupted";
|
|
1332
|
+
EventKindCode2[EventKindCode2["InvokeSync"] = 4] = "InvokeSync";
|
|
1333
|
+
EventKindCode2[EventKindCode2["InvokeAsync"] = 5] = "InvokeAsync";
|
|
1334
|
+
EventKindCode2[EventKindCode2["InvokeFlatMap"] = 6] = "InvokeFlatMap";
|
|
1335
|
+
EventKindCode2[EventKindCode2["InvokeFoldFailure"] = 7] = "InvokeFoldFailure";
|
|
1336
|
+
EventKindCode2[EventKindCode2["InvokeFoldSuccess"] = 8] = "InvokeFoldSuccess";
|
|
1337
|
+
EventKindCode2[EventKindCode2["InvokeFork"] = 9] = "InvokeFork";
|
|
1338
|
+
EventKindCode2[EventKindCode2["InvokeHostAction"] = 10] = "InvokeHostAction";
|
|
1339
|
+
return EventKindCode2;
|
|
1340
|
+
})(EventKindCode || {});
|
|
1341
|
+
function encodeOpcodeProgram(program) {
|
|
1342
|
+
const out = new Uint32Array(3 + program.nodes.length * 4);
|
|
1343
|
+
out[0] = ABI_VERSION;
|
|
1344
|
+
out[1] = program.root >>> 0;
|
|
1345
|
+
out[2] = program.nodes.length >>> 0;
|
|
1346
|
+
writeNodes(out, 3, program.nodes);
|
|
1347
|
+
return out;
|
|
1348
|
+
}
|
|
1349
|
+
function encodeOpcodeNodes(nodes) {
|
|
1350
|
+
const out = new Uint32Array(1 + nodes.length * 4);
|
|
1351
|
+
out[0] = nodes.length >>> 0;
|
|
1352
|
+
writeNodes(out, 1, nodes);
|
|
1353
|
+
return out;
|
|
1354
|
+
}
|
|
1355
|
+
function writeNodes(out, offset, nodes) {
|
|
1356
|
+
let i = offset;
|
|
1357
|
+
for (const node of nodes) {
|
|
1358
|
+
switch (node.tag) {
|
|
1359
|
+
case "Succeed":
|
|
1360
|
+
out[i++] = 0 /* Succeed */;
|
|
1361
|
+
out[i++] = node.valueRef >>> 0;
|
|
1362
|
+
out[i++] = 0;
|
|
1363
|
+
out[i++] = 0;
|
|
1364
|
+
break;
|
|
1365
|
+
case "Fail":
|
|
1366
|
+
out[i++] = 1 /* Fail */;
|
|
1367
|
+
out[i++] = node.errorRef >>> 0;
|
|
1368
|
+
out[i++] = 0;
|
|
1369
|
+
out[i++] = 0;
|
|
1370
|
+
break;
|
|
1371
|
+
case "Sync":
|
|
1372
|
+
out[i++] = 2 /* Sync */;
|
|
1373
|
+
out[i++] = node.fnRef >>> 0;
|
|
1374
|
+
out[i++] = 0;
|
|
1375
|
+
out[i++] = 0;
|
|
1376
|
+
break;
|
|
1377
|
+
case "Async":
|
|
1378
|
+
out[i++] = 3 /* Async */;
|
|
1379
|
+
out[i++] = node.registerRef >>> 0;
|
|
1380
|
+
out[i++] = 0;
|
|
1381
|
+
out[i++] = 0;
|
|
1382
|
+
break;
|
|
1383
|
+
case "FlatMap":
|
|
1384
|
+
out[i++] = 4 /* FlatMap */;
|
|
1385
|
+
out[i++] = node.first >>> 0;
|
|
1386
|
+
out[i++] = node.fnRef >>> 0;
|
|
1387
|
+
out[i++] = 0;
|
|
1388
|
+
break;
|
|
1389
|
+
case "Fold":
|
|
1390
|
+
out[i++] = 5 /* Fold */;
|
|
1391
|
+
out[i++] = node.first >>> 0;
|
|
1392
|
+
out[i++] = node.onFailureRef >>> 0;
|
|
1393
|
+
out[i++] = node.onSuccessRef >>> 0;
|
|
1394
|
+
break;
|
|
1395
|
+
case "Fork":
|
|
1396
|
+
out[i++] = 6 /* Fork */;
|
|
1397
|
+
out[i++] = node.effectRef >>> 0;
|
|
1398
|
+
out[i++] = node.scopeId === void 0 ? NONE_U32 : node.scopeId >>> 0;
|
|
1399
|
+
out[i++] = 0;
|
|
1400
|
+
break;
|
|
1401
|
+
case "HostAction":
|
|
1402
|
+
out[i++] = 7 /* HostAction */;
|
|
1403
|
+
out[i++] = node.actionRef >>> 0;
|
|
1404
|
+
out[i++] = node.decodeRef === void 0 ? NONE_U32 : node.decodeRef >>> 0;
|
|
1405
|
+
out[i++] = 0;
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
function decodeEvent(words, offset = 0) {
|
|
1411
|
+
const kind = words[offset] >>> 0;
|
|
1412
|
+
const fiberId = words[offset + 1];
|
|
1413
|
+
const a = words[offset + 2] >>> 0;
|
|
1414
|
+
const b = words[offset + 3] >>> 0;
|
|
1415
|
+
const c = words[offset + 4] >>> 0;
|
|
1416
|
+
switch (kind) {
|
|
1417
|
+
case 0 /* Continue */:
|
|
1418
|
+
return { kind: "Continue", fiberId };
|
|
1419
|
+
case 1 /* Done */:
|
|
1420
|
+
return { kind: "Done", fiberId, valueRef: a };
|
|
1421
|
+
case 2 /* Failed */:
|
|
1422
|
+
return { kind: "Failed", fiberId, errorRef: a };
|
|
1423
|
+
case 3 /* Interrupted */:
|
|
1424
|
+
return { kind: "Interrupted", fiberId, reasonRef: a };
|
|
1425
|
+
case 4 /* InvokeSync */:
|
|
1426
|
+
return { kind: "InvokeSync", fiberId, fnRef: a };
|
|
1427
|
+
case 5 /* InvokeAsync */:
|
|
1428
|
+
return { kind: "InvokeAsync", fiberId, registerRef: a };
|
|
1429
|
+
case 6 /* InvokeFlatMap */:
|
|
1430
|
+
return { kind: "InvokeFlatMap", fiberId, fnRef: a, valueRef: b };
|
|
1431
|
+
case 7 /* InvokeFoldFailure */:
|
|
1432
|
+
return { kind: "InvokeFoldFailure", fiberId, fnRef: a, errorRef: b };
|
|
1433
|
+
case 8 /* InvokeFoldSuccess */:
|
|
1434
|
+
return { kind: "InvokeFoldSuccess", fiberId, fnRef: a, valueRef: b };
|
|
1435
|
+
case 9 /* InvokeFork */:
|
|
1436
|
+
return c === NONE_U32 || b === NONE_U32 ? { kind: "InvokeFork", fiberId, effectRef: a } : { kind: "InvokeFork", fiberId, effectRef: a, scopeId: b };
|
|
1437
|
+
case 10 /* InvokeHostAction */:
|
|
1438
|
+
return b === NONE_U32 ? { kind: "InvokeHostAction", fiberId, actionRef: a } : { kind: "InvokeHostAction", fiberId, actionRef: a, decodeRef: b };
|
|
1439
|
+
default:
|
|
1440
|
+
return { kind: "Failed", fiberId, errorRef: 0 };
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
function decodeEventBatch(words) {
|
|
1444
|
+
if (!words || words.length === 0) return [];
|
|
1445
|
+
const count = words[0] >>> 0;
|
|
1446
|
+
const events = [];
|
|
1447
|
+
const max = Math.min(count, Math.floor((words.length - 1) / EVENT_WORDS));
|
|
1448
|
+
for (let i = 0; i < max; i++) {
|
|
1449
|
+
events.push(decodeEvent(words, 1 + i * EVENT_WORDS));
|
|
1450
|
+
}
|
|
1451
|
+
return events;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// src/core/runtime/engine/bridge/WasmPackFiberBridge.ts
|
|
1455
|
+
var VM_METRIC_NAMES = [
|
|
1456
|
+
"started",
|
|
1457
|
+
"live",
|
|
1458
|
+
"running",
|
|
1459
|
+
"suspended",
|
|
1460
|
+
"completed",
|
|
1461
|
+
"failed",
|
|
1462
|
+
"interrupted",
|
|
1463
|
+
"boundaryCalls",
|
|
1464
|
+
"batchesEmitted",
|
|
1465
|
+
"eventsEmitted",
|
|
1466
|
+
"eventsPerBoundaryCall",
|
|
1467
|
+
"maxEventsPerBoundaryCall",
|
|
1468
|
+
"binaryPrograms",
|
|
1469
|
+
"jsonPrograms",
|
|
1470
|
+
"binaryPatches",
|
|
1471
|
+
"jsonPatches",
|
|
1472
|
+
"zeroCopyPrograms",
|
|
1473
|
+
"zeroCopyPatches",
|
|
1474
|
+
"zeroCopyEventBatches",
|
|
1475
|
+
"fiberSlabLive",
|
|
1476
|
+
"fiberSlabCapacity",
|
|
1477
|
+
"fiberSlabReused",
|
|
1478
|
+
"fiberSlabReleased",
|
|
1479
|
+
"fiberSlabStaleReads"
|
|
1480
|
+
];
|
|
1481
|
+
var WasmPackFiberBridge = class {
|
|
1482
|
+
kind = "wasm";
|
|
1483
|
+
supportsBinary;
|
|
1484
|
+
supportsZeroCopy;
|
|
1485
|
+
supportsNoJsonMetrics;
|
|
1486
|
+
vm;
|
|
1487
|
+
jsonEventCalls = 0;
|
|
1488
|
+
binaryEventCalls = 0;
|
|
1489
|
+
zeroCopyEventCalls = 0;
|
|
1490
|
+
eventsReceived = 0;
|
|
1491
|
+
maxEventsPerCall = 0;
|
|
1492
|
+
jsonPrograms = 0;
|
|
1493
|
+
binaryPrograms = 0;
|
|
1494
|
+
zeroCopyPrograms = 0;
|
|
1495
|
+
jsonPatches = 0;
|
|
1496
|
+
binaryPatches = 0;
|
|
1497
|
+
zeroCopyPatches = 0;
|
|
1498
|
+
constructor(modulePath) {
|
|
1499
|
+
const mod = loadWasmModule(modulePath);
|
|
1500
|
+
this.vm = new mod.BrassWasmVm();
|
|
1501
|
+
this.supportsBinary = typeof this.vm.create_fiber_bin === "function" && typeof this.vm.drive_batch_bin === "function" && typeof this.vm.provide_value_bin === "function" && typeof this.vm.provide_error_bin === "function" && typeof this.vm.provide_effect_bin === "function" && typeof this.vm.interrupt_bin === "function";
|
|
1502
|
+
this.supportsZeroCopy = typeof this.vm.memory === "function" && typeof this.vm.prepare_program_words === "function" && typeof this.vm.prepare_patch_words === "function" && typeof this.vm.create_fiber_from_program_words === "function" && typeof this.vm.drive_batch_ptr === "function" && typeof this.vm.event_batch_len === "function" && typeof this.vm.provide_value_ptr === "function" && typeof this.vm.provide_error_ptr === "function" && typeof this.vm.provide_effect_from_words === "function" && typeof this.vm.interrupt_ptr === "function";
|
|
1503
|
+
this.supportsNoJsonMetrics = typeof this.vm.metrics_snapshot_ptr === "function" && typeof this.vm.metrics_snapshot_len === "function" && typeof this.vm.memory === "function";
|
|
1504
|
+
this.assertStrictWasmHotPath();
|
|
1505
|
+
}
|
|
1506
|
+
createFiber(program) {
|
|
1507
|
+
const words = encodeOpcodeProgram(program);
|
|
1508
|
+
this.writeWords(this.vm.prepare_program_words(words.length), words);
|
|
1509
|
+
this.zeroCopyPrograms += 1;
|
|
1510
|
+
return this.vm.create_fiber_from_program_words(words.length);
|
|
1511
|
+
}
|
|
1512
|
+
poll(fiberId) {
|
|
1513
|
+
return this.driveBatch(fiberId, 1)[0] ?? { kind: "Continue", fiberId };
|
|
1514
|
+
}
|
|
1515
|
+
driveBatch(fiberId, budget) {
|
|
1516
|
+
return this.decodeZeroCopy(this.vm.drive_batch_ptr(fiberId, budget));
|
|
1517
|
+
}
|
|
1518
|
+
provideValue(fiberId, valueRef) {
|
|
1519
|
+
return this.provideValueBatch(fiberId, valueRef, 1)[0] ?? { kind: "Continue", fiberId };
|
|
1520
|
+
}
|
|
1521
|
+
provideValueBatch(fiberId, valueRef, budget) {
|
|
1522
|
+
return this.decodeZeroCopy(this.vm.provide_value_ptr(fiberId, valueRef, budget));
|
|
1523
|
+
}
|
|
1524
|
+
provideError(fiberId, errorRef) {
|
|
1525
|
+
return this.provideErrorBatch(fiberId, errorRef, 1)[0] ?? { kind: "Continue", fiberId };
|
|
1526
|
+
}
|
|
1527
|
+
provideErrorBatch(fiberId, errorRef, budget) {
|
|
1528
|
+
return this.decodeZeroCopy(this.vm.provide_error_ptr(fiberId, errorRef, budget));
|
|
1529
|
+
}
|
|
1530
|
+
provideEffect(fiberId, root, nodes) {
|
|
1531
|
+
return this.provideEffectBatch(fiberId, root, nodes, 1)[0] ?? { kind: "Continue", fiberId };
|
|
1532
|
+
}
|
|
1533
|
+
provideEffectBatch(fiberId, root, nodes, budget) {
|
|
1534
|
+
const words = encodeOpcodeNodes(nodes);
|
|
1535
|
+
this.writeWords(this.vm.prepare_patch_words(words.length), words);
|
|
1536
|
+
this.zeroCopyPatches += 1;
|
|
1537
|
+
return this.decodeZeroCopy(this.vm.provide_effect_from_words(fiberId, root, words.length, budget));
|
|
1538
|
+
}
|
|
1539
|
+
interrupt(fiberId, reasonRef) {
|
|
1540
|
+
return this.interruptBatch(fiberId, reasonRef, 1)[0] ?? { kind: "Interrupted", fiberId, reasonRef };
|
|
1541
|
+
}
|
|
1542
|
+
interruptBatch(fiberId, reasonRef, budget) {
|
|
1543
|
+
return this.decodeZeroCopy(this.vm.interrupt_ptr(fiberId, reasonRef, budget));
|
|
1544
|
+
}
|
|
1545
|
+
dropFiber(fiberId) {
|
|
1546
|
+
this.vm.drop_fiber(fiberId);
|
|
1547
|
+
}
|
|
1548
|
+
stats() {
|
|
1549
|
+
const wasmStats = this.readMetricsSnapshot();
|
|
1550
|
+
const eventCalls = this.binaryEventCalls + this.jsonEventCalls + this.zeroCopyEventCalls;
|
|
1551
|
+
return {
|
|
1552
|
+
...wasmStats,
|
|
1553
|
+
bridge: {
|
|
1554
|
+
supportsBinary: this.supportsBinary,
|
|
1555
|
+
supportsZeroCopy: this.supportsZeroCopy,
|
|
1556
|
+
supportsNoJsonMetrics: this.supportsNoJsonMetrics,
|
|
1557
|
+
zeroCopyEventCalls: this.zeroCopyEventCalls,
|
|
1558
|
+
binaryEventCalls: this.binaryEventCalls,
|
|
1559
|
+
jsonEventCalls: this.jsonEventCalls,
|
|
1560
|
+
eventsReceived: this.eventsReceived,
|
|
1561
|
+
eventsPerCall: eventCalls === 0 ? 0 : this.eventsReceived / eventCalls,
|
|
1562
|
+
maxEventsPerCall: this.maxEventsPerCall,
|
|
1563
|
+
zeroCopyPrograms: this.zeroCopyPrograms,
|
|
1564
|
+
binaryPrograms: this.binaryPrograms,
|
|
1565
|
+
jsonPrograms: this.jsonPrograms,
|
|
1566
|
+
zeroCopyPatches: this.zeroCopyPatches,
|
|
1567
|
+
binaryPatches: this.binaryPatches,
|
|
1568
|
+
jsonPatches: this.jsonPatches
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
assertStrictWasmHotPath() {
|
|
1573
|
+
const missing = [];
|
|
1574
|
+
const required = [
|
|
1575
|
+
"memory",
|
|
1576
|
+
"prepare_program_words",
|
|
1577
|
+
"prepare_patch_words",
|
|
1578
|
+
"create_fiber_from_program_words",
|
|
1579
|
+
"drive_batch_ptr",
|
|
1580
|
+
"event_batch_len",
|
|
1581
|
+
"provide_value_ptr",
|
|
1582
|
+
"provide_error_ptr",
|
|
1583
|
+
"provide_effect_from_words",
|
|
1584
|
+
"interrupt_ptr",
|
|
1585
|
+
"metrics_snapshot_ptr",
|
|
1586
|
+
"metrics_snapshot_len"
|
|
1587
|
+
];
|
|
1588
|
+
for (const name of required) {
|
|
1589
|
+
if (typeof this.vm[name] !== "function") missing.push(name);
|
|
1590
|
+
}
|
|
1591
|
+
if (missing.length > 0) {
|
|
1592
|
+
throw new Error([
|
|
1593
|
+
"engine='wasm' requires the strict Rust/WASM hot path; TS/JSON/binary fallbacks are disabled.",
|
|
1594
|
+
"Run `npm run build:wasm` from the phase-4+ Rust sources and make sure wasm/pkg is current.",
|
|
1595
|
+
`Missing WASM exports: ${missing.join(", ")}`
|
|
1596
|
+
].join("\n"));
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
decodeZeroCopy(ptr) {
|
|
1600
|
+
this.zeroCopyEventCalls += 1;
|
|
1601
|
+
const len = this.vm.event_batch_len?.() ?? 0;
|
|
1602
|
+
const words = this.readU32(ptr, len);
|
|
1603
|
+
const events = decodeEventBatch(words);
|
|
1604
|
+
this.eventsReceived += events.length;
|
|
1605
|
+
this.maxEventsPerCall = Math.max(this.maxEventsPerCall, events.length);
|
|
1606
|
+
return events;
|
|
1607
|
+
}
|
|
1608
|
+
decodeBinary(words) {
|
|
1609
|
+
this.binaryEventCalls += 1;
|
|
1610
|
+
const events = decodeEventBatch(words);
|
|
1611
|
+
this.eventsReceived += events.length;
|
|
1612
|
+
this.maxEventsPerCall = Math.max(this.maxEventsPerCall, events.length);
|
|
1613
|
+
return events;
|
|
1614
|
+
}
|
|
1615
|
+
decodeJson(json) {
|
|
1616
|
+
this.jsonEventCalls += 1;
|
|
1617
|
+
this.eventsReceived += 1;
|
|
1618
|
+
this.maxEventsPerCall = Math.max(this.maxEventsPerCall, 1);
|
|
1619
|
+
return JSON.parse(json);
|
|
1620
|
+
}
|
|
1621
|
+
memory() {
|
|
1622
|
+
const memory = this.vm.memory?.();
|
|
1623
|
+
if (!memory?.buffer) throw new Error("brass-runtime WASM memory is not available");
|
|
1624
|
+
return memory;
|
|
1625
|
+
}
|
|
1626
|
+
readU32(ptr, len) {
|
|
1627
|
+
if (ptr === 0 || len === 0) return new Uint32Array(0);
|
|
1628
|
+
return new Uint32Array(this.memory().buffer, ptr, len);
|
|
1629
|
+
}
|
|
1630
|
+
readF64(ptr, len) {
|
|
1631
|
+
if (ptr === 0 || len === 0) return new Float64Array(0);
|
|
1632
|
+
return new Float64Array(this.memory().buffer, ptr, len);
|
|
1633
|
+
}
|
|
1634
|
+
writeWords(ptr, words) {
|
|
1635
|
+
if (ptr === 0 && words.length > 0) throw new Error("brass-runtime WASM returned a null word pointer");
|
|
1636
|
+
new Uint32Array(this.memory().buffer, ptr, words.length).set(words);
|
|
1637
|
+
}
|
|
1638
|
+
readMetricsSnapshot() {
|
|
1639
|
+
const ptr = this.vm.metrics_snapshot_ptr?.() ?? 0;
|
|
1640
|
+
const len = this.vm.metrics_snapshot_len?.() ?? 0;
|
|
1641
|
+
const view = this.readF64(ptr, len);
|
|
1642
|
+
const out = {};
|
|
1643
|
+
for (let i = 0; i < Math.min(view.length, VM_METRIC_NAMES.length); i++) {
|
|
1644
|
+
out[VM_METRIC_NAMES[i]] = view[i];
|
|
1645
|
+
}
|
|
1646
|
+
return out;
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
function loadWasmModule(modulePath) {
|
|
1650
|
+
const mod = resolveWasmModule({ modulePath });
|
|
1651
|
+
if (mod) return mod;
|
|
1652
|
+
const errors = wasmModuleResolutionErrors();
|
|
1653
|
+
throw new Error([
|
|
1654
|
+
"engine='wasm' could not load wasm/pkg/brass_runtime_wasm_engine.js.",
|
|
1655
|
+
"Run `npm run build:wasm` first and make sure wasm/pkg is present in the package.",
|
|
1656
|
+
"For Node 18 + webpack, keep brass-runtime's wasm/pkg files available at runtime or externalize brass-runtime.",
|
|
1657
|
+
...errors.map((error) => `- ${error}`)
|
|
1658
|
+
].join("\n"));
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// src/core/runtime/engine/bridge/WasmFiberRegistryBridge.ts
|
|
1662
|
+
var FIBER_STATE_QUEUED = 0;
|
|
1663
|
+
var FIBER_STATE_RUNNING = 1;
|
|
1664
|
+
var FIBER_STATE_SUSPENDED = 2;
|
|
1665
|
+
var FIBER_STATE_DONE = 3;
|
|
1666
|
+
var FIBER_STATE_FAILED = 4;
|
|
1667
|
+
var FIBER_STATE_INTERRUPTED = 5;
|
|
1668
|
+
var WasmFiberRegistryBridge = class {
|
|
1669
|
+
registry;
|
|
1670
|
+
constructor() {
|
|
1671
|
+
const mod = resolveWasmModule();
|
|
1672
|
+
const Ctor = mod?.BrassWasmFiberRegistry;
|
|
1673
|
+
if (!Ctor) {
|
|
1674
|
+
throw new Error("brass-runtime wasm fiber registry is not available. Run npm run build:wasm first.");
|
|
1675
|
+
}
|
|
1676
|
+
this.registry = new Ctor();
|
|
1677
|
+
}
|
|
1678
|
+
registerFiber(fiberId, parentId, scopeId) {
|
|
1679
|
+
this.registry.register_fiber(fiberId, parentId ?? 0, scopeId ?? 0, Date.now());
|
|
1680
|
+
}
|
|
1681
|
+
markQueued(fiberId) {
|
|
1682
|
+
this.registry.mark_queued(fiberId, Date.now());
|
|
1683
|
+
}
|
|
1684
|
+
markRunning(fiberId) {
|
|
1685
|
+
this.registry.mark_running(fiberId, Date.now());
|
|
1686
|
+
}
|
|
1687
|
+
markSuspended(fiberId) {
|
|
1688
|
+
this.registry.mark_suspended(fiberId, Date.now());
|
|
1689
|
+
}
|
|
1690
|
+
markDone(fiberId, status) {
|
|
1691
|
+
return this.registry.mark_done(fiberId, statusToCode(status), Date.now());
|
|
1692
|
+
}
|
|
1693
|
+
dropFiber(fiberId) {
|
|
1694
|
+
this.registry.drop_fiber(fiberId);
|
|
1695
|
+
}
|
|
1696
|
+
addJoiner(fiberId) {
|
|
1697
|
+
this.registry.add_joiner(fiberId);
|
|
1698
|
+
}
|
|
1699
|
+
wake(fiberId) {
|
|
1700
|
+
return this.registry.wake(fiberId);
|
|
1701
|
+
}
|
|
1702
|
+
drainWakeup() {
|
|
1703
|
+
const id = this.registry.drain_wakeup();
|
|
1704
|
+
return id === 0 ? void 0 : id;
|
|
1705
|
+
}
|
|
1706
|
+
drainWakeups() {
|
|
1707
|
+
const ids = [];
|
|
1708
|
+
for (; ; ) {
|
|
1709
|
+
const id = this.drainWakeup();
|
|
1710
|
+
if (id === void 0) return ids;
|
|
1711
|
+
ids.push(id);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
wakeQueueLength() {
|
|
1715
|
+
return this.registry.wake_queue_len();
|
|
1716
|
+
}
|
|
1717
|
+
stateOf(fiberId) {
|
|
1718
|
+
const code = this.registry.state_of(fiberId);
|
|
1719
|
+
if (code === 4294967295) return "missing";
|
|
1720
|
+
return codeToStatus(code);
|
|
1721
|
+
}
|
|
1722
|
+
stats() {
|
|
1723
|
+
return JSON.parse(this.registry.stats_json());
|
|
1724
|
+
}
|
|
1725
|
+
};
|
|
1726
|
+
function statusToCode(status) {
|
|
1727
|
+
switch (status) {
|
|
1728
|
+
case "queued":
|
|
1729
|
+
return FIBER_STATE_QUEUED;
|
|
1730
|
+
case "running":
|
|
1731
|
+
return FIBER_STATE_RUNNING;
|
|
1732
|
+
case "suspended":
|
|
1733
|
+
return FIBER_STATE_SUSPENDED;
|
|
1734
|
+
case "done":
|
|
1735
|
+
return FIBER_STATE_DONE;
|
|
1736
|
+
case "failed":
|
|
1737
|
+
return FIBER_STATE_FAILED;
|
|
1738
|
+
case "interrupted":
|
|
1739
|
+
return FIBER_STATE_INTERRUPTED;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function codeToStatus(code) {
|
|
1743
|
+
switch (code) {
|
|
1744
|
+
case FIBER_STATE_QUEUED:
|
|
1745
|
+
return "queued";
|
|
1746
|
+
case FIBER_STATE_RUNNING:
|
|
1747
|
+
return "running";
|
|
1748
|
+
case FIBER_STATE_SUSPENDED:
|
|
1749
|
+
return "suspended";
|
|
1750
|
+
case FIBER_STATE_DONE:
|
|
1751
|
+
return "done";
|
|
1752
|
+
case FIBER_STATE_FAILED:
|
|
1753
|
+
return "failed";
|
|
1754
|
+
case FIBER_STATE_INTERRUPTED:
|
|
1755
|
+
return "interrupted";
|
|
1756
|
+
default:
|
|
1757
|
+
return "missing";
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// src/core/runtime/engine/bridge/WasmFiberReadyQueueBridge.ts
|
|
1762
|
+
var POLICY_MICRO = 0;
|
|
1763
|
+
var POLICY_MACRO = 1;
|
|
1764
|
+
var POLICY_DROPPED = 3;
|
|
1765
|
+
var DEFAULT_FLUSH_BUDGET = 2048;
|
|
1766
|
+
var DEFAULT_LANE_CAPACITY2 = 1024;
|
|
1767
|
+
var DEFAULT_LANE_BUDGET2 = 64;
|
|
1768
|
+
var DEFAULT_MAX_LANES2 = 256;
|
|
1769
|
+
function resolveWasmReadyQueue() {
|
|
1770
|
+
const mod = resolveWasmModule();
|
|
1771
|
+
return mod?.BrassWasmFiberReadyQueue ?? null;
|
|
1772
|
+
}
|
|
1773
|
+
function decodePolicy(policy) {
|
|
1774
|
+
switch (policy) {
|
|
1775
|
+
case POLICY_MICRO:
|
|
1776
|
+
return "micro";
|
|
1777
|
+
case POLICY_MACRO:
|
|
1778
|
+
return "macro";
|
|
1779
|
+
case POLICY_DROPPED:
|
|
1780
|
+
return "dropped";
|
|
1781
|
+
default:
|
|
1782
|
+
return "none";
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
function makeFiberReadyQueue(options = {}) {
|
|
1786
|
+
const requested = options.engine ?? "ts";
|
|
1787
|
+
if (requested === "ts") return new TsFiberReadyQueue(options);
|
|
1788
|
+
if (requested === "wasm") {
|
|
1789
|
+
const Ctor = resolveWasmReadyQueue();
|
|
1790
|
+
if (!Ctor) throw new Error("brass-runtime wasm fiber ready queue is not available. Run npm run build:wasm first.");
|
|
1791
|
+
return new WasmFiberReadyQueue(Ctor, options);
|
|
1792
|
+
}
|
|
1793
|
+
throw new Error(`brass-runtime fiber ready queue engine must be 'ts' or 'wasm'; received '${String(requested)}'`);
|
|
1794
|
+
}
|
|
1795
|
+
var WasmFiberReadyQueue = class {
|
|
1796
|
+
engine = "wasm";
|
|
1797
|
+
queue;
|
|
1798
|
+
laneCache = /* @__PURE__ */ new Map();
|
|
1799
|
+
constructor(Ctor, options) {
|
|
1800
|
+
this.queue = new Ctor(
|
|
1801
|
+
options.flushBudget ?? DEFAULT_FLUSH_BUDGET,
|
|
1802
|
+
options.microThreshold ?? DEFAULT_FLUSH_BUDGET * 2,
|
|
1803
|
+
options.laneCapacity ?? DEFAULT_LANE_CAPACITY2,
|
|
1804
|
+
options.laneBudget ?? DEFAULT_LANE_BUDGET2,
|
|
1805
|
+
options.maxLanes ?? DEFAULT_MAX_LANES2
|
|
1806
|
+
);
|
|
1807
|
+
if (typeof this.queue.intern_lane !== "function" || typeof this.queue.enqueue_fiber_lane !== "function") {
|
|
1808
|
+
throw new Error("brass-runtime wasm fiber ready queue requires laneId/interning exports; TS/string fallback is disabled");
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
enqueue(fiberId, tag) {
|
|
1812
|
+
const laneKey = inferLane2(tag);
|
|
1813
|
+
let laneId = this.laneCache.get(laneKey);
|
|
1814
|
+
if (laneId === void 0) {
|
|
1815
|
+
laneId = this.queue.intern_lane(laneKey);
|
|
1816
|
+
this.laneCache.set(laneKey, laneId);
|
|
1817
|
+
}
|
|
1818
|
+
return decodePolicy(this.queue.enqueue_fiber_lane(fiberId, laneId));
|
|
1819
|
+
}
|
|
1820
|
+
beginFlush() {
|
|
1821
|
+
return this.queue.begin_flush();
|
|
1822
|
+
}
|
|
1823
|
+
shift() {
|
|
1824
|
+
const fiberId = this.queue.shift_fiber();
|
|
1825
|
+
return fiberId === 0 ? void 0 : fiberId;
|
|
1826
|
+
}
|
|
1827
|
+
endFlush(ran) {
|
|
1828
|
+
return decodePolicy(this.queue.end_flush(ran));
|
|
1829
|
+
}
|
|
1830
|
+
len() {
|
|
1831
|
+
return this.queue.len();
|
|
1832
|
+
}
|
|
1833
|
+
clear() {
|
|
1834
|
+
this.queue.clear();
|
|
1835
|
+
}
|
|
1836
|
+
stats() {
|
|
1837
|
+
return {
|
|
1838
|
+
engine: this.engine,
|
|
1839
|
+
fallbackUsed: false,
|
|
1840
|
+
data: JSON.parse(this.queue.stats_json())
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
var TsFiberReadyQueue = class {
|
|
1845
|
+
constructor(options) {
|
|
1846
|
+
this.options = options;
|
|
1847
|
+
}
|
|
1848
|
+
options;
|
|
1849
|
+
engine = "ts";
|
|
1850
|
+
lanes = /* @__PURE__ */ new Map();
|
|
1851
|
+
laneOrder = [];
|
|
1852
|
+
rrIndex = 0;
|
|
1853
|
+
rrRemaining = 0;
|
|
1854
|
+
phase = "idle";
|
|
1855
|
+
totalLen = 0;
|
|
1856
|
+
scheduledFlushes = 0;
|
|
1857
|
+
completedFlushes = 0;
|
|
1858
|
+
enqueuedFibers = 0;
|
|
1859
|
+
executedFibers = 0;
|
|
1860
|
+
droppedFibers = 0;
|
|
1861
|
+
yieldedByBudget = 0;
|
|
1862
|
+
enqueue(fiberId, tag) {
|
|
1863
|
+
const lane = this.getOrCreateLane(inferLane2(tag));
|
|
1864
|
+
this.enqueuedFibers += 1;
|
|
1865
|
+
lane.enqueuedFibers += 1;
|
|
1866
|
+
const capacity = this.options.laneCapacity ?? DEFAULT_LANE_CAPACITY2;
|
|
1867
|
+
if (lane.len >= capacity) {
|
|
1868
|
+
this.droppedFibers += 1;
|
|
1869
|
+
lane.droppedFibers += 1;
|
|
1870
|
+
return "dropped";
|
|
1871
|
+
}
|
|
1872
|
+
lane.queue[(lane.head + lane.len) % capacity] = fiberId;
|
|
1873
|
+
lane.len += 1;
|
|
1874
|
+
this.totalLen += 1;
|
|
1875
|
+
if (this.phase !== "idle") return "none";
|
|
1876
|
+
this.phase = "scheduled";
|
|
1877
|
+
this.scheduledFlushes += 1;
|
|
1878
|
+
return this.totalLen > (this.options.microThreshold ?? DEFAULT_FLUSH_BUDGET * 2) ? "macro" : "micro";
|
|
1879
|
+
}
|
|
1880
|
+
beginFlush() {
|
|
1881
|
+
if (this.phase === "flushing") return 0;
|
|
1882
|
+
if (this.totalLen === 0) {
|
|
1883
|
+
this.phase = "idle";
|
|
1884
|
+
return 0;
|
|
1885
|
+
}
|
|
1886
|
+
this.phase = "flushing";
|
|
1887
|
+
return Math.min(this.options.flushBudget ?? DEFAULT_FLUSH_BUDGET, this.totalLen);
|
|
1888
|
+
}
|
|
1889
|
+
shift() {
|
|
1890
|
+
const n = this.laneOrder.length;
|
|
1891
|
+
if (n === 0) return void 0;
|
|
1892
|
+
if (this.rrRemaining > 0) {
|
|
1893
|
+
const idx = (this.rrIndex + n - 1) % n;
|
|
1894
|
+
const lane = this.lanes.get(this.laneOrder[idx]);
|
|
1895
|
+
const fiberId = lane ? this.shiftLane(lane) : void 0;
|
|
1896
|
+
if (fiberId !== void 0) {
|
|
1897
|
+
this.rrRemaining -= 1;
|
|
1898
|
+
return fiberId;
|
|
1899
|
+
}
|
|
1900
|
+
this.rrRemaining = 0;
|
|
1901
|
+
}
|
|
1902
|
+
for (let scanned = 0; scanned < n; scanned++) {
|
|
1903
|
+
const idx = this.rrIndex % n;
|
|
1904
|
+
this.rrIndex = (idx + 1) % n;
|
|
1905
|
+
const lane = this.lanes.get(this.laneOrder[idx]);
|
|
1906
|
+
if (!lane || lane.len === 0) continue;
|
|
1907
|
+
this.rrRemaining = Math.max(0, (this.options.laneBudget ?? DEFAULT_LANE_BUDGET2) - 1);
|
|
1908
|
+
return this.shiftLane(lane);
|
|
1909
|
+
}
|
|
1910
|
+
return void 0;
|
|
1911
|
+
}
|
|
1912
|
+
endFlush(ran) {
|
|
1913
|
+
this.completedFlushes += 1;
|
|
1914
|
+
if (this.totalLen === 0) {
|
|
1915
|
+
this.phase = "idle";
|
|
1916
|
+
return "none";
|
|
1917
|
+
}
|
|
1918
|
+
this.phase = "scheduled";
|
|
1919
|
+
this.scheduledFlushes += 1;
|
|
1920
|
+
if (ran >= (this.options.flushBudget ?? DEFAULT_FLUSH_BUDGET)) {
|
|
1921
|
+
this.yieldedByBudget += 1;
|
|
1922
|
+
return "macro";
|
|
1923
|
+
}
|
|
1924
|
+
return this.totalLen > (this.options.microThreshold ?? DEFAULT_FLUSH_BUDGET * 2) ? "macro" : "micro";
|
|
1925
|
+
}
|
|
1926
|
+
len() {
|
|
1927
|
+
return this.totalLen;
|
|
1928
|
+
}
|
|
1929
|
+
clear() {
|
|
1930
|
+
for (const lane of this.lanes.values()) {
|
|
1931
|
+
lane.queue.length = 0;
|
|
1932
|
+
lane.head = 0;
|
|
1933
|
+
lane.len = 0;
|
|
1934
|
+
}
|
|
1935
|
+
this.totalLen = 0;
|
|
1936
|
+
this.phase = "idle";
|
|
1937
|
+
this.rrIndex = 0;
|
|
1938
|
+
this.rrRemaining = 0;
|
|
1939
|
+
}
|
|
1940
|
+
stats() {
|
|
1941
|
+
const lanes = Array.from(this.lanes.values()).map((lane) => ({
|
|
1942
|
+
key: lane.key,
|
|
1943
|
+
len: lane.len,
|
|
1944
|
+
capacity: this.options.laneCapacity ?? DEFAULT_LANE_CAPACITY2,
|
|
1945
|
+
enqueuedFibers: lane.enqueuedFibers,
|
|
1946
|
+
executedFibers: lane.executedFibers,
|
|
1947
|
+
droppedFibers: lane.droppedFibers
|
|
1948
|
+
}));
|
|
1949
|
+
return {
|
|
1950
|
+
engine: this.engine,
|
|
1951
|
+
fallbackUsed: false,
|
|
1952
|
+
data: {
|
|
1953
|
+
phase: this.phase,
|
|
1954
|
+
len: this.totalLen,
|
|
1955
|
+
scheduledFlushes: this.scheduledFlushes,
|
|
1956
|
+
completedFlushes: this.completedFlushes,
|
|
1957
|
+
enqueuedFibers: this.enqueuedFibers,
|
|
1958
|
+
executedFibers: this.executedFibers,
|
|
1959
|
+
droppedFibers: this.droppedFibers,
|
|
1960
|
+
yieldedByBudget: this.yieldedByBudget,
|
|
1961
|
+
lanes
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
getOrCreateLane(key) {
|
|
1966
|
+
const existing = this.lanes.get(key);
|
|
1967
|
+
if (existing) return existing;
|
|
1968
|
+
const laneKey = this.lanes.size >= (this.options.maxLanes ?? DEFAULT_MAX_LANES2) ? "overflow" : key;
|
|
1969
|
+
const overflow = this.lanes.get(laneKey);
|
|
1970
|
+
if (overflow) return overflow;
|
|
1971
|
+
const lane = { key: laneKey, queue: [], head: 0, len: 0, enqueuedFibers: 0, executedFibers: 0, droppedFibers: 0 };
|
|
1972
|
+
this.lanes.set(laneKey, lane);
|
|
1973
|
+
this.laneOrder.push(laneKey);
|
|
1974
|
+
return lane;
|
|
1975
|
+
}
|
|
1976
|
+
shiftLane(lane) {
|
|
1977
|
+
if (lane.len === 0) return void 0;
|
|
1978
|
+
const capacity = this.options.laneCapacity ?? DEFAULT_LANE_CAPACITY2;
|
|
1979
|
+
const fiberId = lane.queue[lane.head];
|
|
1980
|
+
lane.queue[lane.head] = 0;
|
|
1981
|
+
lane.head = (lane.head + 1) % capacity;
|
|
1982
|
+
lane.len -= 1;
|
|
1983
|
+
this.totalLen -= 1;
|
|
1984
|
+
this.executedFibers += 1;
|
|
1985
|
+
lane.executedFibers += 1;
|
|
1986
|
+
return fiberId;
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
function inferLane2(tag) {
|
|
1990
|
+
const explicit = extractTaggedLane2(tag, "lane:");
|
|
1991
|
+
if (explicit) return sanitizeLaneKey(explicit);
|
|
1992
|
+
const caller = extractTaggedLane2(tag, "caller:");
|
|
1993
|
+
if (caller) return sanitizeLaneKey(caller);
|
|
1994
|
+
const firstSep = firstSeparatorIndex2(tag);
|
|
1995
|
+
const first = firstSep < 0 ? tag : tag.slice(0, firstSep);
|
|
1996
|
+
return sanitizeLaneKey(first || "anonymous");
|
|
1997
|
+
}
|
|
1998
|
+
function extractTaggedLane2(tag, prefix) {
|
|
1999
|
+
if (!tag.startsWith(prefix)) return void 0;
|
|
2000
|
+
const end2 = tag.indexOf("|", prefix.length);
|
|
2001
|
+
if (end2 < 0) return void 0;
|
|
2002
|
+
const value = tag.slice(prefix.length, end2);
|
|
2003
|
+
return value.length > 0 ? value : void 0;
|
|
2004
|
+
}
|
|
2005
|
+
function firstSeparatorIndex2(value) {
|
|
2006
|
+
let best = -1;
|
|
2007
|
+
for (const sep of [".", "#", "/"]) {
|
|
2008
|
+
const idx = value.indexOf(sep);
|
|
2009
|
+
if (idx >= 0 && (best < 0 || idx < best)) best = idx;
|
|
2010
|
+
}
|
|
2011
|
+
return best;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// src/core/runtime/engine/bridge/WasmTimerWheelBridge.ts
|
|
2015
|
+
var WasmTimerWheelBridge = class {
|
|
2016
|
+
wheel;
|
|
2017
|
+
timer;
|
|
2018
|
+
onExpired;
|
|
2019
|
+
constructor(Ctor, options) {
|
|
2020
|
+
this.wheel = new Ctor(toU64(options.tickMs ?? 10), options.bucketCount ?? 4096);
|
|
2021
|
+
this.onExpired = options.onExpired;
|
|
2022
|
+
}
|
|
2023
|
+
schedule(subjectId, kind, deadlineMs) {
|
|
2024
|
+
const id = this.wheel.schedule_deadline(subjectId, kind, toU64(deadlineMs));
|
|
2025
|
+
this.schedulePump();
|
|
2026
|
+
return id;
|
|
2027
|
+
}
|
|
2028
|
+
cancel(timerId) {
|
|
2029
|
+
if (timerId === void 0) return;
|
|
2030
|
+
this.wheel.cancel(timerId);
|
|
2031
|
+
this.schedulePump();
|
|
2032
|
+
}
|
|
2033
|
+
flush(nowMs = Date.now()) {
|
|
2034
|
+
const ptr = this.wheel.advance_time(toU64(nowMs));
|
|
2035
|
+
const events = this.readEvents(ptr, this.wheel.expired_len());
|
|
2036
|
+
if (events.length > 0) this.onExpired(events);
|
|
2037
|
+
this.schedulePump();
|
|
2038
|
+
}
|
|
2039
|
+
dispose() {
|
|
2040
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
2041
|
+
this.timer = void 0;
|
|
2042
|
+
}
|
|
2043
|
+
stats() {
|
|
2044
|
+
return {
|
|
2045
|
+
live: this.wheel.metric_u64(0),
|
|
2046
|
+
scheduled: this.wheel.metric_u64(1),
|
|
2047
|
+
canceled: this.wheel.metric_u64(2),
|
|
2048
|
+
expired: this.wheel.metric_u64(3),
|
|
2049
|
+
buckets: this.wheel.metric_u64(4)
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
schedulePump() {
|
|
2053
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
2054
|
+
this.timer = void 0;
|
|
2055
|
+
const next = this.wheel.next_deadline_ms();
|
|
2056
|
+
if (!Number.isFinite(next) || next < 0) return;
|
|
2057
|
+
const delay = Math.max(0, Math.min(2 ** 31 - 1, Math.floor(next - Date.now())));
|
|
2058
|
+
this.timer = setTimeout(() => {
|
|
2059
|
+
this.timer = void 0;
|
|
2060
|
+
this.flush();
|
|
2061
|
+
}, delay);
|
|
2062
|
+
if (typeof this.timer.unref === "function") this.timer.unref();
|
|
2063
|
+
}
|
|
2064
|
+
readEvents(ptr, len) {
|
|
2065
|
+
if (ptr === 0 || len <= 1) return [];
|
|
2066
|
+
const words = new Uint32Array(this.wheel.memory().buffer, ptr, len);
|
|
2067
|
+
const count = words[0] >>> 0;
|
|
2068
|
+
const out = [];
|
|
2069
|
+
for (let i = 0; i < count; i++) {
|
|
2070
|
+
const base = 1 + i * 5;
|
|
2071
|
+
if (base + 4 >= words.length) break;
|
|
2072
|
+
const lo = words[base + 3] >>> 0;
|
|
2073
|
+
const hi = words[base + 4] >>> 0;
|
|
2074
|
+
out.push({
|
|
2075
|
+
timerId: words[base] >>> 0,
|
|
2076
|
+
subjectId: words[base + 1] >>> 0,
|
|
2077
|
+
kind: words[base + 2] >>> 0,
|
|
2078
|
+
deadlineMs: hi * 4294967296 + lo
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
return out;
|
|
2082
|
+
}
|
|
2083
|
+
};
|
|
2084
|
+
function makeWasmTimerWheel(options) {
|
|
2085
|
+
const mod = resolveWasmModule();
|
|
2086
|
+
const Ctor = mod?.BrassWasmTimerWheel;
|
|
2087
|
+
if (!Ctor) throw new Error("brass-runtime wasm timer wheel is not available. Run npm run build:wasm first.");
|
|
2088
|
+
return new WasmTimerWheelBridge(Ctor, options);
|
|
2089
|
+
}
|
|
2090
|
+
function toU64(value) {
|
|
2091
|
+
return BigInt(Math.max(0, Math.floor(value)));
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
// src/core/runtime/engine/WasmFiberEngine.ts
|
|
2095
|
+
var DEFAULT_BUDGET = 4096;
|
|
2096
|
+
var TIMER_KIND_HOST_ACTION = 1;
|
|
2097
|
+
var WasmFiberEngine = class {
|
|
2098
|
+
constructor(runtime, options = {}) {
|
|
2099
|
+
this.runtime = runtime;
|
|
2100
|
+
this.bridge = options.bridge ?? new WasmPackFiberBridge(options.modulePath);
|
|
2101
|
+
if (this.bridge.kind !== "wasm") {
|
|
2102
|
+
throw new Error("brass-runtime strict mode requires a real WASM bridge; wasm-reference/TS fallback bridges are not allowed");
|
|
2103
|
+
}
|
|
2104
|
+
this.kind = this.bridge.kind;
|
|
2105
|
+
this.fiberRegistry = new WasmFiberRegistryBridge();
|
|
2106
|
+
this.readyQueue = makeFiberReadyQueue({ engine: "wasm" });
|
|
2107
|
+
this.timerWheel = makeWasmTimerWheel({ onExpired: (events) => this.onTimerExpired(events) });
|
|
2108
|
+
}
|
|
2109
|
+
runtime;
|
|
2110
|
+
kind;
|
|
2111
|
+
bridge;
|
|
2112
|
+
readyQueue;
|
|
2113
|
+
startedFibers = 0;
|
|
2114
|
+
runningFibers = 0;
|
|
2115
|
+
suspendedFibers = 0;
|
|
2116
|
+
completedFibers = 0;
|
|
2117
|
+
failedFibers = 0;
|
|
2118
|
+
interruptedFibers = 0;
|
|
2119
|
+
pendingHostEffects = 0;
|
|
2120
|
+
readyDrainScheduled = false;
|
|
2121
|
+
readyDraining = false;
|
|
2122
|
+
states = /* @__PURE__ */ new Map();
|
|
2123
|
+
pendingResumes = /* @__PURE__ */ new Map();
|
|
2124
|
+
fiberRegistry;
|
|
2125
|
+
timerWheel;
|
|
2126
|
+
fork(effect, scopeId) {
|
|
2127
|
+
const builder = new ProgramBuilder();
|
|
2128
|
+
const compiled = builder.compile(effect);
|
|
2129
|
+
const fiberId = this.bridge.createFiber(compiled.program);
|
|
2130
|
+
const controller = new AbortController();
|
|
2131
|
+
const handle = new EngineFiberHandle(
|
|
2132
|
+
fiberId,
|
|
2133
|
+
this.runtime,
|
|
2134
|
+
(id) => this.driveById(id),
|
|
2135
|
+
(id, reason) => this.interruptById(id, reason),
|
|
2136
|
+
(id) => this.fiberRegistry?.addJoiner(id),
|
|
2137
|
+
(id) => this.fiberRegistry?.markQueued(id),
|
|
2138
|
+
(id, label) => this.schedulerDropped(id, label),
|
|
2139
|
+
(id, label) => this.enqueueFiberById(id, label)
|
|
2140
|
+
);
|
|
2141
|
+
if (scopeId !== void 0) handle.scopeId = scopeId;
|
|
2142
|
+
const state = {
|
|
2143
|
+
fiberId,
|
|
2144
|
+
handle,
|
|
2145
|
+
builder,
|
|
2146
|
+
registry: compiled.registry,
|
|
2147
|
+
controller,
|
|
2148
|
+
startedAt: Date.now(),
|
|
2149
|
+
completed: false,
|
|
2150
|
+
status: "running",
|
|
2151
|
+
pendingCleanups: /* @__PURE__ */ new Set(),
|
|
2152
|
+
hostActionToken: 0
|
|
2153
|
+
};
|
|
2154
|
+
this.states.set(fiberId, state);
|
|
2155
|
+
this.fiberRegistry?.registerFiber(fiberId, void 0, scopeId);
|
|
2156
|
+
this.startedFibers += 1;
|
|
2157
|
+
this.runningFibers += 1;
|
|
2158
|
+
return handle;
|
|
2159
|
+
}
|
|
2160
|
+
stats() {
|
|
2161
|
+
let hostRefs = 0;
|
|
2162
|
+
let hostCapacity = 0;
|
|
2163
|
+
let hostAllocated = 0;
|
|
2164
|
+
let hostReleased = 0;
|
|
2165
|
+
let hostReused = 0;
|
|
2166
|
+
let hostStaleReads = 0;
|
|
2167
|
+
for (const state of this.states.values()) {
|
|
2168
|
+
const stats = state.registry.stats();
|
|
2169
|
+
hostRefs += stats.live;
|
|
2170
|
+
hostCapacity += stats.capacity;
|
|
2171
|
+
hostAllocated += stats.allocated;
|
|
2172
|
+
hostReleased += stats.released;
|
|
2173
|
+
hostReused += stats.reused;
|
|
2174
|
+
hostStaleReads += stats.staleReads;
|
|
2175
|
+
}
|
|
2176
|
+
return {
|
|
2177
|
+
engine: this.kind,
|
|
2178
|
+
startedFibers: this.startedFibers,
|
|
2179
|
+
runningFibers: this.runningFibers,
|
|
2180
|
+
suspendedFibers: this.suspendedFibers,
|
|
2181
|
+
queuedFibers: this.readyQueue.len(),
|
|
2182
|
+
completedFibers: this.completedFibers,
|
|
2183
|
+
failedFibers: this.failedFibers,
|
|
2184
|
+
interruptedFibers: this.interruptedFibers,
|
|
2185
|
+
pendingHostEffects: this.pendingHostEffects,
|
|
2186
|
+
hostRegistryRefs: hostRefs,
|
|
2187
|
+
hostRegistryStats: {
|
|
2188
|
+
live: hostRefs,
|
|
2189
|
+
capacity: hostCapacity,
|
|
2190
|
+
allocated: hostAllocated,
|
|
2191
|
+
released: hostReleased,
|
|
2192
|
+
reused: hostReused,
|
|
2193
|
+
staleReads: hostStaleReads
|
|
2194
|
+
},
|
|
2195
|
+
wasm: this.bridge.stats(),
|
|
2196
|
+
fiberRegistry: this.fiberRegistry?.stats(),
|
|
2197
|
+
readyQueue: this.readyQueue.stats(),
|
|
2198
|
+
timerWheel: this.timerWheel?.stats()
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
async shutdown() {
|
|
2202
|
+
for (const state of Array.from(this.states.values())) {
|
|
2203
|
+
this.interruptState(state, Cause.interrupt());
|
|
2204
|
+
}
|
|
2205
|
+
this.readyQueue.clear();
|
|
2206
|
+
this.timerWheel?.dispose();
|
|
2207
|
+
}
|
|
2208
|
+
scheduleWakeup(fiberId) {
|
|
2209
|
+
const state = this.states.get(fiberId);
|
|
2210
|
+
if (!state || state.completed) return;
|
|
2211
|
+
if (!this.fiberRegistry) {
|
|
2212
|
+
this.enqueueFiber(state, "wakeup");
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
if (!this.fiberRegistry.wake(fiberId)) return;
|
|
2216
|
+
this.drainWakeups();
|
|
2217
|
+
}
|
|
2218
|
+
drainWakeups() {
|
|
2219
|
+
if (!this.fiberRegistry) return;
|
|
2220
|
+
for (const fiberId of this.fiberRegistry.drainWakeups()) {
|
|
2221
|
+
this.enqueueFiberById(fiberId, `wasm-fiber#${fiberId}.wakeup`);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
enqueueFiber(state, label) {
|
|
2225
|
+
const result = this.enqueueFiberById(state.fiberId, label);
|
|
2226
|
+
if (result === "dropped") {
|
|
2227
|
+
this.completeDie(state, new Error(`Brass WASM ready queue dropped ${label} because the lane queue is full`));
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
enqueueFiberById(fiberId, label) {
|
|
2231
|
+
const state = this.states.get(fiberId);
|
|
2232
|
+
if (!state || state.completed) return "dropped";
|
|
2233
|
+
const tag = this.schedulerTag(state, label);
|
|
2234
|
+
const policy = this.readyQueue.enqueue(fiberId, tag);
|
|
2235
|
+
if (policy === "dropped") return "dropped";
|
|
2236
|
+
this.requestReadyDrain(tag);
|
|
2237
|
+
return "accepted";
|
|
2238
|
+
}
|
|
2239
|
+
requestReadyDrain(tag = laneTag("brass:wasm-ready", "drain")) {
|
|
2240
|
+
if (this.readyDrainScheduled || this.readyDraining) return;
|
|
2241
|
+
this.readyDrainScheduled = true;
|
|
2242
|
+
const result = this.runtime.scheduler.schedule(() => this.drainReadyQueue(), tag);
|
|
2243
|
+
if (result === "dropped") {
|
|
2244
|
+
this.readyDrainScheduled = false;
|
|
2245
|
+
const dropped = [];
|
|
2246
|
+
while (this.readyQueue.len() > 0) {
|
|
2247
|
+
const budget = this.readyQueue.beginFlush();
|
|
2248
|
+
for (let i = 0; i < budget; i++) {
|
|
2249
|
+
const id = this.readyQueue.shift();
|
|
2250
|
+
if (id !== void 0) dropped.push(id);
|
|
2251
|
+
}
|
|
2252
|
+
this.readyQueue.endFlush(budget);
|
|
2253
|
+
}
|
|
2254
|
+
for (const id of dropped) this.schedulerDropped(id, `wasm-fiber#${id}.ready-drain`);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
drainReadyQueue() {
|
|
2258
|
+
this.readyDrainScheduled = false;
|
|
2259
|
+
if (this.readyDraining) return;
|
|
2260
|
+
this.readyDraining = true;
|
|
2261
|
+
const budget = this.readyQueue.beginFlush();
|
|
2262
|
+
let ran = 0;
|
|
2263
|
+
try {
|
|
2264
|
+
while (ran < budget) {
|
|
2265
|
+
const fiberId = this.readyQueue.shift();
|
|
2266
|
+
if (fiberId === void 0) break;
|
|
2267
|
+
ran += 1;
|
|
2268
|
+
this.driveById(fiberId);
|
|
2269
|
+
}
|
|
2270
|
+
} finally {
|
|
2271
|
+
this.readyDraining = false;
|
|
2272
|
+
const policy = this.readyQueue.endFlush(ran);
|
|
2273
|
+
if (policy === "micro" || policy === "macro") this.requestReadyDrain();
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
driveById(fiberId) {
|
|
2277
|
+
const state = this.states.get(fiberId);
|
|
2278
|
+
if (!state || state.completed) return;
|
|
2279
|
+
state.handle.markDequeued();
|
|
2280
|
+
this.fiberRegistry?.markRunning(fiberId);
|
|
2281
|
+
const initialEvents = this.consumePendingResume(state);
|
|
2282
|
+
withCurrentFiber(state.handle, () => this.drive(state, initialEvents));
|
|
2283
|
+
}
|
|
2284
|
+
consumePendingResume(state) {
|
|
2285
|
+
const pending = this.pendingResumes.get(state.fiberId);
|
|
2286
|
+
if (!pending) return void 0;
|
|
2287
|
+
this.pendingResumes.delete(state.fiberId);
|
|
2288
|
+
if (pending.kind === "value") {
|
|
2289
|
+
return this.bridge.provideValueBatch?.(state.fiberId, pending.ref, DEFAULT_BUDGET) ?? [this.bridge.provideValue(state.fiberId, pending.ref)];
|
|
2290
|
+
}
|
|
2291
|
+
return this.bridge.provideErrorBatch?.(state.fiberId, pending.ref, DEFAULT_BUDGET) ?? [this.bridge.provideError(state.fiberId, pending.ref)];
|
|
2292
|
+
}
|
|
2293
|
+
drive(state, initialEvents) {
|
|
2294
|
+
if (state.completed) return;
|
|
2295
|
+
let budget = DEFAULT_BUDGET;
|
|
2296
|
+
const events = initialEvents ? [...initialEvents] : [];
|
|
2297
|
+
state.status = "running";
|
|
2298
|
+
state.handle.setEngineStatus("running");
|
|
2299
|
+
while (!state.completed && budget-- > 0) {
|
|
2300
|
+
try {
|
|
2301
|
+
if (events.length === 0) {
|
|
2302
|
+
const next = this.bridge.driveBatch?.(state.fiberId, budget + 1) ?? [this.bridge.poll(state.fiberId)];
|
|
2303
|
+
events.push(...next);
|
|
2304
|
+
if (events.length === 0) events.push({ kind: "Continue", fiberId: state.fiberId });
|
|
2305
|
+
}
|
|
2306
|
+
const event = events.shift();
|
|
2307
|
+
switch (event.kind) {
|
|
2308
|
+
case "Continue":
|
|
2309
|
+
continue;
|
|
2310
|
+
case "Done": {
|
|
2311
|
+
const value = state.registry.get(event.valueRef);
|
|
2312
|
+
this.completeSuccess(state, value);
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
case "Failed": {
|
|
2316
|
+
const error = event.errorRef === 0 ? new Error("WASM fiber failed") : state.registry.get(event.errorRef);
|
|
2317
|
+
this.completeFailure(state, error);
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
case "Interrupted": {
|
|
2321
|
+
this.completeInterrupted(state);
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
case "InvokeSync": {
|
|
2325
|
+
const fn = state.registry.get(event.fnRef);
|
|
2326
|
+
try {
|
|
2327
|
+
const valueRef = state.registry.register(fn(this.runtime.env));
|
|
2328
|
+
events.unshift(...this.bridge.provideValueBatch?.(state.fiberId, valueRef, budget + 1) ?? [this.bridge.provideValue(state.fiberId, valueRef)]);
|
|
2329
|
+
} catch (error) {
|
|
2330
|
+
events.unshift(...this.bridge.provideErrorBatch?.(state.fiberId, state.registry.register(error), budget + 1) ?? [this.bridge.provideError(state.fiberId, state.registry.register(error))]);
|
|
2331
|
+
}
|
|
2332
|
+
continue;
|
|
2333
|
+
}
|
|
2334
|
+
case "InvokeFlatMap": {
|
|
2335
|
+
const fn = state.registry.get(event.fnRef);
|
|
2336
|
+
const value = state.registry.get(event.valueRef);
|
|
2337
|
+
try {
|
|
2338
|
+
const next = fn(value);
|
|
2339
|
+
const patch = state.builder.append(next);
|
|
2340
|
+
events.unshift(...this.bridge.provideEffectBatch?.(state.fiberId, patch.root, patch.nodes, budget + 1) ?? [this.bridge.provideEffect(state.fiberId, patch.root, patch.nodes)]);
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
events.unshift(...this.bridge.provideErrorBatch?.(state.fiberId, state.registry.register(error), budget + 1) ?? [this.bridge.provideError(state.fiberId, state.registry.register(error))]);
|
|
2343
|
+
}
|
|
2344
|
+
continue;
|
|
2345
|
+
}
|
|
2346
|
+
case "InvokeFoldFailure": {
|
|
2347
|
+
const fn = state.registry.get(event.fnRef);
|
|
2348
|
+
const errorValue = state.registry.get(event.errorRef);
|
|
2349
|
+
try {
|
|
2350
|
+
const next = fn(errorValue);
|
|
2351
|
+
const patch = state.builder.append(next);
|
|
2352
|
+
events.unshift(...this.bridge.provideEffectBatch?.(state.fiberId, patch.root, patch.nodes, budget + 1) ?? [this.bridge.provideEffect(state.fiberId, patch.root, patch.nodes)]);
|
|
2353
|
+
} catch (error) {
|
|
2354
|
+
events.unshift(...this.bridge.provideErrorBatch?.(state.fiberId, state.registry.register(error), budget + 1) ?? [this.bridge.provideError(state.fiberId, state.registry.register(error))]);
|
|
2355
|
+
}
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2358
|
+
case "InvokeFoldSuccess": {
|
|
2359
|
+
const fn = state.registry.get(event.fnRef);
|
|
2360
|
+
const value = state.registry.get(event.valueRef);
|
|
2361
|
+
try {
|
|
2362
|
+
const next = fn(value);
|
|
2363
|
+
const patch = state.builder.append(next);
|
|
2364
|
+
events.unshift(...this.bridge.provideEffectBatch?.(state.fiberId, patch.root, patch.nodes, budget + 1) ?? [this.bridge.provideEffect(state.fiberId, patch.root, patch.nodes)]);
|
|
2365
|
+
} catch (error) {
|
|
2366
|
+
events.unshift(...this.bridge.provideErrorBatch?.(state.fiberId, state.registry.register(error), budget + 1) ?? [this.bridge.provideError(state.fiberId, state.registry.register(error))]);
|
|
2367
|
+
}
|
|
2368
|
+
continue;
|
|
2369
|
+
}
|
|
2370
|
+
case "InvokeFork": {
|
|
2371
|
+
const effect = state.registry.get(event.effectRef);
|
|
2372
|
+
try {
|
|
2373
|
+
const child = this.runtime.fork(effect, event.scopeId);
|
|
2374
|
+
events.unshift(...this.bridge.provideValueBatch?.(state.fiberId, state.registry.register(child), budget + 1) ?? [this.bridge.provideValue(state.fiberId, state.registry.register(child))]);
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
events.unshift(...this.bridge.provideErrorBatch?.(state.fiberId, state.registry.register(error), budget + 1) ?? [this.bridge.provideError(state.fiberId, state.registry.register(error))]);
|
|
2377
|
+
}
|
|
2378
|
+
continue;
|
|
2379
|
+
}
|
|
2380
|
+
case "InvokeAsync": {
|
|
2381
|
+
this.scheduleAsync(state, event.registerRef);
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
case "InvokeHostAction": {
|
|
2385
|
+
this.scheduleHostAction(state, event.actionRef, event.decodeRef);
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
} catch (error) {
|
|
2390
|
+
events.unshift(...this.bridge.provideErrorBatch?.(state.fiberId, state.registry.register(error), budget + 1) ?? [this.bridge.provideError(state.fiberId, state.registry.register(error))]);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
if (!state.completed) this.enqueueFiber(state, "budget-yield");
|
|
2394
|
+
}
|
|
2395
|
+
scheduleAsync(state, registerRef) {
|
|
2396
|
+
this.markSuspended(state, "async");
|
|
2397
|
+
this.pendingHostEffects += 1;
|
|
2398
|
+
const register = state.registry.get(registerRef);
|
|
2399
|
+
let done = false;
|
|
2400
|
+
let asyncRegistered = false;
|
|
2401
|
+
let syncExit = null;
|
|
2402
|
+
let cancelCleanup;
|
|
2403
|
+
const cleanup = () => {
|
|
2404
|
+
done = true;
|
|
2405
|
+
state.pendingCleanups.delete(cleanup);
|
|
2406
|
+
if (cancelCleanup) {
|
|
2407
|
+
state.pendingCleanups.delete(cancelCleanup);
|
|
2408
|
+
cancelCleanup = void 0;
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
const cb = (exitLike) => {
|
|
2412
|
+
if (done) return;
|
|
2413
|
+
const exit = exitLike;
|
|
2414
|
+
if (!asyncRegistered) {
|
|
2415
|
+
syncExit = exit;
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
cleanup();
|
|
2419
|
+
this.pendingHostEffects = Math.max(0, this.pendingHostEffects - 1);
|
|
2420
|
+
this.markRunning(state, "async");
|
|
2421
|
+
this.resumeWithExit(state, exit);
|
|
2422
|
+
};
|
|
2423
|
+
try {
|
|
2424
|
+
const canceler = register(this.runtime.env, cb);
|
|
2425
|
+
asyncRegistered = true;
|
|
2426
|
+
if (syncExit) {
|
|
2427
|
+
cleanup();
|
|
2428
|
+
this.pendingHostEffects = Math.max(0, this.pendingHostEffects - 1);
|
|
2429
|
+
this.markRunning(state, "async-sync");
|
|
2430
|
+
this.resumeWithExit(state, syncExit);
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
if (typeof canceler === "function") {
|
|
2434
|
+
cancelCleanup = () => {
|
|
2435
|
+
if (done) return;
|
|
2436
|
+
done = true;
|
|
2437
|
+
state.pendingCleanups.delete(cleanup);
|
|
2438
|
+
if (cancelCleanup) state.pendingCleanups.delete(cancelCleanup);
|
|
2439
|
+
cancelCleanup = void 0;
|
|
2440
|
+
try {
|
|
2441
|
+
canceler();
|
|
2442
|
+
} catch {
|
|
2443
|
+
}
|
|
2444
|
+
};
|
|
2445
|
+
state.pendingCleanups.add(cancelCleanup);
|
|
2446
|
+
state.handle.addFinalizer(() => cancelCleanup?.());
|
|
2447
|
+
}
|
|
2448
|
+
} catch (error) {
|
|
2449
|
+
cleanup();
|
|
2450
|
+
this.pendingHostEffects = Math.max(0, this.pendingHostEffects - 1);
|
|
2451
|
+
this.markRunning(state, "async-register-error");
|
|
2452
|
+
this.resumeWithError(state, error);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
scheduleHostAction(state, actionRef, decodeRef2) {
|
|
2456
|
+
this.markSuspended(state, "host-action");
|
|
2457
|
+
this.pendingHostEffects += 1;
|
|
2458
|
+
const action = state.registry.get(actionRef);
|
|
2459
|
+
const token = ++state.hostActionToken;
|
|
2460
|
+
const deadlineAt = action.timeoutMs === void 0 ? void 0 : Date.now() + action.timeoutMs;
|
|
2461
|
+
const cleanup = () => {
|
|
2462
|
+
state.pendingCleanups.delete(cleanup);
|
|
2463
|
+
this.timerWheel?.cancel(state.deadlineTimerId);
|
|
2464
|
+
if (state.hostActionToken === token) state.deadlineTimerId = void 0;
|
|
2465
|
+
};
|
|
2466
|
+
state.pendingCleanups.add(cleanup);
|
|
2467
|
+
if (deadlineAt !== void 0 && this.timerWheel) {
|
|
2468
|
+
state.deadlineTimerId = this.timerWheel.schedule(state.fiberId, TIMER_KIND_HOST_ACTION, deadlineAt);
|
|
2469
|
+
}
|
|
2470
|
+
this.runtime.hostExecutor.execute(action, {
|
|
2471
|
+
fiberId: state.fiberId,
|
|
2472
|
+
env: this.runtime.env,
|
|
2473
|
+
signal: state.controller.signal,
|
|
2474
|
+
deadlineAt
|
|
2475
|
+
}).then((result) => {
|
|
2476
|
+
if (state.completed || state.hostActionToken !== token) return;
|
|
2477
|
+
cleanup();
|
|
2478
|
+
this.pendingHostEffects = Math.max(0, this.pendingHostEffects - 1);
|
|
2479
|
+
this.markRunning(state, "host-action");
|
|
2480
|
+
try {
|
|
2481
|
+
if (decodeRef2 !== void 0) {
|
|
2482
|
+
const decode = state.registry.get(decodeRef2);
|
|
2483
|
+
this.resumeWithValue(state, decode(result));
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
if (result.kind === "error") {
|
|
2487
|
+
this.resumeWithError(state, result.error);
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
this.resumeWithValue(state, result.value);
|
|
2491
|
+
} catch (error) {
|
|
2492
|
+
this.resumeWithError(state, error);
|
|
2493
|
+
}
|
|
2494
|
+
}).catch((error) => {
|
|
2495
|
+
if (state.completed || state.hostActionToken !== token) return;
|
|
2496
|
+
cleanup();
|
|
2497
|
+
this.pendingHostEffects = Math.max(0, this.pendingHostEffects - 1);
|
|
2498
|
+
this.markRunning(state, "host-action-error");
|
|
2499
|
+
this.resumeWithError(state, error);
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
onTimerExpired(events) {
|
|
2503
|
+
for (const event of events) {
|
|
2504
|
+
if (event.kind !== TIMER_KIND_HOST_ACTION) continue;
|
|
2505
|
+
const state = this.states.get(event.subjectId);
|
|
2506
|
+
if (!state || state.completed || state.deadlineTimerId !== event.timerId) continue;
|
|
2507
|
+
state.deadlineTimerId = void 0;
|
|
2508
|
+
state.hostActionToken += 1;
|
|
2509
|
+
if (!state.controller.signal.aborted) {
|
|
2510
|
+
state.controller.abort(new Error(`Brass host action timed out at ${event.deadlineMs}ms deadline`));
|
|
2511
|
+
}
|
|
2512
|
+
this.pendingHostEffects = Math.max(0, this.pendingHostEffects - 1);
|
|
2513
|
+
this.markRunning(state, "host-action-timeout");
|
|
2514
|
+
this.resumeWithError(state, new Error(`Brass host action timed out at ${event.deadlineMs}ms deadline`));
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
resumeWithExit(state, exit) {
|
|
2518
|
+
if (state.completed) return;
|
|
2519
|
+
if (exit._tag === "Success") {
|
|
2520
|
+
this.resumeWithValue(state, exit.value);
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
const cause = exit.cause;
|
|
2524
|
+
if (cause._tag === "Interrupt") {
|
|
2525
|
+
this.interruptState(state, cause);
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
if (cause._tag === "Fail") {
|
|
2529
|
+
this.resumeWithError(state, cause.error);
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
this.completeDie(state, cause.defect);
|
|
2533
|
+
}
|
|
2534
|
+
resumeWithValue(state, value) {
|
|
2535
|
+
this.pendingResumes.set(state.fiberId, { kind: "value", ref: state.registry.register(value) });
|
|
2536
|
+
this.enqueueFiber(state, `wasm-fiber#${state.fiberId}.resume`);
|
|
2537
|
+
}
|
|
2538
|
+
resumeWithError(state, error) {
|
|
2539
|
+
this.pendingResumes.set(state.fiberId, { kind: "error", ref: state.registry.register(error) });
|
|
2540
|
+
this.enqueueFiber(state, `wasm-fiber#${state.fiberId}.resume-error`);
|
|
2541
|
+
}
|
|
2542
|
+
schedulerTag(state, label) {
|
|
2543
|
+
const lane = state.handle.lane ?? this.runtime.lane;
|
|
2544
|
+
return lane ? laneTag(lane, label) : label;
|
|
2545
|
+
}
|
|
2546
|
+
schedulerDropped(fiberId, label) {
|
|
2547
|
+
const state = this.states.get(fiberId);
|
|
2548
|
+
if (!state || state.completed) return;
|
|
2549
|
+
this.completeDie(state, new Error(`Brass scheduler dropped ${label} because the lane queue is full`));
|
|
2550
|
+
}
|
|
2551
|
+
interruptById(fiberId, reason) {
|
|
2552
|
+
const state = this.states.get(fiberId);
|
|
2553
|
+
if (!state) return;
|
|
2554
|
+
this.interruptState(state, reason);
|
|
2555
|
+
}
|
|
2556
|
+
interruptState(state, reason) {
|
|
2557
|
+
if (state.completed) return;
|
|
2558
|
+
if (!state.controller.signal.aborted) state.controller.abort(reason);
|
|
2559
|
+
for (const cleanup of Array.from(state.pendingCleanups)) cleanup();
|
|
2560
|
+
const events = this.bridge.interruptBatch?.(state.fiberId, state.registry.register(reason), DEFAULT_BUDGET) ?? [this.bridge.interrupt(state.fiberId, state.registry.register(reason))];
|
|
2561
|
+
this.drive(state, events);
|
|
2562
|
+
}
|
|
2563
|
+
markSuspended(state, reason) {
|
|
2564
|
+
if (state.status !== "suspended") {
|
|
2565
|
+
this.suspendedFibers += 1;
|
|
2566
|
+
state.status = "suspended";
|
|
2567
|
+
this.fiberRegistry?.markSuspended(state.fiberId);
|
|
2568
|
+
state.handle.setEngineStatus("suspended");
|
|
2569
|
+
state.handle.emit({ type: "fiber.suspend", fiberId: state.fiberId, reason });
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
markRunning(state, _reason) {
|
|
2573
|
+
if (state.status === "suspended") {
|
|
2574
|
+
this.suspendedFibers = Math.max(0, this.suspendedFibers - 1);
|
|
2575
|
+
state.status = "running";
|
|
2576
|
+
this.fiberRegistry?.markRunning(state.fiberId);
|
|
2577
|
+
state.handle.setEngineStatus("running");
|
|
2578
|
+
state.handle.emit({ type: "fiber.resume", fiberId: state.fiberId });
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
completeSuccess(state, value) {
|
|
2582
|
+
if (state.completed) return;
|
|
2583
|
+
state.completed = true;
|
|
2584
|
+
state.status = "done";
|
|
2585
|
+
this.fiberRegistry?.markDone(state.fiberId, "done");
|
|
2586
|
+
this.completedFibers += 1;
|
|
2587
|
+
this.cleanupState(state);
|
|
2588
|
+
state.handle.succeed(value);
|
|
2589
|
+
}
|
|
2590
|
+
completeFailure(state, error) {
|
|
2591
|
+
if (state.completed) return;
|
|
2592
|
+
state.completed = true;
|
|
2593
|
+
state.status = "failed";
|
|
2594
|
+
this.fiberRegistry?.markDone(state.fiberId, "failed");
|
|
2595
|
+
this.failedFibers += 1;
|
|
2596
|
+
this.cleanupState(state);
|
|
2597
|
+
state.handle.fail(error);
|
|
2598
|
+
}
|
|
2599
|
+
completeDie(state, defect) {
|
|
2600
|
+
if (state.completed) return;
|
|
2601
|
+
state.completed = true;
|
|
2602
|
+
state.status = "failed";
|
|
2603
|
+
this.fiberRegistry?.markDone(state.fiberId, "failed");
|
|
2604
|
+
this.failedFibers += 1;
|
|
2605
|
+
this.cleanupState(state);
|
|
2606
|
+
state.handle.die(defect);
|
|
2607
|
+
}
|
|
2608
|
+
completeInterrupted(state) {
|
|
2609
|
+
if (state.completed) return;
|
|
2610
|
+
state.completed = true;
|
|
2611
|
+
state.status = "interrupted";
|
|
2612
|
+
this.fiberRegistry?.markDone(state.fiberId, "interrupted");
|
|
2613
|
+
this.interruptedFibers += 1;
|
|
2614
|
+
this.cleanupState(state);
|
|
2615
|
+
state.handle.interrupted();
|
|
2616
|
+
}
|
|
2617
|
+
cleanupState(state) {
|
|
2618
|
+
this.runningFibers = Math.max(0, this.runningFibers - 1);
|
|
2619
|
+
if (state.status === "suspended") this.suspendedFibers = Math.max(0, this.suspendedFibers - 1);
|
|
2620
|
+
this.timerWheel?.cancel(state.deadlineTimerId);
|
|
2621
|
+
state.deadlineTimerId = void 0;
|
|
2622
|
+
for (const cleanup of Array.from(state.pendingCleanups)) cleanup();
|
|
2623
|
+
state.pendingCleanups.clear();
|
|
2624
|
+
this.pendingResumes.delete(state.fiberId);
|
|
2625
|
+
this.bridge.dropFiber(state.fiberId);
|
|
2626
|
+
this.fiberRegistry?.dropFiber(state.fiberId);
|
|
2627
|
+
state.registry.clear();
|
|
2628
|
+
this.states.delete(state.fiberId);
|
|
2629
|
+
}
|
|
2630
|
+
};
|
|
2631
|
+
|
|
2632
|
+
// src/core/runtime/capabilities.ts
|
|
2633
|
+
function runtimeCapabilities() {
|
|
2634
|
+
const mod = resolveWasmModule();
|
|
2635
|
+
return {
|
|
2636
|
+
wasmAvailable: !!mod,
|
|
2637
|
+
wasmFiberEngine: typeof mod?.BrassWasmVm === "function",
|
|
2638
|
+
wasmRingBuffer: typeof mod?.BrassWasmRingBuffer === "function",
|
|
2639
|
+
wasmScheduler: typeof mod?.BrassWasmSchedulerStateMachine === "function",
|
|
2640
|
+
wasmFiberRegistry: typeof mod?.BrassWasmFiberRegistry === "function",
|
|
2641
|
+
wasmFiberReadyQueue: typeof mod?.BrassWasmFiberReadyQueue === "function",
|
|
2642
|
+
wasmBinaryAbi: hasBinaryVmAbi(mod),
|
|
2643
|
+
wasmStreamChunks: typeof mod?.BrassWasmChunkBuffer === "function"
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
function hasBinaryVmAbi(mod) {
|
|
2647
|
+
const Ctor = mod?.BrassWasmVm;
|
|
2648
|
+
if (typeof Ctor !== "function") return false;
|
|
2649
|
+
try {
|
|
2650
|
+
const vm = new Ctor();
|
|
2651
|
+
return typeof vm.create_fiber_bin === "function" && typeof vm.drive_batch_bin === "function" && typeof vm.provide_value_bin === "function" && typeof vm.provide_error_bin === "function" && typeof vm.provide_effect_bin === "function" && typeof vm.interrupt_bin === "function";
|
|
2652
|
+
} catch {
|
|
2653
|
+
return false;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
// src/core/runtime/runtime.ts
|
|
2658
|
+
var NoopHooks = {
|
|
2659
|
+
emit() {
|
|
2660
|
+
}
|
|
2661
|
+
};
|
|
2662
|
+
function normalizeRuntimeEngineMode(value) {
|
|
2663
|
+
if (value === "ts" || value === "wasm") return value;
|
|
2664
|
+
throw new Error(`brass-runtime engine must be either 'ts' or 'wasm' in strict mode; received '${String(value)}'`);
|
|
2665
|
+
}
|
|
2666
|
+
function unreachableEngine(value) {
|
|
2667
|
+
throw new Error(`brass-runtime unsupported engine '${String(value)}'`);
|
|
2668
|
+
}
|
|
2669
|
+
var Runtime = class _Runtime {
|
|
2670
|
+
env;
|
|
2671
|
+
scheduler;
|
|
2672
|
+
hooks;
|
|
2673
|
+
hostExecutor;
|
|
2674
|
+
engineMode;
|
|
2675
|
+
wasmOptions;
|
|
2676
|
+
fiberEngine;
|
|
2677
|
+
fallbackUsed;
|
|
2678
|
+
forkPolicy;
|
|
2679
|
+
lane;
|
|
2680
|
+
inferLane;
|
|
2681
|
+
// opcional: registry para observabilidad
|
|
2682
|
+
registry;
|
|
2683
|
+
constructor(args) {
|
|
2684
|
+
this.env = args.env;
|
|
2685
|
+
this.scheduler = args.scheduler ?? globalScheduler;
|
|
2686
|
+
this.lane = args.lane;
|
|
2687
|
+
this.inferLane = args.inferLane ?? true;
|
|
2688
|
+
this.hooks = args.hooks ?? NoopHooks;
|
|
2689
|
+
this.hostExecutor = args.hostExecutor ?? DefaultHostExecutor;
|
|
2690
|
+
this.engineMode = normalizeRuntimeEngineMode(args.engine ?? "ts");
|
|
2691
|
+
this.wasmOptions = args.wasm;
|
|
2692
|
+
this.forkPolicy = makeForkPolicy(this.env, this.hooks);
|
|
2693
|
+
this.fiberEngine = this.makeFiberEngine(this.engineMode, args.wasm);
|
|
2694
|
+
this.fallbackUsed = false;
|
|
2695
|
+
}
|
|
2696
|
+
makeFiberEngine(mode, wasm) {
|
|
2697
|
+
if (mode === "ts") return new JsFiberEngine(this);
|
|
2698
|
+
if (mode === "wasm") return new WasmFiberEngine(this, wasm);
|
|
2699
|
+
return unreachableEngine(mode);
|
|
2700
|
+
}
|
|
2701
|
+
/** Returns true when the runtime has real hooks (not the no-op singleton). */
|
|
2702
|
+
hasActiveHooks() {
|
|
2703
|
+
return this.hooks !== NoopHooks;
|
|
2704
|
+
}
|
|
2705
|
+
/** Deriva un runtime con env extendido (estilo provide/locally) */
|
|
2706
|
+
provide(env) {
|
|
2707
|
+
return new _Runtime({
|
|
2708
|
+
env: Object.assign({}, this.env, env),
|
|
2709
|
+
scheduler: this.scheduler,
|
|
2710
|
+
hooks: this.hooks,
|
|
2711
|
+
engine: this.engineMode,
|
|
2712
|
+
hostExecutor: this.hostExecutor,
|
|
2713
|
+
wasm: this.wasmOptions,
|
|
2714
|
+
lane: this.lane,
|
|
2715
|
+
inferLane: this.inferLane
|
|
2716
|
+
});
|
|
2717
|
+
}
|
|
2718
|
+
/**
|
|
2719
|
+
* Returns a derived runtime that schedules all work in a caller/lane.
|
|
2720
|
+
* Brass does not need to know the caller implementation; it only sees this stable key.
|
|
2721
|
+
*/
|
|
2722
|
+
withLane(lane) {
|
|
2723
|
+
return new _Runtime({
|
|
2724
|
+
env: this.env,
|
|
2725
|
+
scheduler: this.scheduler,
|
|
2726
|
+
hooks: this.hooks,
|
|
2727
|
+
engine: this.engineMode,
|
|
2728
|
+
hostExecutor: this.hostExecutor,
|
|
2729
|
+
wasm: this.wasmOptions,
|
|
2730
|
+
lane,
|
|
2731
|
+
inferLane: this.inferLane
|
|
2732
|
+
});
|
|
2733
|
+
}
|
|
2734
|
+
resolveFiberLane(parent) {
|
|
2735
|
+
if (this.lane !== void 0) return this.lane;
|
|
2736
|
+
const parentLane = parent?.lane;
|
|
2737
|
+
if (typeof parentLane === "string" && parentLane.length > 0) return parentLane;
|
|
2738
|
+
if (!this.inferLane) return void 0;
|
|
2739
|
+
return inferCallerLaneFromStack(void 0, "runtime");
|
|
2740
|
+
}
|
|
2741
|
+
emit(ev) {
|
|
2742
|
+
if (this.hooks === NoopHooks) return;
|
|
2743
|
+
const f = getCurrentFiber();
|
|
2744
|
+
const ctx = {
|
|
2745
|
+
fiberId: f?.id,
|
|
2746
|
+
scopeId: f?.scopeId,
|
|
2747
|
+
// ✅ FIX: era f?.scope
|
|
2748
|
+
traceId: f?.fiberContext?.trace?.traceId,
|
|
2749
|
+
spanId: f?.fiberContext?.trace?.spanId
|
|
2750
|
+
};
|
|
2751
|
+
this.hooks.emit(ev, ctx);
|
|
2752
|
+
}
|
|
2753
|
+
/**
|
|
2754
|
+
* ✅ CAMBIO: fork(effect, scopeId?) y pasa scopeId a forkPolicy
|
|
2755
|
+
*/
|
|
2756
|
+
fork(effect, scopeId) {
|
|
2757
|
+
const parent = getCurrentFiber();
|
|
2758
|
+
const fiber = this.fiberEngine.fork(effect, scopeId);
|
|
2759
|
+
const lane = this.resolveFiberLane(parent);
|
|
2760
|
+
if (lane !== void 0) fiber.lane = lane;
|
|
2761
|
+
if (scopeId !== void 0) fiber.scopeId = scopeId;
|
|
2762
|
+
this.forkPolicy.initChild(fiber, parent, scopeId);
|
|
2763
|
+
fiber.schedule?.("initial-step");
|
|
2764
|
+
return fiber;
|
|
2765
|
+
}
|
|
2766
|
+
stats() {
|
|
2767
|
+
const data = this.fiberEngine.stats();
|
|
2768
|
+
return { engine: this.fiberEngine.kind, fallbackUsed: false, data };
|
|
2769
|
+
}
|
|
2770
|
+
capabilities() {
|
|
2771
|
+
return runtimeCapabilities();
|
|
2772
|
+
}
|
|
2773
|
+
shutdown() {
|
|
2774
|
+
return this.fiberEngine.shutdown?.();
|
|
2775
|
+
}
|
|
2776
|
+
unsafeRunAsync(effect, cb) {
|
|
2777
|
+
const fiber = this.fork(effect);
|
|
2778
|
+
fiber.join(cb);
|
|
2779
|
+
}
|
|
2780
|
+
toPromise(effect) {
|
|
2781
|
+
return new Promise((resolve, reject) => {
|
|
2782
|
+
const fiber = this.fork(effect);
|
|
2783
|
+
fiber.join((exit) => {
|
|
2784
|
+
if (exit._tag === "Success") resolve(exit.value);
|
|
2785
|
+
else {
|
|
2786
|
+
const c = exit.cause;
|
|
2787
|
+
if (c?._tag === "Fail") reject(c.error);
|
|
2788
|
+
else if (c?._tag === "Die") reject(c.defect instanceof Error ? c.defect : new Error(String(c.defect)));
|
|
2789
|
+
else reject(new Error("Interrupted"));
|
|
2790
|
+
}
|
|
2791
|
+
});
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
// helper: correr un efecto y “tirar” el resultado
|
|
2795
|
+
unsafeRun(effect) {
|
|
2796
|
+
this.unsafeRunAsync(effect, () => {
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
delay(ms, eff) {
|
|
2800
|
+
return async((_env, cb) => {
|
|
2801
|
+
const handle = setTimeout(() => {
|
|
2802
|
+
this.unsafeRunAsync(eff, cb);
|
|
2803
|
+
}, ms);
|
|
2804
|
+
return () => clearTimeout(handle);
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
// util para crear runtime default
|
|
2808
|
+
static make(env, scheduler = globalScheduler) {
|
|
2809
|
+
return new _Runtime({ env, scheduler });
|
|
2810
|
+
}
|
|
2811
|
+
static makeWithEngine(env, engine, options = {}) {
|
|
2812
|
+
return new _Runtime({ ...options, env, engine });
|
|
2813
|
+
}
|
|
2814
|
+
/** Convenience logger: emits a RuntimeEvent of type "log". */
|
|
2815
|
+
log(level, message, fields) {
|
|
2816
|
+
this.emit({ type: "log", level, message, fields });
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
function fork(effect, env) {
|
|
2820
|
+
return Runtime.make(env ?? {}).fork(effect);
|
|
2821
|
+
}
|
|
2822
|
+
function runtimeForCaller(caller, env) {
|
|
2823
|
+
return Runtime.make(env ?? {}).withLane(caller);
|
|
2824
|
+
}
|
|
2825
|
+
function toPromiseByCaller(caller, effect, env) {
|
|
2826
|
+
return runtimeForCaller(caller, env).toPromise(effect);
|
|
2827
|
+
}
|
|
2828
|
+
function unsafeRunAsync(effect, env, cb) {
|
|
2829
|
+
Runtime.make(env ?? {}).unsafeRunAsync(effect, cb);
|
|
2830
|
+
}
|
|
2831
|
+
function toPromise(effect, env) {
|
|
2832
|
+
return Runtime.make(env ?? {}).toPromise(effect);
|
|
2833
|
+
}
|
|
2834
|
+
var abortablePromiseTotals = {
|
|
2835
|
+
active: 0,
|
|
2836
|
+
started: 0,
|
|
2837
|
+
succeeded: 0,
|
|
2838
|
+
failed: 0,
|
|
2839
|
+
interrupted: 0,
|
|
2840
|
+
timedOut: 0,
|
|
2841
|
+
lateSettlements: 0
|
|
2842
|
+
};
|
|
2843
|
+
var abortablePromiseLabels = /* @__PURE__ */ new Map();
|
|
2844
|
+
var getAbortablePromiseLabelStats = (label) => {
|
|
2845
|
+
const existing = abortablePromiseLabels.get(label);
|
|
2846
|
+
if (existing) return existing;
|
|
2847
|
+
const created = {
|
|
2848
|
+
label,
|
|
2849
|
+
active: 0,
|
|
2850
|
+
started: 0,
|
|
2851
|
+
succeeded: 0,
|
|
2852
|
+
failed: 0,
|
|
2853
|
+
interrupted: 0,
|
|
2854
|
+
timedOut: 0,
|
|
2855
|
+
lateSettlements: 0
|
|
2856
|
+
};
|
|
2857
|
+
abortablePromiseLabels.set(label, created);
|
|
2858
|
+
return created;
|
|
2859
|
+
};
|
|
2860
|
+
var recordAbortablePromiseStart = (label) => {
|
|
2861
|
+
const byLabel = getAbortablePromiseLabelStats(label);
|
|
2862
|
+
abortablePromiseTotals.active++;
|
|
2863
|
+
abortablePromiseTotals.started++;
|
|
2864
|
+
byLabel.active++;
|
|
2865
|
+
byLabel.started++;
|
|
2866
|
+
};
|
|
2867
|
+
var recordAbortablePromiseFinish = (label, outcome) => {
|
|
2868
|
+
const byLabel = getAbortablePromiseLabelStats(label);
|
|
2869
|
+
if (abortablePromiseTotals.active > 0) abortablePromiseTotals.active--;
|
|
2870
|
+
if (byLabel.active > 0) byLabel.active--;
|
|
2871
|
+
switch (outcome) {
|
|
2872
|
+
case "success":
|
|
2873
|
+
abortablePromiseTotals.succeeded++;
|
|
2874
|
+
byLabel.succeeded++;
|
|
2875
|
+
return;
|
|
2876
|
+
case "failure":
|
|
2877
|
+
abortablePromiseTotals.failed++;
|
|
2878
|
+
byLabel.failed++;
|
|
2879
|
+
return;
|
|
2880
|
+
case "interrupt":
|
|
2881
|
+
abortablePromiseTotals.interrupted++;
|
|
2882
|
+
byLabel.interrupted++;
|
|
2883
|
+
return;
|
|
2884
|
+
case "timeout":
|
|
2885
|
+
abortablePromiseTotals.timedOut++;
|
|
2886
|
+
byLabel.timedOut++;
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
var recordAbortablePromiseLateSettlement = (label) => {
|
|
2891
|
+
const byLabel = getAbortablePromiseLabelStats(label);
|
|
2892
|
+
abortablePromiseTotals.lateSettlements++;
|
|
2893
|
+
byLabel.lateSettlements++;
|
|
2894
|
+
};
|
|
2895
|
+
function abortablePromiseStats() {
|
|
2896
|
+
return {
|
|
2897
|
+
...abortablePromiseTotals,
|
|
2898
|
+
byLabel: Array.from(abortablePromiseLabels.values()).map((x) => ({ ...x })).sort((a, b) => b.active - a.active || b.started - a.started || a.label.localeCompare(b.label))
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
function resetAbortablePromiseStats() {
|
|
2902
|
+
abortablePromiseTotals.active = 0;
|
|
2903
|
+
abortablePromiseTotals.started = 0;
|
|
2904
|
+
abortablePromiseTotals.succeeded = 0;
|
|
2905
|
+
abortablePromiseTotals.failed = 0;
|
|
2906
|
+
abortablePromiseTotals.interrupted = 0;
|
|
2907
|
+
abortablePromiseTotals.timedOut = 0;
|
|
2908
|
+
abortablePromiseTotals.lateSettlements = 0;
|
|
2909
|
+
abortablePromiseLabels.clear();
|
|
2910
|
+
}
|
|
2911
|
+
var normalizeAbortablePromiseLabel = (label) => {
|
|
2912
|
+
const value = label?.trim();
|
|
2913
|
+
return value && value.length > 0 ? value.slice(0, 160) : "anonymous";
|
|
2914
|
+
};
|
|
2915
|
+
var makeTimeoutReason = (timeoutMs, label) => ({
|
|
2916
|
+
_tag: "Timeout",
|
|
2917
|
+
timeoutMs,
|
|
2918
|
+
message: `Abortable promise '${label}' timed out after ${timeoutMs}ms`
|
|
2919
|
+
});
|
|
2920
|
+
function fromPromiseAbortable(make, onReject, options = {}) {
|
|
2921
|
+
return {
|
|
2922
|
+
_tag: "Async",
|
|
2923
|
+
register: (env, cb) => {
|
|
2924
|
+
const controller = new AbortController();
|
|
2925
|
+
const label = normalizeAbortablePromiseLabel(options.label);
|
|
2926
|
+
const timeoutMs = options.timeoutMs !== void 0 && Number.isFinite(options.timeoutMs) ? Math.max(0, Math.floor(options.timeoutMs)) : void 0;
|
|
2927
|
+
const startedAt = performance.now();
|
|
2928
|
+
let done = false;
|
|
2929
|
+
let timeoutHandle;
|
|
2930
|
+
const cleanup = () => {
|
|
2931
|
+
if (timeoutHandle !== void 0) {
|
|
2932
|
+
clearTimeout(timeoutHandle);
|
|
2933
|
+
timeoutHandle = void 0;
|
|
2934
|
+
}
|
|
2935
|
+
};
|
|
2936
|
+
const finish = (outcome, exit, error) => {
|
|
2937
|
+
if (done) return;
|
|
2938
|
+
done = true;
|
|
2939
|
+
cleanup();
|
|
2940
|
+
recordAbortablePromiseFinish(label, outcome);
|
|
2941
|
+
options.onFinish?.({
|
|
2942
|
+
label,
|
|
2943
|
+
outcome,
|
|
2944
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
2945
|
+
error
|
|
2946
|
+
});
|
|
2947
|
+
cb(exit);
|
|
2948
|
+
};
|
|
2949
|
+
recordAbortablePromiseStart(label);
|
|
2950
|
+
options.onStart?.(label);
|
|
2951
|
+
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
2952
|
+
timeoutHandle = setTimeout(() => {
|
|
2953
|
+
const reason = options.timeoutReason?.() ?? makeTimeoutReason(timeoutMs, label);
|
|
2954
|
+
try {
|
|
2955
|
+
controller.abort(reason);
|
|
2956
|
+
} catch {
|
|
2957
|
+
controller.abort();
|
|
2958
|
+
}
|
|
2959
|
+
finish("timeout", Exit.failCause(Cause.fail(onReject(reason))), reason);
|
|
2960
|
+
}, timeoutMs);
|
|
2961
|
+
}
|
|
2962
|
+
let promise;
|
|
2963
|
+
try {
|
|
2964
|
+
promise = make(controller.signal, env);
|
|
2965
|
+
} catch (err) {
|
|
2966
|
+
finish("failure", Exit.failCause(Cause.fail(onReject(err))), err);
|
|
2967
|
+
return () => void 0;
|
|
2968
|
+
}
|
|
2969
|
+
promise.then((value) => {
|
|
2970
|
+
if (done) {
|
|
2971
|
+
recordAbortablePromiseLateSettlement(label);
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
finish("success", Exit.succeed(value));
|
|
2975
|
+
}).catch((err) => {
|
|
2976
|
+
if (done) {
|
|
2977
|
+
recordAbortablePromiseLateSettlement(label);
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
finish("failure", Exit.failCause(Cause.fail(onReject(err))), err);
|
|
2981
|
+
});
|
|
2982
|
+
return () => {
|
|
2983
|
+
if (done) return;
|
|
2984
|
+
try {
|
|
2985
|
+
controller.abort();
|
|
2986
|
+
} catch {
|
|
2987
|
+
}
|
|
2988
|
+
finish("interrupt", Exit.failCause(Cause.interrupt()));
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
function unsafeRunFoldWithEnv(eff, env, onFailure, onSuccess) {
|
|
2994
|
+
unsafeRunAsync(eff, env, (ex) => {
|
|
2995
|
+
if (ex._tag === "Failure") onFailure(ex.cause);
|
|
2996
|
+
else onSuccess(ex.value);
|
|
2997
|
+
});
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
// src/core/runtime/fiber.ts
|
|
3001
|
+
var _current = null;
|
|
3002
|
+
var STEP = {
|
|
3003
|
+
CONTINUE: "Continue",
|
|
3004
|
+
SUSPEND: "Suspend",
|
|
3005
|
+
DONE: "Done"
|
|
3006
|
+
};
|
|
3007
|
+
var TRAMPOLINE = {
|
|
3008
|
+
CONTINUE: 0,
|
|
3009
|
+
// exited trampoline, more work to do via normal switch
|
|
3010
|
+
SUSPEND: 1,
|
|
3011
|
+
// hit an async that didn't resolve synchronously
|
|
3012
|
+
DONE: 2
|
|
3013
|
+
// fiber completed (success or failure)
|
|
3014
|
+
};
|
|
3015
|
+
var RUN = {
|
|
3016
|
+
QUEUED: "Queued",
|
|
3017
|
+
RUNNING: "Running",
|
|
3018
|
+
SUSPENDED: "Suspended",
|
|
3019
|
+
DONE: "Done"
|
|
3020
|
+
};
|
|
3021
|
+
var nextId = 1;
|
|
3022
|
+
var DEFAULT_BUDGET2 = 16384;
|
|
3023
|
+
var __benchmarkBudget;
|
|
3024
|
+
function setBenchmarkBudget(budget) {
|
|
3025
|
+
__benchmarkBudget = budget;
|
|
3026
|
+
}
|
|
3027
|
+
function getBenchmarkBudget() {
|
|
3028
|
+
return __benchmarkBudget;
|
|
3029
|
+
}
|
|
3030
|
+
var RuntimeFiber = class {
|
|
3031
|
+
id;
|
|
3032
|
+
// 👇 CLAVE: guardar el runtime en el fiber (para getCurrentRuntime())
|
|
3033
|
+
runtime;
|
|
3034
|
+
closing = null;
|
|
3035
|
+
finishing = false;
|
|
3036
|
+
runState = RUN.RUNNING;
|
|
3037
|
+
interrupted = false;
|
|
3038
|
+
result = null;
|
|
3039
|
+
joiners = [];
|
|
3040
|
+
// estado de evaluación
|
|
3041
|
+
current;
|
|
3042
|
+
stack = [];
|
|
3043
|
+
fiberFinalizers = [];
|
|
3044
|
+
finalizersDrained = false;
|
|
3045
|
+
fiberContext;
|
|
3046
|
+
name;
|
|
3047
|
+
scopeId;
|
|
3048
|
+
lane;
|
|
3049
|
+
/**
|
|
3050
|
+
* Cached closure for the scheduler callback — avoids creating a new
|
|
3051
|
+
* closure on every `schedule()` call. The tag parameter used by the
|
|
3052
|
+
* scheduler is only part of the label string, not the callback logic,
|
|
3053
|
+
* so a single cached closure is sufficient.
|
|
3054
|
+
*/
|
|
3055
|
+
boundStep;
|
|
3056
|
+
// Reusable async callback state — avoids allocating a closure per Async step.
|
|
3057
|
+
// These fields are reset at the start of each Async case and reused.
|
|
3058
|
+
_syncResolved = false;
|
|
3059
|
+
_syncExit = null;
|
|
3060
|
+
_asyncRegistered = false;
|
|
3061
|
+
_asyncDetach;
|
|
3062
|
+
_asyncCb;
|
|
3063
|
+
constructor(runtime, effect) {
|
|
3064
|
+
this.id = nextId++;
|
|
3065
|
+
this.runtime = runtime;
|
|
3066
|
+
this.current = effect;
|
|
3067
|
+
this._asyncCb = (exit) => {
|
|
3068
|
+
if (this._syncResolved) return;
|
|
3069
|
+
if (this.result != null || this.closing != null) return;
|
|
3070
|
+
if (!this._asyncRegistered) {
|
|
3071
|
+
this._syncResolved = true;
|
|
3072
|
+
this._syncExit = exit;
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
this._syncResolved = true;
|
|
3076
|
+
this._asyncDetach?.();
|
|
3077
|
+
this._asyncDetach = void 0;
|
|
3078
|
+
if (exit._tag === "Success") {
|
|
3079
|
+
this.current = Async.succeed(exit.value);
|
|
3080
|
+
this.schedule("async-resume");
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
const cause = exit.cause;
|
|
3084
|
+
if (cause._tag === "Interrupt") {
|
|
3085
|
+
this.notify(Exit.failCause(Cause.interrupt()));
|
|
3086
|
+
} else if (cause._tag === "Fail") {
|
|
3087
|
+
this.current = Async.fail(cause.error);
|
|
3088
|
+
this.schedule("async-resume");
|
|
3089
|
+
} else {
|
|
3090
|
+
this.notify(Exit.failCause(Cause.die(cause.defect)));
|
|
3091
|
+
}
|
|
3092
|
+
};
|
|
3093
|
+
this.boundStep = () => {
|
|
3094
|
+
withCurrentFiber(this, () => {
|
|
3095
|
+
if (this.runState === RUN.DONE) return;
|
|
3096
|
+
this.runState = RUN.RUNNING;
|
|
3097
|
+
const decision = this.step();
|
|
3098
|
+
switch (decision) {
|
|
3099
|
+
case STEP.CONTINUE:
|
|
3100
|
+
this.schedule("continue");
|
|
3101
|
+
return;
|
|
3102
|
+
case STEP.SUSPEND:
|
|
3103
|
+
this.runState = RUN.SUSPENDED;
|
|
3104
|
+
this.emit({ type: "fiber.suspend", fiberId: this.id });
|
|
3105
|
+
return;
|
|
3106
|
+
case STEP.DONE:
|
|
3107
|
+
this.runState = RUN.DONE;
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
});
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
// helpers para no tocar el resto del código
|
|
3114
|
+
get env() {
|
|
3115
|
+
return this.runtime.env;
|
|
3116
|
+
}
|
|
3117
|
+
get scheduler() {
|
|
3118
|
+
return this.runtime.scheduler ?? globalScheduler;
|
|
3119
|
+
}
|
|
3120
|
+
emit(ev) {
|
|
3121
|
+
this.runtime.hooks.emit(ev, {
|
|
3122
|
+
fiberId: this.id,
|
|
3123
|
+
scopeId: this.scopeId,
|
|
3124
|
+
traceId: this.fiberContext?.trace?.traceId,
|
|
3125
|
+
spanId: this.fiberContext?.trace?.spanId
|
|
3126
|
+
});
|
|
3127
|
+
}
|
|
3128
|
+
addFinalizer(f) {
|
|
3129
|
+
this.fiberFinalizers.push({ run: f });
|
|
3130
|
+
}
|
|
3131
|
+
/**
|
|
3132
|
+
* Internal finalizers used for suspend cancelers. They are detached as soon
|
|
3133
|
+
* as the async operation completes, so completed HTTP/promises do not keep
|
|
3134
|
+
* canceler closures alive until the fiber itself finishes.
|
|
3135
|
+
*/
|
|
3136
|
+
addTransientFinalizer(f) {
|
|
3137
|
+
const rec = { run: f };
|
|
3138
|
+
this.fiberFinalizers.push(rec);
|
|
3139
|
+
return () => {
|
|
3140
|
+
rec.run = void 0;
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
3143
|
+
status() {
|
|
3144
|
+
if (this.result == null) return "Running";
|
|
3145
|
+
if (this.result._tag === "Failure" && this.result.cause._tag === "Interrupt") return "Interrupted";
|
|
3146
|
+
return "Done";
|
|
3147
|
+
}
|
|
3148
|
+
join(cb) {
|
|
3149
|
+
if (this.result != null) cb(this.result);
|
|
3150
|
+
else this.joiners.push(cb);
|
|
3151
|
+
}
|
|
3152
|
+
interrupt() {
|
|
3153
|
+
if (this.result != null) return;
|
|
3154
|
+
if (this.interrupted) return;
|
|
3155
|
+
this.interrupted = true;
|
|
3156
|
+
this.schedule("interrupt-step");
|
|
3157
|
+
}
|
|
3158
|
+
schedule(tag = "step") {
|
|
3159
|
+
if (this.runState === RUN.DONE || this.runState === RUN.QUEUED) return;
|
|
3160
|
+
if (this.runState === RUN.SUSPENDED) {
|
|
3161
|
+
this.emit({ type: "fiber.resume", fiberId: this.id });
|
|
3162
|
+
}
|
|
3163
|
+
this.runState = RUN.QUEUED;
|
|
3164
|
+
const label = `fiber#${this.id}.${tag}`;
|
|
3165
|
+
const result = this.scheduler.schedule(
|
|
3166
|
+
this.boundStep,
|
|
3167
|
+
this.lane ? laneTag(this.lane, label) : label
|
|
3168
|
+
);
|
|
3169
|
+
if (result === "dropped") {
|
|
3170
|
+
this.runState = RUN.DONE;
|
|
3171
|
+
this.notify(Exit.failCause(Cause.die(new Error(`Brass scheduler dropped ${label} because the lane queue is full`))));
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
runFinalizersOnce(exit) {
|
|
3175
|
+
if (this.finalizersDrained) return;
|
|
3176
|
+
this.finalizersDrained = true;
|
|
3177
|
+
while (this.fiberFinalizers.length > 0) {
|
|
3178
|
+
const fin = this.fiberFinalizers.pop();
|
|
3179
|
+
const run = fin.run;
|
|
3180
|
+
fin.run = void 0;
|
|
3181
|
+
if (!run) continue;
|
|
3182
|
+
try {
|
|
3183
|
+
const eff = run(exit);
|
|
3184
|
+
if (eff && typeof eff === "object" && "_tag" in eff) {
|
|
3185
|
+
unsafeRunAsync(eff, this.env, () => {
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
} catch {
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
notify(exit) {
|
|
3193
|
+
if (this.result != null) return;
|
|
3194
|
+
if (this.closing != null) return;
|
|
3195
|
+
this.finishing = true;
|
|
3196
|
+
this.closing = exit;
|
|
3197
|
+
this.runFinalizersOnce(exit);
|
|
3198
|
+
this.result = exit;
|
|
3199
|
+
const status = exit._tag === "Success" ? "success" : exit.cause._tag === "Interrupt" ? "interrupted" : "failure";
|
|
3200
|
+
this.emit({
|
|
3201
|
+
type: "fiber.end",
|
|
3202
|
+
fiberId: this.id,
|
|
3203
|
+
status,
|
|
3204
|
+
error: exit._tag === "Failure" ? exit.cause : void 0
|
|
3205
|
+
});
|
|
3206
|
+
for (const j of this.joiners) j(exit);
|
|
3207
|
+
this.joiners.length = 0;
|
|
3208
|
+
}
|
|
3209
|
+
onSuccess(value) {
|
|
3210
|
+
const frame = this.stack.pop();
|
|
3211
|
+
if (!frame) {
|
|
3212
|
+
this.notify(Exit.succeed(value));
|
|
3213
|
+
return;
|
|
3214
|
+
}
|
|
3215
|
+
if (frame._tag === "SuccessCont") {
|
|
3216
|
+
try {
|
|
3217
|
+
this.current = frame.k(value);
|
|
3218
|
+
} catch (e) {
|
|
3219
|
+
this.notify(Exit.failCause(Cause.die(e)));
|
|
3220
|
+
}
|
|
3221
|
+
return;
|
|
3222
|
+
}
|
|
3223
|
+
try {
|
|
3224
|
+
this.current = frame.onSuccess(value);
|
|
3225
|
+
} catch (e) {
|
|
3226
|
+
this.notify(Exit.failCause(Cause.die(e)));
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
onFailure(error) {
|
|
3230
|
+
while (this.stack.length > 0) {
|
|
3231
|
+
const fr = this.stack.pop();
|
|
3232
|
+
if (fr._tag === "FoldCont") {
|
|
3233
|
+
try {
|
|
3234
|
+
this.current = fr.onFailure(error);
|
|
3235
|
+
return;
|
|
3236
|
+
} catch (e) {
|
|
3237
|
+
error = e;
|
|
3238
|
+
continue;
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
this.notify(Exit.failCause(Cause.fail(error)));
|
|
3243
|
+
}
|
|
3244
|
+
budget = DEFAULT_BUDGET2;
|
|
3245
|
+
step() {
|
|
3246
|
+
if (this.result != null) return STEP.DONE;
|
|
3247
|
+
if (this.interrupted) {
|
|
3248
|
+
this.notify(Exit.failCause(Cause.interrupt()));
|
|
3249
|
+
return STEP.DONE;
|
|
3250
|
+
}
|
|
3251
|
+
this.budget = __benchmarkBudget ?? DEFAULT_BUDGET2;
|
|
3252
|
+
while (this.budget-- > 0) {
|
|
3253
|
+
if (this.current._tag === "FlatMap") {
|
|
3254
|
+
const trampolineResult = this.syncTrampoline();
|
|
3255
|
+
if (trampolineResult === TRAMPOLINE.DONE) {
|
|
3256
|
+
return STEP.DONE;
|
|
3257
|
+
}
|
|
3258
|
+
if (trampolineResult === TRAMPOLINE.SUSPEND) {
|
|
3259
|
+
return STEP.SUSPEND;
|
|
3260
|
+
}
|
|
3261
|
+
if (this.result != null) return STEP.DONE;
|
|
3262
|
+
if (this.budget <= 0) return STEP.CONTINUE;
|
|
3263
|
+
}
|
|
3264
|
+
const current = this.current;
|
|
3265
|
+
switch (current._tag) {
|
|
3266
|
+
case "Succeed": {
|
|
3267
|
+
this.onSuccess(current.value);
|
|
3268
|
+
break;
|
|
3269
|
+
}
|
|
3270
|
+
case "Fail": {
|
|
3271
|
+
this.onFailure(current.error);
|
|
3272
|
+
break;
|
|
3273
|
+
}
|
|
3274
|
+
case "FlatMap": {
|
|
3275
|
+
this.stack.push({ _tag: "SuccessCont", k: current.andThen });
|
|
3276
|
+
this.current = current.first;
|
|
3277
|
+
break;
|
|
3278
|
+
}
|
|
3279
|
+
case "Fold": {
|
|
3280
|
+
this.stack.push({
|
|
3281
|
+
_tag: "FoldCont",
|
|
3282
|
+
onFailure: current.onFailure,
|
|
3283
|
+
onSuccess: current.onSuccess
|
|
3284
|
+
});
|
|
3285
|
+
this.current = current.first;
|
|
3286
|
+
break;
|
|
3287
|
+
}
|
|
3288
|
+
case "Async": {
|
|
3289
|
+
if (this.finishing) {
|
|
3290
|
+
return this.result != null ? STEP.DONE : STEP.CONTINUE;
|
|
3291
|
+
}
|
|
3292
|
+
this._syncResolved = false;
|
|
3293
|
+
this._syncExit = null;
|
|
3294
|
+
this._asyncRegistered = false;
|
|
3295
|
+
const canceler = current.register(this.env, this._asyncCb);
|
|
3296
|
+
this._asyncRegistered = true;
|
|
3297
|
+
if (this._syncResolved && this._syncExit) {
|
|
3298
|
+
const resolvedExit = this._syncExit;
|
|
3299
|
+
this._syncExit = null;
|
|
3300
|
+
if (resolvedExit._tag === "Success") {
|
|
3301
|
+
this.onSuccess(resolvedExit.value);
|
|
3302
|
+
} else {
|
|
3303
|
+
const cause = resolvedExit.cause;
|
|
3304
|
+
if (cause._tag === "Interrupt") {
|
|
3305
|
+
this.notify(Exit.failCause(Cause.interrupt()));
|
|
3306
|
+
} else if (cause._tag === "Fail") {
|
|
3307
|
+
this.onFailure(cause.error);
|
|
3308
|
+
} else {
|
|
3309
|
+
this.notify(Exit.failCause(Cause.die(cause.defect)));
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
break;
|
|
3313
|
+
}
|
|
3314
|
+
if (typeof canceler === "function") {
|
|
3315
|
+
this._asyncDetach = this.addTransientFinalizer(() => {
|
|
3316
|
+
if (this._syncResolved) return;
|
|
3317
|
+
this._syncResolved = true;
|
|
3318
|
+
this._asyncDetach = void 0;
|
|
3319
|
+
try {
|
|
3320
|
+
canceler();
|
|
3321
|
+
} catch {
|
|
3322
|
+
}
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
return STEP.SUSPEND;
|
|
3326
|
+
}
|
|
3327
|
+
case "Fork": {
|
|
3328
|
+
const child = this.runtime.fork(current.effect, current.scopeId);
|
|
3329
|
+
this.onSuccess(child);
|
|
3330
|
+
break;
|
|
3331
|
+
}
|
|
3332
|
+
case "Sync": {
|
|
3333
|
+
try {
|
|
3334
|
+
const a = current.thunk(this.env);
|
|
3335
|
+
this.onSuccess(a);
|
|
3336
|
+
} catch (e) {
|
|
3337
|
+
this.onFailure(e);
|
|
3338
|
+
}
|
|
3339
|
+
break;
|
|
3340
|
+
}
|
|
3341
|
+
default: {
|
|
3342
|
+
this.onFailure(new Error(`Unknown opcode: ${current._tag}`));
|
|
3343
|
+
return STEP.CONTINUE;
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
if (this.result != null) return STEP.DONE;
|
|
3347
|
+
}
|
|
3348
|
+
return STEP.CONTINUE;
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3351
|
+
* Sync trampoline: processes FlatMap chains in a tight loop without the
|
|
3352
|
+
* overhead of the general switch/case. Handles:
|
|
3353
|
+
* - FlatMap(Async(sync), k) — the queue/stream hot path
|
|
3354
|
+
* - FlatMap(Succeed(v), k) — pure value chains
|
|
3355
|
+
* - FlatMap(Sync(f), k) — synchronous thunks
|
|
3356
|
+
* - FlatMap(FlatMap(...), k) — left-associated chains (reassociates inline)
|
|
3357
|
+
* - FlatMap(Fold(...), k) — pushes fold frame and continues
|
|
3358
|
+
*
|
|
3359
|
+
* Returns TRAMPOLINE.CONTINUE when it hits a node it can't handle,
|
|
3360
|
+
* leaving this.current set to that node for the normal switch to process.
|
|
3361
|
+
*/
|
|
3362
|
+
syncTrampoline() {
|
|
3363
|
+
while (this.budget-- > 0) {
|
|
3364
|
+
let cur = this.current;
|
|
3365
|
+
while (cur._tag === "FlatMap" && cur.first?._tag === "FlatMap") {
|
|
3366
|
+
this.stack.push({ _tag: "SuccessCont", k: cur.andThen });
|
|
3367
|
+
cur = cur.first;
|
|
3368
|
+
}
|
|
3369
|
+
if (cur._tag !== "FlatMap") {
|
|
3370
|
+
this.current = cur;
|
|
3371
|
+
return TRAMPOLINE.CONTINUE;
|
|
3372
|
+
}
|
|
3373
|
+
const first = cur.first;
|
|
3374
|
+
const andThen = cur.andThen;
|
|
3375
|
+
switch (first._tag) {
|
|
3376
|
+
case "Succeed": {
|
|
3377
|
+
try {
|
|
3378
|
+
this.current = andThen(first.value);
|
|
3379
|
+
} catch (e) {
|
|
3380
|
+
this.notify(Exit.failCause(Cause.die(e)));
|
|
3381
|
+
return TRAMPOLINE.DONE;
|
|
3382
|
+
}
|
|
3383
|
+
continue;
|
|
3384
|
+
}
|
|
3385
|
+
case "Sync": {
|
|
3386
|
+
try {
|
|
3387
|
+
const value = first.thunk(this.env);
|
|
3388
|
+
this.current = andThen(value);
|
|
3389
|
+
} catch (e) {
|
|
3390
|
+
this.notify(Exit.failCause(Cause.die(e)));
|
|
3391
|
+
return TRAMPOLINE.DONE;
|
|
3392
|
+
}
|
|
3393
|
+
continue;
|
|
3394
|
+
}
|
|
3395
|
+
case "Fail": {
|
|
3396
|
+
this.stack.push({ _tag: "SuccessCont", k: andThen });
|
|
3397
|
+
this.current = first;
|
|
3398
|
+
return TRAMPOLINE.CONTINUE;
|
|
3399
|
+
}
|
|
3400
|
+
case "Async": {
|
|
3401
|
+
if (this.finishing) {
|
|
3402
|
+
this.current = cur;
|
|
3403
|
+
return TRAMPOLINE.CONTINUE;
|
|
3404
|
+
}
|
|
3405
|
+
this._syncResolved = false;
|
|
3406
|
+
this._syncExit = null;
|
|
3407
|
+
this._asyncRegistered = false;
|
|
3408
|
+
const canceler = first.register(this.env, this._asyncCb);
|
|
3409
|
+
this._asyncRegistered = true;
|
|
3410
|
+
if (this._syncResolved && this._syncExit) {
|
|
3411
|
+
const exit = this._syncExit;
|
|
3412
|
+
this._syncExit = null;
|
|
3413
|
+
if (exit._tag === "Success") {
|
|
3414
|
+
try {
|
|
3415
|
+
this.current = andThen(exit.value);
|
|
3416
|
+
} catch (e) {
|
|
3417
|
+
this.notify(Exit.failCause(Cause.die(e)));
|
|
3418
|
+
return TRAMPOLINE.DONE;
|
|
3419
|
+
}
|
|
3420
|
+
continue;
|
|
3421
|
+
} else {
|
|
3422
|
+
this.stack.push({ _tag: "SuccessCont", k: andThen });
|
|
3423
|
+
const cause = exit.cause;
|
|
3424
|
+
if (cause._tag === "Fail") {
|
|
3425
|
+
this.onFailure(cause.error);
|
|
3426
|
+
} else if (cause._tag === "Interrupt") {
|
|
3427
|
+
this.notify(Exit.failCause(Cause.interrupt()));
|
|
3428
|
+
} else {
|
|
3429
|
+
this.notify(Exit.failCause(Cause.die(cause.defect)));
|
|
3430
|
+
}
|
|
3431
|
+
return this.result != null ? TRAMPOLINE.DONE : TRAMPOLINE.CONTINUE;
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
this.stack.push({ _tag: "SuccessCont", k: andThen });
|
|
3435
|
+
if (typeof canceler === "function") {
|
|
3436
|
+
this._asyncDetach = this.addTransientFinalizer(() => {
|
|
3437
|
+
if (this._syncResolved) return;
|
|
3438
|
+
this._syncResolved = true;
|
|
3439
|
+
this._asyncDetach = void 0;
|
|
3440
|
+
try {
|
|
3441
|
+
canceler();
|
|
3442
|
+
} catch {
|
|
3443
|
+
}
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
return TRAMPOLINE.SUSPEND;
|
|
3447
|
+
}
|
|
3448
|
+
case "Fold": {
|
|
3449
|
+
this.stack.push({ _tag: "SuccessCont", k: andThen });
|
|
3450
|
+
this.stack.push({
|
|
3451
|
+
_tag: "FoldCont",
|
|
3452
|
+
onFailure: first.onFailure,
|
|
3453
|
+
onSuccess: first.onSuccess
|
|
3454
|
+
});
|
|
3455
|
+
this.current = first.first;
|
|
3456
|
+
continue;
|
|
3457
|
+
}
|
|
3458
|
+
default: {
|
|
3459
|
+
this.stack.push({ _tag: "SuccessCont", k: andThen });
|
|
3460
|
+
this.current = first;
|
|
3461
|
+
return TRAMPOLINE.CONTINUE;
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
return TRAMPOLINE.CONTINUE;
|
|
3466
|
+
}
|
|
3467
|
+
};
|
|
3468
|
+
function getCurrentFiber() {
|
|
3469
|
+
return _current;
|
|
3470
|
+
}
|
|
3471
|
+
function unsafeGetCurrentRuntime() {
|
|
3472
|
+
const f = getCurrentFiber();
|
|
3473
|
+
if (!f?.runtime) {
|
|
3474
|
+
throw new Error("unsafeGetCurrentRuntime: no current fiber/runtime");
|
|
3475
|
+
}
|
|
3476
|
+
return f.runtime;
|
|
3477
|
+
}
|
|
3478
|
+
function withCurrentFiber(fiber, f) {
|
|
3479
|
+
const prev = _current;
|
|
3480
|
+
_current = fiber;
|
|
3481
|
+
try {
|
|
3482
|
+
return f();
|
|
3483
|
+
} finally {
|
|
3484
|
+
_current = prev;
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
// src/core/runtime/engine/JsFiberEngine.ts
|
|
3489
|
+
var JsFiberEngine = class {
|
|
3490
|
+
constructor(runtime) {
|
|
3491
|
+
this.runtime = runtime;
|
|
3492
|
+
}
|
|
3493
|
+
runtime;
|
|
3494
|
+
kind = "ts";
|
|
3495
|
+
startedFibers = 0;
|
|
3496
|
+
fork(effect, scopeId) {
|
|
3497
|
+
this.startedFibers += 1;
|
|
3498
|
+
const fiber = new RuntimeFiber(this.runtime, effect);
|
|
3499
|
+
if (scopeId !== void 0) fiber.scopeId = scopeId;
|
|
3500
|
+
return fiber;
|
|
3501
|
+
}
|
|
3502
|
+
stats() {
|
|
3503
|
+
return {
|
|
3504
|
+
engine: this.kind,
|
|
3505
|
+
startedFibers: this.startedFibers,
|
|
3506
|
+
runningFibers: 0,
|
|
3507
|
+
suspendedFibers: 0,
|
|
3508
|
+
queuedFibers: 0,
|
|
3509
|
+
completedFibers: 0,
|
|
3510
|
+
failedFibers: 0,
|
|
3511
|
+
interruptedFibers: 0,
|
|
3512
|
+
pendingHostEffects: 0
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
};
|
|
3516
|
+
|
|
3517
|
+
// src/core/runtime/scope.ts
|
|
3518
|
+
var nextScopeId = 1;
|
|
3519
|
+
function awaitAll(fibers) {
|
|
3520
|
+
return async((_env, cb) => {
|
|
3521
|
+
let remaining = fibers.length;
|
|
3522
|
+
if (remaining === 0) {
|
|
3523
|
+
cb({ _tag: "Success", value: void 0 });
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
for (const f of fibers) {
|
|
3527
|
+
f.join(() => {
|
|
3528
|
+
remaining -= 1;
|
|
3529
|
+
if (remaining === 0) cb({ _tag: "Success", value: void 0 });
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
});
|
|
3533
|
+
}
|
|
3534
|
+
var Scope = class _Scope {
|
|
3535
|
+
constructor(runtime, parentScopeId) {
|
|
3536
|
+
this.runtime = runtime;
|
|
3537
|
+
this.parentScopeId = parentScopeId;
|
|
3538
|
+
this.id = nextScopeId++;
|
|
3539
|
+
const inferredParent = this.parentScopeId ?? getCurrentFiber()?.scopeId;
|
|
3540
|
+
if (this.runtime.hasActiveHooks()) {
|
|
3541
|
+
this.runtime.emit({
|
|
3542
|
+
type: "scope.open",
|
|
3543
|
+
scopeId: this.id,
|
|
3544
|
+
parentScopeId: inferredParent
|
|
3545
|
+
});
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
runtime;
|
|
3549
|
+
parentScopeId;
|
|
3550
|
+
id;
|
|
3551
|
+
closed = false;
|
|
3552
|
+
children = /* @__PURE__ */ new Set();
|
|
3553
|
+
subScopes = /* @__PURE__ */ new Set();
|
|
3554
|
+
finalizers = [];
|
|
3555
|
+
/** registra un finalizer (LIFO) */
|
|
3556
|
+
addFinalizer(f) {
|
|
3557
|
+
if (this.closed) {
|
|
3558
|
+
throw new Error("Trying to add finalizer to closed scope");
|
|
3559
|
+
}
|
|
3560
|
+
this.finalizers.push(f);
|
|
3561
|
+
}
|
|
3562
|
+
/** crea un sub scope (mismo runtime) */
|
|
3563
|
+
subScope() {
|
|
3564
|
+
if (this.closed) throw new Error("Scope closed");
|
|
3565
|
+
const s = new _Scope(this.runtime, this.id);
|
|
3566
|
+
this.subScopes.add(s);
|
|
3567
|
+
return s;
|
|
3568
|
+
}
|
|
3569
|
+
/** ✅ fork en este scope */
|
|
3570
|
+
fork(eff) {
|
|
3571
|
+
if (this.closed) throw new Error("Scope closed");
|
|
3572
|
+
const f = this.runtime.fork(eff, this.id);
|
|
3573
|
+
this.children.add(f);
|
|
3574
|
+
f.join(() => this.children.delete(f));
|
|
3575
|
+
return f;
|
|
3576
|
+
}
|
|
3577
|
+
/** close fire-and-forget (no bloquea) */
|
|
3578
|
+
close(exit = { _tag: "Success", value: void 0 }) {
|
|
3579
|
+
this.runtime.fork(this.closeAsync(exit));
|
|
3580
|
+
}
|
|
3581
|
+
/** Emit the scope.close event if hooks are active. */
|
|
3582
|
+
emitCloseEvent(exit) {
|
|
3583
|
+
if (this.runtime.hasActiveHooks()) {
|
|
3584
|
+
const status = exit._tag === "Success" ? "success" : exit.cause._tag === "Interrupt" ? "interrupted" : "failure";
|
|
3585
|
+
this.runtime.emit({
|
|
3586
|
+
type: "scope.close",
|
|
3587
|
+
scopeId: this.id,
|
|
3588
|
+
status,
|
|
3589
|
+
error: exit._tag === "Failure" && exit.cause._tag === "Fail" ? exit.cause.error : void 0
|
|
3590
|
+
});
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
/**
|
|
3594
|
+
* Build an effect that executes finalizers in LIFO order.
|
|
3595
|
+
*
|
|
3596
|
+
* Optimization over the original: instead of wrapping every finalizer in
|
|
3597
|
+
* `asyncFold(fin(exit), () => unit(), () => unit())` which creates 3 effect
|
|
3598
|
+
* nodes per finalizer (Fold + 2 Succeed), we use a single Sync thunk per
|
|
3599
|
+
* finalizer that catches errors inline. When the finalizer returns a
|
|
3600
|
+
* Succeed effect (like `unit()`), the Sync thunk completes without creating
|
|
3601
|
+
* additional effect nodes.
|
|
3602
|
+
*/
|
|
3603
|
+
buildFinalizerEffect(exit) {
|
|
3604
|
+
const fins = this.finalizers;
|
|
3605
|
+
if (fins.length === 0) return unit();
|
|
3606
|
+
let chain = unit();
|
|
3607
|
+
for (let i = fins.length - 1; i >= 0; i--) {
|
|
3608
|
+
const fin = fins[i];
|
|
3609
|
+
chain = asyncFlatMap(chain, () => {
|
|
3610
|
+
let result;
|
|
3611
|
+
try {
|
|
3612
|
+
result = fin(exit);
|
|
3613
|
+
} catch {
|
|
3614
|
+
return unit();
|
|
3615
|
+
}
|
|
3616
|
+
if (result._tag === "Succeed") {
|
|
3617
|
+
return unit();
|
|
3618
|
+
}
|
|
3619
|
+
return asyncFold(
|
|
3620
|
+
result,
|
|
3621
|
+
() => unit(),
|
|
3622
|
+
() => unit()
|
|
3623
|
+
);
|
|
3624
|
+
});
|
|
3625
|
+
}
|
|
3626
|
+
return chain;
|
|
3627
|
+
}
|
|
3628
|
+
closeAsync(exit = { _tag: "Success", value: void 0 }, opts = { awaitChildren: true }) {
|
|
3629
|
+
return asyncFlatMap(
|
|
3630
|
+
unit(),
|
|
3631
|
+
() => async((env, cb) => {
|
|
3632
|
+
if (this.closed) {
|
|
3633
|
+
cb({ _tag: "Success", value: void 0 });
|
|
3634
|
+
return;
|
|
3635
|
+
}
|
|
3636
|
+
this.closed = true;
|
|
3637
|
+
const children = Array.from(this.children);
|
|
3638
|
+
const subScopes = Array.from(this.subScopes);
|
|
3639
|
+
for (const child of children) {
|
|
3640
|
+
child.interrupt();
|
|
3641
|
+
}
|
|
3642
|
+
const closeSubs = subScopes.reduceRight(
|
|
3643
|
+
(acc, s) => asyncFlatMap(acc, () => s.closeAsync(exit, opts)),
|
|
3644
|
+
unit()
|
|
3645
|
+
);
|
|
3646
|
+
const runFinalizers = this.buildFinalizerEffect(exit);
|
|
3647
|
+
const needsAwait = opts.awaitChildren && children.length > 0;
|
|
3648
|
+
const awaitChildrenEff = needsAwait ? awaitAll(children) : unit();
|
|
3649
|
+
const hasSubScopes = subScopes.length > 0;
|
|
3650
|
+
const hasNoFinalizers = this.finalizers.length === 0;
|
|
3651
|
+
if (!hasSubScopes && !needsAwait && hasNoFinalizers) {
|
|
3652
|
+
this.emitCloseEvent(exit);
|
|
3653
|
+
cb({ _tag: "Success", value: void 0 });
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
const all = asyncFlatMap(closeSubs, () => asyncFlatMap(awaitChildrenEff, () => runFinalizers));
|
|
3657
|
+
this.runtime.fork(all).join(() => {
|
|
3658
|
+
this.emitCloseEvent(exit);
|
|
3659
|
+
cb({ _tag: "Success", value: void 0 });
|
|
3660
|
+
});
|
|
3661
|
+
})
|
|
3662
|
+
);
|
|
3663
|
+
}
|
|
3664
|
+
};
|
|
3665
|
+
function withScopeAsync(runtime, f) {
|
|
3666
|
+
return async((_env, cb) => {
|
|
3667
|
+
const scope = new Scope(runtime);
|
|
3668
|
+
let done = false;
|
|
3669
|
+
const completeAfterClose = (exit) => {
|
|
3670
|
+
runtime.fork(scope.closeAsync(exit)).join(() => {
|
|
3671
|
+
if (done) return;
|
|
3672
|
+
done = true;
|
|
3673
|
+
cb(exit);
|
|
3674
|
+
});
|
|
3675
|
+
};
|
|
3676
|
+
const fiber = runtime.fork(f(scope));
|
|
3677
|
+
fiber.join(completeAfterClose);
|
|
3678
|
+
return () => {
|
|
3679
|
+
if (done) return;
|
|
3680
|
+
fiber.interrupt();
|
|
3681
|
+
runtime.fork(scope.closeAsync(Exit.failCause(Cause.interrupt())));
|
|
3682
|
+
};
|
|
3683
|
+
});
|
|
3684
|
+
}
|
|
3685
|
+
function withScope(runtime, f) {
|
|
3686
|
+
return withScopeAsync(runtime, (scope) => {
|
|
3687
|
+
const out = f(scope);
|
|
3688
|
+
if (out && typeof out === "object" && "_tag" in out) return out;
|
|
3689
|
+
return unit();
|
|
3690
|
+
});
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
// src/core/stream/structuredConcurrency.ts
|
|
3694
|
+
function race(left, right, parentScope) {
|
|
3695
|
+
return async((env, cb) => {
|
|
3696
|
+
const scope = parentScope.subScope();
|
|
3697
|
+
let done = false;
|
|
3698
|
+
const onResult = (exit) => {
|
|
3699
|
+
if (done) return;
|
|
3700
|
+
done = true;
|
|
3701
|
+
scope.close(exit);
|
|
3702
|
+
cb(exit);
|
|
3703
|
+
};
|
|
3704
|
+
const fiberLeft = scope.fork(left);
|
|
3705
|
+
const fiberRight = scope.fork(right);
|
|
3706
|
+
fiberLeft.join(onResult);
|
|
3707
|
+
fiberRight.join(onResult);
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
function zipPar(left, right, parentScope) {
|
|
3711
|
+
return async((env, cb) => {
|
|
3712
|
+
const scope = parentScope.subScope();
|
|
3713
|
+
let leftExit = null;
|
|
3714
|
+
let rightExit = null;
|
|
3715
|
+
let done = false;
|
|
3716
|
+
const checkDone = () => {
|
|
3717
|
+
if (!leftExit || !rightExit || done) return;
|
|
3718
|
+
done = true;
|
|
3719
|
+
if (leftExit._tag === "Success" && rightExit._tag === "Success") {
|
|
3720
|
+
scope.close({ _tag: "Success", value: void 0 });
|
|
3721
|
+
cb({
|
|
3722
|
+
_tag: "Success",
|
|
3723
|
+
value: [leftExit.value, rightExit.value]
|
|
3724
|
+
});
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
let cause;
|
|
3728
|
+
if (leftExit._tag === "Failure") {
|
|
3729
|
+
cause = leftExit.cause;
|
|
3730
|
+
} else if (rightExit._tag === "Failure") {
|
|
3731
|
+
cause = rightExit.cause;
|
|
3732
|
+
} else {
|
|
3733
|
+
throw new Error("zipPar: unreachable state (no Failure exit)");
|
|
3734
|
+
}
|
|
3735
|
+
const errExit = {
|
|
3736
|
+
_tag: "Failure",
|
|
3737
|
+
cause
|
|
3738
|
+
};
|
|
3739
|
+
scope.close(errExit);
|
|
3740
|
+
cb(errExit);
|
|
3741
|
+
};
|
|
3742
|
+
const f1 = scope.fork(left);
|
|
3743
|
+
const f2 = scope.fork(right);
|
|
3744
|
+
f1.join((exit) => {
|
|
3745
|
+
leftExit = exit;
|
|
3746
|
+
checkDone();
|
|
3747
|
+
});
|
|
3748
|
+
f2.join((exit) => {
|
|
3749
|
+
rightExit = exit;
|
|
3750
|
+
checkDone();
|
|
3751
|
+
});
|
|
3752
|
+
});
|
|
3753
|
+
}
|
|
3754
|
+
function collectAllPar(effects, parentScope) {
|
|
3755
|
+
return async((env, cb) => {
|
|
3756
|
+
const scope = parentScope.subScope();
|
|
3757
|
+
const results = new Array(effects.length);
|
|
3758
|
+
let completed = 0;
|
|
3759
|
+
let done = false;
|
|
3760
|
+
effects.forEach((eff, i) => {
|
|
3761
|
+
const f = scope.fork(eff);
|
|
3762
|
+
f.join((exit) => {
|
|
3763
|
+
if (done) return;
|
|
3764
|
+
if (exit._tag === "Failure") {
|
|
3765
|
+
done = true;
|
|
3766
|
+
const errExit = {
|
|
3767
|
+
_tag: "Failure",
|
|
3768
|
+
cause: exit.cause
|
|
3769
|
+
};
|
|
3770
|
+
scope.close(errExit);
|
|
3771
|
+
cb(errExit);
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
3774
|
+
results[i] = exit.value;
|
|
3775
|
+
completed++;
|
|
3776
|
+
if (completed === effects.length) {
|
|
3777
|
+
done = true;
|
|
3778
|
+
const successExit = {
|
|
3779
|
+
_tag: "Success",
|
|
3780
|
+
value: results
|
|
3781
|
+
};
|
|
3782
|
+
scope.close({ _tag: "Success", value: void 0 });
|
|
3783
|
+
cb(successExit);
|
|
3784
|
+
}
|
|
3785
|
+
});
|
|
3786
|
+
});
|
|
3787
|
+
});
|
|
3788
|
+
}
|
|
3789
|
+
function raceWith(left, right, parentScope, onLeft, onRight) {
|
|
3790
|
+
return async((env, cb) => {
|
|
3791
|
+
const scope = parentScope.subScope();
|
|
3792
|
+
let done = false;
|
|
3793
|
+
const fiberLeft = scope.fork(left);
|
|
3794
|
+
const fiberRight = scope.fork(right);
|
|
3795
|
+
const finish = (next) => {
|
|
3796
|
+
scope.fork(next).join((exitNext) => {
|
|
3797
|
+
scope.close(exitNext);
|
|
3798
|
+
cb(exitNext);
|
|
3799
|
+
});
|
|
3800
|
+
};
|
|
3801
|
+
fiberLeft.join((exitL) => {
|
|
3802
|
+
if (done) return;
|
|
3803
|
+
done = true;
|
|
3804
|
+
finish(onLeft(exitL, fiberRight, scope));
|
|
3805
|
+
});
|
|
3806
|
+
fiberRight.join((exitR) => {
|
|
3807
|
+
if (done) return;
|
|
3808
|
+
done = true;
|
|
3809
|
+
finish(onRight(exitR, fiberLeft, scope));
|
|
3810
|
+
});
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
export {
|
|
3815
|
+
Async,
|
|
3816
|
+
asyncFold,
|
|
3817
|
+
asyncCatchAll,
|
|
3818
|
+
asyncMapError,
|
|
3819
|
+
unit,
|
|
3820
|
+
asyncSucceed,
|
|
3821
|
+
asyncFail,
|
|
3822
|
+
asyncSync,
|
|
3823
|
+
asyncTotal,
|
|
3824
|
+
async,
|
|
3825
|
+
asyncMap,
|
|
3826
|
+
asyncFlatMap,
|
|
3827
|
+
acquireRelease,
|
|
3828
|
+
asyncInterruptible,
|
|
3829
|
+
withAsyncPromise,
|
|
3830
|
+
mapAsync,
|
|
3831
|
+
mapTryAsync,
|
|
3832
|
+
none,
|
|
3833
|
+
some,
|
|
3834
|
+
Cause,
|
|
3835
|
+
Exit,
|
|
3836
|
+
succeed,
|
|
3837
|
+
fail,
|
|
3838
|
+
sync,
|
|
3839
|
+
map,
|
|
3840
|
+
flatMap,
|
|
3841
|
+
mapError,
|
|
3842
|
+
catchAll,
|
|
3843
|
+
orElseOptional,
|
|
3844
|
+
end,
|
|
3845
|
+
PushStatus,
|
|
3846
|
+
RingBuffer,
|
|
3847
|
+
resolveWasmModule,
|
|
3848
|
+
makeBoundedRingBuffer,
|
|
3849
|
+
sanitizeLaneKey,
|
|
3850
|
+
laneTag,
|
|
3851
|
+
inferCallerLaneFromStack,
|
|
3852
|
+
Scheduler,
|
|
3853
|
+
globalScheduler,
|
|
3854
|
+
DefaultHostExecutor,
|
|
3855
|
+
JsFiberEngine,
|
|
3856
|
+
HostRegistry,
|
|
3857
|
+
ProgramBuilder,
|
|
3858
|
+
EngineFiberHandle,
|
|
3859
|
+
ABI_VERSION,
|
|
3860
|
+
EVENT_WORDS,
|
|
3861
|
+
NONE_U32,
|
|
3862
|
+
OpcodeTagCode,
|
|
3863
|
+
EventKindCode,
|
|
3864
|
+
encodeOpcodeProgram,
|
|
3865
|
+
encodeOpcodeNodes,
|
|
3866
|
+
decodeEvent,
|
|
3867
|
+
decodeEventBatch,
|
|
3868
|
+
WasmPackFiberBridge,
|
|
3869
|
+
WasmFiberRegistryBridge,
|
|
3870
|
+
makeFiberReadyQueue,
|
|
3871
|
+
WasmFiberEngine,
|
|
3872
|
+
runtimeCapabilities,
|
|
3873
|
+
NoopHooks,
|
|
3874
|
+
Runtime,
|
|
3875
|
+
fork,
|
|
3876
|
+
runtimeForCaller,
|
|
3877
|
+
toPromiseByCaller,
|
|
3878
|
+
unsafeRunAsync,
|
|
3879
|
+
toPromise,
|
|
3880
|
+
abortablePromiseStats,
|
|
3881
|
+
resetAbortablePromiseStats,
|
|
3882
|
+
fromPromiseAbortable,
|
|
3883
|
+
unsafeRunFoldWithEnv,
|
|
3884
|
+
setBenchmarkBudget,
|
|
3885
|
+
getBenchmarkBudget,
|
|
3886
|
+
RuntimeFiber,
|
|
3887
|
+
getCurrentFiber,
|
|
3888
|
+
unsafeGetCurrentRuntime,
|
|
3889
|
+
withCurrentFiber,
|
|
3890
|
+
Scope,
|
|
3891
|
+
withScopeAsync,
|
|
3892
|
+
withScope,
|
|
3893
|
+
race,
|
|
3894
|
+
zipPar,
|
|
3895
|
+
collectAllPar,
|
|
3896
|
+
raceWith
|
|
3897
|
+
};
|