{"id":27947891,"url":"https://github.com/luckyframework/pulsar","last_synced_at":"2025-05-07T14:38:45.466Z","repository":{"id":39643818,"uuid":"280230865","full_name":"luckyframework/pulsar","owner":"luckyframework","description":"Pubsub and Instrumentation for Crystal","archived":false,"fork":false,"pushed_at":"2023-04-09T20:49:46.000Z","size":68,"stargazers_count":14,"open_issues_count":0,"forks_count":2,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-05-06T00:04:54.536Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Crystal","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/luckyframework.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-16T18:40:21.000Z","updated_at":"2023-12-31T09:06:31.000Z","dependencies_parsed_at":"2022-08-28T10:01:03.172Z","dependency_job_id":null,"html_url":"https://github.com/luckyframework/pulsar","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luckyframework%2Fpulsar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luckyframework%2Fpulsar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luckyframework%2Fpulsar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luckyframework%2Fpulsar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/luckyframework","download_url":"https://codeload.github.com/luckyframework/pulsar/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252896916,"owners_count":21821358,"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","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":"2025-05-07T14:38:44.777Z","updated_at":"2025-05-07T14:38:45.446Z","avatar_url":"https://github.com/luckyframework.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pulsar\n\n[![API Documentation Website](https://img.shields.io/website?down_color=red\u0026down_message=Offline\u0026label=API%20Documentation\u0026up_message=Online\u0026url=https%3A%2F%2Fluckyframework.github.io%2Fpulsar%2F)](https://luckyframework.github.io/pulsar)\n\nPulsar is a simple Crystal library for publishing and subscribing to events.\nIt also has timing information for metrics. So what does that mean in\npractice?\n\nYou can define an event and any number of subscribers can subscribe to the\nevent and do whatever they need with it.\n\nFor example, in Lucky, we use Pulsar to create events for things like\nrequests being processed, queries being made, before and after pipes running.\nThen we subscribe to these events to write to the logs. We also use this\ninternally to log debugging information in an upcoming UI called Breeze that\nlet's users debug development information.\n\n## Installation\n\n1. Add the dependency to your `shard.yml`:\n\n   ```yaml\n   dependencies:\n     pulsar:\n       github: luckyframework/pulsar\n   ```\n\n2. Run `shards install`\n\n## How to use Pulsar\n\nLet's say we're writing a library to charge a credit card and we may want to let\npeople run code whenever a charge is made. Here's how you can do that with Pulsar.\n\n### Create and publish an event\n\n```crystal\nclass PaymentProcessor::ChargeCardEvent \u003c Pulsar::Event\n  def initialize(@amount : Int32)\n  end\nend\n\nclass PaymentProcessor\n  def charge_card(amount : Int32)\n    # Run code to charge the card...\n\n    # Then fire an event\n    PaymentProcessor::ChargeCardEvent.publish(amount)\n  end\nend\n```\n\n### Subscribe to it and do whatever you want with it\n\nNow you can subscribe to the event and do whatever you want with it. For example,\nyou might log that a charge was made, or you might send an email to the sales team.\n\n```crystal\nPaymentProcessor::ChargeCardEvent.subscribe do |event|\n  puts \"Charged: #{event.amount} at #{event.started_at}\"\nend\n```\n\n### Recording timing information\n\nYou can also time how long it takes to run an event by inheriting from\n`Pulsar::TimedEvent`. You define them in the same way, but when you subscribe\nyou must also accept a second argument:\n\n```crystal\nclass Database::QueryEvent \u003c Pulsar::TimedEvent\nend\n\nDatabase::QueryEvent.subscribe do |event, duration|\n  # Do something with the event and duration\nend\n\nDatabase::QueryEvent.publish do\n  # Run a query, run some other code, etc.\nend\n```\n\n### Add more information to the event\n\nTo add more information to the event you can use `initialize` like you would\nwith any other Crystal class.\n\nFor example, we can record the database query in the event from above\n\n```crystal\nclass Database::QueryEvent \u003c Pulsar::TimedEvent\n  getter :query\n\n  def initialize(@query : String)\n  end\nend\n\nDatabase::QueryEvent.subscribe do |event, duration|\n  puts event.query\nend\n\nDatabase::QueryEvent.publish(query: \"SELECT * FROM users\") do\n  # Run a query, run some other code, etc.\nend\n```\n\n## Testing Pulsar events\n\nIf you want to test that events are published you can use Pulsar's built-in test mode.\n\n```crystal\n# Typically in spec/spec_helper.cr\n\n# Must come *after* `require \"spec\"`\nPulsar.enable_test_mode!\n```\n\nThis will enable an in-memory log for published events and will set up a hook to\nclear the events before each spec runs.\n\nYou can access events using `{MyEventClass}.logged_events`.\n\n```crystal\n# Create an event\nclass QueryEvent \u003c Pulsar::TimedEvent\n  def initialize(@query : String)\n  end\nend\n\ndef run_my_query(query)\n  # Publish the event\n  QueryEvent.publish(query: query) do\n    # Run the query somehow\n  end\nend\n\nit \"publishes an event when a SQL query is executed\" do\n  run_my_query \"SELECT * FROM users\n\n  the_published_event = QueryEvent.logged_events.first\n  the_published_event.query.should eq(\"SELECT * FROM users\")\nend\n```\n\n## `Pulsar.elapsed_text`\n\n`Pulsar.elapsed_text` will return the time taken (`Time::Span`) as a human readable String.\n\n```crystal\nDatabase::QueryEvent.subscribe do |event, duration|\n  puts Pulsar.elaspted_text(duration) # \"2.3ms\"\nend\n```\n\nThis method can be used with any `Time::Span`.\n\n## Performance gotchas\n\nSubscribers are notified synchronously in the same Fiber as the publisher.\nThis means that if you have a subscriber that takes a long time to run, it\nwill block anything else from running.\n\nIf you are doing some logging it is probably fine, but if you are doing\nsomething more time-intensive or failure prone like making an HTTP request or\nsaving to the database you should pay special attention.\n\n### Example of a problematic subscriber\n\n```crystal\nMyEvent.subscribe do |event|\n  sleep(5)\nend\n\nMyEvent.publish\n\nputs \"I just took 5 seconds to print!\"\n```\n\nOops. To get around this you can spawn a new fiber:\n\n```crystal\nMyEvent.subscribe do |event|\n  # Now the `sleep` will run in a new Fiber and will not block this one\n  spawn do\n    sleep(5)\n  end\nend\n\nMyEvent.publish\n\nputs \"This will print right away!\"\n```\n\n### Potential solutions\n\nAs described above you could run long running code in a new Fiber with `spawn`.\nYou could also use a background job library like https://github.com/robacarp/mosquito.\n\nBe aware that running things in a Fiber will lose the current Fiber's context. This is\nimportant for logging since `Log.context` only works for the current Fiber.\nSo if you plan to log using the built-in Logger, you likely _do not_ want to\nspawn a new fiber. It is fast enough to just log like normal.\n\n## Contributing\n\n1. Fork it (\u003chttps://github.com/luckyframework/pulsar/fork\u003e)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n## Contributors\n\n- [paulcsmith](https://github.com/paulcsmith) - creator and maintainer\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluckyframework%2Fpulsar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluckyframework%2Fpulsar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluckyframework%2Fpulsar/lists"}