{"id":20079278,"url":"https://github.com/jbreckmckye/trkl","last_synced_at":"2025-04-09T20:03:45.389Z","repository":{"id":40499383,"uuid":"48077582","full_name":"jbreckmckye/trkl","owner":"jbreckmckye","description":"Reactive microlibrary with observables and spreadsheet-style computed values in 383 bytes","archived":false,"fork":false,"pushed_at":"2023-04-16T22:07:37.000Z","size":323,"stargazers_count":187,"open_issues_count":1,"forks_count":4,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-09T20:03:39.008Z","etag":null,"topics":["javascript","knockout-computeds","microlib","observable","pubsub","reactive"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jbreckmckye.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,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-12-16T00:44:01.000Z","updated_at":"2024-12-04T18:15:07.000Z","dependencies_parsed_at":"2024-11-13T15:33:55.070Z","dependency_job_id":null,"html_url":"https://github.com/jbreckmckye/trkl","commit_stats":{"total_commits":121,"total_committers":3,"mean_commits":"40.333333333333336","dds":"0.016528925619834656","last_synced_commit":"b3290411b764a1534f84a54b634e8ac61a940aa9"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreckmckye%2Ftrkl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreckmckye%2Ftrkl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreckmckye%2Ftrkl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreckmckye%2Ftrkl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jbreckmckye","download_url":"https://codeload.github.com/jbreckmckye/trkl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248103864,"owners_count":21048245,"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":["javascript","knockout-computeds","microlib","observable","pubsub","reactive"],"created_at":"2024-11-13T15:21:14.303Z","updated_at":"2025-04-09T20:03:45.367Z","avatar_url":"https://github.com/jbreckmckye.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Logo](logo.png)\n\n# trkl\n\nReactive JavaScript programming in less than half a kilobyte.\n\nFor just a meagre **383 bytes** (minified and gzipped), you get\n\n- observables with a pub/sub interface\n- powerful Knockout.js-style computeds with proper \"magical\" dependency tracking\n- circular reference detection\n- TypeScript project support\n\nThe basic idea is to provide the most 'bang for buck' in terms of bytes down the wire versus expressiveness and utility.\n\nMy motto is: \"If you can find a smaller reactive programming microlibrary... keep it to yourself\"\n\n## Give me the gist\n\nData is held in **Observables**, which expose pub / sub capabilities:\n\n```javascript\nconst apples = trkl(2);\n\napples.subscribe(n =\u003e console.log(`There are ${n} apples`));\n\napples(7); // Prints \"There are 7 apples\"\n```\n\nCombine data from observables using **Computeds**, which automatically track their dependencies:\n\n```javascript\nconst bananas = trkl(3);\n\nconst fruit = trkl.computed(()=\u003e {\n    return apples() + bananas();\n});\n\nfruit.subscribe(n =\u003e console.log(`There are ${n} fruit in total`));\n\napples(6); // Prints \"There are 9 fruit in total\"\n```\n\nIn a nutshell: Trkl lets you create observable channels of data, and then combine them with functions to result in further observables.\n\n## Installation\n\nYou can either drop `trkl.min.js` straight into your project, or run\n\n```\nnpm install trkl --save\n```\n\nTrkl works in both CommonJS and browser environments.\n\n**Versions**\n\nIf you need AMD support, use v1.5.1\n\nv2+ is ES6 only; v1.x supports ES5\n\n### Importing\n\nNode / SystemJS: `const trkl = require('trkl');`\n\nES6 / TypeScript: `import * as trkl from 'trkl';`\n\n## TypeScript support\n\nTypes are defined in `index.d.ts`.\n\nIt's assumed that the types inside observables are immutable. If you need to initialise a type-less observable use `foo = trkl(undefined as any)`;\n\n## API\n\n### trkl()\nCreates an observable with optional supplied initial value.\n\n```javascript\nlet observable = trkl('foo');\n```    \n\n### observable()\n\nCall without arguments to get the value, call with an argument to set it.\n\n```javascript\nobservable();       // getter\nobservable('foo');  // setter\n```\n\n### trkl.computed(fn)\n\nCreates an observable that executes a function which calls other observables, and re-runs that function whenever those dependencies change.\n\nIf you've used Knockout computeds, you'll know exactly how these work. Here's an example:\n\n```javascript\nlet a = trkl(0);\nlet b = trkl(0);\n\nlet c = trkl.computed(()=\u003e {\n    return a() + b();\n});\n\nc.subscribe(newVal =\u003e {\n    print(`a + b = ${newVal}`);\n});\n\na(5); // Print \"a + b = 5\"\nb(3); // Print \"a + b = 8\"\n```\n\nYou don't have to provide anything to computed to notify if it of your dependencies. This differs from other libraries, where you have to remember to explicitly pass in all the observables your computation depends on.\n\nDependencies can even be dynamic!\n\n```javascript\nconst a = trkl('A');\nconst b = trkl('B');\nconst readA = trkl(true);\n\nconst reader = trkl.computed(()=\u003e {\n    return readA() ? a() : b();\n});\n\nprint(reader()); // 'A'\n\nreadA(false);\n\nprint(reader()); // 'B'\n```\n\n**What about circular references?**\n\nIf we have an observable *a* that informs an computed *b*, and then we have a new computed *c* that takes the value of *b* and inserts it into *a*, we get a triangular flow of information.\n\nLuckily, trkl will detect such instances and immediately throw an exception:\n\n```\nError: Circular computation\n```\n\n### trkl.from(observable =\u003e {...})\n\nCreate an observable, and pass it to your supplied function. That function may then set up event handlers to change the observable's state.\n\nFor instance, to create an observable that tracks the x/y coordinates of user clicks:\n\n```javascript\nconst clicks = trkl.from(observable =\u003e {\n    window.addEventListener('click', e =\u003e {\n        const coordinates = {x: e.clientX, y: e.clientY};\n        observable(coordinates);\n    });\n});\n\nclicks.subscribe(coordinates =\u003e console.log(coordinates));\n```\n\nEvery time the user clicks, clicks is updated with the latest coordinates.\n\n\n### observable.subscribe(fn, ?immediate)\n\nWhen an observable's value changes, pass its new and old values to the supplied subscriber.\n\n```javascript\nlet numbers = trkl(1);\nnumbers.subscribe((newVal, oldVal) =\u003e {\n    console.log('The observable was changed from', oldVal, 'to', newVal);\n});\n\nnumbers(2); // console outputs 'The observable was changed from 1 to 2'\n```    \n\nIf you pass the same subscriber multiple times, it will be de-duplicated, and only run once. \n\nIf you pass a truthy value to `immediate`, the subscriber will also run immediately.\n\nA subscription can safely mutate the observable's subscriber list (e.g. a subscriber can remove itself)\n\n#### How updates are deduplicated\n\nNote that Trkl will only filter out duplicate updates if the values are primitives, not objects or arrays. Why? Well, if you have two objects or arrays, you can only tell if their values have changed by recursively inspecting the whole tree of their properties. This would be expensive, and could lead us into circular inspections, so for performance and size reasons we don't bother.\n\nIf you really need to filter out duplicates, you could always do\n\n```javascript\nconst filter = trkl.from(observer =\u003e {\n  source.subscribe((newVal, oldVal) =\u003e {\n    if (newVal.length \u0026\u0026 (newVal.length !== oldVal.length)) {\n      observer(newVal);\n    } else if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {\n      observer(newVal);\n    }\n  });\n});\n```\n\nThis will only work if your objects / arrays are JSON-serializable, though.\n\n### observable.unsubscribe(fn)\n\nRemove the specified function as a subscriber.\n\n## React hooks example\n\nYou can easily make a stateful hook out of a Trkl observable. Use this to create a 'global' state hook:\n\n```typescript\nimport { useState, useEffect } from 'react';\n\nconst greeting = trkl('hello');\n\nexport function useGreeting() {\n  const [ state, setState ] = useState(greeting());\n\n  useEffect(() =\u003e {\n    greeting.subscribe(setState)\n\n    return () =\u003e greeting.unsubscribe(setState); // works fine as the setState function is stable between renders :-)\n  }, []);\n\n  return [ state, greeting ];\n}\n```\n\n\n## Why 'trkl'?\n\nBecause it's like a stream, except smaller (a 'trickle'), except even smaller than that ('trkl').\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbreckmckye%2Ftrkl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjbreckmckye%2Ftrkl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbreckmckye%2Ftrkl/lists"}