{"id":30376625,"url":"https://github.com/rjpruitt16/glixir","last_synced_at":"2025-10-24T07:42:05.418Z","repository":{"id":299149729,"uuid":"1002201340","full_name":"rjpruitt16/glixir","owner":"rjpruitt16","description":"Easy Gleam wrapper around elixir otp","archived":false,"fork":false,"pushed_at":"2025-08-30T00:40:01.000Z","size":150,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-23T22:25:31.171Z","etag":null,"topics":["beam","elixir","gleam","interop","otp"],"latest_commit_sha":null,"homepage":"","language":"Gleam","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rjpruitt16.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2025-06-14T23:54:40.000Z","updated_at":"2025-09-17T01:38:26.000Z","dependencies_parsed_at":null,"dependency_job_id":"fa7d873e-07b3-4bf9-894e-6ca6ed9411c0","html_url":"https://github.com/rjpruitt16/glixir","commit_stats":null,"previous_names":["rjpruitt16/genserver","rjpruitt16/glixir"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/rjpruitt16/glixir","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rjpruitt16%2Fglixir","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rjpruitt16%2Fglixir/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rjpruitt16%2Fglixir/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rjpruitt16%2Fglixir/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rjpruitt16","download_url":"https://codeload.github.com/rjpruitt16/glixir/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rjpruitt16%2Fglixir/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280761850,"owners_count":26386245,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-24T02:00:06.418Z","response_time":73,"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":["beam","elixir","gleam","interop","otp"],"created_at":"2025-08-20T15:01:27.812Z","updated_at":"2025-10-24T07:42:05.412Z","avatar_url":"https://github.com/rjpruitt16.png","language":"Gleam","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Glixir 🌟\n\n[![Package Version](https://img.shields.io/hexpm/v/glixir)](https://hex.pm/packages/glixir)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/glixir/)\n\n**A Safe(ish) OTP interop between Gleam and Elixir/Erlang**\n\nBridge the gap between Gleam's type safety and the battle-tested OTP ecosystem. Use GenServers, Supervisors, Agents, Registry, and more from Gleam with confidence.\n\n## Features\n\n- ✅ **GenServer** - Type-safe calls to Elixir GenServers\n- ✅ **DynamicSupervisor** - Runtime process supervision and management\n- ✅ **Agent (now generic!)** - Type-safe state management with async operations\n- ✅ **Registry** - Dynamic process registration and Subject lookup\n- 🚧 **Task** - Async task execution _(coming soon)_\n- ✅ **Phoenix.PubSub** - Distributed messaging with JSON-based type safety\n- ✅ **Zero overhead** - Direct BEAM interop with clean Elixir helpers\n- ✅ **Gradual adoption** - Use alongside existing Elixir code\n- ✅ **syn** - Distributed process registry and PubSub coordination\n\n---\n\n## Type Safety \u0026 Phantom Types\n\n\u003e **A note from your neighborhood type enthusiast:**\n\u003e\n\u003e Some `glixir` APIs (notably process calls, GenServer, Registry) still require passing `Dynamic` values—this is the price of seamless BEAM interop and runtime dynamism. While decoders on return values help catch mismatches, full compile-time type safety isn't always possible... **yet**.\n\u003e\n\u003e But here's the good news:\n\u003e We're actively rolling out [phantom types](https://gleam.run/tour/phantom_types/) and generics across the API, banishing `Dynamic` wherever possible and making misused actors a compile-time relic.\n\n---\n\n### Type safety level\n```\nGenServer:     [■■■■■■■■□□] 80% - Request/reply types enforced by Gleam, decoder required, but runtime BEAM interop can still fail if types disagree.\nSupervisor:    [■■■■■■■■■□] 90% - Phantom-typed with compile-time child spec validation, args/replies bounded by generics.\nRegistry:      [■■■■■■■■■□] 90% - Phantom-typed with compile-time key/message validation, requires key encoders.\nAgent:         [■■■■■■■■■■] 100% - State and API fully generic and type safe!\nPubSub:        [■■■■■■■■□□] 80% - JSON-based type safety with user-defined encoders/decoders. Phantom-typed for message_type.\nsyn:           [■■■■■■■■□□] 80% - Distributed coordination with type-safe message patterns, runtime node discovery.\nTask:          [□□□□□□□□□□] 0% - Not started.\n```\n\n\u003e *Full green bars are the dream; until then, decoders are your seatbelt!*\n\n---\n\n## Why Not 100% Type-Safe Now?\n\nBlame Erlang! (Just kidding, blame dynamic process boundaries.)\nYou get strict safety *inside* Gleam, but as soon as you jump the BEAM-to-BEAM fence, the runtime is your playground. So, until Gleam's type system can tame Elixir's wild world, we work with decoders and are pushing hard to get you closer to total type bliss.\n\n---\n\n# ⚠️ Caveats: BEAM Interop Trade-offs\n\nGlixir provides **bounded type safety** - the maximum safety possible while maintaining full OTP functionality. Some limitations are **inherent to BEAM interop**, not design flaws:\n\n## The Safety Spectrum\n- **Pure Gleam:** 100% compile-time safe, but no distributed features  \n- **Glixir:** 70-90% compile-time safe + runtime validation, full OTP power  \n- **Raw FFI:** ~20% safe, full OTP power, high risk\n\n## Unavoidable BEAM Realities\n- **Process discovery** - distributed systems have runtime process existence\n- **Module loading** - OTP's dynamic nature requires string module names  \n- **Cross-language boundaries** - serialization between Gleam and Elixir\n\n## What You Get\n✅ **Compile-time:** Prevents category errors, type mixing, wrong message types  \n⚠️ **Runtime:** Process existence, module validity, message format compatibility  \n\n**Bottom Line:** Glixir is the sweet spot - maximum practical safety with essential functionality that core Gleam simply doesn't provide.\n\n**Want to help speed this up? File issues, suggest API improvements, or just cheer us on in the repo!**\n\n---\n\n## ⚡ Atom Safety \u0026 Fail-Fast API\n\n**glixir** now requires all process, registry, and module names to be existing atoms—typos or missing modules crash immediately with `{bad_atom, ...}`.\n\n- Avoids BEAM atom leaks and silent bugs.\n- If you typo or use a non-existent module, you'll see a *loud* error.\n- Pro-tip: Always reference the real module, or pre-load it in Elixir before calling from Gleam.\n\n_This makes glixir safer by default—don't trust user input as atom names!_\n\n---\n\n\n### syn - Distributed Process Coordination\n\n**Distributed service discovery and event streaming across BEAM nodes! 🌐**\n\nUse Erlang's battle-tested `syn` library for distributed coordination, consensus algorithms, and fault-tolerant process management.\n\n```gleam\nimport glixir/syn\nimport gleam/json\n\npub fn distributed_example() {\n  // Initialize scopes at application startup\n  syn.init_scopes([\"worker_pools\", \"coordination\"])\n  \n  // Register this process in a distributed pool\n  let load_info = #(cpu_usage: 0.3, queue_size: 5)\n  let assert Ok(_) = syn.register_worker(\"image_processors\", \"worker_1\", load_info)\n  \n  // Find workers across the cluster\n  case syn.find_worker(\"image_processors\", \"worker_1\") {\n    Ok(#(pid, #(cpu_usage, queue_size))) -\u003e {\n      // Send work to the least loaded worker\n      process.send(pid, ProcessImage(\"photo.jpg\"))\n    }\n    Error(_) -\u003e // Worker not available, try another\n  }\n  \n  // Join coordination group for consensus\n  let assert Ok(_) = syn.join_coordination(\"leader_election\")\n  \n  // Broadcast status for distributed coordination\n  let status = json.object([\n    #(\"node\", json.string(\"worker_node_1\")),\n    #(\"load\", json.float(0.3)),\n    #(\"available\", json.bool(True))\n  ])\n  \n  let assert Ok(nodes_notified) = syn.broadcast_status(\"health_check\", status)\n  io.println(\"Status sent to \" \u003c\u003e int.to_string(nodes_notified) \u003c\u003e \" nodes\")\n}\n\n// Advanced: Custom coordination patterns\npub fn consensus_example() {\n  // Register with metadata for consensus algorithms\n  let machine_status = #(queue_lengths: #(0, 5, 2), capacity: 100)\n  let assert Ok(_) = syn.register(\"machines\", \"machine_1\", machine_status)\n  \n  // Publish for distributed decision making\n  let queue_status = json.object([\n    #(\"machine_id\", json.string(\"machine_1\")),\n    #(\"free_queue\", json.int(0)),\n    #(\"paid_queue\", json.int(5)),\n    #(\"capacity\", json.int(100))\n  ])\n  \n  let assert Ok(_) = syn.publish_json(\"coordination\", \"load_balancing\", queue_status, fn(j) { j })\n}\n\n## Installation\n\n```sh\ngleam add glixir\n```\n\nAdd the Elixir helper modules to your project:\n\n```elixir\n# lib/glixir_supervisor.ex and lib/glixir_registry.ex\n# Copy the helper modules from the docs\n```\n\n## Quick Start\n\n### PubSub - Type-Safe Distributed Messaging\n\n**Real-time event broadcasting with JSON-based type safety! 📡**\n\n```gleam\nimport glixir\nimport gleam/json\nimport gleam/dynamic/decode\nimport gleam/erlang/atom\n\n// Define your message types\npub type UserEvent {\n  PageView(user_id: String, page: String)\n  Purchase(user_id: String, amount: Float)\n}\n\n// Create encoder/decoder for type safety\npub fn encode_user_event(event: UserEvent) -\u003e String {\n  case event {\n    PageView(user_id, page) -\u003e \n      json.object([\n        #(\"type\", json.string(\"page_view\")),\n        #(\"user_id\", json.string(user_id)),\n        #(\"page\", json.string(page))\n      ]) |\u003e json.to_string\n    \n    Purchase(user_id, amount) -\u003e\n      json.object([\n        #(\"type\", json.string(\"purchase\")),\n        #(\"user_id\", json.string(user_id)),\n        #(\"amount\", json.float(amount))\n      ]) |\u003e json.to_string\n  }\n}\n\npub fn decode_user_event(json_string: String) -\u003e Result(UserEvent, String) {\n  // Your custom decoder logic here\n  json.decode(json_string, dynamic.decode3(\n    PageView,\n    dynamic.field(\"user_id\", decode.string),\n    dynamic.field(\"page\", decode.string),\n    dynamic.field(\"type\", decode.string)\n  ))\n}\n\n// Handler function for PubSub messages\npub fn handle_user_events(json_message: String) -\u003e Nil {\n  case decode_user_event(json_message) {\n    Ok(PageView(user_id, page)) -\u003e {\n      io.println(\"User \" \u003c\u003e user_id \u003c\u003e \" viewed \" \u003c\u003e page)\n    }\n    Ok(Purchase(user_id, amount)) -\u003e {\n      io.println(\"User \" \u003c\u003e user_id \u003c\u003e \" purchased $\" \u003c\u003e float.to_string(amount))\n    }\n    Error(_) -\u003e {\n      io.println(\"Failed to decode user event\")\n    }\n  }\n}\n\npub fn pubsub_example() {\n  // Start a phantom-typed PubSub system\n  let assert Ok(_pubsub: glixir.PubSub(UserEvent)) = \n    glixir.pubsub_start(atom.create(\"user_events\"))\n  \n  // Subscribe with your handler function\n  let assert Ok(_) = glixir.pubsub_subscribe(\n    atom.create(\"user_events\"),\n    \"user:metrics\",\n    \"my_app\",  // Your module name\n    \"handle_user_events\"  // Your handler function\n  )\n  \n  // Broadcast type-safe events from anywhere\n  let page_event = PageView(\"user_123\", \"/dashboard\")\n  let assert Ok(_) = glixir.pubsub_broadcast(\n    atom.create(\"user_events\"),\n    \"user:metrics\", \n    page_event,\n    encode_user_event  // Your encoder\n  )\n  \n  let purchase_event = Purchase(\"user_123\", 99.99)\n  let assert Ok(_) = glixir.pubsub_broadcast(\n    atom.create(\"user_events\"),\n    \"user:metrics\",\n    purchase_event,\n    encode_user_event\n  )\n  \n  // Cleanup when done\n  let assert Ok(_) = glixir.pubsub_unsubscribe(\n    atom.create(\"user_events\"), \n    \"user:metrics\"\n  )\n}\n```\n\n**Direct Actor Targeting Pattern:**\n```gleam\n// Perfect for metric actors that need their own identity\npub fn handle_metric_update(actor_id: String, json_message: String) -\u003e Nil {\n  case decode_metric_message(json_message) {\n    Ok(metric) -\u003e {\n      // Update this specific actor's metrics\n      io.println(\"Actor \" \u003c\u003e actor_id \u003c\u003e \" received metric: \" \u003c\u003e metric.name)\n      // ... update actor state\n    }\n    Error(_) -\u003e {\n      io.println(\"Invalid metric for actor: \" \u003c\u003e actor_id)\n    }\n  }\n}\n```\n// Subscribe each metric actor with its own ID\nlet assert Ok(_) = glixir.pubsub_subscribe_with_registry_key(\n  atom.create(\"metrics_pubsub\"),\n  \"metric:updates\",\n  \"my_app\",\n  \"handle_metric_update\",\n  \"metric_actor_\" \u003c\u003e actor_id  // Each actor gets its own key\n)\n\n**Multi-Message Handler Example:**\n```gleam\n// Single handler can process multiple message types!\npub fn handle_all_events(json_message: String) -\u003e Nil {\n  case decode_user_event(json_message) {\n    Ok(user_event) -\u003e handle_user_event(user_event)\n    Error(_) -\u003e \n      case decode_system_event(json_message) {\n        Ok(system_event) -\u003e handle_system_event(system_event)\n        Error(_) -\u003e io.println(\"Unknown message type: \" \u003c\u003e json_message)\n      }\n  }\n}\n```\n\n---\n\n### Agent State Management (Now Type Safe!)\n\n```gleam\nimport glixir\nimport gleam/dynamic/decode\nimport gleam/erlang/atom\n\npub fn main() {\n  // Start an agent with initial state (Agent(Int))\n  let assert Ok(counter) = glixir.start_agent(fn() { 42 })\n\n  // Get state with a decoder (type safe!)\n  let assert Ok(value) = glixir.get_agent(counter, fn(x) { x }, decode.int)\n  io.debug(value)  // 42\n\n  // Update state\n  let assert Ok(_) = glixir.update_agent(counter, fn(n) { n + 10 })\n\n  // Get and update in one operation\n  let assert Ok(old_value) = glixir.get_and_update_agent(\n    counter,\n    fn(n) { #(n, n * 2) },\n    decode.int\n  )\n  io.debug(old_value)  // 52\n\n  // Stop the agent with a reason (now explicit)\n  let assert Ok(_) = glixir.stop_agent(counter, atom.create(\"normal\"))\n}\n```\n\n---\n\n### Registry - Dynamic Actor Discovery\n\n```gleam\nimport glixir\nimport gleam/erlang/process\nimport gleam/erlang/atom\nimport gleam/dynamic\nimport gleam/io\n\npub type UserMessage {\n  RecordMetric(name: String, value: Float)\n  GetStats\n}\n\npub fn actor_discovery_example() {\n  // Start phantom-typed registry for actor lookup\n  let assert Ok(_registry: glixir.Registry(atom.Atom, UserMessage)) = \n    glixir.start_registry(atom.create(\"user_actors\"))\n  \n  // Start a type-safe dynamic supervisor\n  let assert Ok(supervisor) = glixir.start_dynamic_supervisor_named(\n    atom.create(\"user_supervisor\")\n  )\n  \n  // String encoder for user ID args\n  fn user_id_encode(user_id: String) -\u003e List(dynamic.Dynamic) {\n    [dynamic.string(user_id)]\n  }\n  \n  // Simple decoder for replies\n  fn simple_decode(_d: dynamic.Dynamic) -\u003e Result(String, String) {\n    Ok(\"started\")\n  }\n  \n  // Create a type-safe child spec\n  let user_spec = glixir.child_spec(\n    id: \"user_123\",\n    module: \"MyApp.UserActor\", \n    function: \"start_link\",\n    args: \"user_123\",  // Typed as String!\n    restart: glixir.permanent,\n    shutdown_timeout: 5000,\n    child_type: glixir.worker,\n    encode: user_id_encode,\n  )\n  \n  // Start the child with compile-time type safety\n  case glixir.start_dynamic_child(supervisor, user_spec, user_id_encode, simple_decode) {\n    glixir.ChildStarted(_user_pid, _reply) -\u003e {\n      // Actor registers itself in the registry with typed key\n      let user_subject = process.new_subject()\n      let assert Ok(_) = glixir.register_subject(\n        atom.create(\"user_actors\"), \n        atom.create(\"user_123\"),  // Typed Atom key\n        user_subject,\n        glixir.atom_key_encoder   // Required encoder\n      )\n      \n      // Later... find and message the actor from anywhere!\n      case glixir.lookup_subject(\n        atom.create(\"user_actors\"), \n        atom.create(\"user_123\"),\n        glixir.atom_key_encoder\n      ) {\n        Ok(subject) -\u003e {\n          process.send(subject, RecordMetric(\"page_views\", 1.0))\n          io.println(\"Metric sent to user actor! 📊\")\n        }\n        Error(_) -\u003e io.println(\"User actor not found\")\n      }\n    }\n    glixir.StartChildError(error) -\u003e {\n      io.println(\"Failed to start user actor: \" \u003c\u003e error)\n    }\n  }\n}\n```\n\n---\n\n### GenServer Interop\n\n```gleam\nimport glixir\nimport gleam/erlang/atom\nimport gleam/dynamic\nimport gleam/dynamic/decode\n\n// Example: Counter server (request type is atom, reply type is Int)\npub fn main() {\n  let assert Ok(server) = glixir.start_genserver(\"MyApp.Counter\", dynamic.int(0))\n  let assert Ok(count) = glixir.call_genserver(server, atom.create(\"get_count\"), decode.int)\n  io.debug(count)  // 0\n\n  let assert Ok(_) = glixir.cast_genserver(server, atom.create(\"increment\"))\n\n  let assert Ok(count) = glixir.call_genserver(server, atom.create(\"get_count\"), decode.int)\n  io.debug(count)  // 1\n}\n```\n\n---\n\n## Type Safety Notes\n\nWhile this library provides type-safe wrappers on the Gleam side, remember:\n\n1. **Runtime Types**: Elixir/Erlang processes are dynamically typed at runtime\n2. **Message Contracts**: Ensure message formats match between Gleam and Elixir\n3. **JSON Serialization**: PubSub uses JSON for cross-process type safety\n4. **Decoder Functions**: Always provide appropriate decoders for return values\n5. **Error Handling**: Handle all potential decode and process errors\n6. **Testing**: Test integration points thoroughly\n\n---\n\n## Real-World Use Cases\n\n**TrackTags** - Auto-scaling metrics platform built with glixir:\n\n* ✅ Dynamic user actors spawned per session\n* ✅ Registry-based actor discovery by user ID  \n* ✅ Real-time metric collection via type-safe PubSub\n* ✅ Fault-tolerant supervision trees\n\n---\n\n## About the Author\n\nBuilt by **Rahmi Pruitt** - Ex-Twitch/Amazon Engineer turned indie hacker, on a mission to bring Gleam to the mainstream! 🚀\n\n*\"Making concurrent programming delightful, one type at a time.\"*\n\n---\n\n**⭐ Star this repo if glixir helped you build something awesome!** Your support helps bring mature OTP tooling to the Gleam ecosystem.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frjpruitt16%2Fglixir","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frjpruitt16%2Fglixir","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frjpruitt16%2Fglixir/lists"}