{"_id":"@andrewmacmurray/elm-concurrent-task","_rev":"4-a4d0cd6fbbec6f3115b81b59b512d6d2","name":"@andrewmacmurray/elm-concurrent-task","dist-tags":{"latest":"2.0.0"},"versions":{"1.0.0":{"name":"@andrewmacmurray/elm-concurrent-task","version":"1.0.0","author":{"name":"Andrew MacMurray"},"license":"MIT","_id":"@andrewmacmurray/elm-concurrent-task@1.0.0","maintainers":[{"name":"andrewmacmurray","email":"a.macmurray@icloud.com"}],"dist":{"shasum":"170bdfe3c88304e63d0965a53b4826816a35f384","tarball":"https://registry.npmjs.org/@andrewmacmurray/elm-concurrent-task/-/elm-concurrent-task-1.0.0.tgz","fileCount":18,"integrity":"sha512-KnrJY4hMS9RqBC0HR9ytVN6w1zvAs5Mgg+UMzJBNs01DdH1LhAs4wIPoECzusoz6e64IdZLBJ3575o39VrRaaw==","signatures":[{"sig":"MEUCIGMxKi3pBp3CYsuSu1SpbaLAyN6uP0pkdBUtPLBupo6hAiEAz0KoP7yeO7cncS4EhwDJ7jLrtcPrVPv05IO7APoYhLA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":35419},"main":"lib/index.js","types":"lib/index.d.ts","gitHead":"4e8fe713ad934f56f0062b5ffb2bf9c362043e8f","scripts":{"docs":"elm-doc-preview","test":"elm-test","review":"elm-review --fix","docs:verify":"elm make --docs docs.json","review:watch":"elm-review --watch","runner:compile":"tsc"},"_npmUser":{"name":"andrewmacmurray","email":"a.macmurray@icloud.com"},"_npmVersion":"9.5.1","description":"Run a tree of Tasks concurrently, call JS functions as Tasks (Task Ports)","directories":{},"_nodeVersion":"18.16.1","_hasShrinkwrap":false,"devDependencies":{"elm-test":"^0.19.1-revision12","elm-review":"^2.10.2","typescript":"^5.2.2","elm-doc-preview":"^1.0.7"},"_npmOperationalInternal":{"tmp":"tmp/elm-concurrent-task_1.0.0_1694627847890_0.8947314638383401","host":"s3://npm-registry-packages"}},"1.1.0":{"name":"@andrewmacmurray/elm-concurrent-task","version":"1.1.0","author":{"name":"Andrew MacMurray"},"license":"MIT","_id":"@andrewmacmurray/elm-concurrent-task@1.1.0","maintainers":[{"name":"andrewmacmurray","email":"a.macmurray@icloud.com"}],"dist":{"shasum":"ccdfdcd7b4400e8de342fcff09cf188800df3da7","tarball":"https://registry.npmjs.org/@andrewmacmurray/elm-concurrent-task/-/elm-concurrent-task-1.1.0.tgz","fileCount":35,"integrity":"sha512-xm6TdEzV65S4D5ynQSXZqR/zIc2FwIxcVSXNIq5rhloxFtELRvImEj9alACfYh1qe6AcF//PyzHdHnQZVUWZaQ==","signatures":[{"sig":"MEUCIQCb+n4sHpQ1tehmio4raWNI4mBui3E0/vAsIC1ktDoffQIgdy9MVPblBN6ywRv0hNSj62x2JVpnOmu6zVzTz3Ls+yg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":68458},"main":"lib/cjs/index.js","types":"lib/mjs/index.d.ts","module":"lib/mjs/index.js","exports":{".":{"types":"./lib/mjs/index.d.ts","import":"./lib/mjs/index.js","require":"./lib/cjs/index.js"}},"gitHead":"2b7f7c30a7163cc6b2ad3fdaf94445c55698e3e1","scripts":{"docs":"elm-doc-preview","test":"elm-test","review":"elm-review --fix","review:ci":"elm-review","docs:verify":"elm make --docs docs.json","review:watch":"elm-review --watch","check-versions":"node ./scripts/versions.js","runner:compile":"rm -rf lib/* && tsc -p tsconfig-mjs.json && tsc -p tsconfig-cjs.json && ./scripts/package-fixup.sh"},"_npmUser":{"name":"andrewmacmurray","email":"a.macmurray@icloud.com"},"_npmVersion":"9.6.7","description":"Run a tree of Tasks concurrently, call JS functions as Tasks (Task Ports)","directories":{},"_nodeVersion":"18.17.0","_hasShrinkwrap":false,"devDependencies":{"elm":"^0.19.1-6","elm-test":"^0.19.1-revision12","elm-review":"^2.10.2","typescript":"^5.2.2","elm-doc-preview":"^1.0.7"},"_npmOperationalInternal":{"tmp":"tmp/elm-concurrent-task_1.1.0_1702818569647_0.20864590265113403","host":"s3://npm-registry-packages"}},"1.1.1":{"name":"@andrewmacmurray/elm-concurrent-task","version":"1.1.1","author":{"name":"Andrew MacMurray"},"license":"MIT","_id":"@andrewmacmurray/elm-concurrent-task@1.1.1","maintainers":[{"name":"andrewmacmurray","email":"a.macmurray@icloud.com"}],"dist":{"shasum":"4e1a35341cc43c8988addb73bf078bc7ceeeb1e0","tarball":"https://registry.npmjs.org/@andrewmacmurray/elm-concurrent-task/-/elm-concurrent-task-1.1.1.tgz","fileCount":35,"integrity":"sha512-LCYraGNLLi/Qf7UYbENUyfwovXgIp+D4lYRAMuwNofNylCKhhFFSmPS4IYmKVJ/gKv+wgP56YPtJNq4yWTq9bA==","signatures":[{"sig":"MEQCIAjz+3Ockca8smJF0jqUcr3VoVKJ9Pb7hqQLXVZ7KculAiAR4Znm5YBzMoHOMcQpbipCtOEQP+cIQdb9H3DhMd9itw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":68854},"main":"lib/cjs/index.js","types":"lib/mjs/index.d.ts","module":"lib/mjs/index.js","exports":{".":{"types":"./lib/mjs/index.d.ts","import":"./lib/mjs/index.js","require":"./lib/cjs/index.js"}},"gitHead":"008316e4cc0a5d3b6ef134be9f521f435a77b8c9","scripts":{"docs":"elm-doc-preview","test":"elm-test","review":"elm-review --fix","review:ci":"elm-review","docs:verify":"elm make --docs docs.json","review:watch":"elm-review --watch","check-versions":"node ./scripts/versions.js","runner:compile":"rm -rf lib/* && tsc -p tsconfig-mjs.json && tsc -p tsconfig-cjs.json && ./scripts/package-fixup.sh"},"_npmUser":{"name":"andrewmacmurray","email":"a.macmurray@icloud.com"},"_npmVersion":"10.2.4","description":"Run a tree of Tasks concurrently, call JS functions as Tasks (Task Ports)","directories":{},"_nodeVersion":"20.11.0","_hasShrinkwrap":false,"devDependencies":{"elm":"^0.19.1-6","elm-test":"^0.19.1-revision12","elm-review":"^2.10.2","typescript":"^5.2.2","elm-doc-preview":"^1.0.7"},"_npmOperationalInternal":{"tmp":"tmp/elm-concurrent-task_1.1.1_1710492212024_0.3422430872842883","host":"s3://npm-registry-packages"}},"2.0.0":{"name":"@andrewmacmurray/elm-concurrent-task","version":"2.0.0","description":"Run a tree of Tasks concurrently, call JS functions as Tasks (Task Ports)","main":"lib/cjs/index.js","module":"lib/mjs/index.js","types":"lib/mjs/index.d.ts","exports":{".":{"types":"./lib/mjs/index.d.ts","import":"./lib/mjs/index.js","require":"./lib/cjs/index.js"}},"scripts":{"test":"elm-test","docs":"elm-doc-preview","docs:verify":"elm make --docs docs.json","review":"elm-review --fix","review:ci":"elm-review","review:watch":"elm-review --watch","runner:sub-package-json":"node --experimental-strip-types ./scripts/package-fixup.mts","runner:compile":"rm -rf lib/* && tsc -p tsconfig-mjs.json && tsc -p tsconfig-cjs.json && npm run runner:sub-package-json","check-versions":"node --experimental-strip-types ./scripts/versions.mts"},"author":{"name":"Andrew MacMurray"},"license":"MIT","devDependencies":{"elm":"^0.19.1-6","elm-doc-preview":"^6.0.1","elm-review":"^2.13.2","elm-test":"^0.19.1-revision12","typescript":"^5.2.2"},"_id":"@andrewmacmurray/elm-concurrent-task@2.0.0","gitHead":"ae4668f2f5643f48cedb98bfd4b5bcf8031ac45b","_nodeVersion":"22.18.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-8L9/6ca6HLaOk98vPQYtNnCnTRzJYdqogke+CAS07NahPeSJ6vfk4UMHalC1mo5WVh3Oyi30y4aZs9DyBqQd9w==","shasum":"c97ee336ac613e824c6b9e5c263ed8d4983730e1","tarball":"https://registry.npmjs.org/@andrewmacmurray/elm-concurrent-task/-/elm-concurrent-task-2.0.0.tgz","fileCount":35,"unpackedSize":60956,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD9hztezYm/s+j6fPPul/BTaUiB2EKRecqAAHv04q669QIgVm+MmGBGos5sq7jzlKYj6RsyN/f71Q8vX0FJj4inDo4="}]},"_npmUser":{"name":"andrewmacmurray","email":"a.macmurray@icloud.com"},"directories":{},"maintainers":[{"name":"andrewmacmurray","email":"a.macmurray@icloud.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/elm-concurrent-task_2.0.0_1767368132813_0.9917951297524976"},"_hasShrinkwrap":false}},"time":{"created":"2023-09-13T17:57:27.775Z","modified":"2026-01-02T15:35:33.148Z","1.0.0":"2023-09-13T17:57:28.157Z","1.1.0":"2023-12-17T13:09:29.860Z","1.1.1":"2024-03-15T08:43:32.217Z","2.0.0":"2026-01-02T15:35:32.951Z"},"author":{"name":"Andrew MacMurray"},"license":"MIT","description":"Run a tree of Tasks concurrently, call JS functions as Tasks (Task Ports)","maintainers":[{"name":"andrewmacmurray","email":"a.macmurray@icloud.com"}],"readme":"# Elm Concurrent Task\n\n[![CI](https://github.com/andrewMacmurray/elm-concurrent-task/actions/workflows/ci.yml/badge.svg)](https://github.com/andrewMacmurray/elm-concurrent-task/actions/workflows/ci.yml)\n[![npm version](https://badge.fury.io/js/@andrewmacmurray%2Felm-concurrent-task.svg)](https://badge.fury.io/js/@andrewmacmurray%2Felm-concurrent-task)\n\n## What?\n\n- An alternative `Task` api - run a tree of tasks concurrently.\n- A hack free implementation of `Task Ports` - call JavaScript functions as tasks.\n- Run anywhere - works in the Browser or NodeJS.\n\nThis package is heavily inspired by [elm-pages](https://github.com/dillonkearns/elm-pages)' `BackendTask` and is intended to be a standalone implementation that can be dropped into any Elm app - big kudos to [Dillon](https://github.com/dillonkearns) for the idea.\n\nSee the [examples](https://github.com/andrewMacmurray/elm-concurrent-task/tree/main/examples) for more things you can do!\n\nView [the elm-package docs here](https://package.elm-lang.org/packages/andrewMacmurray/elm-concurrent-task/2.0.0/).\n\n## Why?\n\n### Structured Concurrency\n\n`Task.map2`, `Task.map3`+ In `elm/core` run each subtask in sequence.\n\nWhilst it's possible to run these subtasks concurrently as separate `Cmd`s, it can be a lot of wiring and boilerplate, including:\n\n- Batching task commands together.\n- Handling each task's success case.\n- Handling each task's error case.\n- Checking if all other tasks are completed every time an individual task finishes.\n\n[Elm Task Parallel](https://github.com/0ui/elm-task-parallel) handles this nicely but only at the top level (sub tasks cannot be parallelised).\n\nAnd what if you want to speed up a complex nested task like:\n\n```elm\nTask.map2 combine\n    (task1\n        |> Task.andThen task2\n        |> Task.andThen\n            (\\res ->\n                Task.map2 combine\n                    (task3 res)\n                    task4\n            )\n    )\n    (Task.map2 combine\n        task5\n        task6\n    )\n```\n\nThis is the elm equivalent of \"callback hell\".\n\nThis library helps you do this with a lot less boilerplate.\n\n### A Sequenceable JavaScript FFI\n\nSometimes you want to call JavaScript from elm in order. For example sequencing updates to localstorage:\n\n**NOTE**: See a [full working localstorage example here](https://github.com/andrewMacmurray/elm-concurrent-task/tree/main/examples/localstorage-fruit-trees).\n\n```elm\nimport ConcurrentTask exposing (ConcurrentTask)\nimport Json.Decode as Decode exposing (Decoder)\nimport Json.Encode as Encode\n\n\n\n-- Preferences\n\n\ntype alias Preferences =\n    { contrast : Int\n    , brightness : Int\n    }\n\n\nsetContrast : Int -> ConcurrentTask Error ()\nsetContrast contrast =\n    getItem \"preferences\" decodePreferences\n        |> ConcurrentTask.map (\\preferences -> { preferences | contrast = contrast })\n        |> ConcurrentTask.andThen (encodePreferences >> setItem \"preferences\")\n\n\nencodePreferences : Preferences -> Encode.Value\nencodePreferences p =\n    Encode.object\n        [ ( \"contrast\", Encode.int p.contrast )\n        , ( \"brightness\", Encode.int p.brightness )\n        ]\n\n\ndecodePreferences : Decoder Preferences\ndecodePreferences =\n    Decode.map2 Preferences\n        (Decode.field \"contrast\" Decode.int)\n        (Decode.field \"brightness\" Decode.int)\n\n\n\n-- Localstorage\n\n\ntype Error\n    = NoValue\n    | ReadBlocked\n    | DecodeError Decode.Error\n    | WriteError String\n\n\ngetItem : String -> Decoder a -> ConcurrentTask Error a\ngetItem key decoder =\n    ConcurrentTask.define\n        { function = \"localstorage:getItem\"\n        , expect = ConcurrentTask.expectString\n        , errors = ConcurrentTask.expectErrors decodeReadErrors\n        , args = Encode.object [ ( \"key\", Encode.string key ) ]\n        }\n        |> ConcurrentTask.map (Decode.decodeString decoder >> Result.mapError DecodeError)\n        |> ConcurrentTask.andThen ConcurrentTask.fromResult\n\n\nsetItem : String -> Encode.Value -> ConcurrentTask Error ()\nsetItem key value =\n    ConcurrentTask.define\n        { function = \"localstorage:setItem\"\n        , expect = ConcurrentTask.expectWhatever\n        , errors = ConcurrentTask.expectThrows WriteError\n        , args =\n            Encode.object\n                [ ( \"key\", Encode.string key )\n                , ( \"value\", Encode.string (Encode.encode 0 value) )\n                ]\n        }\n\n\ndecodeReadErrors : Decoder Error\ndecodeReadErrors =\n    Decode.string\n        |> Decode.andThen\n            (\\reason ->\n                case reason of\n                    \"NO_VALUE\" ->\n                        Decode.succeed NoValue\n\n                    \"READ_BLOCKED\" ->\n                        Decode.succeed ReadBlocked\n\n                    _ ->\n                        Decode.fail (\"Unrecognized Read Error: \" ++ reason)\n            )\n```\n\n## Hack Free you say?\n\nOther implementations of `Task Ports` rely on either:\n\n- `ServiceWorkers` - intercept certain http requests and call custom JavaScript from the service worker.\n- Monkeypatching `XMLHttpRequest` - Modify methods on the global `XMLHttpRequest` to intercept http requests and call custom JavaScript.\n\nBoth methods are not ideal (modifying global methods is pretty dodgy), and neither are portable to other environments like node (`ServiceWorker` and `XMLHttpRequest` are only native in the browser and require pollyfills).\n\n### It's just ports!\n\n`elm-concurrent-task` uses plain ports and a bit of wiring to create a nice Task api.\n\nThis makes it dependency free - so more portable (🤓) and less likely to break (😄).\n\n## Caveats\n\nBecause `elm-concurrent-task` uses a different type to `elm/core` `Task` it's unfortunately not compatible with `elm/core` `Task`s.\n\nHowever, there are a number of tasks built into the JavaScript runner and supporting modules that should cover a large amount of the existing functionality of `elm/core` `Task`s.\n\nCheck out the built-ins for more details:\n\n- [`Browser.Dom`](https://package.elm-lang.org/packages/andrewMacmurray/elm-concurrent-task/2.0.0/ConcurrentTask-Browser-Dom/)\n- [`Http`](https://package.elm-lang.org/packages/andrewMacmurray/elm-concurrent-task/2.0.0/ConcurrentTask-Http/)\n- [`Process`](https://package.elm-lang.org/packages/andrewMacmurray/elm-concurrent-task/2.0.0/ConcurrentTask-Process/)\n- [`Random`](https://package.elm-lang.org/packages/andrewMacmurray/elm-concurrent-task/2.0.0/ConcurrentTask-Random/)\n- [`Time`](https://package.elm-lang.org/packages/andrewMacmurray/elm-concurrent-task/2.0.0/ConcurrentTask-Time/)\n\n## How?\n\n### Getting Started\n\n### 1. Install Elm and JavaScript/TypeScript packages\n\nInstall the elm package with\n\n```\nelm install andrewMacmurray/elm-concurrent-task\n```\n\nInstall the JavaScript/TypeScript runner with\n\n```\nnpm install @andrewmacmurray/elm-concurrent-task\n```\n\n### 2. Add to your Elm app\n\nYour Elm program needs:\n\n- A single `ConcurrentTask.Pool` in your `Model` to keep track of each task attempt:\n\n  ```elm\n  type alias Model =\n      { tasks : ConcurrentTask.Pool Msg\n      }\n  ```\n\n- 2 `Msg`s to handle task updates:\n\n  ```elm\n  type Msg\n      = OnProgress ( ConcurrentTask.Pool Msg, Cmd Msg ) -- updates task progress\n      | OnComplete (ConcurrentTask.Response Error Success) -- called when a task completes\n  ```\n\n- 2 ports with the following signatures:\n\n  ```elm\n  port send : Decode.Value -> Cmd msg\n  port receive : (Decode.Value -> msg) -> Sub msg\n  ```\n\nHere's a simple complete program that fetches 3 resources concurrently:\n\n```elm\nport module Example exposing (main)\n\nimport ConcurrentTask exposing (ConcurrentTask)\nimport ConcurrentTask.Http as Http\nimport Json.Decode as Decode\n\n\ntype alias Model =\n    { tasks : ConcurrentTask.Pool Msg\n    }\n\n\ntype Msg\n    = OnProgress ( ConcurrentTask.Pool Msg, Cmd Msg )\n    | OnComplete (ConcurrentTask.Response Error Titles)\n\n\ntype alias Error =\n    Http.Error\n\n\n\n-- Get All Titles Task\n\n\ntype alias Titles =\n    { todo : String\n    , post : String\n    , album : String\n    }\n\n\ngetAllTitles : ConcurrentTask Error Titles\ngetAllTitles =\n    ConcurrentTask.succeed Titles\n        |> ConcurrentTask.andMap (getTitle \"/todos/1\")\n        |> ConcurrentTask.andMap (getTitle \"/posts/1\")\n        |> ConcurrentTask.andMap (getTitle \"/albums/1\")\n\n\ngetTitle : String -> ConcurrentTask Error String\ngetTitle path =\n    Http.get\n        { url = \"https://jsonplaceholder.typicode.com\" ++ path\n        , headers = []\n        , expect = Http.expectJson (Decode.field \"title\" Decode.string)\n        , timeout = Nothing\n        }\n\n\n\n-- Program\n\n\ninit : ( Model, Cmd Msg )\ninit =\n    let\n        ( tasks, cmd ) =\n            ConcurrentTask.attempt\n                { send = send\n                , pool = ConcurrentTask.pool\n                , onComplete = OnComplete\n                }\n                getAllTitles\n    in\n    ( { tasks = tasks }, cmd )\n\n\nupdate : Msg -> Model -> ( Model, Cmd Msg )\nupdate msg model =\n    case msg of\n        OnComplete response ->\n            let\n                _ =\n                    Debug.log \"response\" response\n            in\n            ( model, Cmd.none )\n\n        OnProgress ( tasks, cmd ) ->\n            ( { model | tasks = tasks }, cmd )\n\n\nsubscriptions : Model -> Sub Msg\nsubscriptions model =\n    ConcurrentTask.onProgress\n        { send = send\n        , receive = receive\n        , onProgress = OnProgress\n        }\n        model.tasks\n\n\nport send : Decode.Value -> Cmd msg\n\n\nport receive : (Decode.Value -> msg) -> Sub msg\n\n\nmain : Program {} Model Msg\nmain =\n    Platform.worker\n        { init = always init\n        , update = update\n        , subscriptions = subscriptions\n        }\n```\n\n### 3. Register the runner in your JavaScript/TypeScript app\n\nConnect the runner to your Elm app (the runner supports both `import` and `require` syntax):\n\n```ts\nimport * as ConcurrentTask from \"@andrewmacmurray/elm-concurrent-task\";\n\nconst app = Elm.Main.init({});\n\nConcurrentTask.register({\n  tasks: {},\n  ports: {\n    send: app.ports.send,\n    receive: app.ports.receive,\n  },\n});\n```\n\nThe value passed to `tasks` is an object of task names to functions (the functions can return plain synchronous values or promises)\n\ne.g. tasks for reading and writing to localStorage:\n\n```js\nconst tasks = {\n  \"localstorage:getItem\": (args) => localStorage.getItem(args.key),\n  \"localstorage:setItem\": (args) => localStorage.setItem(args.key, args.item),\n};\n```\n\n**NOTE**: for a more complete `localStorage` integration with proper error handling [check out the localstorage example](https://github.com/andrewMacmurray/elm-concurrent-task/blob/ba7c8af4b1afeff138ba839511d4411a0a40bbb1/examples/localstorage-fruit-trees/src/index.ts).\n\n## Re-using ports\n\nFor most cases a single `ConcurrentTask.Pool` is paired a `send` and `receive` port pair.\n\nIf you need multiple `Pool`s subscribed to a port pair at the same time or need to continually instantiate new `Pool`s (e.g. switching between pages), make sure each pool has a unique id with `ConcurrentTask.withPoolId`. Otherwise `Pool`s may receive results for other pools which can lead to some confusing states!\n\n## Develop Locally\n\nInstall Dependencies:\n\n```\nnpm install\n```\n\nRun the tests with:\n\n```\nnpm test\n```\n\nTo preview any changes, try some of the examples in the [examples folder](https://github.com/andrewMacmurray/elm-concurrent-task/tree/main/examples).\n\nView the docs locally with:\n\n```\nnpm run docs\n```\n\n## Publishing a new release\n\n1. Run `elm bump` to bump the elm version.\n2. Update the `version` in [package.json](https://github.com/andrewMacmurray/elm-concurrent-task/tree/main/package.json) to match the new elm version.\n3. Ensure all links in the README / docs are pointing to the correct new version.\n4. Commit and push the updates and ensure CI passes.\n5. Tag the current commit with the new version number and push e.g. `git tag -a 2.0.0 && git push origin 2.0.0`\n6. Publish the runner to npm with `npm run runner:compile && npm publish`\n7. Publish the package to elm packages with `elm publish`\n","readmeFilename":"README.md"}