{"id":28724151,"url":"https://github.com/processone/jamler","last_synced_at":"2025-10-14T09:32:10.097Z","repository":{"id":5588511,"uuid":"6794852","full_name":"processone/jamler","owner":"processone","description":null,"archived":false,"fork":false,"pushed_at":"2023-12-27T14:54:12.000Z","size":560,"stargazers_count":14,"open_issues_count":0,"forks_count":3,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-06-15T10:09:21.280Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/processone.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,"governance":null,"roadmap":null,"authors":null}},"created_at":"2012-11-21T11:53:29.000Z","updated_at":"2024-11-04T23:58:56.000Z","dependencies_parsed_at":"2023-12-27T15:58:21.958Z","dependency_job_id":null,"html_url":"https://github.com/processone/jamler","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/processone/jamler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/processone%2Fjamler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/processone%2Fjamler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/processone%2Fjamler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/processone%2Fjamler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/processone","download_url":"https://codeload.github.com/processone/jamler/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/processone%2Fjamler/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279018630,"owners_count":26086404,"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-14T02:00:06.444Z","response_time":60,"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":"2025-06-15T10:09:20.064Z","updated_at":"2025-10-14T09:32:10.082Z","avatar_url":"https://github.com/processone.png","language":"OCaml","readme":"# Jamler\n\nJamler is an experimental XMPP server.  It was developed mainly in 2011 as an\nattempt to rewrite [ejabberd](https://github.com/processone/ejabberd) in OCaml\nto see how static typing would affect it.\nAs ejabberd has evolved a lot since 2011, please use ejabberd version 2.1.8 to\ncompare it to jamler.\nCurrently (2023) it's updated to use actual versions of OCaml and libraries,\nbut no new features are added.\n\n## Comparison with Erlang and ejabberd\n\nThis section compares implementation details, not features.\n\n### XML elements\n\nXML elements were defined in ejabberd like this:\n```erlang\n-type(xmlelement() :: {xmlelement, name(), attrs(), [xmlelement() | cdata()]}).\n-type(cdata() :: {xmlcdata, binary()}).\n```\n\nIt is similar in jamler:\n```ocaml\ntype element_cdata =\n  [ `XmlElement of name * attributes * element_cdata list\n  | `XmlCdata of string ]\n\ntype element =\n  [ `XmlElement of name * attributes * element_cdata list ]\n```\n\n### Stringprep\n\nTo compare JIDs, they must be normalized first\n([RFC6122](https://tools.ietf.org/html/rfc6122)).  There are 3 stringprep\nprofiles for corresponing JID parts (e.g. `(user, server, resource)` must be\nnormalized into `(nodeprep(user), nameprep(server), resourceprep(resource))`).\nIt's error-prone (e.g. using `nodeprep` where `nameprep` is needed, or\nforgetting to normalize).  As a result, some ejabberd functions always do\nnormalization to be on a safe side, and some values are normalized several\ntimes during processing.\n\nJamler has 3 private types for normalized values:\n```ocaml\ntype namepreped = private string\ntype nodepreped = private string\ntype resourcepreped = private string\n\nval nameprep : string -\u003e namepreped option\nval nodeprep : string -\u003e nodepreped option\nval resourceprep : string -\u003e resourcepreped option\n```\nSo the only way to create e.g. `namepreped` value is via a call to the `nameprep`\nfunction.  Other functions can define that they expect only a normalized value:\n```ocaml\nval jid_replace_resource' : jid -\u003e resourcepreped -\u003e jid\n```\nAt the same time those values are still strings and can be used as strings:\n`(host :\u003e string)`.\n\n### SQL queries\n\nIn 2011 ejabberd used manual escaping for SQL queries:\n```erlang\nUsername = ejabberd_odbc:escape(LUser),\nejabberd_odbc:sql_query(\n  LServer,\n  [\"select password from users where username='\", Username, \"';\"]).\n```\n\nFor jamler camlp4 (and now ppx) syntax extension was implemented to prepare SQL\nqueries:\n```ocaml\nlet suser = (user : Jlib.nodepreped :\u003e string) in\nlet query = [%sql {|SELECT @(password)s from users where username = %(suser)s|}] in\n...\n```\nIt was so convenient, so later got ported to ejabberd and extended further:\n```erlang\nejabberd_sql:sql_query(\n  LServer,\n  ?SQL(\"select @(password)s from users where username=%(LUser)s\")).\n```\n\nNowadays you can find similar functionality in\n[ocaml-sqlexpr](https://github.com/mfp/ocaml-sqlexpr).\n\n\n### Processes\n\nErlang processes are mimicked in jamler using Lwt threads.  Processes have the\nfollowing type:\n```ocaml\ntype -'a pid\n```\nwhere `'a` is a type of messages the process can receive.\n\nInternally `pid` is `int` pointing to a record in the processes table.  That\nwas probably not a good decision, as forgetting to cleanup pid references can\nlead to a bug where another process gets the same pid as a now-dead process and\nreceives incompatible messages.\n\nAPI is very similar to erlang:\n| Erlang                   | Jamler                                   |\n|--------------------------|------------------------------------------|\n| `spawn(f)`               | `spawn f`                                |\n| `Pid ! Msg`              | `pid $! msg`                             |\n| `receive Msg -\u003e ... end` | `match%lwt receive self with msg -\u003e ...` |\n\n\n### gen_server\n\nErlang's `gen_server` module is also mimicked.  A callback module should have the\nfollowing type:\n```ocaml\nmodule type Type =\nsig\n  type msg\n  type state\n  type init_data\n  type stop_reason\n  val init : init_data -\u003e msg pid -\u003e (state, stop_reason) init_result\n  val handle : msg -\u003e state -\u003e (state, stop_reason) result\n  val terminate : state -\u003e stop_reason -\u003e unit Lwt.t\nend\n```\nThen `gen_server` instance can be created:\n```ocaml\nmodule FooServer = Gen_server.Make(Callbacks)\n...\nFooServer.start init_data\n```\n\n### Erlang terms\n\nThere are erlang term external format encoding/decoding functions in the\n`Erlang` module.  These functions get type description of the encoded/decoded\nvalue:\n```ocaml\nErlType.(from_term (list atom) nodes)\n```\nThey are based on this article:\nhttp://okmij.org/ftp/ML/first-class-modules/#generics\n\n\n### Erlang distribution\n\nJamler was able to connect to epmd and other erlang nodes and exchange\nmessages.  Currently connecting from another erlang node fails with\n```\nConnection attempt to node jamler@localhost aborted since it cannot handle [\"UTF8_ATOMS\", \"NEW_FUN_TAGS\"].\n```\n\n## Compiling and running\n\nUse opam to install dependencies:\n\n```\nopam install yojson lwt lwt_log lwt_ppx lwt_ssl cryptokit pgocaml ppxlib\n```\n\nAlso install development libraries for expat and GNU Libidn.  E.g. in Debian:\n```\napt-get install libidn11-dev libexpat1-dev\n```\n\nThen run `make`.  Create a config based on `jamler.cfg.example` and start it with\n```\n_build/default/src/main.exe -c jamler.cfg\n```\n\nIt requires PostgreSQL to run, use\n[schema](https://github.com/processone/ejabberd/blob/v2.1.8/src/odbc/pg.sql)\nfrom ejabberd.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprocessone%2Fjamler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprocessone%2Fjamler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprocessone%2Fjamler/lists"}