@andrew_l/app 0.3.8 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs DELETED
@@ -1,2678 +0,0 @@
1
- 'use strict';
2
-
3
- const toolkit = require('@andrew_l/toolkit');
4
- const os = require('node:os');
5
- const path = require('node:path');
6
- const consola = require('consola');
7
- const graceful = require('@andrew_l/graceful');
8
- const cp = require('node:child_process');
9
- const node_events = require('node:events');
10
- const commander = require('commander');
11
- const fs = require('node:fs');
12
- const dotenv = require('dotenv');
13
- const utils = require('consola/utils');
14
- const jsxRuntime = require('react/jsx-runtime');
15
- const ink = require('ink');
16
- const react = require('react');
17
-
18
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
19
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
20
-
21
- function _interopNamespaceCompat(e) {
22
- if (e && typeof e === 'object' && 'default' in e) return e;
23
- const n = Object.create(null);
24
- if (e) {
25
- for (const k in e) {
26
- n[k] = e[k];
27
- }
28
- }
29
- n.default = e;
30
- return n;
31
- }
32
-
33
- const os__default = /*#__PURE__*/_interopDefaultCompat(os);
34
- const path__default = /*#__PURE__*/_interopDefaultCompat(path);
35
- const cp__namespace = /*#__PURE__*/_interopNamespaceCompat(cp);
36
- const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
37
- const dotenv__default = /*#__PURE__*/_interopDefaultCompat(dotenv);
38
-
39
- const CONFIG = Object.freeze({
40
- IS_VRUN: toolkit.env.bool("VRUN", false),
41
- WATCH_MODE: toolkit.env.bool("VRUN_WATCH", false),
42
- /**
43
- * Root path of application
44
- */
45
- APP_ROOT: toolkit.env.string("APP_ROOT") || null,
46
- /**
47
- * Typescript resolver mode
48
- */
49
- TS_MODE: toolkit.env.string(
50
- "VRUN_TS_MODE",
51
- process.execArgv.some((v) => v.includes("tsx/dist/loader.mjs")) ? "tsx" : ""
52
- ) || null,
53
- /**
54
- * List of disabled workers
55
- */
56
- WORKER_DISABLED: new Set(toolkit.env.list("WORKER_DISABLE", "string"))
57
- });
58
-
59
- const isHelpArgument = (argv) => {
60
- if (!Array.isArray(argv)) {
61
- argv = [argv];
62
- }
63
- return argv.includes("--help") || argv.includes("-h");
64
- };
65
- function extractOptionsArgs(argv) {
66
- const result = [];
67
- let flag = false;
68
- for (const value of argv) {
69
- if (value.startsWith("--")) {
70
- flag = true;
71
- result.push(value);
72
- continue;
73
- }
74
- if (flag) {
75
- result.push(value);
76
- flag = false;
77
- }
78
- }
79
- return result;
80
- }
81
-
82
- function filePathFromStack(stack) {
83
- const stackLines = stack.trim().split("\n");
84
- const callerLine = stackLines[0];
85
- let filePath = null;
86
- const patterns = [
87
- /\((.+):(\d+):(\d+)\)$/,
88
- // (file:line:col)
89
- /at\s+(.+):(\d+):(\d+)$/,
90
- // at file:line:col
91
- /\s+at\s+(.+):(\d+):(\d+)$/,
92
- // at file:line:col with spaces
93
- /\(file:\/\/(.+):(\d+):(\d+)\)$/,
94
- // (file://path:line:col)
95
- /file:\/\/(.+):(\d+):(\d+)$/
96
- // file://path:line:col
97
- ];
98
- for (const pattern of patterns) {
99
- const match = callerLine.match(pattern);
100
- if (match) {
101
- filePath = match[1];
102
- break;
103
- }
104
- }
105
- if (!filePath) {
106
- return null;
107
- }
108
- if (filePath.startsWith("file://")) {
109
- filePath = filePath.replace("file://", "");
110
- }
111
- if (process.platform === "win32") {
112
- filePath = filePath.replace(/\//g, "\\");
113
- if (filePath.match(/^[A-Za-z]:\\/)) ; else if (filePath.startsWith("\\")) ; else {
114
- filePath = path__default.resolve(filePath);
115
- }
116
- }
117
- return path__default.normalize(filePath);
118
- }
119
-
120
- function isMainFile(filepath) {
121
- const cwd = process.cwd();
122
- return process.argv.some((arg) => filepath.endsWith(path__default.resolve(cwd, arg)));
123
- }
124
-
125
- const createLogger = (tagName) => {
126
- const log2 = consola.createConsola({
127
- formatOptions: { date: false },
128
- level: consola.LogLevels[toolkit.getLoggerLevel()],
129
- fancy: true,
130
- defaults: {
131
- tag: tagName
132
- }
133
- });
134
- log2.extend = (tagName2) => createLogger(tagName2);
135
- return log2;
136
- };
137
- const log = createLogger();
138
- const noopLogger = {
139
- debug: toolkit.noop,
140
- error: toolkit.noop,
141
- extend: () => noopLogger,
142
- info: toolkit.noop,
143
- log: toolkit.noop,
144
- warn: toolkit.noop
145
- };
146
-
147
- function formatLogEvent(event, fields) {
148
- if (!fields) return event;
149
- const parts = [];
150
- for (const key in fields) {
151
- if (key === "vrun_app_thread_message") continue;
152
- const v = fields[key];
153
- if (v === void 0) continue;
154
- parts.push(`${key}=${formatField(v)}`);
155
- }
156
- return parts.length ? `${event} ${parts.join(" ")}` : event;
157
- }
158
- function formatField(value) {
159
- switch (toolkit.typeOf(value)) {
160
- case "array":
161
- case "object":
162
- return toolkit.EJSON.stringify(value);
163
- default: {
164
- return String(value);
165
- }
166
- }
167
- }
168
- function createAppLogger(definition) {
169
- return definition.logger === false ? noopLogger : toolkit.isFunction(definition.logger) ? definition.logger(definition) : definition.logger || createLogger(definition.name);
170
- }
171
-
172
- const APP_INSTANCE_STATE = {
173
- INIT: "init",
174
- IN_SETUP: "in-setup",
175
- SETUP: "setup",
176
- IN_STOP: "in-stop",
177
- IN_RUN: "in-run",
178
- RUN: "run",
179
- STOP: "stop",
180
- IN_SHUTDOWN: "in-shutdown",
181
- SHUTDOWN: "shutdown",
182
- ERROR: "error"
183
- };
184
- const APP_DEF = Symbol("app-definition");
185
- function defineApp(definition) {
186
- const result = {
187
- filePath: filePathFromStack(toolkit.captureStackTrace(defineApp)),
188
- ...definition
189
- };
190
- toolkit.def(result, APP_DEF, true);
191
- if (!CONFIG.IS_VRUN) {
192
- if (result.filePath) {
193
- if (isMainFile(result.filePath)) {
194
- Promise.resolve().then(function () { return index; }).then((m) => {
195
- m.cli.runApp({
196
- cliName: "app",
197
- scriptFile: result.filePath,
198
- argv: extractOptionsArgs(process.argv.slice(1))
199
- });
200
- });
201
- }
202
- }
203
- }
204
- return result;
205
- }
206
- function createAppInstance(definition) {
207
- const setupState = createSetupState(definition);
208
- return {
209
- definition,
210
- props: null,
211
- setupState,
212
- eventBus: new toolkit.SimpleEventEmitter(),
213
- mutexName: null,
214
- mutexQueue: Promise.resolve(),
215
- state: APP_INSTANCE_STATE.INIT,
216
- logger: setupState.log
217
- };
218
- }
219
- function isAppDefinition(value) {
220
- return value?.[APP_DEF] === true;
221
- }
222
- async function startApp(app, props) {
223
- const instance = isAppDefinition(app) ? createAppInstance(app) : app;
224
- const setupResult = await setupApp(instance, props);
225
- if (toolkit.isSkip(setupResult)) {
226
- return setupResult;
227
- }
228
- const runResult = await runApp$1(instance);
229
- if (toolkit.isSkip(runResult)) {
230
- await shutdownApp(instance);
231
- return runResult;
232
- }
233
- return {
234
- success: true,
235
- code: "start_app",
236
- app: instance
237
- };
238
- }
239
- async function setupApp(instance, props) {
240
- const mutexResolve = await mutexAcquire(instance, "setup");
241
- if (instance.state !== APP_INSTANCE_STATE.INIT) {
242
- mutexResolve();
243
- return {
244
- skip: true,
245
- code: "setup_app",
246
- reason: `application in ${instance.state} state`
247
- };
248
- }
249
- setState$1(instance, APP_INSTANCE_STATE.IN_SETUP);
250
- const { setup } = instance.definition;
251
- if (toolkit.isFunction(setup)) {
252
- try {
253
- const setupResult = await setup.call(instance.setupState, props);
254
- Object.assign(instance.setupState, setupResult);
255
- } catch (error) {
256
- setState$1(instance, APP_INSTANCE_STATE.ERROR);
257
- mutexResolve();
258
- return {
259
- code: "setup_app",
260
- skip: true,
261
- reason: "setup function throw error",
262
- error: toolkit.toError(error)
263
- };
264
- }
265
- }
266
- instance.props = props;
267
- setState$1(instance, APP_INSTANCE_STATE.SETUP);
268
- mutexResolve();
269
- return {
270
- success: true,
271
- code: "setup_app"
272
- };
273
- }
274
- async function runApp$1(instance) {
275
- const mutexResolve = await mutexAcquire(instance, "run");
276
- if (instance.state !== APP_INSTANCE_STATE.SETUP) {
277
- mutexResolve();
278
- return {
279
- skip: true,
280
- code: "execute_app",
281
- reason: `application in ${instance.state} state`
282
- };
283
- }
284
- const { entry } = instance.definition;
285
- setState$1(instance, APP_INSTANCE_STATE.IN_RUN);
286
- instance.logger.info("Starting...");
287
- if (toolkit.isFunction(entry)) {
288
- try {
289
- await entry.call(instance.setupState, instance.props);
290
- } catch (error) {
291
- setState$1(instance, APP_INSTANCE_STATE.ERROR);
292
- mutexResolve();
293
- return {
294
- skip: true,
295
- code: "execute_app",
296
- reason: "entry function throw error",
297
- error: toolkit.toError(error)
298
- };
299
- }
300
- }
301
- setState$1(instance, APP_INSTANCE_STATE.RUN);
302
- mutexResolve();
303
- instance.logger.info("Started");
304
- return {
305
- success: true,
306
- code: "execute_app"
307
- };
308
- }
309
- async function stopApp(instance) {
310
- const mutexResolve = await mutexAcquire(instance, "stop");
311
- if (instance.state !== APP_INSTANCE_STATE.RUN) {
312
- mutexResolve();
313
- return {
314
- skip: true,
315
- code: "stop_app",
316
- reason: `application in ${instance.state} state`
317
- };
318
- }
319
- const { stop } = instance.definition;
320
- setState$1(instance, APP_INSTANCE_STATE.IN_STOP);
321
- instance.logger.info("Stopping...");
322
- let stopErr;
323
- if (toolkit.isFunction(stop)) {
324
- try {
325
- await stop.call(instance.setupState, instance.props);
326
- } catch (err) {
327
- setState$1(instance, APP_INSTANCE_STATE.ERROR);
328
- stopErr = toolkit.toError(err);
329
- }
330
- }
331
- mutexResolve();
332
- if (stopErr) {
333
- return {
334
- skip: true,
335
- code: "stop_app",
336
- reason: "stop function throw error",
337
- error: stopErr
338
- };
339
- }
340
- setState$1(instance, APP_INSTANCE_STATE.STOP);
341
- instance.logger.info("Stopped");
342
- return {
343
- success: true,
344
- code: "stop_app"
345
- };
346
- }
347
- async function shutdownApp(instance) {
348
- await stopApp(instance);
349
- const mutexResolve = await mutexAcquire(instance, "shutdown");
350
- if (instance.state !== APP_INSTANCE_STATE.STOP && instance.state !== APP_INSTANCE_STATE.SETUP && instance.state !== APP_INSTANCE_STATE.ERROR) {
351
- mutexResolve();
352
- return {
353
- skip: true,
354
- code: "shutdown_app",
355
- reason: `application in ${instance.state} state`
356
- };
357
- }
358
- const { shutdown } = instance.definition;
359
- setState$1(instance, APP_INSTANCE_STATE.IN_SHUTDOWN);
360
- instance.logger.info("Shutdown...");
361
- let shutdownErr;
362
- if (toolkit.isFunction(shutdown)) {
363
- try {
364
- await shutdown.call(instance.setupState, instance.props);
365
- } catch (err) {
366
- shutdownErr = toolkit.toError(err);
367
- }
368
- }
369
- instance.props = null;
370
- instance.setupState = createSetupState(instance.definition);
371
- mutexResolve();
372
- if (shutdownErr) {
373
- setState$1(instance, APP_INSTANCE_STATE.ERROR);
374
- return {
375
- skip: true,
376
- code: "shutdown_app",
377
- reason: "shutdown function throw error",
378
- error: shutdownErr
379
- };
380
- }
381
- setState$1(instance, APP_INSTANCE_STATE.SHUTDOWN);
382
- return {
383
- success: true,
384
- code: "shutdown_app"
385
- };
386
- }
387
- function appWaitShutdown(instance) {
388
- if (instance.state === APP_INSTANCE_STATE.SHUTDOWN) {
389
- return Promise.resolve();
390
- }
391
- const q = toolkit.defer();
392
- instance.eventBus.once("state:shutdown", q.resolve);
393
- instance.eventBus.once("error", q.reject);
394
- return q.promise.finally(() => {
395
- instance.eventBus.off("state:shutdown", q.resolve);
396
- instance.eventBus.off("error", q.reject);
397
- });
398
- }
399
- function createSetupState(definition) {
400
- const setupState = {
401
- log: createAppLogger(definition)
402
- };
403
- if (definition.methods) {
404
- for (const [key, fn] of Object.entries(definition.methods)) {
405
- setupState[key] = fn.bind(setupState);
406
- }
407
- }
408
- return setupState;
409
- }
410
- function mutexAcquire(instance, name) {
411
- let outerResolve;
412
- const acquirePromise = new Promise((res) => {
413
- outerResolve = res;
414
- });
415
- instance.mutexQueue = instance.mutexQueue.then(
416
- () => new Promise((innerResolve) => {
417
- instance.mutexName = name;
418
- outerResolve(() => {
419
- instance.mutexName = null;
420
- innerResolve();
421
- });
422
- })
423
- );
424
- return acquirePromise;
425
- }
426
- function setState$1(app, newState) {
427
- const oldState = app.state;
428
- if (newState !== oldState) {
429
- app.state = newState;
430
- app.eventBus.emit("state", newState, oldState);
431
- app.eventBus.emit(`state:${newState}`);
432
- }
433
- }
434
-
435
- function createAppHub(definitions) {
436
- const app = defineApp({
437
- name: "app-hub",
438
- description: `Application wrapper around: ${definitions.map((v) => v.name).join(", ")}`,
439
- logger: false,
440
- props: prefixifyProps(definitions),
441
- setup(props) {
442
- const instances = [];
443
- const setupSkips = [];
444
- return toolkit.asyncForEach(definitions, (definition) => {
445
- const appProps = getPrefixedProps(definition, props);
446
- const instance = createAppInstance(definition);
447
- instances.push(instance);
448
- return setupApp(instance, appProps).then((setupResult) => {
449
- if (toolkit.isSkip(setupResult)) {
450
- setupSkips.push({ ...setupResult, app: instance });
451
- }
452
- });
453
- }).then(() => {
454
- if (setupSkips.length) {
455
- throw skipExecsToError("setup", setupSkips);
456
- }
457
- return { instances };
458
- });
459
- },
460
- entry() {
461
- const runSkips = [];
462
- return toolkit.asyncForEach(this.instances, (instance) => {
463
- return runApp$1(instance).then((runResult) => {
464
- if (toolkit.isSkip(runResult)) {
465
- runSkips.push({ ...runResult, app: instance });
466
- }
467
- });
468
- }).then(() => {
469
- if (runSkips.length) {
470
- throw skipExecsToError("start", runSkips);
471
- }
472
- });
473
- },
474
- stop() {
475
- const stopSkips = [];
476
- return toolkit.asyncForEach(this.instances, (instance) => {
477
- return stopApp(instance).then((stopResult) => {
478
- if (toolkit.isSkip(stopResult)) {
479
- stopSkips.push({ ...stopResult, app: instance });
480
- }
481
- });
482
- }).then(() => {
483
- if (stopSkips.length) {
484
- throw skipExecsToError("stop", stopSkips);
485
- }
486
- });
487
- },
488
- shutdown() {
489
- const shutdownSkips = [];
490
- return toolkit.asyncForEach(this.instances, (instance) => {
491
- return shutdownApp(instance).then((shutdownResult) => {
492
- if (toolkit.isSkip(shutdownResult)) {
493
- shutdownSkips.push({ ...shutdownResult, app: instance });
494
- }
495
- });
496
- }).then(() => {
497
- if (shutdownSkips.length) {
498
- throw skipExecsToError("shutdown", shutdownSkips);
499
- }
500
- });
501
- }
502
- });
503
- return app;
504
- }
505
- function skipExecsToError(verb, skips) {
506
- const getError = (r) => {
507
- if ("error" in r && toolkit.isError(r.error)) {
508
- return r.error.stack ? `
509
- ${r.error.message}
510
- ${r.error.stack}` : `
511
- ${r.error.message}`;
512
- }
513
- return "";
514
- };
515
- const lines = skips.map(
516
- (s) => s.reason ? ` ${s.app.definition.name} [${s.code}]: ${s.reason}${getError(s).replaceAll("\n", "\n ")}` : ` ${s.app.definition.name} [${s.code}]${getError(s).replaceAll("\n", "\n ")}`
517
- );
518
- return new Error(
519
- `Failed to ${verb} ${skips.length} app(s):
520
- ${lines.join("\n")}`,
521
- { cause: "error" in skips[0] ? skips[0].error : void 0 }
522
- );
523
- }
524
- function getPrefixedProps(definition, props) {
525
- const result = toolkit.pickPrefixed(props, {
526
- prefix: toolkit.camelCase(definition.name),
527
- prefixTrim: true
528
- });
529
- for (const [key, value] of Object.entries(result)) {
530
- const propName = key.charAt(0).toLowerCase() + key.slice(1).toLowerCase();
531
- result[propName] = value;
532
- delete result[key];
533
- }
534
- return result;
535
- }
536
- function prefixifyProps(definitions) {
537
- const props = {};
538
- for (const definition of definitions) {
539
- if (definition.props) {
540
- for (const [propName, prop] of Object.entries(definition.props)) {
541
- props[toolkit.camelCase(`${definition.name}${toolkit.capitalize(propName)}`)] = prop;
542
- }
543
- }
544
- }
545
- return props;
546
- }
547
-
548
- const MAX_RESTARTS = 3;
549
- const HEARTBEAT_INTERVAL_MS = 5e3;
550
- const HEARTBEAT_TIMEOUT_MS = 1e4;
551
- const SHUTDOWN_EXIT_TIMEOUT_MS = 5e3;
552
- const SHUTDOWN_REPLY_TIMEOUT_MS = 3e4;
553
- const appModulePath = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)).endsWith(".ts") ? new URL("./index.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href));
554
- const bootstrapCode = `
555
- import { delay } from '@andrew_l/toolkit'
556
- import { processGraceful, onShutdown, onShutdownError } from '@andrew_l/graceful'
557
- import process from 'node:process';
558
-
559
- const scriptFile = process.env.VRUN_PROCESS_SCRIPT_FILE;
560
- const threadId = parseInt(process.env.VRUN_PROCESS_ID, 10);
561
- const appUrl = ${JSON.stringify(appModulePath)};
562
-
563
- if (
564
- scriptFile.endsWith('.ts') ||
565
- appUrl.endsWith('.ts') ||
566
- appUrl.includes('/src/')
567
- ) {
568
- const { register } = await import('tsx/esm/api');
569
- register();
570
- }
571
-
572
- const { isAppDefinition, createAppThreadInstance, shutdownApp } = await import(appUrl);
573
-
574
- const definition = await import(scriptFile).then(r => r.default);
575
-
576
- if (!isAppDefinition(definition)) {
577
- throw new Error('Default export must be an app definition: ' + scriptFile);
578
- }
579
-
580
- const listeners = new Set();
581
- const port = {
582
- on(event, cb) {
583
- if (event === 'message') {
584
- process.on('message', cb);
585
- listeners.add(cb);
586
- }
587
- },
588
- postMessage(msg, cb) {
589
- process.send(msg, cb);
590
- },
591
- close() {
592
- for (const cb of listeners) process.off('message', cb);
593
- },
594
- };
595
-
596
- const app = createAppThreadInstance({
597
- threadId,
598
- definition,
599
- parentPort: port,
600
- // onShutdown: () => processGraceful()
601
- });
602
-
603
- const log = app.logger;
604
-
605
- onShutdownError((error) => {
606
- log.error('Shutdown error:', error);
607
- });
608
-
609
- onShutdown('app', async () => {
610
- log.info('Shutdown signal received');
611
- await shutdownApp(app);
612
- });
613
- `;
614
- function setState(w, next) {
615
- const prev = w.state;
616
- if (prev === next) return;
617
- w.state = next;
618
- w.writeLog("debug", "state", { from: prev, to: next });
619
- w.eventBus.emit("state", next, prev);
620
- }
621
- function spawnChild(w) {
622
- w.writeLog("debug", "thread.spawn.start", {
623
- script: path__default.basename(w.scriptFile),
624
- thread: w.threadId
625
- });
626
- setState(w, "init");
627
- const child = cp__namespace.spawn(
628
- process.execPath,
629
- ["--input-type=module", "-e", bootstrapCode],
630
- {
631
- stdio: w.threadProps.__inheritIO !== false ? ["pipe", "inherit", "inherit", "ipc"] : ["pipe", "pipe", "pipe", "ipc"],
632
- env: {
633
- ...process.env,
634
- VRUN_PROCESS_SCRIPT_FILE: w.scriptFile,
635
- VRUN_PROCESS_ID: String(w.threadId)
636
- }
637
- }
638
- );
639
- w.child = child;
640
- const onData = (chunk) => {
641
- const text = chunk.toString("utf8");
642
- for (const line of text.split(/\r?\n/)) {
643
- if (line.length === 0) continue;
644
- w.eventBus.emit("log", parseRawLogLine(line));
645
- }
646
- };
647
- child.stdout?.on("data", onData);
648
- child.stderr?.on("data", onData);
649
- child.on("message", (msg) => {
650
- if (isThreadMessage(msg)) {
651
- w.eventBus.emit("message", msg);
652
- w.eventBus.emit(`msg:${msg.type}`, msg);
653
- }
654
- });
655
- child.once("exit", (code, signal) => {
656
- w.eventBus.emit("exit", code, signal);
657
- });
658
- child.on("error", (err) => {
659
- w.writeLog("error", "thread.spawn.error", { message: err.message });
660
- w.eventBus.emit("error", err);
661
- });
662
- }
663
- function startHeartbeat(w) {
664
- w.lastPong = Date.now();
665
- w.heartbeatTimer = setInterval(() => {
666
- if (w.state !== APP_INSTANCE_STATE.RUN) {
667
- clearInterval(w.heartbeatTimer);
668
- return;
669
- }
670
- const since = Date.now() - w.lastPong;
671
- if (since > HEARTBEAT_TIMEOUT_MS) {
672
- clearInterval(w.heartbeatTimer);
673
- w.writeLog("warn", "heartbeat.miss", { since_ms: since });
674
- restartThreadApp(w).catch((err) => w.eventBus.emit("error", err));
675
- return;
676
- }
677
- if (w.child.connected) {
678
- w.child.send(createThreadMessage("ping", {}));
679
- }
680
- }, HEARTBEAT_INTERVAL_MS);
681
- }
682
- function waitForExit(w, timeoutMs) {
683
- if (isThreadExit(w)) {
684
- return Promise.resolve();
685
- }
686
- return new Promise((resolve) => {
687
- w.writeLog("info", "thread.exit.waiting", { timeout_ms: timeoutMs });
688
- const cleanup = () => {
689
- clearTimeout(slowTimer);
690
- clearTimeout(killTimer);
691
- w.child.off("exit", onExit);
692
- };
693
- const onExit = () => {
694
- cleanup();
695
- resolve();
696
- };
697
- const halfway = Math.floor(timeoutMs / 2);
698
- const slowTimer = setTimeout(() => {
699
- w.writeLog("warn", "thread.exit.slow", { elapsed_ms: halfway });
700
- }, halfway);
701
- const killTimer = setTimeout(() => {
702
- cleanup();
703
- try {
704
- w.writeLog("warn", "thread.exit.force-kill", { timeout_ms: timeoutMs });
705
- w.child.kill("SIGKILL");
706
- } catch {
707
- }
708
- resolve();
709
- }, timeoutMs);
710
- w.child.once("exit", onExit);
711
- });
712
- }
713
- function waitForThreadReady(w) {
714
- if (w.state === "ready") {
715
- return Promise.resolve();
716
- }
717
- return new Promise((resolve, reject) => {
718
- const cleanup = () => {
719
- w.eventBus.off("ready", onReady);
720
- w.eventBus.off("error", onError);
721
- w.eventBus.off("exit", onExit);
722
- };
723
- const onReady = () => {
724
- resolve();
725
- cleanup();
726
- };
727
- const onExit = (code) => {
728
- reject(new Error(`Child exited before ready (code=${code ?? "null"})`));
729
- cleanup();
730
- };
731
- const onError = (error) => {
732
- reject(error);
733
- cleanup();
734
- };
735
- w.eventBus.once("ready", onReady);
736
- w.eventBus.once("error", onError);
737
- w.eventBus.once("exit", onExit);
738
- });
739
- }
740
- function sendAndWait(w, message, replyType, timeout = 3e4) {
741
- return new Promise((resolve) => {
742
- const eventName = `msg:${replyType}`;
743
- const cleanup = () => {
744
- w.eventBus.off(eventName, onReply);
745
- w.eventBus.off("exit", onExit);
746
- clearTimeout(timer);
747
- };
748
- const timer = setTimeout(() => {
749
- cleanup();
750
- w.writeLog("error", "parent.ipc.timeout", {
751
- waiting: replyType,
752
- ms: timeout
753
- });
754
- resolve({
755
- skip: true,
756
- code: "parent.ipc.timeout",
757
- reason: `Thread ${path__default.basename(w.scriptFile)} timed out waiting for "${replyType}"`
758
- });
759
- }, timeout);
760
- const onReply = (reply) => {
761
- cleanup();
762
- resolve({
763
- success: true,
764
- code: "parent.ipc.recv",
765
- reply
766
- });
767
- };
768
- const onExit = () => {
769
- const reason = `thread.exit (code=${w.child.exitCode} signal=${w.child.signalCode ?? "null"})`;
770
- w.writeLog("debug", "parent.ipc.skipped", {
771
- waiting: replyType,
772
- reason
773
- });
774
- resolve({ skip: true, code: "thread.exit", reason });
775
- return;
776
- };
777
- if (isThreadExit(w)) {
778
- return void onExit();
779
- }
780
- w.eventBus.once("exit", onExit);
781
- w.eventBus.once(eventName, onReply);
782
- w.writeLog("debug", "parent.ipc.send", message);
783
- try {
784
- w.child.send(message);
785
- } catch (err) {
786
- const error = toolkit.toError(err);
787
- w.writeLog("error", "parent.ipc.fail", {
788
- message,
789
- error: error.message
790
- });
791
- cleanup();
792
- resolve({
793
- skip: true,
794
- code: "parent.ipc.fail",
795
- reason: error.message
796
- });
797
- }
798
- });
799
- }
800
- function initThread(threadId, scriptFile, threadProps) {
801
- const w = {
802
- threadId,
803
- scriptFile,
804
- threadProps,
805
- pid: 0,
806
- state: APP_INSTANCE_STATE.INIT,
807
- restartCount: 0,
808
- lastPong: 0,
809
- child: null,
810
- scheduler: new toolkit.Scheduler(),
811
- eventBus: new node_events.EventEmitter(),
812
- writeLog(level, event, fields) {
813
- w.eventBus.emit("log", {
814
- level,
815
- text: formatLogEvent(event, fields),
816
- ts: Date.now()
817
- });
818
- }
819
- };
820
- w.eventBus.setMaxListeners(0);
821
- const onJobPhase = (phase) => (job) => {
822
- w.writeLog("debug", `job.${job.jobName ?? "untitled"}.${phase}`);
823
- };
824
- w.scheduler.onJob(onJobPhase("queue"));
825
- w.scheduler.onJobStart(onJobPhase("start"));
826
- w.scheduler.onJobComplete(onJobPhase("done"));
827
- w.eventBus.on("exit", (code, signal) => {
828
- const prevState = w.state;
829
- if (signal) {
830
- w.writeLog("warn", "thread.exit.signal", { signal, code });
831
- } else if (code === 0 || code === null) {
832
- w.writeLog("info", "thread.exit.clean", { code });
833
- } else {
834
- w.writeLog("error", "thread.exit.crash", { code });
835
- }
836
- setState(w, "shutdown");
837
- clearInterval(w.heartbeatTimer);
838
- w.pid = 0;
839
- if (prevState === APP_INSTANCE_STATE.RUN && !graceful.isShuttingDown()) {
840
- restartThreadApp(w).catch((err) => w.eventBus.emit("error", err));
841
- }
842
- });
843
- w.eventBus.on("message", (msg) => {
844
- w.writeLog("debug", "parent.ipc.recv", msg);
845
- switch (msg.type) {
846
- case "app-state": {
847
- setState(w, msg.state);
848
- break;
849
- }
850
- case "ready": {
851
- w.pid = msg.pid;
852
- w.eventBus.emit("pid", msg.pid);
853
- setState(w, "ready");
854
- w.writeLog("debug", "thread.spawn.ready");
855
- w.eventBus.emit("ready");
856
- break;
857
- }
858
- case "pong": {
859
- w.lastPong = Date.now();
860
- break;
861
- }
862
- }
863
- });
864
- spawnChild(w);
865
- return w;
866
- }
867
- function withLifecycle(w, phase, fn) {
868
- w.writeLog("debug", `lifecycle.${phase}.begin`);
869
- return fn().then(
870
- (result) => {
871
- w.writeLog("debug", `lifecycle.${phase}.done`);
872
- return result;
873
- },
874
- (error) => {
875
- w.writeLog("error", `lifecycle.${phase}.fail`, {
876
- message: String(error && error.message || error)
877
- });
878
- throw error;
879
- }
880
- );
881
- }
882
- function setupThreadApp(w) {
883
- return w.scheduler.queueJobWait(
884
- () => withLifecycle(w, "setupThreadApp", () => doSetupThread(w)),
885
- { jobName: "setupThreadApp" }
886
- );
887
- }
888
- function doSetupThread(w) {
889
- setState(w, APP_INSTANCE_STATE.IN_SETUP);
890
- return sendAndWait(
891
- w,
892
- createThreadMessage("setup", {
893
- props: w.threadProps
894
- }),
895
- "setup_done"
896
- ).then(toolkit.noop);
897
- }
898
- function startThreadApp(w) {
899
- return w.scheduler.queueJobWait(
900
- () => withLifecycle(w, "startThreadApp", () => doStartThreadApp(w)),
901
- { jobName: "startThreadApp" }
902
- );
903
- }
904
- function doStartThreadApp(w) {
905
- setState(w, APP_INSTANCE_STATE.IN_RUN);
906
- return sendAndWait(w, createThreadMessage("start", {}), "start_done").then(
907
- () => {
908
- if (w.state === APP_INSTANCE_STATE.RUN) {
909
- startHeartbeat(w);
910
- }
911
- }
912
- );
913
- }
914
- function stopThreadApp(w) {
915
- return w.scheduler.queueJobWait(
916
- () => withLifecycle(w, "stopThreadApp", () => doStopThreadApp(w)),
917
- { jobName: "stopThreadApp" }
918
- );
919
- }
920
- function doStopThreadApp(w) {
921
- setState(w, APP_INSTANCE_STATE.IN_STOP);
922
- clearInterval(w.heartbeatTimer);
923
- return sendAndWait(w, createThreadMessage("stop", {}), "stop_done").then(
924
- toolkit.noop
925
- );
926
- }
927
- function shutdownThreadApp(w) {
928
- return w.scheduler.queueJobWait(
929
- () => withLifecycle(w, "shutdownThreadApp", () => doShutdownThreadApp(w)),
930
- { jobName: "shutdownThreadApp" }
931
- );
932
- }
933
- function doShutdownThreadApp(w) {
934
- setState(w, "shutdown");
935
- clearInterval(w.heartbeatTimer);
936
- return (w.child.connected ? sendAndWait(
937
- w,
938
- createThreadMessage("shutdown", {}),
939
- "shutdown_done",
940
- SHUTDOWN_REPLY_TIMEOUT_MS
941
- ) : Promise.resolve()).then(() => waitForExit(w, SHUTDOWN_EXIT_TIMEOUT_MS));
942
- }
943
- function restartThreadApp(w) {
944
- if (w.restartCount >= MAX_RESTARTS) {
945
- return Promise.reject(
946
- new Error(`Thread ${w.threadId} exceeded max restarts (${MAX_RESTARTS})`)
947
- );
948
- }
949
- return w.scheduler.queueJobWait(
950
- () => withLifecycle(
951
- w,
952
- "restartThreadApp",
953
- () => doShutdownThreadApp(w).then(() => {
954
- w.restartCount++;
955
- spawnChild(w);
956
- return waitForThreadReady(w);
957
- }).then(() => doSetupThread(w)).then(() => doStartThreadApp(w))
958
- ),
959
- { jobName: "restartThreadApp" }
960
- );
961
- }
962
- function isThreadMessage(value) {
963
- return toolkit.isObject(value) && !!value.vrun_app_thread_message;
964
- }
965
- function createThreadMessage(type, msg) {
966
- return {
967
- type,
968
- ...msg,
969
- vrun_app_thread_message: true
970
- };
971
- }
972
- function parseRawLogLine(line) {
973
- const trimmed = line.trimEnd();
974
- const lower = trimmed.toLowerCase();
975
- let level = "info";
976
- if (lower.includes("error") || lower.includes("\u2716") || lower.includes("[error]")) {
977
- level = "error";
978
- } else if (lower.includes("warn") || lower.includes("\u26A0")) {
979
- level = "warn";
980
- } else if (lower.includes("debug") || lower.includes("\u2699")) {
981
- level = "debug";
982
- }
983
- return { ts: Date.now(), level, text: trimmed };
984
- }
985
- function isThreadExit(w) {
986
- return w.child.exitCode !== null || w.child.signalCode !== null || !w.child.connected;
987
- }
988
-
989
- function createAppThread(definition) {
990
- const scriptFile = definition.filePath;
991
- toolkit.assert.ok(
992
- scriptFile,
993
- `Failed to detect definition file path for: ${definition.name}`
994
- );
995
- const app = defineApp({
996
- name: definition.name,
997
- description: definition.description,
998
- props: {
999
- ...definition.props || {},
1000
- threads: {
1001
- type: Number,
1002
- description: "Amount of threads",
1003
- default: () => 1,
1004
- parser: (v) => parseInt(v)
1005
- }
1006
- },
1007
- logger: false,
1008
- setup(props) {
1009
- const { threads = 1, ...threadProps } = props;
1010
- toolkit.assert.greaterThan(threads, 0, "threads expected to be greater than 0");
1011
- const managedThreads = Array.from(
1012
- { length: threads },
1013
- (_, i) => initThread(i + 1, scriptFile, threadProps)
1014
- );
1015
- if (props.__inheritIO !== false) {
1016
- for (const w of managedThreads) {
1017
- const childLogger = createAppLogger({
1018
- ...definition,
1019
- name: `${definition.name}.${w.threadId}`
1020
- });
1021
- w.eventBus.on("log", (entry) => {
1022
- childLogger[entry.level](entry.text);
1023
- });
1024
- }
1025
- }
1026
- return Promise.all(managedThreads.map((w) => waitForThreadReady(w))).then(() => Promise.all(managedThreads.map((w) => setupThreadApp(w)))).then(() => ({ managedThreads }));
1027
- },
1028
- entry() {
1029
- return Promise.all(this.managedThreads.map((w) => startThreadApp(w))).then(
1030
- toolkit.noop
1031
- );
1032
- },
1033
- stop() {
1034
- return Promise.all(this.managedThreads.map((w) => stopThreadApp(w))).then(
1035
- toolkit.noop
1036
- );
1037
- },
1038
- shutdown() {
1039
- return Promise.all(
1040
- this.managedThreads.map((w) => shutdownThreadApp(w))
1041
- ).then(() => {
1042
- this.managedThreads = [];
1043
- });
1044
- }
1045
- });
1046
- return app;
1047
- }
1048
- function createAppThreadInstance({
1049
- definition,
1050
- parentPort,
1051
- threadId,
1052
- onShutdown = toolkit.noop
1053
- }) {
1054
- definition = { ...definition };
1055
- definition.name += "." + threadId;
1056
- const instance = createAppInstance(definition);
1057
- const log = instance.logger;
1058
- const postMessage = (message, cb) => {
1059
- log.debug(formatLogEvent("child.ipc.send", message));
1060
- parentPort.postMessage(message, cb);
1061
- };
1062
- instance.eventBus.on("state", (newState) => {
1063
- postMessage(createThreadMessage("app-state", { state: newState }));
1064
- });
1065
- parentPort.on("message", async (message) => {
1066
- if (!isThreadMessage(message)) return;
1067
- log.debug(formatLogEvent("child.ipc.recv", message));
1068
- switch (message.type) {
1069
- case "setup": {
1070
- const result = await setupApp(instance, message.props);
1071
- postMessage(createThreadMessage("setup_done", { result }));
1072
- break;
1073
- }
1074
- case "start": {
1075
- const result = await runApp$1(instance);
1076
- postMessage(createThreadMessage("start_done", { result }));
1077
- break;
1078
- }
1079
- case "stop": {
1080
- const result = await stopApp(instance);
1081
- postMessage(createThreadMessage("stop_done", { result }));
1082
- break;
1083
- }
1084
- case "shutdown": {
1085
- const result = await shutdownApp(instance);
1086
- postMessage(createThreadMessage("shutdown_done", { result }), () => {
1087
- setImmediate(() => {
1088
- toolkit.catchError(onShutdown);
1089
- if (toolkit.isFunction(parentPort.close)) {
1090
- parentPort.close();
1091
- }
1092
- });
1093
- });
1094
- break;
1095
- }
1096
- case "ping": {
1097
- postMessage(createThreadMessage("pong", {}));
1098
- break;
1099
- }
1100
- }
1101
- });
1102
- postMessage(
1103
- createThreadMessage("ready", {
1104
- pid: process.pid
1105
- })
1106
- );
1107
- return instance;
1108
- }
1109
-
1110
- const TYPESCRIPT_EXTENSIONS = ["mts", "cts", "ts"];
1111
- const JAVASCRIPT_EXTENSIONS = ["js", "mjs", "cjs"];
1112
- const ALL_EXTENSIONS = [
1113
- ...TYPESCRIPT_EXTENSIONS,
1114
- ...JAVASCRIPT_EXTENSIONS,
1115
- "json"
1116
- ];
1117
- function createProject(projectPath) {
1118
- const packageJsonPath = locateNearestFile(projectPath, "package.json");
1119
- toolkit.assert.ok(
1120
- packageJsonPath,
1121
- "package.json not found in path: " + path__default.dirname(projectPath)
1122
- );
1123
- const packageJson = importJson(packageJsonPath);
1124
- const projectLocation = path__default.dirname(packageJsonPath);
1125
- const project = {
1126
- path: projectLocation,
1127
- name: packageJson.name || path__default.basename(projectPath),
1128
- description: packageJson.description || "",
1129
- version: packageJson.version || "0.0.0",
1130
- tsconfigPath: locateNearestFile(projectLocation, "tsconfig.json"),
1131
- dotenvPath: locateNearestFile(projectLocation, ".env"),
1132
- autoImports: ["inversify.config"],
1133
- importLookupPaths: createImportLookupPaths(projectLocation),
1134
- allowTs: false,
1135
- packageJson
1136
- };
1137
- project.autoImports = project.autoImports.map((v) => projectResolveFile(project, v)).filter(toolkit.isString);
1138
- return project;
1139
- }
1140
- function projectImportFile(project, filePath) {
1141
- const existedFile = projectResolveFile(project, filePath);
1142
- if (!existedFile) {
1143
- throw new Error(
1144
- `Cannot resolve import for path.
1145
- Path:
1146
- ${filePath}
1147
- Available extensions: ${ALL_EXTENSIONS.join(", ")}`
1148
- );
1149
- }
1150
- const ext = toolkit.getFileExtension(existedFile, false);
1151
- if (ext === "json") {
1152
- const data = importJson(existedFile);
1153
- return Promise.resolve({ default: data });
1154
- }
1155
- return import(existedFile);
1156
- }
1157
- function projectResolveFile(project, filePath) {
1158
- const pathVariants = project.importLookupPaths.map(
1159
- (dir) => path__default.resolve(dir, filePath)
1160
- );
1161
- for (const filePath2 of pathVariants) {
1162
- const pathVariants2 = ALL_EXTENSIONS.includes(
1163
- toolkit.getFileExtension(filePath2, false)
1164
- ) ? [filePath2] : [...ALL_EXTENSIONS.map((ext) => `${filePath2}.${ext}`)];
1165
- const existedPath = pathVariants2.find((v) => fs__default.existsSync(v));
1166
- if (existedPath) {
1167
- return existedPath;
1168
- }
1169
- }
1170
- return null;
1171
- }
1172
- async function projectLoadEnv(project) {
1173
- if (!project.dotenvPath) {
1174
- return false;
1175
- }
1176
- dotenv__default.config({
1177
- path: project.dotenvPath,
1178
- quiet: true
1179
- });
1180
- return true;
1181
- }
1182
- async function projectAutoImport(project) {
1183
- const result = [];
1184
- const filePaths = project.autoImports.map(
1185
- (filePath) => projectResolveFile(project, filePath)
1186
- );
1187
- for (const filePath of filePaths) {
1188
- if (filePath) {
1189
- await projectImportFile(project, filePath);
1190
- result.push(filePath.replace(project.path, ""));
1191
- }
1192
- }
1193
- return result;
1194
- }
1195
- function projectPrintInfo(project) {
1196
- const tags = [];
1197
- if (project.allowTs) {
1198
- tags.push(utils.getColor("bgBlue")(" TS "));
1199
- }
1200
- if (CONFIG.WATCH_MODE) {
1201
- tags.push(utils.getColor("bgMagenta")(" WATCH "));
1202
- }
1203
- if (project.dotenvPath) {
1204
- tags.push(utils.getColor("bgYellow")(` ${path__default.basename(project.dotenvPath)} `));
1205
- }
1206
- if (project.autoImports.length) {
1207
- tags.push(
1208
- utils.getColor("bgGreen")(
1209
- ` IMPORT: ${project.autoImports.map((v) => path__default.relative(project.path, v)).join(" | ")} `
1210
- )
1211
- );
1212
- }
1213
- log.info(
1214
- "%s (%s)%s",
1215
- project.name,
1216
- project.version,
1217
- tags.length ? ` ${tags.join(" ")}` : ""
1218
- );
1219
- }
1220
- function createImportLookupPaths(projectPath, appRoot = CONFIG.APP_ROOT) {
1221
- const cwd = process.cwd();
1222
- const result = [];
1223
- if (appRoot) {
1224
- result.push(path__default.resolve(cwd, appRoot));
1225
- }
1226
- result.push(path__default.resolve(cwd));
1227
- result.push(path__default.resolve(projectPath));
1228
- return toolkit.uniq(result);
1229
- }
1230
- function locateNearestFile(location, fileName) {
1231
- let currentDir = path__default.extname(location) ? path__default.dirname(path__default.resolve(location)) : path__default.resolve(location);
1232
- while (currentDir) {
1233
- const filePath = path__default.join(currentDir, fileName);
1234
- if (fs__default.existsSync(filePath)) {
1235
- return filePath;
1236
- }
1237
- const parentDir = path__default.dirname(currentDir);
1238
- if (parentDir === currentDir) break;
1239
- currentDir = parentDir;
1240
- }
1241
- return null;
1242
- }
1243
- function importJson(filePath) {
1244
- if (!filePath.endsWith(".json")) {
1245
- filePath += ".json";
1246
- }
1247
- if (!fs__default.existsSync(filePath)) {
1248
- throw new Error(`JSON file not found.
1249
- Path:
1250
- ${filePath}`);
1251
- }
1252
- const data = fs__default.readFileSync(filePath, { encoding: "utf-8" });
1253
- return JSON.parse(data);
1254
- }
1255
-
1256
- const LOG_BUFFER_CAP = 2e3;
1257
- class RingBuffer {
1258
- constructor(cap) {
1259
- this.cap = cap;
1260
- }
1261
- buf = [];
1262
- start = 0;
1263
- push(item) {
1264
- if (this.buf.length < this.cap) {
1265
- this.buf.push(item);
1266
- } else {
1267
- this.buf[this.start] = item;
1268
- this.start = (this.start + 1) % this.cap;
1269
- }
1270
- }
1271
- size() {
1272
- return this.buf.length;
1273
- }
1274
- toArray() {
1275
- if (this.start === 0) return this.buf.slice();
1276
- return this.buf.slice(this.start).concat(this.buf.slice(0, this.start));
1277
- }
1278
- clear() {
1279
- this.buf = [];
1280
- this.start = 0;
1281
- }
1282
- }
1283
- const noopHandlers = {
1284
- stop: () => Promise.resolve(),
1285
- start: () => Promise.resolve(),
1286
- restart: () => Promise.resolve()
1287
- };
1288
- function createTuiStore() {
1289
- const emitter = new node_events.EventEmitter();
1290
- emitter.setMaxListeners(0);
1291
- const logs = /* @__PURE__ */ new Map();
1292
- const apps = [];
1293
- const bufferOf = (id) => {
1294
- let buf = logs.get(id);
1295
- if (!buf) {
1296
- buf = new RingBuffer(LOG_BUFFER_CAP);
1297
- logs.set(id, buf);
1298
- }
1299
- return buf;
1300
- };
1301
- const notify = () => {
1302
- emitter.emit("change");
1303
- };
1304
- const isSelectedFeed = (nodeId) => {
1305
- if (!store.selectedId) return false;
1306
- if (store.selectedId === nodeId) return true;
1307
- for (const app of apps) {
1308
- if (app.id !== store.selectedId) continue;
1309
- return !!app.threads?.some((t) => t.id === nodeId);
1310
- }
1311
- return false;
1312
- };
1313
- const store = {
1314
- apps,
1315
- logs,
1316
- system: null,
1317
- selectedId: null,
1318
- filter: "info",
1319
- logScroll: 0,
1320
- handlers: noopHandlers,
1321
- pushLog(nodeId, entry) {
1322
- bufferOf(nodeId).push(entry);
1323
- if (store.logScroll > 0 && isSelectedFeed(nodeId) && passesFilter(entry, store.filter)) {
1324
- store.logScroll += 1;
1325
- }
1326
- notify();
1327
- },
1328
- setState(nodeId, state) {
1329
- for (const app of apps) {
1330
- if (app.id === nodeId) {
1331
- app.state = state;
1332
- notify();
1333
- return;
1334
- }
1335
- if (app.threads) {
1336
- for (const t of app.threads) {
1337
- if (t.id === nodeId) {
1338
- t.state = state;
1339
- notify();
1340
- return;
1341
- }
1342
- }
1343
- }
1344
- }
1345
- },
1346
- setSelected(nodeId) {
1347
- store.selectedId = nodeId;
1348
- store.logScroll = 0;
1349
- notify();
1350
- },
1351
- setFilter(filter) {
1352
- store.filter = filter;
1353
- store.logScroll = 0;
1354
- notify();
1355
- },
1356
- setLogScroll(offset) {
1357
- store.logScroll = Math.max(0, offset);
1358
- notify();
1359
- },
1360
- setSystem(entry) {
1361
- store.system = entry;
1362
- notify();
1363
- },
1364
- toggleExpand(appId) {
1365
- for (const app of apps) {
1366
- if (app.id === appId) {
1367
- app.expanded = !app.expanded;
1368
- notify();
1369
- return;
1370
- }
1371
- }
1372
- },
1373
- addApp(node) {
1374
- apps.push(node);
1375
- if (!store.selectedId) store.selectedId = node.id;
1376
- notify();
1377
- },
1378
- setThreads(appId, threads) {
1379
- for (const app of apps) {
1380
- if (app.id === appId) {
1381
- app.threads = threads;
1382
- notify();
1383
- return;
1384
- }
1385
- }
1386
- },
1387
- setPid(nodeId, pid) {
1388
- for (const app of apps) {
1389
- if (app.threads) {
1390
- for (const t of app.threads) {
1391
- if (t.id === nodeId) {
1392
- t.pid = pid;
1393
- notify();
1394
- return;
1395
- }
1396
- }
1397
- }
1398
- }
1399
- },
1400
- getLogs(nodeId) {
1401
- const buf = logs.get(nodeId);
1402
- return buf ? buf.toArray() : [];
1403
- },
1404
- clearLogs(nodeId) {
1405
- logs.get(nodeId)?.clear();
1406
- notify();
1407
- },
1408
- subscribe(fn) {
1409
- emitter.on("change", fn);
1410
- return () => emitter.off("change", fn);
1411
- }
1412
- };
1413
- return store;
1414
- }
1415
- const LEVEL_RANK = {
1416
- debug: 0,
1417
- log: 1,
1418
- info: 2,
1419
- warn: 3,
1420
- error: 4
1421
- };
1422
- const FILTER_THRESHOLD = {
1423
- all: 0,
1424
- info: LEVEL_RANK.info,
1425
- warn: LEVEL_RANK.warn,
1426
- error: LEVEL_RANK.error
1427
- };
1428
- function passesFilter(entry, filter) {
1429
- return LEVEL_RANK[entry.level] >= FILTER_THRESHOLD[filter];
1430
- }
1431
- function getSelectedFilteredCount(store) {
1432
- if (!store.selectedId) return 0;
1433
- const passes = (e) => passesFilter(e, store.filter);
1434
- for (const app of store.apps) {
1435
- if (app.id !== store.selectedId) continue;
1436
- if (!app.threads || app.threads.length === 0) {
1437
- return store.getLogs(app.id).filter(passes).length;
1438
- }
1439
- let count = 0;
1440
- for (const t of app.threads) {
1441
- for (const e of store.getLogs(t.id)) if (passes(e)) count += 1;
1442
- }
1443
- return count;
1444
- }
1445
- return store.getLogs(store.selectedId).filter(passes).length;
1446
- }
1447
- function nextFilter(filter) {
1448
- switch (filter) {
1449
- case "all":
1450
- return "info";
1451
- case "info":
1452
- return "warn";
1453
- case "warn":
1454
- return "error";
1455
- case "error":
1456
- return "all";
1457
- }
1458
- }
1459
- function flattenVisibleTree(apps) {
1460
- const out = [];
1461
- for (const app of apps) {
1462
- out.push(app);
1463
- if (app.expanded && app.threads) {
1464
- for (const t of app.threads) out.push(t);
1465
- }
1466
- }
1467
- return out;
1468
- }
1469
-
1470
- const TuiStoreContext = react.createContext(null);
1471
- function useTuiStore() {
1472
- const store = react.useContext(TuiStoreContext);
1473
- if (!store) throw new Error("TuiStoreContext is not provided");
1474
- return store;
1475
- }
1476
- function useStoreSnapshot() {
1477
- const store = useTuiStore();
1478
- const [version, setVersion] = react.useState(0);
1479
- react.useEffect(() => {
1480
- return store.subscribe(() => setVersion((v) => v + 1));
1481
- }, [store]);
1482
- return version;
1483
- }
1484
-
1485
- const STATE_COLORS = {
1486
- "in-run": "yellow",
1487
- run: "green",
1488
- "in-stop": "yellow",
1489
- "in-setup": "yellow",
1490
- setup: "yellow",
1491
- ready: "gray",
1492
- "in-shutdown": "yellow",
1493
- init: "gray",
1494
- shutdown: "gray",
1495
- stop: "gray",
1496
- error: "red"
1497
- };
1498
- function isApp(n) {
1499
- return n.kind === "app";
1500
- }
1501
- function rowLength(n) {
1502
- if (n.kind === "app") return 1 + 1 + 1 + 1 + 1 + (n.name?.length ?? 0);
1503
- return 1 + 2 + 1 + 1 + `PID ${n.pid}`.length;
1504
- }
1505
- function AppList() {
1506
- useStoreSnapshot();
1507
- const store = useTuiStore();
1508
- const nodes = flattenVisibleTree(store.apps);
1509
- const maxRow = nodes.reduce(
1510
- (m, n) => Math.max(m, rowLength(n)),
1511
- "Apps".length
1512
- );
1513
- const width = maxRow + 4;
1514
- return /* @__PURE__ */ jsxRuntime.jsxs(
1515
- ink.Box,
1516
- {
1517
- flexDirection: "column",
1518
- width,
1519
- flexShrink: 0,
1520
- borderStyle: "single",
1521
- paddingX: 1,
1522
- overflow: "hidden",
1523
- children: [
1524
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Apps" }),
1525
- nodes.map((node) => {
1526
- const selected = node.id === store.selectedId;
1527
- const color = STATE_COLORS[node.state];
1528
- const marker = selected ? "\u258C" : " ";
1529
- if (isApp(node)) {
1530
- const caret = node.threads && node.threads.length ? node.expanded ? "\u25BE" : "\u25B8" : " ";
1531
- return /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { wrap: "truncate-end", bold: selected, children: [
1532
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "cyan", children: marker }),
1533
- caret,
1534
- " ",
1535
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color, children: "\u25CF" }),
1536
- " ",
1537
- node.name
1538
- ] }, node.id);
1539
- }
1540
- return /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { wrap: "truncate-end", bold: selected, children: [
1541
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "cyan", children: marker }),
1542
- " ",
1543
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color, children: "\u25CF" }),
1544
- " ",
1545
- node.name
1546
- ] }, node.id);
1547
- })
1548
- ]
1549
- }
1550
- );
1551
- }
1552
-
1553
- const LEVEL_COLORS = {
1554
- debug: "gray",
1555
- log: "white",
1556
- info: "cyan",
1557
- warn: "yellow",
1558
- error: "red"
1559
- };
1560
- function formatTime(ts) {
1561
- const d = new Date(ts);
1562
- const pad = (n) => n.toString().padStart(2, "0");
1563
- return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1564
- }
1565
- function describeSelection(store) {
1566
- if (!store.selectedId) return null;
1567
- for (const app of store.apps) {
1568
- if (app.id === store.selectedId) {
1569
- const threads = app.threads ?? [];
1570
- const pids = Array.from(
1571
- new Set(threads.map((t) => t.pid).filter((p) => p > 0))
1572
- );
1573
- return {
1574
- appName: app.name,
1575
- processCount: threads.length,
1576
- pids,
1577
- states: threads.map((v) => v.state),
1578
- showProcessTag: threads.length > 1
1579
- };
1580
- }
1581
- if (app.threads) {
1582
- for (const t of app.threads) {
1583
- if (t.id === store.selectedId) {
1584
- return {
1585
- appName: app.name,
1586
- pid: t.pid,
1587
- pids: t.pid > 0 ? [t.pid] : [],
1588
- state: t.state,
1589
- states: [t.state],
1590
- processCount: app.threads.length,
1591
- showProcessTag: false
1592
- };
1593
- }
1594
- }
1595
- }
1596
- }
1597
- return null;
1598
- }
1599
- function formatPids(pids) {
1600
- if (pids.length === 0) return "?";
1601
- if (pids.length === 1) return String(pids[0]);
1602
- return pids.join(", ");
1603
- }
1604
- function gatherEntries(store, info) {
1605
- if (!store.selectedId) return [];
1606
- for (const app of store.apps) {
1607
- if (app.id !== store.selectedId) continue;
1608
- if (!app.threads || app.threads.length === 0) {
1609
- return store.getLogs(app.id);
1610
- }
1611
- const merged = [];
1612
- for (const t of app.threads) {
1613
- for (const entry of store.getLogs(t.id)) {
1614
- merged.push(
1615
- info.showProcessTag ? { ...entry, text: `[${t.pid}] ${entry.text}` } : entry
1616
- );
1617
- }
1618
- }
1619
- merged.sort((a, b) => a.ts - b.ts);
1620
- return merged;
1621
- }
1622
- return store.getLogs(store.selectedId);
1623
- }
1624
- function LogView() {
1625
- useStoreSnapshot();
1626
- const store = useTuiStore();
1627
- const { stdout } = ink.useStdout();
1628
- const rows = stdout?.rows ?? 24;
1629
- const visibleRows = Math.max(1, rows - 5);
1630
- const info = describeSelection(store);
1631
- let entries = [];
1632
- let scrollLabel = "";
1633
- if (info) {
1634
- const all = gatherEntries(store, info);
1635
- const filtered = all.filter((e) => passesFilter(e, store.filter));
1636
- const total = filtered.length;
1637
- const maxOffset = Math.max(0, total - visibleRows);
1638
- const offset = Math.min(store.logScroll, maxOffset);
1639
- const end = total - offset;
1640
- const start = Math.max(0, end - visibleRows);
1641
- entries = filtered.slice(start, end);
1642
- if (offset > 0) scrollLabel = ` \xB7 scrolled +${offset}`;
1643
- }
1644
- return /* @__PURE__ */ jsxRuntime.jsxs(
1645
- ink.Box,
1646
- {
1647
- flexDirection: "column",
1648
- flexGrow: 1,
1649
- flexShrink: 1,
1650
- borderStyle: "single",
1651
- paddingX: 1,
1652
- overflow: "hidden",
1653
- children: [
1654
- /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexShrink: 0, flexDirection: "column", children: info ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { justifyContent: "space-between", children: [
1655
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, wrap: "truncate-end", children: [
1656
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "cyan", children: info.appName }),
1657
- info.processCount > 1 && info.pid == null ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", children: [
1658
- " (",
1659
- info.processCount,
1660
- " processes)"
1661
- ] }) : null,
1662
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", children: [
1663
- " ",
1664
- "\xB7 filter: ",
1665
- store.filter,
1666
- scrollLabel
1667
- ] })
1668
- ] }),
1669
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, wrap: "truncate-end", children: [
1670
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", children: [
1671
- "STATE ",
1672
- info.states.join(", ")
1673
- ] }),
1674
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", children: " \xB7 " }),
1675
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "magenta", children: [
1676
- "PID ",
1677
- formatPids(info.pids)
1678
- ] })
1679
- ] })
1680
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", children: " (no selection)" }) }) }),
1681
- /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", children: entries.map((entry, idx) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { wrap: "wrap", children: [
1682
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", children: [
1683
- formatTime(entry.ts),
1684
- " "
1685
- ] }),
1686
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: LEVEL_COLORS[entry.level], children: entry.level.toUpperCase().padEnd(5) }),
1687
- " ",
1688
- entry.text
1689
- ] }, idx)) })
1690
- ]
1691
- }
1692
- );
1693
- }
1694
-
1695
- function StatusBar() {
1696
- useStoreSnapshot();
1697
- const store = useTuiStore();
1698
- const sys = store.system;
1699
- return /* @__PURE__ */ jsxRuntime.jsxs(
1700
- ink.Box,
1701
- {
1702
- flexDirection: "row",
1703
- justifyContent: "space-between",
1704
- paddingX: 1,
1705
- flexShrink: 0,
1706
- height: 1,
1707
- children: [
1708
- /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexShrink: 1, overflow: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { wrap: "truncate-end", children: sys ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1709
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", children: "[system] " }),
1710
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: sys.level === "error" ? "red" : "white", children: sys.text })
1711
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", children: "[system] ready" }) }) }),
1712
- /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexShrink: 0, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", children: "\u2191/\u2193 \xB7 \u2192/\u2190 \xB7 PgUp/PgDn \xB7 s \xB7 \u21E7S \xB7 r \xB7 f \xB7 q" }) })
1713
- ]
1714
- }
1715
- );
1716
- }
1717
-
1718
- function findNodeAndApp(store, id) {
1719
- for (const app of store.apps) {
1720
- if (app.id === id) return { node: app, app };
1721
- if (app.threads) {
1722
- for (const t of app.threads) {
1723
- if (t.id === id) return { node: t, app };
1724
- }
1725
- }
1726
- }
1727
- return null;
1728
- }
1729
- function InnerTui({ store, onExit }) {
1730
- useStoreSnapshot();
1731
- const { stdout } = ink.useStdout();
1732
- const rows = stdout?.rows ?? 24;
1733
- ink.useInput((input, key) => {
1734
- const visible = flattenVisibleTree(store.apps);
1735
- if (visible.length === 0) return;
1736
- const currentIdx = visible.findIndex((n) => n.id === store.selectedId);
1737
- const idx = currentIdx < 0 ? 0 : currentIdx;
1738
- const selected = visible[idx];
1739
- const visibleLogRows = Math.max(1, rows - 5);
1740
- const halfPage = Math.max(1, Math.floor(visibleLogRows / 2));
1741
- if (key.upArrow && key.shift) {
1742
- const maxOffset = Math.max(
1743
- 0,
1744
- getSelectedFilteredCount(store) - visibleLogRows
1745
- );
1746
- store.setLogScroll(Math.min(maxOffset, store.logScroll + halfPage));
1747
- return;
1748
- }
1749
- if (key.downArrow && key.shift) {
1750
- store.setLogScroll(Math.max(0, store.logScroll - halfPage));
1751
- return;
1752
- }
1753
- if (key.home) {
1754
- const total = getSelectedFilteredCount(store);
1755
- store.setLogScroll(Math.max(0, total - visibleLogRows));
1756
- return;
1757
- }
1758
- if (key.end) {
1759
- store.setLogScroll(0);
1760
- return;
1761
- }
1762
- if (key.upArrow) {
1763
- const next = visible[Math.max(0, idx - 1)];
1764
- if (next) store.setSelected(next.id);
1765
- return;
1766
- }
1767
- if (key.downArrow) {
1768
- const next = visible[Math.min(visible.length - 1, idx + 1)];
1769
- if (next) store.setSelected(next.id);
1770
- return;
1771
- }
1772
- if (key.rightArrow) {
1773
- if (selected.kind === "app" && selected.threads && selected.threads.length) {
1774
- if (!selected.expanded) store.toggleExpand(selected.id);
1775
- }
1776
- return;
1777
- }
1778
- if (key.leftArrow) {
1779
- if (selected.kind === "app" && selected.expanded) {
1780
- store.toggleExpand(selected.id);
1781
- } else if (selected.kind === "thread") {
1782
- store.setSelected(selected.parentId);
1783
- }
1784
- return;
1785
- }
1786
- if (input === "f") {
1787
- store.setFilter(nextFilter(store.filter));
1788
- return;
1789
- }
1790
- if (input === "s") {
1791
- const target = findNodeAndApp(store, selected.id);
1792
- if (target) void store.handlers.stop(target.node.id);
1793
- return;
1794
- }
1795
- if (input === "S") {
1796
- const target = findNodeAndApp(store, selected.id);
1797
- if (target) void store.handlers.start(target.node.id);
1798
- return;
1799
- }
1800
- if (input === "r") {
1801
- const target = findNodeAndApp(store, selected.id);
1802
- if (target) void store.handlers.restart(target.node.id);
1803
- return;
1804
- }
1805
- if (input === "q" || key.ctrl && input === "c") {
1806
- void onExit();
1807
- return;
1808
- }
1809
- });
1810
- const columns = stdout?.columns ?? 80;
1811
- return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", width: columns, height: rows, children: [
1812
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", flexGrow: 1, width: columns, children: [
1813
- /* @__PURE__ */ jsxRuntime.jsx(AppList, {}),
1814
- /* @__PURE__ */ jsxRuntime.jsx(LogView, {})
1815
- ] }),
1816
- /* @__PURE__ */ jsxRuntime.jsx(StatusBar, {})
1817
- ] });
1818
- }
1819
- function TuiRoot(props) {
1820
- return /* @__PURE__ */ jsxRuntime.jsx(TuiStoreContext.Provider, { value: props.store, children: /* @__PURE__ */ jsxRuntime.jsx(InnerTui, { ...props }) });
1821
- }
1822
-
1823
- function getAppProps(definition, props, isSingle) {
1824
- if (isSingle) return { ...props };
1825
- return getPrefixedProps(definition, props);
1826
- }
1827
- function appIdFor(definition, idx) {
1828
- return `${definition.name}#${idx}`;
1829
- }
1830
- function resolveThreadCount(appProps) {
1831
- if (toolkit.isNumber(appProps.threads) && appProps.threads > 0) {
1832
- return appProps.threads;
1833
- }
1834
- return 1;
1835
- }
1836
- const APP_STATE_RANK = {
1837
- error: 10,
1838
- run: 9,
1839
- "in-run": 8,
1840
- setup: 7,
1841
- "in-setup": 6,
1842
- stop: 5,
1843
- "in-stop": 4,
1844
- shutdown: 3,
1845
- "in-shutdown": 2,
1846
- ready: 1,
1847
- init: 0
1848
- };
1849
- function aggregateAppState(threads) {
1850
- let best = "init";
1851
- for (const w of threads) {
1852
- const s = APP_STATE_RANK[w.state];
1853
- if (s > APP_STATE_RANK[best]) best = w.state;
1854
- }
1855
- return best;
1856
- }
1857
- function launchAppsTui(definitions, props) {
1858
- const store = createTuiStore();
1859
- const records = [];
1860
- const isSingle = definitions.length === 1;
1861
- if (props.watch) {
1862
- store.setSystem({
1863
- ts: Date.now(),
1864
- level: "warn",
1865
- text: "TUI does not yet support --watch mode;"
1866
- });
1867
- }
1868
- for (let i = 0; i < definitions.length; i++) {
1869
- const def = definitions[i];
1870
- const appId = appIdFor(def, i);
1871
- const appProps = getAppProps(def, props, isSingle);
1872
- const threadCount = resolveThreadCount(appProps);
1873
- appProps.__inheritIO = false;
1874
- delete appProps.threads;
1875
- const appNode = {
1876
- kind: "app",
1877
- id: appId,
1878
- name: def.name,
1879
- state: APP_INSTANCE_STATE.INIT,
1880
- expanded: false,
1881
- threads: []
1882
- };
1883
- store.addApp(appNode);
1884
- const threads = [];
1885
- const threadNodes = [];
1886
- for (let threadId = 1; threadId <= threadCount; threadId++) {
1887
- const nodeId = `${appId}:${threadId}`;
1888
- const threadNode = {
1889
- id: `${appId}:${threadId}`,
1890
- kind: "thread",
1891
- name: `${appNode.name}.${threadId}`,
1892
- parentId: appId,
1893
- threadId,
1894
- pid: 0,
1895
- state: APP_INSTANCE_STATE.INIT
1896
- };
1897
- threadNodes.push(threadNode);
1898
- const w = initThread(threadId, def.filePath, appProps);
1899
- w.eventBus.on("log", (entry) => {
1900
- store.pushLog(nodeId, {
1901
- ts: entry.ts,
1902
- level: entry.level,
1903
- text: entry.text
1904
- });
1905
- });
1906
- w.eventBus.on("state", (newState) => {
1907
- store.setState(nodeId, newState);
1908
- store.setState(appId, aggregateAppState(threads));
1909
- });
1910
- w.eventBus.on("pid", (pid) => {
1911
- store.setPid(nodeId, pid);
1912
- });
1913
- w.eventBus.on("error", (err) => {
1914
- store.pushLog(nodeId, {
1915
- ts: Date.now(),
1916
- level: "error",
1917
- text: `Thread error: ${err.message}`
1918
- });
1919
- });
1920
- threads.push(w);
1921
- }
1922
- store.setThreads(appId, threadNodes);
1923
- records.push({ appId, node: appNode, threads });
1924
- }
1925
- const findRecordByAppId = (id) => records.find((r) => r.appId === id);
1926
- const findThread = (id) => {
1927
- for (const r of records) {
1928
- for (const w of r.threads) {
1929
- if (`${r.appId}:${w.threadId}` === id) return { record: r, thread: w };
1930
- }
1931
- }
1932
- return void 0;
1933
- };
1934
- const startSingleThread = (w) => {
1935
- if (w.state === APP_INSTANCE_STATE.IN_RUN || APP_INSTANCE_STATE.RUN) {
1936
- return Promise.resolve();
1937
- }
1938
- if (w.state === APP_INSTANCE_STATE.STOP) return startThreadApp(w);
1939
- return setupThreadApp(w).then(() => startThreadApp(w));
1940
- };
1941
- store.handlers = {
1942
- stop(id) {
1943
- const r = findRecordByAppId(id);
1944
- if (r)
1945
- return Promise.all(r.threads.map((w) => stopThreadApp(w))).then(() => {
1946
- });
1947
- const found = findThread(id);
1948
- if (found) return stopThreadApp(found.thread);
1949
- return Promise.resolve();
1950
- },
1951
- start(id) {
1952
- const r = findRecordByAppId(id);
1953
- if (r) {
1954
- return Promise.all(r.threads.map(startSingleThread)).then(() => {
1955
- });
1956
- }
1957
- const found = findThread(id);
1958
- if (found) return startSingleThread(found.thread);
1959
- return Promise.resolve();
1960
- },
1961
- restart(id) {
1962
- const r = findRecordByAppId(id);
1963
- if (r) {
1964
- return Promise.all(
1965
- r.threads.map((w) => {
1966
- w.restartCount = 0;
1967
- return restartThreadApp(w);
1968
- })
1969
- ).then(() => {
1970
- });
1971
- }
1972
- const found = findThread(id);
1973
- if (found) {
1974
- found.thread.restartCount = 0;
1975
- return restartThreadApp(found.thread);
1976
- }
1977
- return Promise.resolve();
1978
- }
1979
- };
1980
- const startRecord = (r) => Promise.all(
1981
- r.threads.map(
1982
- (w) => waitForThreadReady(w).then(() => setupThreadApp(w)).then(() => startThreadApp(w))
1983
- )
1984
- ).then(toolkit.noop);
1985
- graceful.onShutdownError((error) => {
1986
- console.error("[Graceful Shutdown] error:", error);
1987
- });
1988
- const q = toolkit.defer();
1989
- graceful.onShutdown("app", () => {
1990
- store.setSystem({
1991
- ts: Date.now(),
1992
- level: "warn",
1993
- text: "shutdown initiated"
1994
- });
1995
- return Promise.all(
1996
- records.map((r) => Promise.all(r.threads.map((w) => shutdownThreadApp(w))))
1997
- ).then(() => {
1998
- store.setSystem({
1999
- ts: Date.now(),
2000
- level: "info",
2001
- text: "shutdown complete"
2002
- });
2003
- }).then(() => inkInstance.waitUntilRenderFlush()).then(() => inkInstance.unmount()).then(() => q.resolve()).catch(console.error);
2004
- });
2005
- const onExit = () => {
2006
- return Promise.resolve().then(() => graceful.processGraceful());
2007
- };
2008
- const inkInstance = ink.render(/* @__PURE__ */ jsxRuntime.jsx(TuiRoot, { store, onExit }), {
2009
- exitOnCtrlC: false
2010
- });
2011
- return Promise.all(records.map(startRecord)).then(() => q.promise);
2012
- }
2013
-
2014
- const DB_PATH = path__default.join(os__default.tmpdir(), "vrun.json");
2015
- class TempDB {
2016
- constructor(filepath) {
2017
- this.filepath = filepath;
2018
- }
2019
- _storage = /* @__PURE__ */ new Map();
2020
- _dirty = false;
2021
- get isDirty() {
2022
- return this._dirty;
2023
- }
2024
- get(key) {
2025
- return this._storage.get(key);
2026
- }
2027
- set(key, value) {
2028
- this._storage.set(key, value);
2029
- this._dirty = true;
2030
- }
2031
- save() {
2032
- if (!this._dirty) return;
2033
- const data = toolkit.EJSON.stringify(Array.from(this._storage.entries()));
2034
- fs__default.writeFileSync(this.filepath, data);
2035
- this._dirty = false;
2036
- }
2037
- load() {
2038
- try {
2039
- const data = toolkit.EJSON.parse(fs__default.readFileSync(this.filepath, "utf-8"));
2040
- for (const [key, value] of data) {
2041
- this._storage.set(key, value);
2042
- }
2043
- return true;
2044
- } catch (_) {
2045
- return false;
2046
- }
2047
- }
2048
- }
2049
- const db = new TempDB(DB_PATH);
2050
- db.load();
2051
-
2052
- const TYPE_TO_PLACEHOLDER = /* @__PURE__ */ new Map([
2053
- [String, "<string>"],
2054
- [Boolean, "<boolean>"],
2055
- [Number, "<number>"],
2056
- [Date, "<date>"],
2057
- [Array, "<array>"]
2058
- ]);
2059
- function propsToOptions(props) {
2060
- const result = [];
2061
- for (const [
2062
- name,
2063
- {
2064
- description,
2065
- type,
2066
- default: defaultValue,
2067
- required,
2068
- enum: variants,
2069
- alias,
2070
- env,
2071
- parser
2072
- }
2073
- ] of Object.entries(props)) {
2074
- const flagName = toolkit.kebabCase(name);
2075
- const typePlaceholder = TYPE_TO_PLACEHOLDER.get(type);
2076
- const envVariables = toolkit.arrayable(env);
2077
- const option = new commander.Option(
2078
- [alias ? `-${alias}, ` : "", `--${flagName}`, typePlaceholder].filter(Boolean).join(" "),
2079
- description
2080
- );
2081
- if (variants?.length) {
2082
- option.choices(variants);
2083
- }
2084
- if (envVariables.length) {
2085
- option.env(envVariables.join(", "));
2086
- option.default(getEnv(envVariables) || defaultValue?.());
2087
- } else if (defaultValue) {
2088
- option.default(defaultValue());
2089
- }
2090
- if (required) {
2091
- option.mandatory = true;
2092
- }
2093
- option.attributeName();
2094
- switch (type) {
2095
- case Boolean: {
2096
- option.isBoolean();
2097
- break;
2098
- }
2099
- case Number: {
2100
- option.argParser(parseNumberArgument);
2101
- }
2102
- }
2103
- if (parser) {
2104
- option.argParser(parser);
2105
- }
2106
- result.push(option);
2107
- }
2108
- return result;
2109
- }
2110
- function getEnv(variables) {
2111
- for (const env of variables) {
2112
- const value = process.env[env];
2113
- if (value) {
2114
- return value;
2115
- }
2116
- }
2117
- }
2118
- function parseNumberArgument(value) {
2119
- const parsedValue = parseInt(value, 10);
2120
- if (!toolkit.isNumber(parsedValue)) {
2121
- throw new commander.InvalidArgumentError("Not a number.");
2122
- }
2123
- return parsedValue;
2124
- }
2125
-
2126
- function runApp({
2127
- scriptFile,
2128
- cliName = "app",
2129
- cliDescription = "Application cli",
2130
- argv = process.argv
2131
- }) {
2132
- const promise = scriptFile ? loadScriptCommand(scriptFile) : Promise.resolve(buildRootCommand({ cliName, cliDescription }));
2133
- return promise.then((program) => program.parseAsync(["node", cliName, ...argv])).then(toolkit.noop).catch((error) => {
2134
- log.error("Failed to run app:", error);
2135
- process.exit(1);
2136
- });
2137
- }
2138
- function buildRootCommand({
2139
- cliDescription,
2140
- cliName
2141
- }) {
2142
- const program = new commander.Command().name(cliName).description(cliDescription).enablePositionalOptions().passThroughOptions().helpOption(false).helpCommand(false).action(function() {
2143
- const cmd = this;
2144
- if (isHelpArgument(cmd.args)) {
2145
- return cmd.help();
2146
- }
2147
- });
2148
- program.command("start", { isDefault: true }).argument("<string...>", "Path to script file").description("Start application.").addOption(new commander.Option("--watch", "Watch file changes")).addOption(new commander.Option("--dev", "Allow to import ts files")).passThroughOptions(true).allowUnknownOption(true).allowExcessArguments(true).action(function(scriptFiles, _opts) {
2149
- toolkit.assert.arrayStrings(scriptFiles, "scriptFiles expected string[]");
2150
- const cmd = this;
2151
- const remainingArgs = cmd.args;
2152
- const flagIdx = scriptFiles.findIndex((v) => v.startsWith("-"));
2153
- scriptFiles = (flagIdx > -1 ? scriptFiles.slice(0, flagIdx) : scriptFiles).filter((v) => !v.startsWith("-"));
2154
- if (!scriptFiles.length) {
2155
- return cmd.parent?.args?.includes("start") ? cmd.help() : program.help();
2156
- }
2157
- const project = createProject(path__default.resolve(scriptFiles[0]));
2158
- return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(
2159
- () => Promise.all(scriptFiles.map((f) => loadAppDefinition(project, f)))
2160
- ).then((apps) => buildStartCommand(apps)).then((appCmd) => {
2161
- if (isHelpArgument(remainingArgs)) {
2162
- return appCmd.help();
2163
- }
2164
- return appCmd.parseAsync(remainingArgs, { from: "user" });
2165
- }).then(toolkit.noop);
2166
- });
2167
- program.command("json").description(
2168
- "Start apps from a JSON file containing an array of script paths."
2169
- ).argument("[string]", "Path to JSON file").allowUnknownOption(true).allowExcessArguments(true).action(function(jsonFile) {
2170
- const cmd = this;
2171
- if (!jsonFile || isHelpArgument(jsonFile)) {
2172
- return cmd.help();
2173
- }
2174
- const project = createProject(path__default.dirname(path__default.resolve(jsonFile)));
2175
- const remainingArgs = cmd.args;
2176
- return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(
2177
- () => projectImportFile(
2178
- project,
2179
- path__default.resolve(jsonFile)
2180
- )
2181
- ).then((m) => {
2182
- const paths = m.default;
2183
- toolkit.assert.arrayStrings(paths, "JSON file must contain a string[].");
2184
- return paths.map(
2185
- (p) => path__default.resolve(path__default.dirname(path__default.resolve(jsonFile)), p)
2186
- );
2187
- }).then(
2188
- (scriptFiles) => Promise.all(scriptFiles.map((f) => loadAppDefinition(project, f)))
2189
- ).then((apps) => buildStartCommand(apps)).then((appCmd) => {
2190
- if (isHelpArgument(remainingArgs)) {
2191
- return appCmd.help();
2192
- }
2193
- return appCmd.parseAsync(remainingArgs, { from: "user" });
2194
- }).then(toolkit.noop);
2195
- });
2196
- program.command("list").description("Interactively pick apps to run from a folder.").argument("[string]", "Path to folder", ".").addOption(
2197
- new commander.Option("--pattern <glob>", "Glob pattern for app files").default(
2198
- "**/*.{app,worker}.{ts,js}"
2199
- )
2200
- ).allowUnknownOption(true).allowExcessArguments(true).action(function(folderPath, opts) {
2201
- const cmd = this;
2202
- if (isHelpArgument(cmd.args)) {
2203
- return cmd.help();
2204
- }
2205
- const remainingArgs = cmd.args;
2206
- const project = createProject(path__default.resolve(folderPath));
2207
- return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => findAppFiles(path__default.resolve(folderPath), opts.pattern)).then((scriptFiles) => {
2208
- if (!scriptFiles.length) {
2209
- log.error("No files found. Pattern: %s", opts.pattern);
2210
- process.exit(1);
2211
- }
2212
- return log.prompt("Pick apps to run.", {
2213
- type: "multiselect",
2214
- options: scriptFiles.map((f) => ({
2215
- label: path__default.basename(f),
2216
- value: f
2217
- })),
2218
- initial: db.get("app_last_run"),
2219
- cancel: "null"
2220
- });
2221
- }).then((selected) => {
2222
- const selectedFiles = selected || [];
2223
- if (!selectedFiles.length) process.exit(0);
2224
- db.set("app_last_run", selectedFiles);
2225
- db.save();
2226
- return Promise.all(
2227
- selectedFiles.map((f) => loadAppDefinition(project, f))
2228
- );
2229
- }).then((apps) => buildStartCommand(apps)).then((appCmd) => {
2230
- if (isHelpArgument(remainingArgs)) {
2231
- return appCmd.help();
2232
- }
2233
- return appCmd.parseAsync(remainingArgs, { from: "user" });
2234
- }).then(toolkit.noop);
2235
- });
2236
- program.command("folder").description("Start all apps found in a folder.").argument("<string>", "Path to folder").addOption(
2237
- new commander.Option("--pattern <glob>", "Glob pattern for app files").default(
2238
- "**/*.{ts,js}"
2239
- )
2240
- ).allowUnknownOption(true).allowExcessArguments(true).action(function(folderPath, opts) {
2241
- const cmd = this;
2242
- if (!folderPath || isHelpArgument(folderPath)) {
2243
- return cmd.help();
2244
- }
2245
- const remainingArgs = cmd.args;
2246
- const project = createProject(path__default.resolve(folderPath));
2247
- return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => findAppFiles(path__default.resolve(folderPath), opts.pattern)).then((scriptFiles) => {
2248
- if (!scriptFiles.length) {
2249
- log.error("No files found. Pattern: %s", opts.pattern);
2250
- process.exit(1);
2251
- }
2252
- return Promise.all(
2253
- scriptFiles.map((f) => loadAppDefinition(project, f))
2254
- );
2255
- }).then((apps) => {
2256
- if (!apps.length) {
2257
- log.error("No valid AppDefinitions found in folder.");
2258
- process.exit(1);
2259
- }
2260
- return apps;
2261
- }).then((apps) => buildStartCommand(apps)).then((appCmd) => {
2262
- if (isHelpArgument(remainingArgs)) {
2263
- return appCmd.help();
2264
- }
2265
- return appCmd.parseAsync(remainingArgs, { from: "user" });
2266
- }).then(toolkit.noop);
2267
- });
2268
- return program;
2269
- }
2270
- function findAppFiles(folderPath, pattern) {
2271
- return fs__default.globSync(pattern, {
2272
- cwd: folderPath,
2273
- exclude: (f) => f.includes("node_modules")
2274
- }).map((f) => path__default.resolve(folderPath, f)).sort();
2275
- }
2276
- function loadScriptCommand(scriptFile) {
2277
- const project = createProject(path__default.dirname(scriptFile));
2278
- return Promise.resolve().then(() => registerTsx(project)).then(() => loadProject(project)).then(() => loadAppDefinition(project, scriptFile)).then((app) => buildStartCommand([app]));
2279
- }
2280
- function registerTsx(project) {
2281
- if (CONFIG.TS_MODE === "tsx") {
2282
- project.allowTs = true;
2283
- return Promise.resolve();
2284
- }
2285
- if (CONFIG.TS_MODE === "tsx-register") {
2286
- return Promise.resolve().then(() => import('tsx/esm/api')).then((m) => m.register({ tsconfig: project.tsconfigPath || false })).then(() => {
2287
- project.allowTs = true;
2288
- });
2289
- }
2290
- return Promise.resolve();
2291
- }
2292
- function loadProject(project) {
2293
- projectPrintInfo(project);
2294
- return Promise.resolve().then(() => projectLoadEnv(project)).then(() => projectAutoImport(project)).then(toolkit.noop);
2295
- }
2296
- function loadAppDefinition(project, scriptFile) {
2297
- return projectImportFile(project, scriptFile).then((appModule) => {
2298
- const app = appModule.default;
2299
- if (!isAppDefinition(app)) {
2300
- log.warn(`Default export must be a app definition: ${scriptFile}`);
2301
- process.exit(1);
2302
- }
2303
- return app;
2304
- });
2305
- }
2306
- function buildStartCommand(definitions) {
2307
- const rootDefinition = definitions.length > 1 ? createAppHub(definitions.map((v) => createAppThread(v))) : createAppThread(definitions[0]);
2308
- const program = new commander.Command().name(rootDefinition.name).description(rootDefinition.description || "An application");
2309
- if (rootDefinition.props) {
2310
- for (const option of propsToOptions(rootDefinition.props)) {
2311
- program.addOption(option);
2312
- }
2313
- }
2314
- program.addOption(new commander.Option("--watch", "Watch file changes")).addOption(new commander.Option("--dev", "Allow to import ts files")).addOption(new commander.Option("--tui", "Launch interactive terminal UI")).helpCommand(false).command("start", { isDefault: true, hidden: true }).allowUnknownOption(true).allowExcessArguments(true).action((...args) => {
2315
- const props = program.optsWithGlobals();
2316
- if (props.tui) {
2317
- return launchAppsTui(definitions, props);
2318
- }
2319
- return launchApp(
2320
- definitions.length > 1 || props.threads > 1 ? rootDefinition : definitions[0],
2321
- props
2322
- );
2323
- });
2324
- return program;
2325
- }
2326
- function launchApp(definition, props) {
2327
- const startDefer = toolkit.defer();
2328
- let app;
2329
- graceful.onShutdown("app", () => {
2330
- log.warn("Graceful shutdown initiated, waiting for process to complete");
2331
- if (CONFIG.TS_MODE === "tsx") {
2332
- log.warn("Graceful in tsx + watch mode is not supported.");
2333
- }
2334
- return startDefer.promise.then(() => {
2335
- if (app) {
2336
- return shutdownApp(app);
2337
- }
2338
- });
2339
- });
2340
- return startApp(definition, props).then((startResult) => {
2341
- if (toolkit.isSkip(startResult)) {
2342
- startDefer.resolve();
2343
- log.warn("Failed to start %s", startResult, startResult.error);
2344
- return;
2345
- }
2346
- app = startResult.app;
2347
- startDefer.resolve();
2348
- return appWaitShutdown(app);
2349
- }).then(() => graceful.processGraceful());
2350
- }
2351
-
2352
- const cli = Object.freeze({
2353
- runApp
2354
- });
2355
-
2356
- const index = {
2357
- __proto__: null,
2358
- cli: cli
2359
- };
2360
-
2361
- class IntervalStrategy {
2362
- constructor(options) {
2363
- this.options = options;
2364
- }
2365
- timerSequence = 0;
2366
- timer = null;
2367
- worker;
2368
- doSetup({ worker }) {
2369
- this.worker = worker;
2370
- }
2371
- startSignal() {
2372
- this.timer = setInterval(() => {
2373
- if (this.worker.isIdle) {
2374
- this.worker.addTask(this.createTask());
2375
- } else {
2376
- log.warn(
2377
- "[%s] Worker busy, skipping tick",
2378
- this.worker.definition.name
2379
- );
2380
- }
2381
- }, this.options.intervalSeconds * 1e3);
2382
- }
2383
- stopSignal(done) {
2384
- clearInterval(this.timer);
2385
- this.timer = null;
2386
- done();
2387
- }
2388
- doShutdown() {
2389
- }
2390
- createTask() {
2391
- return { timerSequence: ++this.timerSequence };
2392
- }
2393
- }
2394
-
2395
- const WORKER_DEF = Symbol("worker-definition");
2396
- function createWorkerPool(size) {
2397
- return new toolkit.ResourcePool({
2398
- poolSize: size,
2399
- auto: true,
2400
- createResource: () => Symbol("worker-slot")
2401
- });
2402
- }
2403
- function createWorkerInstance(definition, logger) {
2404
- const instance = {
2405
- definition,
2406
- log: logger,
2407
- queue: new toolkit.AsyncIterableQueue(),
2408
- pool: createWorkerPool(definition.taskParallel ?? 2),
2409
- runTask: null,
2410
- queueMaxSize: definition.taskLimit ?? 50,
2411
- taskParallel: definition.taskParallel ?? 2,
2412
- overloadedSignaled: false,
2413
- addTask(ctx) {
2414
- addWorkerTask(this, ctx);
2415
- },
2416
- get isOverloaded() {
2417
- return this.queueSize > this.queueMaxSize * 0.8;
2418
- },
2419
- get isIdle() {
2420
- return this.pool.isIdle && this.queueSize === 0;
2421
- },
2422
- get queueSize() {
2423
- return this.queue._queue.items.length;
2424
- }
2425
- };
2426
- return instance;
2427
- }
2428
- function addWorkerTask(instance, ctx) {
2429
- instance.queue.put(ctx);
2430
- checkWorkerPressure(instance);
2431
- }
2432
- async function executeWorkerTask(instance, entryContext, props, abortSignal) {
2433
- const { executeStrategy, entry } = instance.definition;
2434
- if (toolkit.isFunction(executeStrategy.executeSignal)) {
2435
- const [signalError, signalResult] = await toolkit.catchError(
2436
- () => executeStrategy.executeSignal(entryContext)
2437
- );
2438
- if (signalError) {
2439
- instance.log.error("Strategy executeSignal error, dropping task", {
2440
- error: signalError
2441
- });
2442
- return;
2443
- }
2444
- if (toolkit.isSkip(signalResult)) {
2445
- instance.log.warn(
2446
- "Strategy executeSignal returned skip, dropping task",
2447
- signalResult
2448
- );
2449
- return;
2450
- }
2451
- }
2452
- let [executeError, executeResult] = await toolkit.catchError(
2453
- () => entry?.call(entryContext, props)
2454
- );
2455
- if (executeError) {
2456
- if (executeStrategy.handleEntryError) {
2457
- executeResult = executeStrategy.handleEntryError(executeError);
2458
- }
2459
- if (!executeResult) {
2460
- executeResult = {
2461
- skip: true,
2462
- error: true,
2463
- code: "critical_error",
2464
- reason: executeError.message,
2465
- stack: executeError.stack
2466
- };
2467
- }
2468
- } else if (!executeResult || Array.isArray(executeResult) && !executeResult.length) {
2469
- executeResult = { success: true, code: "void" };
2470
- }
2471
- const results = Array.isArray(executeResult) ? executeResult : [executeResult];
2472
- let hasError = false;
2473
- let hasSkip = false;
2474
- for (const result of results) {
2475
- hasError = hasError || toolkit.isSkip(result) && !!result.error;
2476
- hasSkip = toolkit.isSkip(result);
2477
- }
2478
- if (hasError) {
2479
- instance.log.error("Task error", results);
2480
- } else if (hasSkip) {
2481
- instance.log.warn("Task skipped", results);
2482
- } else {
2483
- instance.log.debug("Task completed", results);
2484
- }
2485
- if (executeStrategy.completeSignal) {
2486
- const [completeError] = await toolkit.catchError(
2487
- () => executeStrategy.completeSignal(entryContext, executeResult)
2488
- );
2489
- if (completeError) {
2490
- instance.log.error("Strategy completeSignal error", {
2491
- error: completeError
2492
- });
2493
- }
2494
- }
2495
- }
2496
- function defineWorker(definition) {
2497
- const result = defineApp({
2498
- filePath: filePathFromStack(toolkit.captureStackTrace(defineWorker)),
2499
- ...definition,
2500
- setup(props) {
2501
- return setupWorker(this, definition, props);
2502
- },
2503
- entry(props) {
2504
- return runWorkerLoop(this, definition, props);
2505
- },
2506
- stop(props) {
2507
- return stopWorker(this, definition, props);
2508
- },
2509
- shutdown(props) {
2510
- return shutdownWorker(this, definition, props);
2511
- }
2512
- });
2513
- result.disabled = result.disabled || CONFIG.WORKER_DISABLED.has(result.name);
2514
- toolkit.def(result, WORKER_DEF, true);
2515
- return result;
2516
- }
2517
- function setupWorker(setupContext, definition, props) {
2518
- const setupFn = definition.setup;
2519
- const ctx = {
2520
- ...setupContext,
2521
- worker: createWorkerInstance(definition, setupContext.log)
2522
- };
2523
- return Promise.resolve().then(() => setupFn ? setupFn.call(ctx, props) : void 0).then((setupResult) => {
2524
- return {
2525
- ...ctx,
2526
- ...setupResult || {}
2527
- };
2528
- });
2529
- }
2530
- async function runWorkerLoop(runtimeContext, definition, props) {
2531
- const worker = runtimeContext.worker;
2532
- const { executeStrategy } = definition;
2533
- await executeStrategy.doSetup({ worker });
2534
- const abortController = new AbortController();
2535
- abortController.signal;
2536
- const onStopSignal = () => {
2537
- return Promise.resolve().then(() => abortController.abort()).then(
2538
- () => toolkit.catchError(
2539
- () => executeStrategy.stopSignal(() => worker.queue.close())
2540
- )
2541
- ).then((stopResult) => {
2542
- const stopError = stopResult[0];
2543
- if (stopError) {
2544
- worker.queue.close();
2545
- worker.log.error("Strategy stopSignal error", { error: stopError });
2546
- }
2547
- });
2548
- };
2549
- worker.runTask = new toolkit.CancellablePromise(
2550
- async (resolve, _reject, onCancel) => {
2551
- onCancel(onStopSignal);
2552
- const [startError] = await toolkit.catchError(
2553
- () => executeStrategy.startSignal()
2554
- );
2555
- if (startError) {
2556
- worker.log.error("Strategy startSignal error", { error: startError });
2557
- worker.runTask = null;
2558
- return resolve();
2559
- }
2560
- worker.log.info("Worker started (parallel=%d)", worker.taskParallel);
2561
- for await (const taskContext of worker.queue) {
2562
- const jobTicket = await worker.pool.acquire();
2563
- const entryContext = {
2564
- ...runtimeContext,
2565
- ...taskContext
2566
- };
2567
- executeWorkerTask(worker, entryContext, props).finally(
2568
- () => {
2569
- worker.pool.release(jobTicket);
2570
- checkWorkerPressure(worker);
2571
- }
2572
- );
2573
- }
2574
- if (worker.pool.usedCount > 0) {
2575
- worker.log.info(
2576
- "Draining task pool (active=%d)",
2577
- worker.pool.usedCount
2578
- );
2579
- }
2580
- await worker.pool.destroy();
2581
- worker.queue = new toolkit.AsyncIterableQueue();
2582
- worker.runTask = null;
2583
- resolve();
2584
- }
2585
- );
2586
- worker.runTask.catch((err) => {
2587
- worker.log.error("Worker crash: %s", { error: toolkit.toError(err) });
2588
- });
2589
- }
2590
- function checkWorkerPressure(worker) {
2591
- try {
2592
- if (worker.isOverloaded) {
2593
- if (!worker.definition.executeStrategy.overloadedSignal)
2594
- return void worker.log.warn(
2595
- "Worker under pressure (queue=%d, limit=%d)",
2596
- worker.queueSize,
2597
- worker.queueMaxSize
2598
- );
2599
- if (worker.overloadedSignaled) return;
2600
- worker.overloadedSignaled = true;
2601
- worker.definition.executeStrategy.overloadedSignal();
2602
- } else {
2603
- if (!worker.overloadedSignaled) return;
2604
- if (!worker.definition.executeStrategy.availableSignal) {
2605
- worker.log.warn("Worker capacity restored");
2606
- } else {
2607
- worker.definition.executeStrategy.availableSignal();
2608
- }
2609
- worker.overloadedSignaled = false;
2610
- }
2611
- } catch (err) {
2612
- worker.log.error("Backpressure signal error", { error: err });
2613
- }
2614
- }
2615
- async function stopWorker(runtimeContext, definition, props) {
2616
- const worker = runtimeContext.worker;
2617
- const userStop = definition.stop;
2618
- if (worker.runTask) {
2619
- if (toolkit.isFunction(userStop)) {
2620
- await userStop.call(runtimeContext, props);
2621
- }
2622
- if (!worker.runTask) return;
2623
- worker.runTask.cancel();
2624
- await worker.runTask.catch(() => {
2625
- });
2626
- worker.runTask = null;
2627
- }
2628
- }
2629
- async function shutdownWorker(runtimeContext, definition, props) {
2630
- const worker = runtimeContext.worker;
2631
- const userShutdown = definition.shutdown;
2632
- if (toolkit.isFunction(userShutdown)) {
2633
- await userShutdown.call(runtimeContext, props);
2634
- }
2635
- await definition.executeStrategy.doShutdown();
2636
- worker.queue = new toolkit.AsyncIterableQueue();
2637
- worker.pool = createWorkerPool(definition.taskParallel ?? 2);
2638
- }
2639
- function isWorkerDefinition(value) {
2640
- return value?.[WORKER_DEF] === true;
2641
- }
2642
- function isWorkerInstance(value) {
2643
- return isWorkerDefinition(value?.definition);
2644
- }
2645
-
2646
- exports.APP_INSTANCE_STATE = APP_INSTANCE_STATE;
2647
- exports.IntervalStrategy = IntervalStrategy;
2648
- exports.addWorkerTask = addWorkerTask;
2649
- exports.appWaitShutdown = appWaitShutdown;
2650
- exports.cli = cli;
2651
- exports.createAppHub = createAppHub;
2652
- exports.createAppInstance = createAppInstance;
2653
- exports.createAppThread = createAppThread;
2654
- exports.createAppThreadInstance = createAppThreadInstance;
2655
- exports.createThreadMessage = createThreadMessage;
2656
- exports.createWorkerInstance = createWorkerInstance;
2657
- exports.defineApp = defineApp;
2658
- exports.defineWorker = defineWorker;
2659
- exports.getPrefixedProps = getPrefixedProps;
2660
- exports.initThread = initThread;
2661
- exports.isAppDefinition = isAppDefinition;
2662
- exports.isThreadExit = isThreadExit;
2663
- exports.isThreadMessage = isThreadMessage;
2664
- exports.isWorkerDefinition = isWorkerDefinition;
2665
- exports.isWorkerInstance = isWorkerInstance;
2666
- exports.prefixifyProps = prefixifyProps;
2667
- exports.restartThreadApp = restartThreadApp;
2668
- exports.runApp = runApp$1;
2669
- exports.setupApp = setupApp;
2670
- exports.setupThreadApp = setupThreadApp;
2671
- exports.shutdownApp = shutdownApp;
2672
- exports.shutdownThreadApp = shutdownThreadApp;
2673
- exports.startApp = startApp;
2674
- exports.startThreadApp = startThreadApp;
2675
- exports.stopApp = stopApp;
2676
- exports.stopThreadApp = stopThreadApp;
2677
- exports.waitForThreadReady = waitForThreadReady;
2678
- //# sourceMappingURL=index.cjs.map