{"id":50676969,"url":"https://github.com/valiot/temporal_ex","last_synced_at":"2026-06-08T16:04:35.562Z","repository":{"id":341628467,"uuid":"1161845638","full_name":"valiot/temporal_ex","owner":"valiot","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-21T15:32:27.000Z","size":91,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-22T00:34:53.276Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/valiot.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-19T15:29:43.000Z","updated_at":"2026-05-21T15:40:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/valiot/temporal_ex","commit_stats":null,"previous_names":["valiot/temporal_ex"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/valiot/temporal_ex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valiot%2Ftemporal_ex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valiot%2Ftemporal_ex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valiot%2Ftemporal_ex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valiot%2Ftemporal_ex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/valiot","download_url":"https://codeload.github.com/valiot/temporal_ex/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valiot%2Ftemporal_ex/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34069500,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-08T16:04:30.407Z","updated_at":"2026-06-08T16:04:35.550Z","avatar_url":"https://github.com/valiot.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TemporalEx\n\nErgonomic [Temporal](https://temporal.io) client SDK for Elixir.\n\nProvides a high-level, protobuf-free API for interacting with Temporal workflow services. Work with plain Elixir terms — maps, keyword lists, strings — without constructing protobuf structs.\n\n## Installation\n\nAdd `temporal_ex` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:temporal_ex, \"~\u003e 0.1.0\"}\n  ]\nend\n```\n\n## Quick Start\n\n```elixir\n# Start a client (as part of your supervision tree)\n{:ok, client} = TemporalEx.connect(\n  target: \"localhost:7233\",\n  namespace: \"default\"\n)\n\n# Start a workflow\n{:ok, handle} = TemporalEx.start_workflow(client, \"MyWorkflow\", [%{key: \"value\"}],\n  id: \"my-workflow-123\",\n  task_queue: \"my-task-queue\"\n)\n\n# Interact with the workflow\n:ok = TemporalEx.WorkflowHandle.signal(handle, \"my-signal\", [%{data: 1}])\n{:ok, description} = TemporalEx.WorkflowHandle.describe(handle)\n{:ok, result} = TemporalEx.WorkflowHandle.result(handle)\n```\n\n## Supervision Tree\n\nFor production use, start `TemporalEx.Client` under your application supervisor:\n\n```elixir\nchildren = [\n  {TemporalEx.Client,\n    name: :temporal_client,\n    target: \"localhost:7233\",\n    namespace: \"default\"\n  }\n]\n\nSupervisor.start_link(children, strategy: :one_for_one)\n```\n\nThen reference the client by name:\n\n```elixir\nhandle = TemporalEx.get_workflow_handle(:temporal_client, \"my-workflow-123\")\n{:ok, desc} = TemporalEx.WorkflowHandle.describe(handle)\n```\n\n## Connection Options\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `:target` | `\"localhost:7233\"` | Temporal server address |\n| `:namespace` | `\"default\"` | Default namespace |\n| `:api_key` | `nil` | API key or Bearer token |\n| `:tls` | `%{}` | TLS/mTLS config (see below) |\n| `:name` | `nil` | GenServer registration name |\n| `:call_timeout` | `5000` | Default RPC timeout (ms) |\n| `:connect_retry` | `0` | gRPC connection retries |\n| `:data_converter` | `TemporalEx.DataConverter.Json` | Custom data converter module |\n| `:identity` | auto | Client identity string |\n\n### TLS / mTLS\n\nFor Temporal Cloud or self-hosted TLS:\n\n```elixir\n{TemporalEx.Client,\n  target: \"my-ns.tmprl.cloud:7233\",\n  namespace: \"my-ns\",\n  api_key: \"my-api-key\",\n  tls: %{\n    client_cert_pem_b64: System.get_env(\"TEMPORAL_CLIENT_CERT\"),\n    client_key_pem_b64: System.get_env(\"TEMPORAL_CLIENT_KEY\"),\n    ca_cert_file: \"/path/to/ca.pem\"  # optional\n  }\n}\n```\n\nTemporal Cloud domains (`.tmprl.cloud`, `.api.temporal.io`) automatically use HTTPS.\n\n## API\n\n### Workflow Operations\n\n```elixir\n# Start a workflow\n{:ok, handle} = TemporalEx.start_workflow(client, \"WorkflowType\", args, opts)\n\n# Start with an initial signal (atomic)\n{:ok, handle} = TemporalEx.signal_with_start(client, \"WorkflowType\", args, \"signal\", signal_args, opts)\n\n# Get a handle to an existing workflow\nhandle = TemporalEx.get_workflow_handle(client, \"workflow-id\")\nhandle = TemporalEx.get_workflow_handle(client, \"workflow-id\", \"run-id\")\n```\n\n### WorkflowHandle Operations\n\n```elixir\n{:ok, description} = TemporalEx.WorkflowHandle.describe(handle)\n:ok              = TemporalEx.WorkflowHandle.signal(handle, \"signal-name\", [args])\n{:ok, result}    = TemporalEx.WorkflowHandle.query(handle, \"query-type\", [args])\n:ok              = TemporalEx.WorkflowHandle.cancel(handle)\n:ok              = TemporalEx.WorkflowHandle.terminate(handle, reason: \"reason\")\n:ok              = TemporalEx.WorkflowHandle.delete(handle)\n{:ok, history}   = TemporalEx.WorkflowHandle.get_history(handle)\n{:ok, result}    = TemporalEx.WorkflowHandle.result(handle)\n{:ok, reset}     = TemporalEx.WorkflowHandle.reset(handle, opts)\n```\n\n### Schedule Operations\n\nTemporal Schedules are server-side crons — Temporal starts workflow executions on the configured interval. The worker doesn't need to be running when the schedule is created; it just needs to be available to pick up workflow tasks from the queue.\n\n```elixir\n# Create a schedule that runs every 60 seconds\n{:ok, handle} = TemporalEx.create_schedule(client, \"my-schedule\",\n  spec: [intervals: [[every: 60]]],\n  action: [\n    workflow_type: \"MyWorkflow\",\n    workflow_id: \"my-workflow\",\n    task_queue: \"my-queue\",\n    args: [%{key: \"value\"}]\n  ],\n  policies: [overlap_policy: :skip]\n)\n\n# Get a handle to an existing schedule\nhandle = TemporalEx.get_schedule_handle(client, \"my-schedule\")\n\n# List all schedules\n{:ok, schedules, next_token} = TemporalEx.list_schedules(client)\n```\n\n### ScheduleHandle Operations\n\n```elixir\n{:ok, description} = TemporalEx.ScheduleHandle.describe(handle)\n:ok              = TemporalEx.ScheduleHandle.pause(handle, note: \"maintenance\")\n:ok              = TemporalEx.ScheduleHandle.unpause(handle, note: \"back online\")\n:ok              = TemporalEx.ScheduleHandle.trigger(handle)\n:ok              = TemporalEx.ScheduleHandle.update(handle, schedule: [...])\n:ok              = TemporalEx.ScheduleHandle.delete(handle)\n```\n\n#### Schedule Spec Options\n\n```elixir\n# Interval-based (every N seconds, with optional offset)\nspec: [intervals: [[every: 60], [every: 300, offset: 10]]]\n\n# Calendar-based\nspec: [calendars: [[hour: \"8\", minute: \"30\", day_of_week: \"MON-FRI\"]]]\n\n# Cron expressions\nspec: [cron_expressions: [\"0 */5 * * *\"]]\n\n# With timezone and jitter\nspec: [intervals: [[every: 60]], timezone: \"America/Chicago\", jitter: 5]\n```\n\n#### Overlap Policies\n\n| Policy | Description |\n|--------|-------------|\n| `:skip` | Skip if previous is still running |\n| `:buffer_one` | Buffer one execution |\n| `:buffer_all` | Buffer all executions |\n| `:cancel_other` | Cancel the running execution |\n| `:terminate_other` | Terminate the running execution |\n| `:allow_all` | Allow concurrent executions |\n\n### Visibility\n\n```elixir\n{:ok, workflows, next_token} = TemporalEx.list_workflows(client, \"WorkflowType = 'MyWorkflow'\")\n{:ok, count} = TemporalEx.count_workflows(client, \"WorkflowType = 'MyWorkflow'\")\n```\n\n### System\n\n```elixir\n{:ok, info} = TemporalEx.get_system_info(client)\n```\n\n## Error Handling\n\nAll errors are returned as typed structs:\n\n- `TemporalEx.Error.WorkflowAlreadyStarted`\n- `TemporalEx.Error.WorkflowNotFound`\n- `TemporalEx.Error.ScheduleAlreadyExists`\n- `TemporalEx.Error.ScheduleNotFound`\n- `TemporalEx.Error.NamespaceNotFound`\n- `TemporalEx.Error.QueryFailed`\n- `TemporalEx.Error.RPCError` (catch-all)\n\n```elixir\ncase TemporalEx.start_workflow(client, \"MyWorkflow\", [], id: \"wf-1\", task_queue: \"q\") do\n  {:ok, handle} -\u003e handle\n  {:error, %TemporalEx.Error.WorkflowAlreadyStarted{}} -\u003e # handle duplicate\n  {:error, %TemporalEx.Error.RPCError{message: msg}} -\u003e # handle other errors\nend\n```\n\n## Custom Data Converter\n\nImplement the `TemporalEx.DataConverter` behaviour to use a custom serialization format:\n\n```elixir\ndefmodule MyConverter do\n  @behaviour TemporalEx.DataConverter\n\n  @impl true\n  def encoding, do: \"my-encoding\"\n\n  @impl true\n  def encode(term), do: {:ok, {serialize(term), %{}}}\n\n  @impl true\n  def decode(binary, _metadata), do: {:ok, deserialize(binary)}\nend\n\n{TemporalEx.Client, data_converter: MyConverter, ...}\n```\n\n## License\n\nMIT - see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvaliot%2Ftemporal_ex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvaliot%2Ftemporal_ex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvaliot%2Ftemporal_ex/lists"}