Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions bin/lively
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# frozen_string_literal: true

require "async/service"
require "async/container/threaded"
require_relative "../lib/lively/environment/application"

ARGV.each do |path|
Expand All @@ -11,7 +12,7 @@ end
configuration = Async::Service::Configuration.build do
service "lively" do
include Lively::Environment::Application
end
end
end

Async::Service::Controller.run(configuration)
Async::Service::Controller.run(configuration, container_class: Async::Container::Threaded)
1 change: 1 addition & 0 deletions lib/lively/assets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Released under the MIT License.
# Copyright, 2021-2025, by Samuel Williams.

require "uri"
require "protocol/http/middleware"
require "protocol/http/body/file"
require "console"
Expand Down
84 changes: 48 additions & 36 deletions lib/lively/environment/application.rb
Original file line number Diff line number Diff line change
@@ -1,56 +1,68 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2021-2025, by Samuel Williams.
# Copyright, 2021-2026, by Samuel Williams.

require_relative "../application"
require_relative "../assets"

require "falcon/environment/server"
require_relative "middleware"
require_relative "http"
require_relative "htty"

# @namespace
module Lively
# @namespace
module Environment
# Represents the environment configuration for a Lively application server.
#
# This module provides server configuration including URL binding, process count,
# application class resolution, and middleware stack setup. It integrates with
# Falcon's server environment to provide a complete hosting solution.
# Multiplexing environment for Lively applications.
#
# Declares the transport selection as explicit, overridable evaluator keys and uses `make_service` to compose the appropriate child environment at service startup time. This keeps transport selection in the service layer rather than in module inclusion hooks.
#
# The `htty` key controls which transport is used. Override it in a service block to force a specific transport regardless of the environment variable:
#
# ~~~ ruby
# service "myapp" do
# include Lively::Environment::Application
# def htty = false # always use HTTP
# end
# ~~~
module Application
include Falcon::Environment::Server
include Lively::Environment::Middleware
# Note: does not include Falcon::Environment::Server directly. Falcon is
# brought in exclusively via http_environment so that the combined
# evaluator's service_class resolves correctly without shadowing.

# Whether to use HTTY transport. Reads ENV["HTTY"] by default.
# @returns [Boolean]
def htty
ENV["HTTY"] == "1"
end

# Get the server URL for this application.
# @returns [String] The base URL where the server will be accessible.
def url
"http://localhost:9292"
# The environment module to use for HTTY transport.
# @returns [Module]
def htty_environment
Lively::Environment::HTTY
end

# Get the number of server processes to run.
# @returns [Integer] The number of worker processes.
def count
1
# The environment module to use for HTTP transport.
# @returns [Module]
def http_environment
Lively::Environment::HTTP
end

# Resolve the application class to use.
# @returns [Class] The application class, either user-defined or default.
def application
if Object.const_defined?(:Application)
Object.const_get(:Application)
else
Console.warn(self, "No Application class defined, using default.")
::Lively::Application
end
# The environment module for the selected transport.
# @returns [Module]
def transport_environment
htty ? htty_environment : http_environment
end

# Build the middleware stack for this application.
# @returns [Protocol::HTTP::Middleware] The complete middleware stack.
def middleware
::Protocol::HTTP::Middleware.build do |builder|
builder.use Lively::Assets, root: File.expand_path("public", self.root)
builder.use Lively::Assets, root: File.expand_path("../../../public", __dir__)
builder.use self.application
end
# Build the service by composing the transport environment on top of this one.
# Called by Async::Service::Generic.wrap — self is the evaluator at call time.
# @parameter environment [Async::Service::Environment]
# @returns [Async::Service::Generic]
def make_service(environment)
combined = environment.with(transport_environment)
combined_evaluator = combined.evaluator

# Call `service_class.new` directly rather than `Async::Service::Generic.wrap` — the combined evaluator still has `Application` (and therefore `make_service`) in its ancestor chain, so `wrap` would recurse back into this method.
return combined_evaluator.service_class.new(combined, combined_evaluator)
end
end
end
Expand Down
34 changes: 34 additions & 0 deletions lib/lively/environment/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2021-2026, by Samuel Williams.

require_relative "middleware"
require "falcon/environment/server"

# @namespace
module Lively
# @namespace
module Environment
# Falcon (TCP/HTTP) environment for Lively applications.
#
# Combines {Falcon::Environment::Server} for HTTP transport with
# {Lively::Environment::Middleware} for application and asset serving.
module HTTP
include Falcon::Environment::Server
include Lively::Environment::Middleware

