@@ -4,15 +4,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process";
44import os from "node:os" ;
55import path from "node:path" ;
66import { Codework } from "./codework.ts" ;
7- import {
8- type CliAction ,
9- type CliInstall ,
10- type CliServer ,
11- type ServerAdd ,
12- ServersError ,
13- type ServerStatus ,
14- type ServerUpdate ,
15- } from "@codeworksh/contract" ;
7+ import { type CliAction , type CliInstall , type CliServer , ServersError , type ServerStatus } from "@codeworksh/contract" ;
168import { Servers } from "./servers.ts" ;
179
1810// Where the install script puts the binary unless told otherwise.
@@ -131,15 +123,22 @@ const alive = (pid: number) =>
131123 }
132124 } ) ;
133125
126+ /** The server a home names, while its process lives. */
127+ export const liveAt = ( home : string ) =>
128+ Effect . gen ( function * ( ) {
129+ const registered = yield * Codework . registrationAt ( home ) ;
130+ return Option . isSome ( registered ) && ( yield * alive ( registered . value . pid ) ) ? registered : Option . none ( ) ;
131+ } ) ;
132+
134133/**
135134 * The server a home names. The default one is asked through the app's own connection, since a build
136135 * whose replies differ still answers `server.info`: a call the app depends on decides whether it
137136 * works with this app. Any other is taken from its registration and process.
138137 */
139- const serverAt = ( home : string , isDefault : boolean ) =>
138+ export const serverAt = ( home : string , isDefault : boolean ) =>
140139 Effect . gen ( function * ( ) {
141- const registered = yield * Codework . registrationAt ( home ) ;
142- if ( Option . isNone ( registered ) || ! ( yield * alive ( registered . value . pid ) ) ) return { _tag : "Stopped" } as const ;
140+ const registered = yield * liveAt ( home ) ;
141+ if ( Option . isNone ( registered ) ) return { _tag : "Stopped" } as const ;
143142 const { url, pid, version : registeredVersion } = registered . value ;
144143 if ( ! isDefault ) return { _tag : "Running" , url, pid, version : registeredVersion } satisfies CliServer ;
145144 const info = yield * Codework . call ( ( client ) => client ( "server.info" , { } ) ) . pipe ( Effect . option ) ;
@@ -151,7 +150,7 @@ const serverAt = (home: string, isDefault: boolean) =>
151150
152151// The app's connection reconnects with a backoff; waiting until it reaches the server keeps the
153152// status read right after from calling a healthy server unreachable. Only when there is one to reach.
154- const awaitConnection = Effect . gen ( function * ( ) {
153+ export const awaitConnection = Effect . gen ( function * ( ) {
155154 if ( Option . isNone ( yield * Codework . registration ) ) return ;
156155 yield * Codework . call ( ( client ) => client ( "server.info" , { } ) ) . pipe (
157156 Effect . retry ( Schedule . spaced ( "250 millis" ) ) ,
@@ -160,88 +159,56 @@ const awaitConnection = Effect.gen(function* () {
160159 ) ;
161160} ) ;
162161
163- export const handlers = {
164- "servers.list" : Effect . fn ( "servers.list" ) ( function * ( ) {
165- const servers = yield * Servers . Service ;
166- const foundOnce = yield * Effect . cached ( found ) ;
167- return yield * Effect . forEach (
168- yield * servers . list ,
169- ( row ) =>
170- Effect . gen ( function * ( ) {
171- const home = servers . homeOf ( row ) ;
172- const [ cli , server ] = yield * Effect . all (
173- [ executableOf ( row , foundOnce ) , serverAt ( home . path , row . isDefault ) ] ,
174- {
175- concurrency : "unbounded" ,
176- } ,
177- ) ;
178- return {
179- id : row . id ,
180- kind : row . kind ,
181- name : row . name ,
182- isDefault : row . isDefault ,
183- home,
184- cli,
185- server,
186- args : row . args ,
187- env : row . env . map ( ( { name, sensitive, value } ) =>
188- sensitive ? { name, sensitive } : { name, sensitive, value } ,
189- ) ,
190- } satisfies ServerStatus ;
191- } ) ,
192- { concurrency : "unbounded" } ,
193- ) ;
194- } ) ,
195-
196- "servers.add" : Effect . fn ( "servers.add" ) ( function * ( input : ServerAdd ) {
197- return yield * ( yield * Servers . Service ) . add ( input ) ;
198- } ) ,
199-
200- "servers.update" : Effect . fn ( "servers.update" ) ( function * ( input : ServerUpdate ) {
201- const servers = yield * Servers . Service ;
202- const before = yield * servers . defaultHome ;
203- yield * servers . update ( input ) ;
204- if ( ( yield * servers . defaultHome ) !== before ) yield * awaitConnection ;
205- } ) ,
206-
207- "servers.remove" : Effect . fn ( "servers.remove" ) ( function * ( { id } : { readonly id : string } ) {
208- yield * ( yield * Servers . Service ) . remove ( id ) ;
209- } ) ,
210-
211- "servers.setDefault" : Effect . fn ( "servers.setDefault" ) ( function * ( { id } : { readonly id : string } ) {
212- const servers = yield * Servers . Service ;
213- const before = yield * servers . defaultHome ;
214- yield * servers . setDefault ( id ) ;
215- if ( ( yield * servers . defaultHome ) !== before ) yield * awaitConnection ;
216- } ) ,
217-
218- // The CLI owns a server's lifecycle; the app only asks it, with the server's variables and in its
219- // home, which a variable never overrides.
220- "servers.service" : Effect . fn ( "servers.service" ) ( function * ( {
221- id,
222- action,
223- } : {
224- readonly id : string ;
225- readonly action : CliAction ;
226- } ) {
227- const servers = yield * Servers . Service ;
228- const row = yield * servers . get ( id ) ;
229- const cli = yield * executableOf ( row , found ) ;
230- if ( cli === null || ! ( "version" in cli ) )
231- return yield * new ServersError ( { message : "No working codework CLI was found." } ) ;
232- const env = Object . fromEntries ( row . env . map ( ( { name, value } ) => [ name , value ] ) ) ;
233- // `stop` takes no server options, so the arguments go only to what starts a server.
234- const args = action === "stop" ? [ ] : Servers . splitArgs ( row . args ) ;
235- const { file, prefix } = row . executable === null ? { file : cli . path , prefix : [ ] } : split ( row . executable ) ;
236- const { output, code } = yield * run ( file , [ ...prefix , "service" , action , ...args ] , "30 seconds" , {
237- ...env ,
238- CODEWORK_HOME_DIR : servers . homeOf ( row ) . path ,
239- } ) . pipe (
240- Effect . mapError ( ( error ) => new ServersError ( { message : `codework service ${ action } : ${ error . message } ` } ) ) ,
241- ) ;
242- if ( code !== 0 ) return yield * new ServersError ( { message : `codework service ${ action } ${ reason ( output , code ) } ` } ) ;
243- if ( row . isDefault && action !== "stop" ) yield * awaitConnection ;
244- } ) ,
245- } ;
162+ /** Every server and how it is set up, with its executable and process as found now. */
163+ export const statuses = Effect . gen ( function * ( ) {
164+ const servers = yield * Servers . Service ;
165+ const foundOnce = yield * Effect . cached ( found ) ;
166+ return yield * Effect . forEach (
167+ yield * servers . list ,
168+ ( row ) =>
169+ Effect . gen ( function * ( ) {
170+ const home = servers . homeOf ( row ) ;
171+ const [ cli , server ] = yield * Effect . all (
172+ [ executableOf ( row , foundOnce ) , serverAt ( home . path , row . isDefault ) ] ,
173+ {
174+ concurrency : "unbounded" ,
175+ } ,
176+ ) ;
177+ return {
178+ id : row . id ,
179+ kind : row . kind ,
180+ name : row . name ,
181+ isDefault : row . isDefault ,
182+ home,
183+ cli,
184+ server,
185+ args : row . args ,
186+ env : row . env . map ( ( { name, sensitive, value } ) =>
187+ sensitive ? { name, sensitive } : { name, sensitive, value } ,
188+ ) ,
189+ } satisfies ServerStatus ;
190+ } ) ,
191+ { concurrency : "unbounded" } ,
192+ ) ;
193+ } ) ;
194+
195+ // The CLI owns a server's lifecycle; the app only asks it, with the server's variables and in its
196+ // home, which a variable never overrides.
197+ export const service = Effect . fn ( "Cli.service" ) ( function * ( row : Servers . Row , action : CliAction ) {
198+ const servers = yield * Servers . Service ;
199+ const cli = yield * executableOf ( row , found ) ;
200+ if ( cli === null || ! ( "version" in cli ) )
201+ return yield * new ServersError ( { message : "No working codework CLI was found." } ) ;
202+ const env = Object . fromEntries ( row . env . map ( ( { name, value } ) => [ name , value ] ) ) ;
203+ // `stop` takes no server options, so the arguments go only to what starts a server.
204+ const args = action === "stop" ? [ ] : Servers . splitArgs ( row . args ) ;
205+ const { file, prefix } = row . executable === null ? { file : cli . path , prefix : [ ] } : split ( row . executable ) ;
206+ const { output, code } = yield * run ( file , [ ...prefix , "service" , action , ...args ] , "30 seconds" , {
207+ ...env ,
208+ CODEWORK_HOME_DIR : servers . homeOf ( row ) . path ,
209+ } ) . pipe ( Effect . mapError ( ( error ) => new ServersError ( { message : `codework service ${ action } : ${ error . message } ` } ) ) ) ;
210+ if ( code !== 0 ) return yield * new ServersError ( { message : `codework service ${ action } ${ reason ( output , code ) } ` } ) ;
211+ if ( row . isDefault && action !== "stop" ) yield * awaitConnection ;
212+ } ) ;
246213
247214export * as Cli from "./cli.ts" ;
0 commit comments