effect 4.0.0-beta.79 → 4.0.0-beta.80

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.
@@ -66,8 +66,8 @@ export const isPrompt = (u: unknown): u is Prompt<unknown> => Predicate.hasPrope
66
66
  export type Environment = FileSystem.FileSystem | Path.Path | Terminal.Terminal
67
67
 
68
68
  /**
69
- * Represents the action that should be taken by a `Prompt` based upon the
70
- * user input received during the current frame.
69
+ * Represents the action that should be taken by a `Prompt` based upon user
70
+ * input or an external event received during the current frame.
71
71
  *
72
72
  * @category models
73
73
  * @since 4.0.0
@@ -93,6 +93,18 @@ export interface ActionDefinition extends Data.TaggedEnum.WithGenerics<2> {
93
93
  readonly taggedEnum: Action<this["A"], this["B"]>
94
94
  }
95
95
 
96
+ /**
97
+ * Represents the input that should be processed by a `Prompt` based upon user
98
+ * input or an external event received during the current frame.
99
+ *
100
+ * @category models
101
+ * @since 4.0.0
102
+ */
103
+ export type ProcessInput<A> = Data.TaggedEnum<{
104
+ readonly Input: { readonly input: Terminal.UserInput }
105
+ readonly Event: { readonly value: A }
106
+ }>
107
+
96
108
  /**
97
109
  * Represents the set of handlers used by a `Prompt`.
98
110
  *
@@ -104,7 +116,7 @@ export interface ActionDefinition extends Data.TaggedEnum.WithGenerics<2> {
104
116
  * @category models
105
117
  * @since 4.0.0
106
118
  */
