{"id":9243905,"url":"https://github.com/purescript-codegen/purescript-js-object","last_synced_at":"2025-08-17T08:33:39.042Z","repository":{"id":43344264,"uuid":"503128679","full_name":"purescript-codegen/purescript-js-object","owner":"purescript-codegen","description":"Simple helpers to build FFI for js object methods and properies.","archived":false,"fork":false,"pushed_at":"2023-05-14T12:46:57.000Z","size":38,"stargazers_count":2,"open_issues_count":7,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-08-25T22:45:42.243Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/purescript-codegen.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":"2022-06-13T22:05:54.000Z","updated_at":"2022-07-19T15:21:46.000Z","dependencies_parsed_at":"2022-07-08T02:47:39.794Z","dependency_job_id":null,"html_url":"https://github.com/purescript-codegen/purescript-js-object","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/purescript-codegen%2Fpurescript-js-object","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purescript-codegen%2Fpurescript-js-object/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purescript-codegen%2Fpurescript-js-object/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purescript-codegen%2Fpurescript-js-object/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/purescript-codegen","download_url":"https://codeload.github.com/purescript-codegen/purescript-js-object/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230108746,"owners_count":18174540,"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":"2024-05-08T00:11:53.269Z","updated_at":"2024-12-17T11:30:33.728Z","avatar_url":"https://github.com/purescript-codegen.png","language":"PureScript","funding_links":[],"categories":["FFI"],"sub_categories":["Deku"],"readme":"# purescript-js-object\n\nAccess js object methods and properties without writing JS bindings... ...or just generate mutable JS object FFI (without any codegen) from the type.\n\n\n## The Problem\n\nIn PureScript we usually write FFI to object methods by implementing dedicated functions on both sides. It seems that we can provide set of generic helpers which without sacrificing the performance which are able to bind to properties and methods of a JS object (by using \"uncurried\" approach similar to `Effect.Uncurried` from `purescript-effect` under the hood).\n\n## Usage\n\nLet's imagine that we have a simple counter prototype defined on the JS side and we expose an \"effectful\" function which creates an instance for us (we are not able to use `new` directly from PS side):\n\n```javascript\nexports.counter = (function () {\n  let Counter = function () {\n    this.value = 0;\n  };\n  Counter.prototype.increase = function () {\n    this.value++;\n  };\n  Counter.prototype.decrease = function () {\n    this.value--;\n  };\n  // I'm working to cover constructors as well soon.\n  // At the moment we need to expose a function ourselves.\n  return function () {\n    return new Counter();\n  };\n})();\n```\n\nNow we should be able to bind to this interface using generic helpers provided by this library:\n\n```purescript\nimport Prelude\n\nimport Data.Newtype (class Newtype)\nimport Effect (Effect)\nimport Effect.Aff (launchAff_)\nimport Effect.Class (liftEffect)\nimport JS.Object (EffectMth0, EffectMth1, EffectProp, JSObject, runEffectMth0, runEffectMth1, runEffectProp)\nimport JS.Object.Generic (mkFFI, mkNewtypedFFI)\nimport Test.Spec (describe, it)\nimport Test.Spec.Assertions (shouldEqual)\nimport Test.Spec.Reporter (consoleReporter)\nimport Test.Spec.Runner (runSpec)\nimport Type.Prelude (Proxy(..))\nimport Type.Row (type (+))\n\ntype Counter = JSObject (increase :: EffectMth0 Unit, decrease :: EffectMth0 Unit, value :: EffectProp Int)\n\nforeign import counter :: Effect Counter\n\n\n-- You can generate this FFI record during the compilation time.\n-- There is no runtime footprint over the manual binding.\n-- Type signature is derived automatically but because we don't\n-- use newtype... yet it would be fully expanded by default.\n_Counter ::\n  { increase :: Counter -\u003e Effect Unit\n  , decrease :: Counter -\u003e Effect Unit\n  , value :: Effect Int\n  }\n_Counter = mkFFI (Proxy :: Proxy Counter)\n\n\n-- If you want you can use newtypes as well.\n-- Here is a binding for hypothetical Person `JSObject`.\nnewtype Person = Person\n  ( JSObject\n      ( firstName :: EffectProp String\n      , setFirstName :: EffectMth1 String Unit\n      , lastName :: EffectProp String\n      , setLastName :: EffectMth1 String Unit\n      )\n  )\n\nderive instance Newtype Person _\n\n_Person ::\n  { firstName :: Person -\u003e Effect String\n  , lastName :: Person -\u003e Effect String\n  , setFirstName :: Person -\u003e String -\u003e Effect Unit\n  , setLastName :: Person -\u003e String -\u003e Effect Unit\n  }\n_Person = mkNewtypedFFI (Proxy :: Proxy Person)\n\n\n-- You can also use lower level functions to construct bindings yourself.\nincrease :: Counter -\u003e Effect Unit\nincrease = runEffectMth0 (Proxy :: Proxy \"increase\")\n\ndecrease :: Counter -\u003e Effect Unit\ndecrease = runEffectMth0 (Proxy :: Proxy \"increase\")\n\nvalue :: Counter -\u003e Effect Int\nvalue = runEffectProp (Proxy :: Proxy \"value\")\n\nmain :: Effect Unit\nmain = launchAff_ $ runSpec [ consoleReporter ] do\n  describe \"JS.Object\" do\n    it \"property access\" do\n      c \u003c- liftEffect counter\n      v \u003c- liftEffect $ value c\n      v `shouldEqual` 0\n    it \"method call\" do\n      v \u003c- liftEffect $ do\n        c \u003c- counter\n        increase c\n        increase c\n        v \u003c- value c\n        pure v\n      v `shouldEqual` 2\n\n```\n\nThere are two nice properties of this generic method of binding to JS object:\n\n  * whenever we feed the \"method name proxy\" to the one of `runEffectMth*` helpers there is no additional overhead - we get back a function which is not dependent on any type class dict and is only passing the rest of the arguments to the uncurried object method by using standard `runEffectFn*` under the hood.\n\n  * we can use the same binding functions to different objects as long as they share a particular method signature / interface. We can think about the row as the TS interface and about the particular object as an instance which implements it. The above example could be written as:\n\n  ```purescript\n  type IncreaseInterface r = ( increase :: EffectMth0 Unit | r)\n\n  type DecreaseInterface r = (decrease :: EffectMth0 Unit | r)\n\n  type ValueInterface r = (value :: EffectProp Int | r)\n\n  increase :: forall r. JSObject (IncreaseInterface r) -\u003e Effect Unit\n  increase = runEffectMth0 (Proxy :: Proxy \"increase\")\n\n  decrease :: forall r. JSObject (DecreaseInterface r) -\u003e Effect Unit\n  decrease = runEffectMth0 (Proxy :: Proxy \"decrease\")\n\n  value :: forall r. JSObject (ValueInterface r) -\u003e Effect Int\n  value = runEffectProp (Proxy :: Proxy \"value\")\n\n  type Counter = JSObject (IncreaseInterface + DecreaseInterface + ValueInterface + ())\n\n  ```\n\n\n## Testing\n\n```shell\n$ spago --config devel.dhall test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpurescript-codegen%2Fpurescript-js-object","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpurescript-codegen%2Fpurescript-js-object","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpurescript-codegen%2Fpurescript-js-object/lists"}