# The URL this server binds to.
# @returns [String]
def url
ENV.fetch("LIVELY_URL", "http://localhost:9292")
end

# The number of worker processes/threads to run.
# @returns [Integer]
def count
1
end
end
end
end
22 changes: 22 additions & 0 deletions lib/lively/environment/htty.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2026, by Samuel Williams.

require_relative "middleware"
require "async/htty/environment/server"

# @namespace
module Lively
# @namespace
module Environment
# HTTY (terminal side-channel) environment for Lively applications.
#
# Combines {Async::HTTY::Environment} for HTTY transport with
# {Lively::Environment::Middleware} for application and asset serving.
module HTTY
include Async::HTTY::Environment::Server
include Lively::Environment::Middleware
end
end
end
48 changes: 48 additions & 0 deletions lib/lively/environment/middleware.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2021-2026, by Samuel Williams.

require_relative "../application"
require_relative "../assets"

require "protocol/http/middleware/builder"

# @namespace
module Lively
# @namespace
module Environment
# Shared middleware configuration for Lively application environments.
#
# Provides the application class resolver, asset middleware, and the
# Lively middleware stack. Included by both {HTTP} and {HTTY} environments.
module Middleware
# Get the root directory for this application.
# @returns [String] The current working directory.
def root
Dir.pwd
end

# Resolve the application class to use.
# @returns [Class] The application class, either user-defined or default.
def application
if Object.const_defined?(:Application)
Object.const_get(:Application)
else
Console.warn(self, "No Application class defined, using default.")
::Lively::Application
end
end

# Build the middleware stack for this application.
# @returns [Protocol::HTTP::Middleware] The complete middleware stack.
def middleware
::Protocol::HTTP::Middleware.build do |builder|
builder.use Lively::Assets, root: File.expand_path("public", self.root)
builder.use Lively::Assets, root: File.expand_path("../../../public", __dir__)
builder.use self.application
end
end
end
end
end
2 changes: 2 additions & 0 deletions lively.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Gem::Specification.new do |spec|
spec.required_ruby_version = ">= 3.3"

spec.add_dependency "agent-context"
spec.add_dependency "async-htty"
spec.add_dependency "async-service", "~> 0.23"
spec.add_dependency "falcon", "~> 0.47"
spec.add_dependency "io-watch"
spec.add_dependency "live", "~> 0.18"
Expand Down
48 changes: 18 additions & 30 deletions test/lively/environment/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,28 @@
let(:environment) {Async::Service::Environment.build(subject, root: __dir__)}
let(:evaluator) {environment.evaluator}

with "module methods" do
it "provides default URL" do
expect(evaluator.url).to be == "http://localhost:9292"
with "transport selection" do
it "selects HTTP transport by default" do
expect(evaluator.transport_environment).to be == Lively::Environment::HTTP
end

it "provides default count" do
expect(evaluator.count).to be == 1
it "selects HTTY transport when htty is true" do
environment = Async::Service::Environment.build(subject, root: __dir__) do
htty true
end
expect(environment.evaluator.transport_environment).to be == Lively::Environment::HTTY
end
end

with "transport evaluator" do
let(:transport_evaluator) {environment.with(evaluator.transport_environment).evaluator}

it "provides default application class" do
application_class = evaluator.application
expect(application_class).to be == Lively::Application
it "provides default URL" do
expect(transport_evaluator.url).to be == "http://localhost:9292"
end

it "provides middleware stack" do
middleware = evaluator.middleware
expect(middleware).to be_a(Protocol::HTTP::Middleware)
it "provides default count" do
expect(transport_evaluator.count).to be == 1
end
end

Expand Down Expand Up @@ -67,25 +72,8 @@
end

with "middleware configuration" do
it "includes Assets middleware for public directory" do
middleware = evaluator.middleware

# The middleware should be configured with Assets
expect(middleware).to be_a(Protocol::HTTP::Middleware)
end

it "includes Assets middleware for gem public directory" do
middleware = evaluator.middleware

# Should include the gem's public directory assets
expect(middleware).to be_a(Protocol::HTTP::Middleware)
end

it "includes application middleware" do
middleware = evaluator.middleware

# Should include the application class in the middleware stack
expect(middleware).to be_a(Protocol::HTTP::Middleware)
it "provides a middleware stack" do
expect(evaluator.middleware).to be_a(Protocol::HTTP::Middleware)
end
end
end
Loading