107
- export interface Handlers<State, Output> {
119
+ export interface Handlers<State, Output, Input = Terminal.UserInput> {
108
120
  /**
109
121
  * A function that is called to render the current frame of the `Prompt`.
110
122
  */
@@ -117,7 +129,7 @@ export interface Handlers<State, Output> {
117
129
  * `Prompt.Action` that should be taken.
118
130
  */
119
131
  readonly process: (
120
- input: Terminal.UserInput,
132
+ input: Input,
121
133
  state: State
122
134
  ) => Effect.Effect<Action<State, Output>, never, Environment>
123
135
  /**
@@ -751,19 +763,85 @@ export const confirm = (options: ConfirmOptions): Prompt<boolean> => {
751
763
  * next prompt action, and `clear` returns ANSI output used to clear the previous
752
764
  * frame.
753
765
  *
766
+ * Optionally, an external `events` dequeue can be provided as the third
767
+ * argument. When present, the render loop will race user input against events
768
+ * from the dequeue, allowing background events to trigger re-renders without
769
+ * waiting for a keypress. When an event is received from the dequeue, the
770
+ * `receive` handler is called instead of `process`.
771
+ *
754
772
  * @category constructors
755
773
  * @since 4.0.0
756
774
  */
757
- export const custom = <State, Output>(
775
+ export const custom: {
776
+ /**
777
+ * Creates a custom `Prompt` from the specified initial state and handlers.
778
+ *
779
+ * **Details**
780
+ *
781
+ * The initial state can either be a pure value or an `Effect`. This is
782
+ * particularly useful when the initial state of the `Prompt` must be computed
783
+ * by performing an effectful computation, such as reading data from the file
784
+ * system. A `Prompt` runs as a render loop: `render` returns ANSI output for
785
+ * the current frame, the `Terminal` obtains user input, `process` returns the
786
+ * next prompt action, and `clear` returns ANSI output used to clear the previous
787
+ * frame.
788
+ *
789
+ * Optionally, an external `events` dequeue can be provided as the third
790
+ * argument. When present, the render loop will race user input against events
791
+ * from the dequeue, allowing background events to trigger re-renders without
792
+ * waiting for a keypress. When an event is received from the dequeue, the
793
+ * `receive` handler is called instead of `process`.
794
+ *
795
+ * @category constructors
796
+ * @since 4.0.0
797
+ */
798
+ <State, Output>(
799
+ initialState: State | Effect.Effect<State, never, Environment>,
800
+ handlers: Handlers<State, Output>
801
+ ): Prompt<Output>
802
+ /**
803
+ * Creates a custom `Prompt` from the specified initial state and handlers.
804
+ *
805
+ * **Details**
806
+ *
807
+ * The initial state can either be a pure value or an `Effect`. This is
808
+ * particularly useful when the initial state of the `Prompt` must be computed
809
+ * by performing an effectful computation, such as reading data from the file
810
+ * system. A `Prompt` runs as a render loop: `render` returns ANSI output for
811
+ * the current frame, the `Terminal` obtains user input, `process` returns the
812
+ * next prompt action, and `clear` returns ANSI output used to clear the previous
813
+ * frame.
814
+ *
815
+ * Optionally, an external `events` dequeue can be provided as the third
816
+ * argument. When present, the render loop will race user input against events
817
+ * from the dequeue, allowing background events to trigger re-renders without
818
+ * waiting for a keypress. When an event is received from the dequeue, the
819
+ * `receive` handler is called instead of `process`.
820
+ *
821
+ * @category constructors
822
+ * @since 4.0.0
823
+ */
824
+ <State, Output, A>(
825
+ initialState: State | Effect.Effect<State, never, Environment>,
826
+ events: Queue.Dequeue<A, never>,
827
+ handlers: Handlers<State, Output, ProcessInput<A>>
828
+ ): Prompt<Output>
829
+ } = <State, Output, A>(
758
830
  initialState: State | Effect.Effect<State, never, Environment>,
759
- handlers: Handlers<State, Output>
831
+ ...args:
832
+ | [handlers: Handlers<State, Output, Terminal.UserInput>]
833
+ | [events: Queue.Dequeue<A, never>, handlers: Handlers<State, Output, ProcessInput<A>>]
760
834
  ): Prompt<Output> => {
835
+ const [events, handlers] = args.length === 1
836
+ ? [undefined, args[0]] as const
837
+ : [args[0], args[1]] as const
761
838
  const op = Object.create(proto)
762
839
  op._tag = "Loop"
763
840
  op.initialState = initialState
764
841
  op.render = handlers.render
765
842
  op.process = handlers.process
766
843
  op.clear = handlers.clear
844
+ op.events = events
767
845
  return op
768
846
  }
769
847
 
@@ -1266,8 +1344,12 @@ interface Loop extends
1266
1344
  Op<"Loop", {
1267
1345
  readonly initialState: unknown | Effect.Effect<unknown, never, Environment>
1268
1346
  readonly render: Handlers<unknown, unknown>["render"]
1269
- readonly process: Handlers<unknown, unknown>["process"]
1347
+ readonly process: (
1348
+ input: unknown,
1349
+ state: unknown
1350
+ ) => Effect.Effect<Action<unknown, unknown>, never, Environment>
1270
1351
  readonly clear: Handlers<unknown, unknown>["clear"]
1352
+ readonly events: Queue.Dequeue<unknown, never> | undefined
1271
1353
  }>
1272
1354
  {}
1273
1355
 
@@ -1338,8 +1420,19 @@ const runLoop = Effect.fnUntraced(
1338
1420
  while (true) {
1339
1421
  const msg = yield* loop.render(state, action)
1340
1422
  yield* Effect.orDie(terminal.display(msg))
1341
- const event = yield* Queue.take(input)
1342
- action = yield* loop.process(event, state)
1423
+ if (loop.events) {
1424
+ const takeInput = Queue.take(input).pipe(
1425
+ Effect.map((event) => ({ _tag: "Input" as const, event }))
1426
+ )
1427
+ const result = yield* Effect.raceFirst(
1428
+ takeInput,
1429
+ Queue.take(loop.events).pipe(Effect.map((value) => ({ _tag: "Event" as const, value })))
1430
+ )
1431
+ action = yield* loop.process(result, state)
1432
+ } else {
1433
+ const result = yield* Queue.take(input)
1434
+ action = yield* loop.process(result, state)
1435
+ }
1343
1436
  switch (action._tag) {
1344
1437
  case "Beep":
1345
1438
  continue