11/**
2- * Effect Test Application
2+ * Interactive Effect Test Client
33 *
4- * A simple Effect app that connects to the DevTools TUI server
5- * and generates spans for testing the tree view.
4+ * An interactive app that generates different spans based on keyboard input.
5+ * Connect this to the DevTools TUI server to test navigation.
6+ *
7+ * Controls:
8+ * 1 - Run userWorkflow (nested: fetchUser, processUser [validateEmail, enrichData], saveUser)
9+ * 2 - Run databaseQuery (nested: connect, executeQuery, parseResults)
10+ * 3 - Run apiRequest (nested: authenticate, fetchData [rateLimit, transform], cacheResponse)
11+ * t - Toggle auto-timer (runs userWorkflow every 3s)
12+ * q - Quit
613 */
714
815import * as Effect from "effect/Effect" ;
16+ import * as Ref from "effect/Ref" ;
17+ import * as Fiber from "effect/Fiber" ;
918import * as Schedule from "effect/Schedule" ;
1019import * as Layer from "effect/Layer" ;
1120import { DevTools } from "@effect/experimental" ;
1221import * as NodeSocket from "@effect/platform-node/NodeSocket" ;
22+ import * as readline from "node:readline" ;
1323
1424// Connect to the DevTools TUI server
1525const DEVTOOLS_URL = "ws://localhost:34437" ;
1626
17- /**
18- * Simulates fetching user data from a database
19- */
27+ // ============================================================================
28+ // Workflow 1: User Workflow
29+ // ============================================================================
30+
2031const fetchUser = ( userId : number ) =>
2132 Effect . gen ( function * ( ) {
2233 yield * Effect . log ( `Fetching user ${ userId } ` ) ;
@@ -28,9 +39,6 @@ const fetchUser = (userId: number) =>
2839 } ;
2940 } ) . pipe ( Effect . withSpan ( "fetchUser" , { attributes : { userId } } ) ) ;
3041
31- /**
32- * Simulates processing user data
33- */
3442const processUser = ( user : { id : number ; name : string ; email : string } ) =>
3543 Effect . gen ( function * ( ) {
3644 yield * Effect . log ( `Processing user ${ user . name } ` ) ;
@@ -51,18 +59,12 @@ const processUser = (user: { id: number; name: string; email: string }) =>
5159 return { ...user , processed : true } ;
5260 } ) . pipe ( Effect . withSpan ( "processUser" ) ) ;
5361
54- /**
55- * Simulates saving to database
56- */
5762const saveUser = ( user : any ) =>
5863 Effect . gen ( function * ( ) {
5964 yield * Effect . log ( `Saving user ${ user . name } ` ) ;
6065 yield * Effect . sleep ( "75 millis" ) ;
6166 } ) . pipe ( Effect . withSpan ( "saveUser" , { attributes : { userId : user . id } } ) ) ;
6267
63- /**
64- * Main workflow that orchestrates the operations
65- */
6668const userWorkflow = ( userId : number ) =>
6769 Effect . gen ( function * ( ) {
6870 const user = yield * fetchUser ( userId ) ;
@@ -71,25 +73,174 @@ const userWorkflow = (userId: number) =>
7173 yield * Effect . log ( `Completed workflow for user ${ userId } ` ) ;
7274 } ) . pipe ( Effect . withSpan ( "userWorkflow" , { attributes : { userId } } ) ) ;
7375
74- /**
75- * Main program that runs multiple workflows with DevTools connection retry
76- */
76+ // ============================================================================
77+ // Workflow 2: Database Query
78+ // ============================================================================
79+
80+ const connectToDatabase = Effect . gen ( function * ( ) {
81+ yield * Effect . log ( "Connecting to database" ) ;
82+ yield * Effect . sleep ( "80 millis" ) ;
83+ } ) . pipe ( Effect . withSpan ( "connect" ) ) ;
84+
85+ const executeQuery = ( query : string ) =>
86+ Effect . gen ( function * ( ) {
87+ yield * Effect . log ( `Executing query: ${ query } ` ) ;
88+ yield * Effect . sleep ( "120 millis" ) ;
89+ return [
90+ { id : 1 , data : "row1" } ,
91+ { id : 2 , data : "row2" } ,
92+ ] ;
93+ } ) . pipe ( Effect . withSpan ( "executeQuery" , { attributes : { query } } ) ) ;
94+
95+ const parseResults = ( results : any [ ] ) =>
96+ Effect . gen ( function * ( ) {
97+ yield * Effect . log ( `Parsing ${ results . length } results` ) ;
98+ yield * Effect . sleep ( "40 millis" ) ;
99+ return results . map ( ( r ) => ( { ...r , parsed : true } ) ) ;
100+ } ) . pipe ( Effect . withSpan ( "parseResults" ) ) ;
101+
102+ const databaseQuery = ( query : string ) =>
103+ Effect . gen ( function * ( ) {
104+ yield * connectToDatabase ;
105+ const results = yield * executeQuery ( query ) ;
106+ const parsed = yield * parseResults ( results ) ;
107+ yield * Effect . log ( `Database query completed with ${ parsed . length } rows` ) ;
108+ } ) . pipe ( Effect . withSpan ( "databaseQuery" , { attributes : { query } } ) ) ;
109+
110+ // ============================================================================
111+ // Workflow 3: API Request
112+ // ============================================================================
113+
114+ const authenticate = Effect . gen ( function * ( ) {
115+ yield * Effect . log ( "Authenticating API request" ) ;
116+ yield * Effect . sleep ( "60 millis" ) ;
117+ return "token-12345" ;
118+ } ) . pipe ( Effect . withSpan ( "authenticate" ) ) ;
119+
120+ const fetchData = ( _token : string , endpoint : string ) =>
121+ Effect . gen ( function * ( ) {
122+ yield * Effect . log ( `Fetching data from ${ endpoint } ` ) ;
123+ yield * Effect . sleep ( "90 millis" ) ;
124+
125+ // Nested: check rate limit
126+ yield * Effect . gen ( function * ( ) {
127+ yield * Effect . log ( "Checking rate limit" ) ;
128+ yield * Effect . sleep ( "15 millis" ) ;
129+ } ) . pipe ( Effect . withSpan ( "rateLimit" ) ) ;
130+
131+ // Nested: transform data
132+ yield * Effect . gen ( function * ( ) {
133+ yield * Effect . log ( "Transforming response data" ) ;
134+ yield * Effect . sleep ( "25 millis" ) ;
135+ } ) . pipe ( Effect . withSpan ( "transform" ) ) ;
136+
137+ return { data : "api-response" , endpoint } ;
138+ } ) . pipe ( Effect . withSpan ( "fetchData" , { attributes : { endpoint } } ) ) ;
139+
140+ const cacheResponse = ( _response : any ) =>
141+ Effect . gen ( function * ( ) {
142+ yield * Effect . log ( "Caching API response" ) ;
143+ yield * Effect . sleep ( "35 millis" ) ;
144+ } ) . pipe ( Effect . withSpan ( "cacheResponse" ) ) ;
145+
146+ const apiRequest = ( endpoint : string ) =>
147+ Effect . gen ( function * ( ) {
148+ const token = yield * authenticate ;
149+ const response = yield * fetchData ( token , endpoint ) ;
150+ yield * cacheResponse ( response ) ;
151+ yield * Effect . log ( `API request to ${ endpoint } completed` ) ;
152+ } ) . pipe ( Effect . withSpan ( "apiRequest" , { attributes : { endpoint } } ) ) ;
153+
154+ // ============================================================================
155+ // Helper for reading input
156+ // ============================================================================
157+
158+ const readLine = ( prompt : string ) : Effect . Effect < string > =>
159+ Effect . async < string > ( ( resume ) => {
160+ const rl = readline . createInterface ( {
161+ input : process . stdin ,
162+ output : process . stdout ,
163+ } ) ;
164+ rl . question ( prompt , ( answer ) => {
165+ rl . close ( ) ;
166+ resume ( Effect . succeed ( answer ) ) ;
167+ } ) ;
168+ } ) ;
169+
170+ // ============================================================================
171+ // Interactive Program
172+ // ============================================================================
173+
77174const program = Effect . gen ( function * ( ) {
78- yield * Effect . log ( "Starting Effect test app" ) ;
79- yield * Effect . log ( `Connecting to DevTools at ${ DEVTOOLS_URL } ` ) ;
80-
81- // Run workflows periodically
82- yield * Effect . repeat (
83- Effect . gen ( function * ( ) {
84- // Process 3 users in parallel
85- yield * Effect . all ( [ userWorkflow ( 1 ) , userWorkflow ( 2 ) , userWorkflow ( 3 ) ] , {
86- concurrency : 3 ,
87- } ) ;
88- } ) ,
89- Schedule . spaced ( "3 seconds" ) ,
175+ const timerRunning = yield * Ref . make ( false ) ;
176+ const timerFiber = yield * Ref . make < Fiber . RuntimeFiber < any , any > | null > ( null ) ;
177+
178+ console . log ( "\n=== Interactive Effect Test Client ===" ) ;
179+ console . log ( "Connected to DevTools at " + DEVTOOLS_URL ) ;
180+ console . log ( "\nControls:" ) ;
181+ console . log (
182+ " 1 - Run userWorkflow (fetchUser -> processUser [validateEmail, enrichData] -> saveUser)" ,
183+ ) ;
184+ console . log (
185+ " 2 - Run databaseQuery (connect -> executeQuery -> parseResults)" ,
90186 ) ;
187+ console . log (
188+ " 3 - Run apiRequest (authenticate -> fetchData [rateLimit, transform] -> cacheResponse)" ,
189+ ) ;
190+ console . log ( " t - Toggle auto-timer (runs userWorkflow every 3s)" ) ;
191+ console . log ( " q - Quit\n" ) ;
192+
193+ // Main input loop
194+ while ( true ) {
195+ const input = yield * readLine ( "> " ) ;
196+
197+ if ( input === "q" ) {
198+ console . log ( "Quitting..." ) ;
199+ // Stop timer if running
200+ const fiber = yield * Ref . get ( timerFiber ) ;
201+ if ( fiber ) {
202+ yield * Fiber . interrupt ( fiber ) ;
203+ }
204+ break ;
205+ } else if ( input === "1" ) {
206+ console . log ( "Running userWorkflow..." ) ;
207+ yield * Effect . fork ( userWorkflow ( 1 ) ) ;
208+ } else if ( input === "2" ) {
209+ console . log ( "Running databaseQuery..." ) ;
210+ yield * Effect . fork ( databaseQuery ( "SELECT * FROM users" ) ) ;
211+ } else if ( input === "3" ) {
212+ console . log ( "Running apiRequest..." ) ;
213+ yield * Effect . fork ( apiRequest ( "/api/v1/data" ) ) ;
214+ } else if ( input === "t" ) {
215+ const running = yield * Ref . get ( timerRunning ) ;
216+ if ( running ) {
217+ // Stop timer
218+ const fiber = yield * Ref . get ( timerFiber ) ;
219+ if ( fiber ) {
220+ yield * Fiber . interrupt ( fiber ) ;
221+ }
222+ yield * Ref . set ( timerRunning , false ) ;
223+ console . log ( "Auto-timer stopped." ) ;
224+ } else {
225+ // Start timer
226+ const fiber = yield * Effect . fork (
227+ Effect . repeat (
228+ Effect . gen ( function * ( ) {
229+ console . log ( "[Timer] Running userWorkflow..." ) ;
230+ yield * userWorkflow ( 1 ) ;
231+ } ) ,
232+ Schedule . spaced ( "3 seconds" ) ,
233+ ) ,
234+ ) ;
235+ yield * Ref . set ( timerFiber , fiber ) ;
236+ yield * Ref . set ( timerRunning , true ) ;
237+ console . log ( "Auto-timer started (runs every 3s)." ) ;
238+ }
239+ } else {
240+ console . log ( `Unknown command: ${ input } ` ) ;
241+ }
242+ }
91243} ) . pipe (
92- // Retry the entire program if DevTools connection fails
93244 Effect . retry (
94245 Schedule . spaced ( "2 seconds" ) . pipe ( Schedule . intersect ( Schedule . recurs ( 5 ) ) ) ,
95246 ) ,
@@ -103,9 +254,5 @@ const DevToolsLive = DevTools.layer(DEVTOOLS_URL);
103254const WebSocketLive = NodeSocket . layerWebSocketConstructor ;
104255const MainLive = Layer . provide ( DevToolsLive , WebSocketLive ) ;
105256
106- // Run the program with retry logic
107- Effect . runFork ( Effect . provide ( program , MainLive ) ) ;
108-
109- console . log (
110- `Effect test app started. Will retry connection up to 5 times. Press Ctrl+C to exit.` ,
111- ) ;
257+ // Run the program
258+ Effect . runFork ( program . pipe ( Effect . provide ( MainLive ) ) ) ;
0 commit comments