{"id":19163903,"url":"https://github.com/pggalaviz/exnowflake","last_synced_at":"2025-05-07T11:22:42.717Z","repository":{"id":62429243,"uuid":"223501008","full_name":"pggalaviz/exnowflake","owner":"pggalaviz","description":"A decentralized, unique, time based ID generator.","archived":false,"fork":false,"pushed_at":"2019-11-26T17:26:59.000Z","size":25,"stargazers_count":11,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-13T06:20:56.223Z","etag":null,"topics":["distributed","elixir","idgenerator","redis","snowflake","snowflake-twitter"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/exnowflake","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/pggalaviz.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":"2019-11-22T23:09:21.000Z","updated_at":"2024-08-01T03:21:21.000Z","dependencies_parsed_at":"2022-11-01T20:06:56.904Z","dependency_job_id":null,"html_url":"https://github.com/pggalaviz/exnowflake","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pggalaviz%2Fexnowflake","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pggalaviz%2Fexnowflake/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pggalaviz%2Fexnowflake/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pggalaviz%2Fexnowflake/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pggalaviz","download_url":"https://codeload.github.com/pggalaviz/exnowflake/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252866249,"owners_count":21816426,"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":["distributed","elixir","idgenerator","redis","snowflake","snowflake-twitter"],"created_at":"2024-11-09T09:17:08.300Z","updated_at":"2025-05-07T11:22:42.687Z","avatar_url":"https://github.com/pggalaviz.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Exnowflake\n\n[![Build Status](https://www.travis-ci.org/pggalaviz/exnowflake.svg?branch=master)](https://www.travis-ci.org/pggalaviz/exnowflake)\n[![Hex.pm](https://img.shields.io/hexpm/dt/exnowflake.svg)](https://hex.pm/packages/exnowflake)\n\nExnowflake is an Elixir application used to generate decentralized, unique, time based IDs. It's inspired on Twitter's Snowflake.\n\n## Description\n\nThis app generates 64 bit integers, based on a timestamp, worker ID and a sequence:\n\n* timestamp (milliseconds) - 42 bits\n* worker - 10 bits (0 - 1023)\n* sequence - 12 bits (0 - 4095)\n\nThe worker ID can be an arbitrary integer between **0-1023** and can be assigned via configuration, optionally a **Redis** connection must be configured to auto register/unregister nodes in order to get the worker number.\n\n\n### Example\n\n```elixir\n=\u003e {:ok, id} = Exnowflake.generate()\n{:ok, 9522559057920}\n\n=\u003e Exnowflake.timestamp(id)\n1565636645355\n```\n\nWhen using **Redis**, a local registry is called for ID generation, so we don't incurr in extra latency.\n\n## Installation\n\nYou can find **Exnowflake** in [Hex.pm](https://hex.pm/packages/exnowflake) and you can add it to your project dependencies:\n\n```elixir\n# mix.exs\ndef deps do\n  [\n    {:exnowflake, \"~\u003e 0.1.0\"}\n  ]\nend\n```\n\n## Configuration\n\nExample:\n\n```elixir\nconfig :exnowflake,\n  worker_id: {:system, \"WORKER_ID\"}, # Must be an integer between 0-1023\n  epoch: 1574787672858 # custom epoch in milliseconds\n```\n#### Warning: if a custom `epoch` timestamp is given, it should not be replaced later or IDs overlap can occur.\n\nIf no `:worker_id` configuration option is given, **Exnowflake** will attempt to connect to **Redis** with the given defaults which you can override:\n\n```elixir\nconfig :exnowflake,\n  host: \"127.0.0.1\",\n  port: 6379,\n  database: 0\n```\n\nYou can also add the following options to redis connection:\n\n```elixir\nconfig :exnowflake,\n  # ...other config options\n  password: {:system, \"REDIS_PWD\"},\n  ssl: true, # defaults to false\n  sync_connect: false #defaults to true\n```\nFor more info check [Redix Documentation](https://hexdocs.pm/redix/Redix.html#start_link/1).\n\n## Usage\n\nGenerating an ID is very simple:\n\n```elixir\n=\u003e {:ok, id} = Exnowflake.generate()\n{:ok, 234527838437376}\n```\n\nWe can also retrieve the timestamp inside the ID:\n\n```elixir\n=\u003e Exnowflake.timestamp(id)\n1574524265794\n```\n\nOr the internal timestamp, which returns how many milliseconds since `epoch`\npassed when ID was generated:\n\n```elixir\n=\u003e Exnowflake.internal_timestamp(id)\n55915794\n```\nTo get the current node worker ID, you can call:\n\n```elixir\n=\u003e Exnowflake.worker_id()\n0\n```\n\n### Ecto\n\nIf working with **Ecto**, you can create a custom type in order to autogenerate IDs:\n\n```elixir\ndefmodule MyApp.Types.Exnowflake do\n  @moduledoc \"\"\"\n  A custom Ecto type to generate Exnowflake IDs.\n  \"\"\"\n  @behaviour Ecto.Type\n  require Logger\n\n  @type  t :: integer()\n\n  @doc \"\"\"\n  Generates a new ID.\n  \"\"\"\n  @spec generate() :: t()\n  def generate do\n    {:ok, id} = Exnowflake.generate()\n    id\n  rescue\n    exeption -\u003e\n      Logger.error(\"Ecto type Exnowflake failed: #{inspect(exeption)}\")\n  end\n\n  def autogenerate, do: generate()\n\n  @impl true\n  def type, do: :integer\n\n  @impl true\n  def cast(term) when is_integer(term), do: {:ok, term}\n  def cast(_), do: :error\n\n  @impl true\n  def dump(term) when is_integer(term), do: {:ok, term}\n  def dump(_), do: :error\n\n  @impl true\n  def load(term), do: {:ok, term}\n\n  @impl true\n  def equal?(term, term), do: true\n  def equal?(_, _), do: false\nend\n\n```\n\nThen in your schema you can configure the ID autogeneration:\n\n```elixir\ndefmodule MyApp.User do\n# ...\n  @primary_key {:id, MyApp.Types.Exnowflake, autogenerate: true}\n  alias MyApp.Types.Exnowflake\n# ...\nend\n```\n### Absinthe\n\nWhen working with **Absinthe** (or any type of API for that matter), we should transform the **Integer** IDs to the **String** type. This because **JavaScript** does not support 64 bit integers and we'll get undesired behavior or errors.\n\n```elixir\ndefmodule MyApp.Schema.ScalarTypes do\n  @moduledoc \"\"\"\n  Custom Scalar types\n  \"\"\"\n  use Absinthe.Schema.Notation\n\n  @desc \"\"\"\n  `Exnowflake` type represents a 64 bit number, appears in JSON responses as a\n  UTF-8 String due to Javascript's lack of support for  numbers \u003e 53-bits.\n  Its parsed again to an integer after received.\n  \"\"\"\n  scalar :exnowflake, name: \"Exnowflake\" do\n    serialize(\u0026Integer.to_string/1)\n    parse(\u0026decode_exnowflake/1)\n  end\n\n\n  @spec decode_exnowflake(struct()) :: {:ok, integer()} | {:ok, nil} | :error\n  defp decode_exnowflake(%Absinthe.Blueprint.Input.String{value: value}) do\n    case Integer.parse(value) do\n      {int, _} -\u003e {:ok, int}\n      _error -\u003e :error\n    end\n  end\n  defp decode_exnowflake(%Absinthe.Blueprint.Input.Integer{value: value}), do: {:ok, value}\n  defp decode_exnowflake(%Absinthe.Blueprint.Input.Null{}), do: {:ok, nil}\n  defp decode_exnowflake(_), do: :error\nend\n\n```\n\nNow we can use the `:exnowflake` type for our IDs:\n\n```elixir\n# some schema query file\n@desc \"Fetches a User\"\nfield :user, :user do\n  arg :id, non_null(:exnowflake)\n  resolve \u0026MyApp.Users.get/2\nend\n```\n\nIf not using **Absinthe** (eg. REST API), similar procedures are recomended on server requests \u0026 responses.\n\n## Benchmarks\n\nBenchmarks can be run with: `mix bench`.\n\nThese benchmarks were run with a **Redis** worker registry (no static worker ID given), on an iMac 4 GHz Intel Core i7 w/ 32GB RAM.\n\n```shell\nName                ips        average  deviation         median         99th %\ngenerate       283.71 K        3.52 μs   ±414.17%           3 μs           5 μs\nworker_id      622.99 K        1.61 μs   ±429.14%           2 μs           2 μs\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpggalaviz%2Fexnowflake","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpggalaviz%2Fexnowflake","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpggalaviz%2Fexnowflake/lists"}