@rip-lang/server 1.1.8 → 1.1.10

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 (3) hide show
  1. package/README.md +36 -13
  2. package/package.json +1 -1
  3. package/server.rip +38 -3
package/README.md CHANGED
@@ -311,7 +311,12 @@ The server uses a single-file, self-spawning architecture:
311
311
  └─────────────────────────────────────────────────┘
312
312
  ```
313
313
 
314
- When `RIP_WORKER_MODE=1` is set, the same `server.rip` file runs as a worker instead of the main server.
314
+ When `RIP_SETUP_MODE=1` is set, the same file runs the one-time setup phase. When `RIP_WORKER_MODE=1` is set, it runs as a worker.
315
+
316
+ ### Startup Lifecycle
317
+
318
+ 1. **Setup** — If `setup.rip` exists next to the entry file, it runs once in a temporary process before any workers spawn. Use this for database migrations, table creation, and seeding.
319
+ 2. **Workers** — N worker processes are spawned, each loading the entry file and serving requests.
315
320
 
316
321
  ### Request Flow
317
322
 
@@ -323,12 +328,10 @@ When `RIP_WORKER_MODE=1` is set, the same `server.rip` file runs as a worker ins
323
328
 
324
329
  ### Hot Reloading
325
330
 
326
- In development mode, the server watches for file changes:
327
-
328
- - **Default**: Only the entry file is watched
329
- - **With `-w`**: All matching files in the app directory are watched
331
+ Two layers of hot reload work together in development:
330
332
 
331
- When a change is detected, a rolling restart of all workers is triggered zero downtime.
333
+ - **API changes** (`-w` flag) The Manager watches for `.rip` file changes in the API directory and triggers rolling worker restarts (zero downtime, server-side).
334
+ - **UI changes** (`watch: true` in `ripUI`) — Workers register their app's component directories with the Manager via the control socket. The Manager watches those directories and broadcasts SSE reload events to connected browsers (client-side). SSE connections are held by the long-lived Server process, not by workers.
332
335
 
333
336
  Use `--static` in production to disable hot reload entirely.
334
337
 
@@ -409,6 +412,24 @@ export default
409
412
  fetch: (req) -> new Response('Hello!')
410
413
  ```
411
414
 
415
+ ## One-Time Setup
416
+
417
+ If a `setup.rip` file exists next to your entry file, rip-server runs it
418
+ automatically **once** before spawning any workers. This is ideal for database
419
+ migrations, table creation, and seeding.
420
+
421
+ ```coffee
422
+ # setup.rip — runs once before workers start
423
+ export setup = ->
424
+ await createTables()
425
+ await seedData()
426
+ console.log 'Database ready'
427
+ ```
428
+
429
+ The setup function can export as `setup` or `default`. If the file doesn't
430
+ exist, the setup phase is skipped entirely (no overhead). If setup fails,
431
+ the server exits immediately.
432
+
412
433
  ## Environment Variables
413
434
 
414
435
  Most settings are configured via CLI flags, but environment variables provide an alternative for containers, CI/CD, or system-wide defaults.
@@ -501,14 +522,16 @@ This gives you:
501
522
 
502
523
  When running with `-w`, two layers of hot reload work together:
503
524
 
504
- 1. **rip-server file watching** (`-w` flag) — watches for `.rip` file changes
505
- and triggers rolling worker restarts (server-side reload)
506
- 2. **ripUI SSE watching** (`watch: true`) — watches the `pages/` and `includes`
507
- directories and notifies connected browsers via SSE (client-side reload)
525
+ 1. **API hot reload** (`-w` flag) — The Manager watches for `.rip` file changes
526
+ in the API directory and triggers rolling worker restarts (server-side).
527
+ 2. **UI hot reload** (`watch: true`) — Workers register their component
528
+ directories with the Manager via the control socket. The Manager watches
529
+ those directories and tells the Server to broadcast SSE reload events to
530
+ connected browsers (client-side).
508
531
 
509
- For development, the SSE hot-reload is usually sufficient it recompiles
510
- components in the browser without restarting workers. The `-w` flag is useful
511
- when server-side code changes (routes, middleware, etc.).
532
+ SSE connections are held by the long-lived Server process, not by recyclable
533
+ workers, ensuring stable hot-reload connections. Each app prefix gets its own
534
+ SSE pool for multi-app isolation.
512
535
 
513
536
  ## Comparison with Other Servers
514
537
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/server",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
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
@@ -352,6 +352,18 @@ parseFlags = (argv) ->
352
352
  # Worker Mode
353
353
  # ==============================================================================
354
354
 
355
+ runSetup = ->
356
+ setupFile = process.env.RIP_SETUP_FILE
357
+ try
358
+ mod = import!(setupFile)
359
+ await Promise.resolve()
360
+ fn = mod?.setup or mod?.default
361
+ if typeof fn is 'function'
362
+ await fn()
363
+ catch e
364
+ console.error "rip-server: setup failed:", e
365
+ process.exit(1)
366
+
355
367
  runWorker = ->
356
368
  workerId = parseInt(process.env.WORKER_ID or '0')
357
369
  maxRequests = parseInt(process.env.MAX_REQUESTS or '10000')
@@ -493,6 +505,24 @@ class Manager
493
505
 
494
506
  start: ->
495
507
  @stop!
508
+
509
+ # Run one-time setup if setup.rip exists next to the entry file
510
+ setupFile = join(dirname(@flags.appEntry), 'setup.rip')
511
+ if existsSync(setupFile)
512
+ setupEnv = Object.assign {}, process.env,
513
+ RIP_SETUP_MODE: '1'
514
+ RIP_SETUP_FILE: setupFile
515
+ proc = Bun.spawn ['rip', import.meta.path],
516
+ stdout: 'inherit'
517
+ stderr: 'inherit'
518
+ stdin: 'ignore'
519
+ cwd: process.cwd()
520
+ env: setupEnv
521
+ code = await proc.exited
522
+ if code isnt 0
523
+ console.error "rip-server: setup exited with code #{code}"
524
+ process.exit(1)
525
+
496
526
  @workers = []
497
527
  for i in [0...@flags.workers]
498
528
  w = @spawnWorker!(@currentVersion)
@@ -787,11 +817,14 @@ class Server
787
817
 
788
818
  return @status() if url.pathname is '/status'
789
819
 
790
- # SSE hot-reload: intercept /{prefix}/watch for registered watch groups
820
+ # SSE hot-reload: intercept /{prefix}/watch
791
821
  path = url.pathname
792
822
  if path.endsWith('/watch')
793
823
  watchPrefix = path.slice(0, -6) # strip '/watch'
794
- return @handleWatch(watchPrefix) if @watchGroups.has(watchPrefix)
824
+ if @watchGroups.has(watchPrefix)
825
+ return @handleWatch(watchPrefix)
826
+ else
827
+ return new Response('', { status: 503, headers: { 'Retry-After': '2' } })
795
828
 
796
829
  if url.pathname is '/server'
797
830
  headers = new Headers({ 'content-type': 'text/plain' })
@@ -1281,7 +1314,9 @@ main = ->
1281
1314
  # Entry Point
1282
1315
  # ==============================================================================
1283
1316
 
1284
- if process.env.RIP_WORKER_MODE
1317
+ if process.env.RIP_SETUP_MODE
1318
+ runSetup()
1319
+ else if process.env.RIP_WORKER_MODE
1285
1320
  runWorker()
1286
1321
  else
1287
1322
  main!