Skip to content

Type-Safe Mode-Specific Handles & FluentPromise Extraction #10

@mildronize

Description

@mildronize

Summary

Version 4 focuses on full type-safety for $-based fluent shell execution and modularization of its fluent interface.
createShell() will now provide $ with output-mode-dependent handles while keeping result behavior unchanged.
The FluentPromise abstraction will be split into its own file (and prepared for future npm release).


Motivation

  • Simplify usage — createShell() instantly returns a fluent interface without .asFluent().

  • Strengthen TypeScript inference:

    • live → handle exposes only .result()
    • capture / all → full PromiseLike<string> chain with .toLines(), .parse(), .safeParse().
  • Keep compatibility with existing ExecutionResult design (stdout/stderr remain null in live).

  • Decouple the fluent interface into an independent reusable module (FluentPromise).


Key Changes

  1. createShell()

    • Returns a Shell instance with a $ property (fluent executor) by default.
    • .run() / .safeRun() remain functional but are marked @deprecated.
  2. DollarFunction (Fluent entrypoint)

    • Tagged template and function-call overloads return mode-specific handle types.

    • Example:

      const $ = createShell({ outputMode: 'live' }).$;
      const r = await $`npm run dev`.result(); // OK
      await $`npm run dev`; // ❌ TS error
  3. Type System Enhancements

    • Conditional HandleForMode<M>:

      type HandleForMode<M extends OutputMode> =
        CaptureForMode<M> extends true
          ? (BaseHandle<M> & FluentPromise)
          : BaseHandle<M>;
    • Keeps original ExecutionResult, StrictResult, and SafeResult untouched.

  4. FluentPromise Extraction

    • New file: src/fluent/FluentPromise.ts

    • Defines the reusable fluent helper surface:

      export type FluentPromise = PromiseLike<string> & {
        toLines(): Promise<string[]>;
        parse<T extends StandardSchemaV1>(schema: T): Promise<InferOutput<T>>;
        safeParse<T extends StandardSchemaV1>(schema: T): Promise<ValidationResult<InferOutput<T>>>;
      };
    • Shell imports this for its fluent handles.

    • Future npm target: @thaitype/fluent-promise

  5. Runtime Behavior (unchanged)

    • live uses inherit I/O; returns null outputs.
    • capture / all buffer as before.
    • safeRun() and .result() never throw.

File Structure

src/
  fluent/
    FluentPromise.ts
    index.ts
  shell/
    Shell.ts
  standard-schema.ts
  index.ts

Examples

const shell = createShell({ outputMode: 'capture' });
const $ = shell.$;

// capture / all modes
const msg = await $`echo hello`; // "hello"
const cfg = await $('cat package.json', { outputMode: 'all' }).parse(ConfigSchema);

// live mode
const liveShell = createShell({ outputMode: 'live' });
const $live = liveShell.$;
const res = await $live`npm run dev`.result(); // OK, no toLines()

Migration Notes

  • Existing .run() / .safeRun() continue working but show @deprecated hint.
  • No runtime changes: scripts using capture / all remain identical.
  • FluentPromise can be imported directly for independent reuse.

Future Plans

  • Publish @thaitype/fluent-promise package.
  • Add optional stream hooks (tap()) or stdin piping utilities.
  • Evaluate typed pipeline chaining for non-shell async tasks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions