11import z from "zod"
2+ import { Effect , Layer , PubSub , ServiceMap , Stream } from "effect"
23import { Log } from "../util/log"
34import { Instance } from "../project/instance"
45import { BusEvent } from "./bus-event"
56import { GlobalBus } from "./global"
7+ import { runCallbackInstance , runPromiseInstance } from "../effect/runtime"
68
79export namespace Bus {
810 const log = Log . create ( { service : "bus" } )
9- type Subscription = ( event : any ) => void
1011
1112 export const InstanceDisposed = BusEvent . define (
1213 "server.instance.disposed" ,
@@ -15,91 +16,105 @@ export namespace Bus {
1516 } ) ,
1617 )
1718
18- const state = Instance . state (
19- ( ) => {
20- const subscriptions = new Map < any , Subscription [ ] > ( )
19+ // ---------------------------------------------------------------------------
20+ // Service definition
21+ // ---------------------------------------------------------------------------
2122
22- return {
23- subscriptions,
23+ type Payload < D extends BusEvent . Definition = BusEvent . Definition > = {
24+ type : D [ "type" ]
25+ properties : z . infer < D [ "properties" ] >
26+ }
27+
28+ export interface Interface {
29+ readonly publish : < D extends BusEvent . Definition > (
30+ def : D ,
31+ properties : z . output < D [ "properties" ] > ,
32+ ) => Effect . Effect < void >
33+ readonly subscribe : < D extends BusEvent . Definition > ( def : D ) => Stream . Stream < Payload < D > >
34+ readonly subscribeAll : ( ) => Stream . Stream < Payload >
35+ }
36+
37+ export class Service extends ServiceMap . Service < Service , Interface > ( ) ( "@opencode/Bus" ) { }
38+
39+ export const layer = Layer . effect (
40+ Service ,
41+ Effect . gen ( function * ( ) {
42+ const pubsubs = new Map < string , PubSub . PubSub < Payload > > ( )
43+ const wildcardPubSub = yield * PubSub . unbounded < Payload > ( )
44+
45+ const getOrCreate = Effect . fnUntraced ( function * ( type : string ) {
46+ let ps = pubsubs . get ( type )
47+ if ( ! ps ) {
48+ ps = yield * PubSub . unbounded < Payload > ( )
49+ pubsubs . set ( type , ps )
50+ }
51+ return ps
52+ } )
53+
54+ function publish < D extends BusEvent . Definition > (
55+ def : D ,
56+ properties : z . output < D [ "properties" ] > ,
57+ ) {
58+ return Effect . gen ( function * ( ) {
59+ const payload : Payload = { type : def . type , properties }
60+ log . info ( "publishing" , { type : def . type } )
61+
62+ const ps = pubsubs . get ( def . type )
63+ if ( ps ) yield * PubSub . publish ( ps , payload )
64+ yield * PubSub . publish ( wildcardPubSub , payload )
65+
66+ GlobalBus . emit ( "event" , {
67+ directory : Instance . directory ,
68+ payload,
69+ } )
70+ } )
2471 }
25- } ,
26- async ( entry ) = > {
27- const wildcard = entry . subscriptions . get ( "*" )
28- if ( ! wildcard ) return
29- const event = {
30- type : InstanceDisposed . type ,
31- properties : {
32- directory : Instance . directory ,
33- } ,
72+
73+ function subscribe < D extends BusEvent . Definition > ( def : D ) : Stream . Stream < Payload < D > > {
74+ log . info ( "subscribing" , { type : def . type } )
75+ return Stream . unwrap (
76+ Effect . gen ( function * ( ) {
77+ const ps = yield * getOrCreate ( def . type )
78+ return Stream . fromPubSub ( ps ) as Stream . Stream < Payload < D > >
79+ } ) ,
80+ ) . pipe ( Stream . ensuring ( Effect . sync ( ( ) => log . info ( "unsubscribing" , { type : def . type } ) ) ) )
3481 }
35- for ( const sub of [ ...wildcard ] ) {
36- sub ( event )
82+
83+ function subscribeAll ( ) : Stream . Stream < Payload > {
84+ log . info ( "subscribing" , { type : "*" } )
85+ return Stream . fromPubSub ( wildcardPubSub ) . pipe (
86+ Stream . ensuring ( Effect . sync ( ( ) => log . info ( "unsubscribing" , { type : "*" } ) ) ) ,
87+ )
3788 }
38- } ,
89+
90+ return Service . of ( { publish, subscribe, subscribeAll } )
91+ } ) ,
3992 )
4093
41- export async function publish < Definition extends BusEvent . Definition > (
42- def : Definition ,
43- properties : z . output < Definition [ "properties" ] > ,
44- ) {
45- const payload = {
46- type : def . type ,
47- properties,
48- }
49- log . info ( "publishing" , {
50- type : def . type ,
51- } )
52- const pending = [ ]
53- for ( const key of [ def . type , "*" ] ) {
54- const match = state ( ) . subscriptions . get ( key )
55- for ( const sub of match ?? [ ] ) {
56- pending . push ( sub ( payload ) )
57- }
58- }
59- GlobalBus . emit ( "event" , {
60- directory : Instance . directory ,
61- payload,
62- } )
63- return Promise . all ( pending )
94+ // ---------------------------------------------------------------------------
95+ // Legacy adapters — plain function API wrapping the Effect service
96+ // ---------------------------------------------------------------------------
97+
98+ function runStream ( stream : ( svc : Interface ) => Stream . Stream < Payload > , callback : ( event : any ) => void ) {
99+ return runCallbackInstance (
100+ Service . use ( ( svc ) =>
101+ stream ( svc ) . pipe ( Stream . runForEach ( ( msg ) => Effect . sync ( ( ) => callback ( msg ) ) ) ) ,
102+ ) ,
103+ )
64104 }
65105
66- export function subscribe < Definition extends BusEvent . Definition > (
67- def : Definition ,
68- callback : ( event : { type : Definition [ "type" ] ; properties : z . infer < Definition [ "properties" ] > } ) => void ,
69- ) {
70- return raw ( def . type , callback )
106+ export function publish < D extends BusEvent . Definition > ( def : D , properties : z . output < D [ "properties" ] > ) {
107+ return runPromiseInstance ( Service . use ( ( svc ) => svc . publish ( def , properties ) ) )
71108 }
72109
73- export function once < Definition extends BusEvent . Definition > (
74- def : Definition ,
75- callback : ( event : {
76- type : Definition [ "type" ]
77- properties : z . infer < Definition [ "properties" ] >
78- } ) => "done" | undefined ,
110+ export function subscribe < D extends BusEvent . Definition > (
111+ def : D ,
112+ callback : ( event : Payload < D > ) => void ,
79113 ) {
80- const unsub = subscribe ( def , ( event ) => {
81- if ( callback ( event ) ) unsub ( )
82- } )
114+ return runStream ( ( svc ) => svc . subscribe ( def ) , callback )
83115 }
84116
85117 export function subscribeAll ( callback : ( event : any ) => void ) {
86- return raw ( "*" , callback )
87- }
88-
89- function raw ( type : string , callback : ( event : any ) => void ) {
90- log . info ( "subscribing" , { type } )
91- const subscriptions = state ( ) . subscriptions
92- let match = subscriptions . get ( type ) ?? [ ]
93- match . push ( callback )
94- subscriptions . set ( type , match )
95-
96- return ( ) => {
97- log . info ( "unsubscribing" , { type } )
98- const match = subscriptions . get ( type )
99- if ( ! match ) return
100- const index = match . indexOf ( callback )
101- if ( index === - 1 ) return
102- match . splice ( index , 1 )
103- }
118+ return runStream ( ( svc ) => svc . subscribeAll ( ) , callback )
104119 }
105120}
0 commit comments