feat: more clear definition and first base implementation for lua

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-23 12:05:12 +02:00
parent 3f5744308c
commit a449778b44
18 changed files with 859 additions and 16 deletions

108
lib/bds/scripting.ex Normal file
View File

@@ -0,0 +1,108 @@
defmodule BDS.Scripting do
@moduledoc """
Facade for the configured user-script runtime.
"""
alias BDS.Scripting.Runtime
@type job_status :: :queued | :running | :completed | :failed | :cancelled
@type job_snapshot :: %{
id: String.t(),
status: job_status(),
progress: map(),
result: term() | nil,
error: term() | nil,
inserted_at: DateTime.t(),
started_at: DateTime.t() | nil,
finished_at: DateTime.t() | nil
}
@spec runtime() :: module()
def runtime do
Application.fetch_env!(:bds, :scripting)
|> Keyword.fetch!(:runtime)
end
@spec validate(String.t()) :: :ok | {:error, term()}
def validate(source) when is_binary(source) do
runtime().validate(source)
end
@spec execute(String.t(), String.t(), [term()], [Runtime.execution_option()]) ::
{:ok, term()} | {:error, term()}
def execute(source, entrypoint, args \\ [], opts \\ [])
when is_binary(source) and is_binary(entrypoint) and is_list(args) and is_list(opts) do
runtime().execute(source, entrypoint, args, opts)
end
@spec start_job(String.t(), String.t(), [term()], [Runtime.execution_option()]) ::
{:ok, job_snapshot()} | {:error, term()}
def start_job(source, entrypoint, args \\ [], opts \\ [])
when is_binary(source) and is_binary(entrypoint) and is_list(args) and is_list(opts) do
job_id = "script-job-" <> Integer.to_string(System.unique_integer([:positive, :monotonic]))
job = %{
id: job_id,
status: :queued,
progress: %{},
result: nil,
error: nil,
inserted_at: DateTime.utc_now(),
started_at: nil,
finished_at: nil
}
:ok = BDS.Scripting.JobStore.put_job(job)
child_spec =
{BDS.Scripting.JobRunner,
job_id: job_id,
runtime: runtime(),
source: source,
entrypoint: entrypoint,
args: args,
opts: batch_job_defaults(opts)}
case DynamicSupervisor.start_child(BDS.Scripting.JobSupervisor, child_spec) do
{:ok, _pid} -> {:ok, BDS.Scripting.JobStore.fetch_job!(job_id)}
{:error, reason} ->
:ok =
BDS.Scripting.JobStore.update_job(job_id, %{
status: :failed,
error: reason,
finished_at: DateTime.utc_now()
})
{:error, reason}
end
end
@spec get_job(String.t()) :: job_snapshot() | nil
def get_job(job_id) when is_binary(job_id) do
BDS.Scripting.JobStore.fetch_job(job_id)
end
@spec cancel_job(String.t()) :: :ok | {:error, :not_found | :not_running}
def cancel_job(job_id) when is_binary(job_id) do
case BDS.Scripting.JobStore.runner_for(job_id) do
nil ->
case BDS.Scripting.JobStore.fetch_job(job_id) do
nil -> {:error, :not_found}
_job -> {:error, :not_running}
end
pid -> BDS.Scripting.JobRunner.cancel(pid)
end
end
defp batch_job_defaults(opts) do
config = Application.fetch_env!(:bds, :scripting)
defaults = [
timeout: Keyword.get(config, :job_timeout, :infinity),
max_reductions: Keyword.get(config, :job_max_reductions, :none)
]
Keyword.merge(defaults, opts)
end
end