@rip-lang/server 1.1.7 → 1.1.8

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.rip +58 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/server",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "Pure Rip application server — multi-worker, hot reload, HTTPS, mDNS",
5
5
  "type": "module",
6
6
  "main": "server.rip",
package/server.rip CHANGED
@@ -485,8 +485,8 @@ class Manager
485
485
  @nextWorkerId = -1
486
486
  @retiringIds = new Set()
487
487
  @currentVersion = 1
488
- @onFileChange = null
489
- @_broadcastTimer = null
488
+ @server = null
489
+ @appWatchers = new Map()
490
490
 
491
491
  process.on 'SIGTERM', => @shutdown!
492
492
  process.on 'SIGINT', => @shutdown!
@@ -534,13 +534,6 @@ class Manager
534
534
  utimesSync(entryFile, now, now)
535
535
  catch
536
536
  null
537
- # Debounced SSE broadcast to connected clients
538
- if @onFileChange
539
- clearTimeout(@_broadcastTimer) if @_broadcastTimer
540
- @_broadcastTimer = setTimeout =>
541
- @_broadcastTimer = null
542
- @onFileChange()
543
- , debounceMs
544
537
  catch e
545
538
  console.warn "rip-server: directory watch failed: #{e.message}"
546
539
 
@@ -550,6 +543,25 @@ class Manager
550
543
  try unlinkSync(w.socketPath) catch then null
551
544
  @workers = []
552
545
 
546
+ watchDirs: (prefix, dirs) ->
547
+ return if @appWatchers.has(prefix)
548
+ timer = null
549
+ watchers = []
550
+ broadcast = =>
551
+ clearTimeout(timer) if timer
552
+ timer = setTimeout =>
553
+ timer = null
554
+ @server?.broadcastChange(prefix)
555
+ , 250
556
+ for dir in dirs
557
+ try
558
+ w = watch dir, { recursive: true }, (event, filename) ->
559
+ broadcast() if filename?.endsWith('.rip')
560
+ watchers.push(w)
561
+ catch e
562
+ console.warn "rip-server: watch failed for #{dir}: #{e.message}"
563
+ @appWatchers.set prefix, { watchers, timer }
564
+
553
565
  spawnWorker: (version) ->
554
566
  workerId = ++@nextWorkerId
555
567
  socketPath = getWorkerSocketPath(@flags.socketPrefix, workerId)
@@ -683,7 +695,8 @@ class Server
683
695
  @httpsActive = false
684
696
  @hostRegistry = new Set(['localhost', '127.0.0.1', 'rip.local'])
685
697
  @mdnsProcesses = new Map()
686
- @sseClients = new Set()
698
+ @watchGroups = new Map()
699
+ @manager = null
687
700
  try
688
701
  pkg = JSON.parse(readFileSync(import.meta.dir + '/package.json', 'utf8'))
689
702
  @serverVersion = pkg.version
@@ -773,7 +786,12 @@ class Server
773
786
  return new Response Bun.file(import.meta.dir + '/server.html')
774
787
 
775
788
  return @status() if url.pathname is '/status'
776
- return @watch() if url.pathname is '/watch'
789
+
790
+ # SSE hot-reload: intercept /{prefix}/watch for registered watch groups
791
+ path = url.pathname
792
+ if path.endsWith('/watch')
793
+ watchPrefix = path.slice(0, -6) # strip '/watch'
794
+ return @handleWatch(watchPrefix) if @watchGroups.has(watchPrefix)
777
795
 
778
796
  if url.pathname is '/server'
779
797
  headers = new Headers({ 'content-type': 'text/plain' })
@@ -818,31 +836,39 @@ class Server
818
836
  @maybeAddSecurityHeaders(headers)
819
837
  new Response(body, { headers })
820
838
 
821
- watch: ->
839
+ registerWatch: (prefix) ->
840
+ return if @watchGroups.has(prefix)
841
+ @watchGroups.set prefix, { sseClients: new Set() }
842
+
843
+ handleWatch: (prefix) ->
844
+ group = @watchGroups.get(prefix)
845
+ return new Response('not-found', { status: 404 }) unless group
822
846
  encoder = new TextEncoder()
823
847
  client = null
824
848
  new Response new ReadableStream(
825
- start: (controller) =>
849
+ start: (controller) ->
826
850
  send = (event) ->
827
851
  try controller.enqueue encoder.encode("event: #{event}\n\n")
828
852
  catch then null
829
853
  client = { send }
830
- @sseClients.add(client)
854
+ group.sseClients.add(client)
831
855
  send('connected')
832
- cancel: =>
833
- @sseClients.delete(client) if client
856
+ cancel: ->
857
+ group.sseClients.delete(client) if client
834
858
  ),
835
859
  headers:
836
860
  'Content-Type': 'text/event-stream'
837
861
  'Cache-Control': 'no-cache'
838
862
  'Connection': 'keep-alive'
839
863
 
840
- broadcastChange: ->
864
+ broadcastChange: (prefix) ->
865
+ group = @watchGroups.get(prefix)
866
+ return unless group
841
867
  dead = []
842
- for client as @sseClients
868
+ for client as group.sseClients
843
869
  try client.send('reload')
844
870
  catch then dead.push(client)
845
- @sseClients.delete(c) for c in dead
871
+ group.sseClients.delete(c) for c in dead
846
872
 
847
873
  getNextAvailableSocket: ->
848
874
  while @availableWorkers.length > 0
@@ -979,6 +1005,17 @@ class Server
979
1005
  null
980
1006
  return new Response(JSON.stringify({ ok: false }), { status: 400, headers: { 'content-type': 'application/json' } })
981
1007
 
1008
+ if req.method is 'POST' and url.pathname is '/watch'
1009
+ try
1010
+ j = req.json!
1011
+ if j?.op is 'watch' and typeof j.prefix is 'string' and Array.isArray(j.dirs)
1012
+ @registerWatch(j.prefix)
1013
+ @manager?.watchDirs(j.prefix, j.dirs) if j.dirs.length > 0
1014
+ return new Response(JSON.stringify({ ok: true }), { headers: { 'content-type': 'application/json' } })
1015
+ catch
1016
+ null
1017
+ return new Response(JSON.stringify({ ok: false }), { status: 400, headers: { 'content-type': 'application/json' } })
1018
+
982
1019
  if url.pathname is '/registry' and req.method is 'GET'
983
1020
  return new Response(JSON.stringify({ ok: true, hosts: Array.from(@hostRegistry.values()) }), { headers: { 'content-type': 'application/json' } })
984
1021
 
@@ -1214,7 +1251,8 @@ main = ->
1214
1251
 
1215
1252
  svr = new Server(flags)
1216
1253
  mgr = new Manager(flags)
1217
- mgr.onFileChange = -> svr.broadcastChange()
1254
+ svr.manager = mgr
1255
+ mgr.server = svr
1218
1256
 
1219
1257
  cleanup = ->
1220
1258
  console.log 'rip-server: shutting down...'