{"id":18472696,"url":"https://github.com/gregros/doddle","last_synced_at":"2025-10-12T17:48:03.681Z","repository":{"id":193950833,"uuid":"689800153","full_name":"GregRos/doddle","owner":"GregRos","description":"Tiny yet feature-packed (async) iteration toolkit.","archived":false,"fork":false,"pushed_at":"2025-08-08T22:02:00.000Z","size":44183,"stargazers_count":8,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-10-01T22:11:15.336Z","etag":null,"topics":["functional-programming","iteration","javascript","lazy","library","linq","package","typescript","utility"],"latest_commit_sha":null,"homepage":"https://gregros.github.io/doddle/","language":"TypeScript","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/GregRos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-09-10T23:38:36.000Z","updated_at":"2025-08-17T15:48:58.000Z","dependencies_parsed_at":"2024-06-07T21:27:21.809Z","dependency_job_id":"9e196c93-3147-4917-abd7-b112be9a94db","html_url":"https://github.com/GregRos/doddle","commit_stats":null,"previous_names":["gregros/seqs","gregros/lazies","gregros/doddle"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/GregRos/doddle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GregRos%2Fdoddle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GregRos%2Fdoddle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GregRos%2Fdoddle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GregRos%2Fdoddle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GregRos","download_url":"https://codeload.github.com/GregRos/doddle/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GregRos%2Fdoddle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279012194,"owners_count":26085079,"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-12T02:00:06.719Z","response_time":53,"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":["functional-programming","iteration","javascript","lazy","library","linq","package","typescript","utility"],"created_at":"2024-11-06T10:21:51.319Z","updated_at":"2025-10-12T17:48:03.676Z","avatar_url":"https://github.com/GregRos.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Doddle\n\n[![Doddle workflow status](https://img.shields.io/github/actions/workflow/status/GregRos/doddle/push.yaml?style=for-the-badge)](https://github.com/GregRos/doddle/actions/workflows/push.yaml)\n[![Doddle package version](https://img.shields.io/npm/v/doddle?style=for-the-badge)](https://www.npmjs.com/package/doddle)\n[![Codacy coverage](https://img.shields.io/codacy/coverage/7650988ddf4741639fe6140bc28ff650?style=for-the-badge)](https://app.codacy.com/gh/GregRos/doddle/coverage)\n[![Doddle minified size(gzip)](https://img.shields.io/bundlejs/size/doddle?exports=seq,doddle\u0026style=for-the-badge\u0026label=gzip)](https://bundlejs.com/?q=doddle\u0026treeshake=%5B%7Bseq%2Cdoddle%7D%5D)\n\n[**Reference**](https://gregros.github.io/doddle/)\n\nDoddle is a tiny yet feature-packed (async) iteration toolkit, designed to make working with iterables as simple as possible.\n\nHere are some of its features:\n\n- 🪞 One consistent API shared between sync and async iterables.\n\n- 🤏 Tiny bundle size, without compromising user experience.\n\n- 🔥 Packed with operators from the best APIs in software.\n\n- 🛡️ Strongly typed and extensively validated.\n\nIt’s inspired by popular libraries like LINQ, lodash, and rxjs.\n\nGet it now:\n\n```bash\n# yarn\nyarn add doddle\n\n# npm\nnpm install doddle\n```\n\n## How operators work\nDoddle offers its functionality through *operators*. These operators transform or reduce iterables in various ways. There are lots of them, but they all share a set of common principles.\n\nOperators are methods defined on [wrapper objects](#how-wrappers-work) as instance methods, making them easy to find and invoke. There are two of these wrappers:\n\n- **[Seq](https://gregros.github.io/doddle/classes/Seq.html)**, which is used for sync iterables.\n- **[ASeq](https://gregros.github.io/doddle/classes/ASeq.html)**, which is used for async iterables.\n\nThey have exactly the same members, except that **ASeq** accepts functions (like projections or predicates) that return promises.\n\nTo see a complete list of operators, check out the linked API reference documentation.\n### All operators are lazy\nOperators never do anything directly. Instead, they return objects that must be evaluated. This gives you a lot of control over when side-effects happen.\n\nFor example, operators that return Iterables, such as `map`, must be iterated. The function gets called on an element right before iteration reaches it!\n\n```ts\nimport { seq } from \"doddle\"\nconst result = seq([1, 2, 3]).map(x =\u003e {\n    console.log(\"I'm a side-effect!\")\n    return x\n})\n// Nothing happens until we iterate over it:\nfor (const x of result) {\n    // Repeatedly prints 'I'm a side-effect.'\n}\n```\n\nOperators that don't return Iterables instead return a lazy primitive called a **Doddle**. You need to call the Doddle’s `pull` method to run to computation and get the result:\n\n```ts\nimport { seq } from \"doddle\"\nconst minimum = seq([3, 2, 1]).each(() =\u003e {\n    console.log(\"I'm a side-effect!\")\n}).minBy(x =\u003e x)\n\n// Nothing happens until we pull:\nconsole.log(\n    `The minimum value was ${minimum.pull()}`\n)\n```\n\nWe’ll talk more about Doddles later.\n### All operators are debuggable\nHave you ever stared at a stack trace from an async library and couldn’t understand anything?\n\nDoddle isn’t like that. It produces legible stack traces with one entry per operator, even when minified:\n\n```txt\nError    \n    at async ASeq.each (src\\seqs\\aseq.class.ts:380:21)\n    at async ASeq.concatMap (src\\seqs\\aseq.class.ts:307:30)\n    at async ASeq.filter (src\\seqs\\aseq.class.ts:432:30)\n    at async ASeq.each (src\\seqs\\aseq.class.ts:378:30)\n```\n\nNot only that – each operator also validates its inputs and throw descriptive errors that explain three critical things:\n\n- Which operator was involved\n- What went wrong\n- Where it happened\n\nHere’s how one looks like:\n\n```txt\nargument 'projection' of operator 'ASeq.map' must be a function but got \"hello world\"\n```\n\n## How wrappers work\nYou create wrappers using the functions:\n\n- **[seq](https://gregros.github.io/doddle/functions/seq.html)** for creating the **Seq** wrapper.\n- [**aseq**](https://gregros.github.io/doddle/functions/aseq.html) for the **ASeq** wrapper.\n\nEach function is a bit different in what in accepts, since the **seq** function only works with sync inputs. But neither just accepts iterables — they accept generator functions, array-like collections, and other stuff.\n\n**However, neither accepts strings.** This because JavaScript will eagerly convert objects and other values to strings when you least expect it. If these strings are then treated as collections, you end up with lots of hard to track bugs.\n\nMeanwhile, parsing strings using a library like `doddle` doesn’t really make much sense.\n\n```ts\n// ‼️ DoddleError: Strings not allowed\nseq(\"this will error\")\n// TypeScript: Type `string` is not assignable to type ...\n```\n\n### seq\nLet’s take a look at some of the things **seq** accepts. We’ll start with an Iterable, like an array:\n\n```ts\nseq([1, 2, 3])\n```\n\nTry a generator function. This works perfectly:\n\n```ts\nseq(function* () {\n    yield 1\n    yield 2\n})\n```\n\nBut generator functions are just functions that return iterables. So you can pass one of those instead:\n\n```ts\nseq(() =\u003e [1, 2, 3])\n```\n\nThe function will be called every time the **Seq** is iterated over\n\nYou can pass it a **Doddle** that returns an Iterable too:\n\n```ts\nconst doddle1 = doddle(() =\u003e 1)\nconst doddle123 = doddle(() =\u003e [1, 2, 3])\nseq(doddle(() =\u003e [1, 2, 3]))\n```\n\nYou can also pass it an array-like object, which works with `NodeList` and similar:\n\n```ts\nconst s3 = seq({\n    0: 1,\n    1: 1,\n    2: 3,\n    length: 3\n}) // {1, 2, 3}\n```\n\n### aseq\nThis function accepts everything that **seq** does, as well as async variations on those things.\n\nThat means async generator functions:\n\n```ts\naseq(async function* () {\n    yield 1\n    yield 2\n})\n```\n\nAsync iterables:\n\n```ts\naseq(aseq([1]))\n```\n\nAn async function that returns an array:\n\n```ts\naseq(async () =\u003e [1, 2, 3])\n```\n\nOr even an async function that returns an async Iterable:\n\n```ts\naseq(async () =\u003e aseq([1, 2, 3]))\n```\n\nYou can also insert **Doddles** all over the place. **aseq** will flatten all of them.\n\n## The Doddle\nThe **[Doddle](https://gregros.github.io/doddle/classes/Doddle.html)** is the library’s flagship lazy primitive. It’s simple, flexible, and really expressive. Its API is heavily inspired by Promises.\n\nLazy primitives are common in most programing languages. They represent values that are only produced when they are needed, as well as computations that only happen once.\n\nYou can get a **Doddle** to produce a value by calling its `pull` method:\n\n```ts\nconst aDoddle = seq([1, 2, 3]).first()\naDoddle.pull()\n```\n\nYou can create one using the **[doddle](https://gregros.github.io/doddle/functions/doddle.html)** function, which accepts a callback that will be invoked the first time the `pull` method is called.\n\n```ts\ndoddle(() =\u003e {\n    // Expensive computation\n    return 10 ** 5 / 2 \n})\n```\n\nThe same **Doddle** works for both sync and async computations. An async **Doddle** is one that yields a `Promise`. This variation is lovingly nicknamed `DoddleAsync`:\n\n```ts\ntype DoddleAsync\u003cT\u003e = Doddle\u003cPromise\u003cT\u003e\u003e\n```\n\n**Doddles** chain and flatten both with themselves and with Promises. Here is an example of chaining through several levels of these types:\n\n```ts\nawait doddle(async () =\u003e {\n    return doddle(() =\u003e 1)\n}).pull() // 1\n\nawait doddle(() =\u003e {\n    return doddle(async () =\u003e 100)\n}).pull() // 100\n```\n\n### Operators\n**Doddles** support several really useful operators of their own. For example, [map](https://gregros.github.io/doddle/classes/Doddle.html#map) lets you transform the result of a **Doddle** without actually pulling it. It works similarly to `Promise.then`.\n\n```ts\ndoddle(() =\u003e 1).map(x =\u003e x + 1).pull() // 2\n```\n\nWhen the input **Doddle** is async, the projection receives the *awaited value* of the **Doddle** rather than the promise itself.\n\n```ts\nawait doddle(async () =\u003e 1).map(x =\u003e x + 1).pull() // 2\n```\n\nThis makes it easy to chain `map` operators even when the input is async:\n\n```ts\nawait doddle(async () =\u003e 1)\n    .map(x =\u003e x + 1)\n    .map(x =\u003e `${x}`)\n    .pull() // \"2\"\n```\n\nCheck out the reference to see all the operators a **[Doddle](https://gregros.github.io/doddle/classes/Doddle.html)** supports.\n\n## More about ASeq\nThe **ASeq** wrapper is a very powerful tool for working with async iterables, but it’s one designed for ease of use and not robust stream processing. Let’s look at some of its features.\n\n### Full promise support\nAny function that is used as an argument for an **ASeq** operator can return a Promise. That includes something like `map`:\n\n```ts\naseq([1, 2, 3]).map(async x =\u003e x + 1) // (2, 3, 4)\n```\n\nAs well as `filter`, `some`, and everything else.\n\n```ts\naseq([1, 2]).filter(async x =\u003e x \u003e 1) // (2)\naseq([1, 2]).some(async x =\u003e !!x).pull() // true\n```\n\nWhen **ASeq** encounters a Promise like this, it will await it before continuing to iterate over the input. This happens even if the return value of the function isn’t used, like with the `each` operator:\n\n```ts\nimport { setTimeout } from \"timers/promises\"\n\naseq([1, 2, 3]).each(async () =\u003e {\n    await setTimeout(100)\n})\n```\n\nThis means that **ASeq** is not good at I/O heavy stream processing, since it will just end up awaiting every operation. You’re better off sticking with rxjs for that kind of thing, at least for now.\n\n### Avoiding double async code\nYou’ll often find yourself using `aseq` inside an async function, after awaiting something.\n\nHere is an example:\n\n```ts\nasync function example() {\n    const strings = await getStrings()\n    return aseq(strings).map(x =\u003e x.toUpperCase())\n}\n```\n\nBut this code returns a `Promise\u003cASeq\u003cstring\u003e\u003e`, which is really annoying to work with.\n\nThere is a better way, though! You can just put the `await` *inside* the definition of the sequence, like this:\n\n```ts\nfunction example() {\n    return aseq(async () =\u003e {\n        const strings = await getStrings()\n        return aseq(strings).map(x =\u003e x.toUpperCase())\n    })\n}\n```\n\nThe `aseq` function will flatten the entire thing, giving you a simple `ASeq\u003cstring\u003e`. The async function will only be executed if and when the Iterable is actually used.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgregros%2Fdoddle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgregros%2Fdoddle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgregros%2Fdoddle/lists"}