{"id":13800287,"url":"https://github.com/paluh/purescript-boomboom","last_synced_at":"2026-02-13T03:17:16.635Z","repository":{"id":58242410,"uuid":"126237308","full_name":"paluh/purescript-boomboom","owner":"paluh","description":"Never hard code your urls again. Boomboom them all!","archived":false,"fork":false,"pushed_at":"2019-06-19T15:45:13.000Z","size":92,"stargazers_count":5,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-29T20:52:28.874Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PureScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/paluh.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":"2018-03-21T20:34:49.000Z","updated_at":"2019-10-06T14:35:51.000Z","dependencies_parsed_at":"2022-08-31T00:40:56.945Z","dependency_job_id":null,"html_url":"https://github.com/paluh/purescript-boomboom","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/paluh/purescript-boomboom","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paluh%2Fpurescript-boomboom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paluh%2Fpurescript-boomboom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paluh%2Fpurescript-boomboom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paluh%2Fpurescript-boomboom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paluh","download_url":"https://codeload.github.com/paluh/purescript-boomboom/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paluh%2Fpurescript-boomboom/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274648309,"owners_count":25324297,"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-09-11T02:00:13.660Z","response_time":74,"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":"2024-08-04T00:01:11.132Z","updated_at":"2026-02-13T03:17:16.607Z","avatar_url":"https://github.com/paluh.png","language":"PureScript","funding_links":[],"categories":["URL Routers"],"sub_categories":[],"readme":"# purescript-boomboom\n\n## Disclaimer\n\nNever hard code your urls again. ~~Boomboom~~ [routing-duplex](https://github.com/natefaubion/purescript-routing-duplex) them all!\n\nIt was quite a nice coding challange and experiment for me, but I feel that `routing-duplex` exposes much cleaner and user friendly API at this moment. Please check it first!\n\n## Description\n\nBidirectional routing library with principled plumbing which provides easy to use generic sugar for variants and records.\n\nStill β stage...\n\n## `BoomBoom`\n\nThe core type of this library is `BoomBoom.BoomBoom` which translates really to this simple record:\n\n```purescript\nnewtype BoomBoom tok a = BoomBoom { prs ∷ tok → Maybe { a ∷ a, tok ∷ tok }, ser ∷ a → tok }\n```\n\nSo our `BoomBoom tok a` is a simple parser from `tok` to `a` and also a total serializer function in opposite direction. Composability of this type requires usually that `tok` has a `Semigroup` instance. For details check `BoomBoom.BoomBoom` module docs.\n\n## Usage\n\n### Basic generic helpers\n\nPlease note that more user friendly API is described in the next section.\n\nThese are tests fragments (`test/BoomBoom/Generic.purs`) which use basic generic helpers:\n\n* `tok` in this case is an `Array String`\n\n* `int` is `BoomBoom (Array String) Int`\n\n* `record` is a helper which builds `BoomBoom` for a give record of `BoomBooms`\n\n* `R` is a record wrapper which provides `Eq` ;-)\n\nRecord `BoomBoom` generation and usage:\n\n```purescript\nlet\n  recordB = BoomBoom.Generic.record { x: int, y: int, z: int }\nTest.Unit.suite \"simple record boomboom\" $ do\n\n  test \"serializes correctly\" $ do\n    equal (\"1\":\"2\":\"3\":Nil) (serialize recordB { x: 1, y: 2, z: 3 })\n\n  test \"parses correctly\" $ do\n    equal (Just $ R { x: 1, y: 2, z: 3 }) (R \u003c$\u003e parse recordB (\"1\":\"2\":\"3\":Nil))\n```\n\nVariant `BoomBoom` generation and usage (with `variant` helper):\n\n```purescript\nlet\n  variantB = variant\n    { zero: BoomBoom $ pure unit\n    , one: int\n    -- | Here we are wrapping our record in `R`\n    -- | it is only required because we want\n    -- | to perform `eq`...\n    , two: BoomBoom $ R \u003c$\u003e unwrap \u003e- recordB\n    }\nTest.Unit.suite \"simple variant boomboom\" $ do\n  let\n    wrong = (\"wrong\":\"8\":Nil)\n    zi = (\"zero\":Nil)\n    oi = (\"one\":\"1\":Nil)\n    ti = (\"two\":\"2\":\"3\":\"4\":Nil)\n    zv = inj (SProxy ∷ SProxy \"zero\") unit\n    ov = inj (SProxy ∷ SProxy \"one\") 1\n    tv = inj (SProxy ∷ SProxy \"two\") (R {x: 2, y: 3, z: 4})\n  test \"serializes correctly\" $ do\n    equal zi (serialize variantB zv)\n    equal oi (serialize variantB ov)\n    equal ti (serialize variantB tv)\n  test \"parses correctly\" $ do\n    equal Nothing (parse variantB wrong)\n    equal (Just zv) (parse variantB zi)\n    equal (Just ov) (parse variantB oi)\n    equal (Just tv) (parse variantB ti)\n```\n\nOf course you can compose and mix these `BoomBooms` and build arbitrarly large routers/serializers:\n\n```purescript\nlet\n  nestedB = variant\n    { p1: variant\n        -- | Here again our R which is here just for `eq`\n        { sp1: ((xrap $ record { x: int, y: int, z: int }) ∷ BoomBoom _ R)\n        , sp2: int\n        }\n    , p2: int\n    }\n  i = (\"p1\":\"sp1\":\"1\":\"2\":\"3\":Nil)\n  iv = inj (SProxy ∷ SProxy \"p1\") (inj (SProxy ∷ SProxy \"sp1\") (R {x: 1, y: 2, z: 3}))\ntest \"serializes correctly\" $ do\n  equal i (serialize nestedB iv)\ntest \"parses correctly\" $ do\n  equal (Just iv) (parse nestedB i)\n```\n\n### Typelevel interpretation and helpers\n\nLet's assume that we have routes from previous example and want to produce this serialization `p1/sp1/1/2/3` result. To do this we have to build our variant by hand (as in previous example):\n\n```purescript\n  serialize $ inj (SProxy ∷ SProxy \"p1\") (inj (SProxy ∷ SProxy \"sp1\") {x: 1, y: 2, z: 3})\n```\n\nThis API is not really readable and easy to use as we have to nest all these `inj` functions with `SProxy` constructors.\n\nIn `BoomBoom.Generic.Interpret` you can find \"type level interpreters\" which are able to produce `BoomBooms` but also easy to use builders of values (variants/records) from \"records tree\".\nInstead of building `BoomBooms` directly you should define your tree using provided constructors:\n\n```purescript\nimport BoomBoom.Generic.Interpret (V, R, B)\nimport BoomBoom.Strings (int)\n\ndesc = V\n  { p1: V\n      { sp1: R { x: B int, y: B int, z: B int }\n      , sp2: B int\n      }\n  , p2: B int\n  }\n```\n\nNow you can produce your `BoomBoom` value but also a `builder`:\n\n```purescript\nimport BoomBoom.Generic.Interpret (interpret)\nimport Type.Prelude (SProxy(..))\n\n-- | Generate a builder\nbuilder = interpret (SProxy ∷ SProxy \"builder\") desc\n\n-- | Generate a BoomBoom\nboomboom = interpret (SProxy ∷ SProxy \"boomboom\") desc\n\n```\n\nThis `builder` is a record (for variants build up) or function (for records build up) which helps us build values ready for serialization. So for example this:\n\n```purescript\nv = builder.p1.sp1 {x: 1, y: 2, z: 3}\n```\n\nIs equivalent of this:\n\n```purescript\nv = inj (SProxy ∷ SProxy \"p1\") (inj (SProxy ∷ SProxy \"sp1\") {x: 1, y: 2, z: 3})\n```\n\nNow we can use this value and serialize it:\n\n```purescript\nserialize boomboom v\n```\n\nOutput (in case of `BoomBoom.Strings` serialization) value:\n\n```purescript\n(\"p1\":\"sp1\":\"1\":\"2\":\"3\":Nil)\n```\n\n### `Applicative` API\n\nAnd here is completely different approach which uses `apply` and `BoomBoom.diverge` (aka `(\u003e-)`):\n\n\n```purescript\npath :: BoomBoom String { x :: Int, y :: Int }\npath = BoomBoom $\n  { x: _, y: _ }\n    \u003c$\u003e _.x \u003e- int\n    \u003c* lit \"test\"\n    \u003c*\u003e _.y \u003e- int\n\nmain :: forall e. Eff (console :: CONSOLE | e) Unit\nmain = do\n  log $ unsafeStringify (parse path (\"8080\":\"test\":\"200\":Nil))\n\n  log (serialize path { x: 300, y: 800 })\n```\n\nOutput values:\n\n```shell\n{\"value0\":{\"x\":8080,\"y\":200}}\n\n-- | I've replaced here `Cons` with (:) to simplify reading\n(\"300\":\"test\":\"800\":Nil)\n```\n\n## TODO\n\nThere is ongoing work for \"API interpreters\" which would generate records clients and server helpers but also docs etc.\n\n## Credits\n\n* A lot of inspirations and crucial suggestions I received from @MonoidMusician. Thanks!\n\n* Initial design of diverging instances was inspired by this @sebastiaanvisser [answer](https://www.reddit.com/r/haskell/comments/38o0f7/a_mixture_of_applicative_and_divisible/#thing_t1_crwh6le).\n\n* Name loosely inspired by this @notcome [gist](https://gist.github.com/notcome/c9d4c750985230d7e346).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaluh%2Fpurescript-boomboom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpaluh%2Fpurescript-boomboom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaluh%2Fpurescript-boomboom/lists"}