feat: exit() accepts result value, waitUntilExit resolves with it
Change exit(error?: Error) to exit(errorOrResult?: unknown). When an
Error is passed, waitUntilExit rejects; otherwise it resolves with the
value. This matches Ink's behavior where exit("hello") resolves the
promise with "hello".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,10 +3,11 @@ import { AppContextKey } from "../context.ts";
|
||||
|
||||
/**
|
||||
* Returns the exit function so a component can end the app from inside the
|
||||
* tree. Pass an Error to reject `app.waitUntilExit()` (and any awaiter); call
|
||||
* with no args to resolve cleanly.
|
||||
* tree. Pass an Error to reject `app.waitUntilExit()` (and any awaiter); pass
|
||||
* any other value to resolve with it as the result; call with no args to
|
||||
* resolve with undefined.
|
||||
*/
|
||||
export function useExit(): (error?: Error) => void {
|
||||
export function useExit(): (errorOrResult?: unknown) => void {
|
||||
const ctx = inject(AppContextKey);
|
||||
if (!ctx) throw new Error("useExit() must be called inside a vue-tui render tree");
|
||||
return ctx.exit;
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { InjectionKey, ShallowRef } from "vue";
|
||||
import type { EventEmitter } from "node:events";
|
||||
|
||||
export interface AppContext {
|
||||
exit: (error?: Error) => void;
|
||||
exit: (errorOrResult?: unknown) => void;
|
||||
stdout: NodeJS.WriteStream;
|
||||
stderr: NodeJS.WriteStream;
|
||||
stdin: NodeJS.ReadStream;
|
||||
|
||||
@@ -31,7 +31,7 @@ export interface MountOptions {
|
||||
|
||||
export interface TuiApp extends Omit<VueApp<TuiNode>, "mount"> {
|
||||
mount(options?: MountOptions): ComponentPublicInstance;
|
||||
waitUntilExit(): Promise<void>;
|
||||
waitUntilExit(): Promise<unknown>;
|
||||
}
|
||||
|
||||
type RootProps = Record<string, unknown>;
|
||||
@@ -39,9 +39,9 @@ type RootProps = Record<string, unknown>;
|
||||
export function createApp(root: Component, rootProps?: RootProps | null): TuiApp {
|
||||
// exit promise — created at createApp time so waitUntilExit() works even
|
||||
// before mount (it just hangs until mount + exit).
|
||||
let exitResolve!: () => void;
|
||||
let exitResolve!: (result?: unknown) => void;
|
||||
let exitReject!: (e: Error) => void;
|
||||
const exitPromise = new Promise<void>((res, rej) => {
|
||||
const exitPromise = new Promise<unknown>((res, rej) => {
|
||||
exitResolve = res;
|
||||
exitReject = rej;
|
||||
});
|
||||
@@ -120,14 +120,17 @@ export function createApp(root: Component, rootProps?: RootProps | null): TuiApp
|
||||
mountedDebug = debug;
|
||||
|
||||
const appContext: AppContext = {
|
||||
exit(err?: Error) {
|
||||
if (err) exitReject(err);
|
||||
exit(errorOrResult?: unknown) {
|
||||
// Defer teardown to a microtask: exit() is frequently called from
|
||||
// inside the Vue update cycle (useInput handler, setup(), errorHandler)
|
||||
// and unmounting synchronously would tear Vue down mid-flush.
|
||||
queueMicrotask(() => {
|
||||
teardown();
|
||||
exitResolve();
|
||||
if (errorOrResult instanceof Error) {
|
||||
exitReject(errorOrResult);
|
||||
} else {
|
||||
exitResolve(errorOrResult);
|
||||
}
|
||||
});
|
||||
},
|
||||
stdout,
|
||||
@@ -238,7 +241,7 @@ export function createApp(root: Component, rootProps?: RootProps | null): TuiApp
|
||||
exitResolve();
|
||||
};
|
||||
|
||||
app.waitUntilExit = function waitUntilExit(): Promise<void> {
|
||||
app.waitUntilExit = function waitUntilExit(): Promise<unknown> {
|
||||
return exitPromise;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user