{"_id":"@ajeeb/signals","_rev":"5-bf1b4b2129080b3c569a4d98fcd88366","name":"@ajeeb/signals","dist-tags":{"latest":"0.1.4"},"versions":{"0.1.0":{"name":"@ajeeb/signals","version":"0.1.0","keywords":["coroutines","generators","signals","games","ajeeb","state-machine"],"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","_id":"@ajeeb/signals@0.1.0","maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"homepage":"https://ajeeb.games","bugs":{"url":"https://todo.sr.ht/~nasser/ajeeb"},"dist":{"shasum":"a1024e2c5b41c1f1d716a20b6de53a6a35848348","tarball":"https://registry.npmjs.org/@ajeeb/signals/-/signals-0.1.0.tgz","fileCount":5,"integrity":"sha512-p8coQrZ3OVA3j4/90vzucteEeT27mWsEwPueFtEmq83a4qyp6Xd8+qWjljCFF8fUTDr//g2GXgUdFflnhXK0pg==","signatures":[{"sig":"MEUCIQDrDWGX/Ru38p5dKa+DzS+U+CQbM+vTqhwHkwNY6iFhUgIgbQhUkVISRRNxRLk5ikRdEl84qHqz8iK356A1DmwE+Do=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":30524},"main":"index.js","type":"module","gitHead":"71073b9505f3928c143370d7d9dd555158c4e7ad","scripts":{"test":"vitest","docs:build":"typedoc index.js","npm:publish":"npm publish --access public","docs:publish":"rsync -achv docs/* root@ajeeb.games:/var/www/ajeeb.games/js/signals --delete","test:coverage":"vitest --coverage"},"_npmUser":{"name":"ramseynasser","email":"ram@nas.sr"},"repository":{"url":"https://git.sr.ht/~nasser/ajeeb.git","type":"git"},"_npmVersion":"10.2.3","description":"Yieldable signals -- Work In Progress","directories":{},"_nodeVersion":"20.10.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.6.0","@vitest/coverage-v8":"^1.6.0"},"_npmOperationalInternal":{"tmp":"tmp/signals_0.1.0_1750032799906_0.8431868753671075","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@ajeeb/signals","version":"0.1.1","keywords":["coroutines","generators","signals","games","ajeeb","state-machine"],"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","_id":"@ajeeb/signals@0.1.1","maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"homepage":"https://ajeeb.games","bugs":{"url":"https://todo.sr.ht/~nasser/ajeeb"},"dist":{"shasum":"7bb421026ea65b6264b731181ff06c3bbcec9bb9","tarball":"https://registry.npmjs.org/@ajeeb/signals/-/signals-0.1.1.tgz","fileCount":5,"integrity":"sha512-GJXj3vczd1zv2lC8Qt+WIB/JOFaCLfXSCDZONcI/FKeJUBrUkIrbeNGAI7yp5aoTPt+EcEnKTFWXeAuucwG0ug==","signatures":[{"sig":"MEUCIQDLfVo3/9mEppWhUux/CQiWeu7hIETgsn2DxYsVYXM+pgIgS6dGa1eS1VNnTbEhcDdFoIx3mkI5yl66EeZZm6xbstg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":32276},"main":"index.js","type":"module","gitHead":"64f0df3f63c85d640fd48d25b31ed2e3bdb5489c","scripts":{"test":"vitest","docs:build":"typedoc index.js","npm:publish":"npm publish --access public","docs:publish":"rsync -achv docs/* root@ajeeb.games:/var/www/ajeeb.games/js/signals --delete","test:coverage":"vitest --coverage"},"_npmUser":{"name":"ramseynasser","email":"ram@nas.sr"},"repository":{"url":"https://git.sr.ht/~nasser/ajeeb.git","type":"git"},"_npmVersion":"10.2.3","description":"Yieldable signals -- Work In Progress","directories":{},"_nodeVersion":"20.10.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.6.0","@vitest/coverage-v8":"^1.6.0"},"_npmOperationalInternal":{"tmp":"tmp/signals_0.1.1_1750034130045_0.6264844963687854","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@ajeeb/signals","version":"0.1.2","keywords":["coroutines","generators","signals","games","ajeeb","state-machine"],"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","_id":"@ajeeb/signals@0.1.2","maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"homepage":"https://ajeeb.games","bugs":{"url":"https://todo.sr.ht/~nasser/ajeeb"},"dist":{"shasum":"7fd1c9ae6db6339ef301961312f56a024e5a1bad","tarball":"https://registry.npmjs.org/@ajeeb/signals/-/signals-0.1.2.tgz","fileCount":5,"integrity":"sha512-+UT2GCFQpkyq1qOQ5zdb5hLoGH6yW1NOYobR+SZc15NZNa4ZLa0VUYD2Lf+HlMZbwj5K13H65oJjoyGnibuVXA==","signatures":[{"sig":"MEYCIQCUukTjQRaUvS5ukPNjUrFX2zNPBuAs1QitpnfsiWrmXAIhAPGx4BX0CCDdpnDhJLphQaWkemzyA2KmWEeA79/3fFM5","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31611},"main":"index.js","type":"module","gitHead":"62dd4d727b32bd9b0229faf4cce2aab80b2b9026","scripts":{"test":"vitest","docs:build":"typedoc index.js","npm:publish":"npm publish --access public","docs:publish":"rsync -achv docs/* root@ajeeb.games:/var/www/ajeeb.games/js/signals --delete","test:coverage":"vitest --coverage"},"_npmUser":{"name":"ramseynasser","email":"ram@nas.sr"},"repository":{"url":"https://git.sr.ht/~nasser/ajeeb.git","type":"git"},"_npmVersion":"10.2.3","description":"Yieldable signals -- Work In Progress","directories":{},"_nodeVersion":"20.10.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.6.0","@vitest/coverage-v8":"^1.6.0"},"_npmOperationalInternal":{"tmp":"tmp/signals_0.1.2_1754154356062_0.8370506807297751","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@ajeeb/signals","version":"0.1.3","keywords":["coroutines","generators","signals","games","ajeeb","state-machine"],"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","_id":"@ajeeb/signals@0.1.3","maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"homepage":"https://ajeeb.games","bugs":{"url":"https://todo.sr.ht/~nasser/ajeeb"},"dist":{"shasum":"d612e8bc5861d6dcdb21b9882e0221d66bd51f06","tarball":"https://registry.npmjs.org/@ajeeb/signals/-/signals-0.1.3.tgz","fileCount":6,"integrity":"sha512-fb743O7XaGBKl/NJsuvI4jBWQGePtrJ50mQ6Nrh1oIa4vTbnjIQKhmvpz60AYpa8YHO6XghZN87dOpmefStCCg==","signatures":[{"sig":"MEUCIQCwQeC0CaahzEZq9TAegcjqyz2CojEijbX6E/tu0+uvxwIgBecXBzJZYorYbmuF9PGVPdFug1CNmgvbnrd7wGTwgO0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":36981},"main":"index.js","type":"module","gitHead":"06fd361aa756412e7eb0062cfb8a05a55f3ffef4","scripts":{"test":"vitest","docs:build":"typedoc index.js","npm:publish":"npm publish --access public","docs:publish":"rsync -achv docs/* root@ajeeb.games:/var/www/ajeeb.games/js/signals --delete","test:coverage":"vitest --coverage"},"_npmUser":{"name":"ramseynasser","email":"ram@nas.sr"},"repository":{"url":"https://git.sr.ht/~nasser/ajeeb.git","type":"git"},"_npmVersion":"10.2.3","description":"Yieldable signals -- Work In Progress","directories":{},"_nodeVersion":"20.10.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.6.0","@vitest/coverage-v8":"^1.6.0"},"_npmOperationalInternal":{"tmp":"tmp/signals_0.1.3_1755369698259_0.5054469089709945","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@ajeeb/signals","version":"0.1.4","description":"Yieldable signals -- Work In Progress","main":"index.js","type":"module","scripts":{"npm:publish":"npm publish --access public","docs:build":"typedoc index.js","docs:publish":"rsync -achv docs/* root@ajeeb.games:/var/www/ajeeb.games/js/signals --delete","test":"vitest","test:coverage":"vitest --coverage"},"repository":{"type":"git","url":"https://git.sr.ht/~nasser/ajeeb.git"},"keywords":["coroutines","generators","signals","games","ajeeb","state-machine"],"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","bugs":{"url":"https://todo.sr.ht/~nasser/ajeeb"},"homepage":"https://ajeeb.games","devDependencies":{"@vitest/coverage-v8":"^1.6.0","vitest":"^1.6.0"},"_id":"@ajeeb/signals@0.1.4","gitHead":"e1aa3bbc29751075ec815e647f06c21bac5c251a","_nodeVersion":"20.10.0","_npmVersion":"10.2.3","dist":{"integrity":"sha512-sUcR/cFqHnKs5nwsjl00mXpGXkUi9EeXz7fTk7lw/KVUwfojGL1DN5qQPlrQroZ+jeV5yhEkyWcSGAW15uS45g==","shasum":"f3d957b5baa2a22ab43217a460e7bea9570cdeec","tarball":"https://registry.npmjs.org/@ajeeb/signals/-/signals-0.1.4.tgz","fileCount":6,"unpackedSize":37277,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDZ7QbpKK9pmr6my+Qd5wTthvEua4sZnR0WK3C8zulcVwIgIe3vZpCJiYwO6NOg6yetH0L1Gvy5oZzEiJZ+WDmEyvU="}]},"_npmUser":{"name":"ramseynasser","email":"ram@nas.sr"},"directories":{},"maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/signals_0.1.4_1758772365531_0.922835044443308"},"_hasShrinkwrap":false}},"time":{"created":"2025-06-16T00:13:19.827Z","modified":"2025-09-25T03:52:45.893Z","0.1.0":"2025-06-16T00:13:20.068Z","0.1.1":"2025-06-16T00:35:30.231Z","0.1.2":"2025-08-02T17:05:56.270Z","0.1.3":"2025-08-16T18:41:38.449Z","0.1.4":"2025-09-25T03:52:45.704Z"},"bugs":{"url":"https://todo.sr.ht/~nasser/ajeeb"},"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","homepage":"https://ajeeb.games","keywords":["coroutines","generators","signals","games","ajeeb","state-machine"],"repository":{"type":"git","url":"https://git.sr.ht/~nasser/ajeeb.git"},"description":"Yieldable signals -- Work In Progress","maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"readme":"# Ajeeb Signals\n\nSignals system for Ajeeb, based on [ES6 Generators][generators]. This package is intended to unite and supersede [@ajeeb/coroutines][ajeeb-coro] and [@ajeeb/input][ajeeb-input].\n\nThis package is a work in progress. Use at your own risk.\n\n## Overview\nA signal is an object that represents a stream of events, a suspension point for coroutines, and a store of value. The four things you can do with a signal are:\n\n* *emit* a value through a signal\n* *await* a signal's emission in a coroutine\n* *read* a signal's most recently emitted value\n* *subscribe* to a signal's emissions with a callback\n\nThese operations turn out to be really effective building blocks for the kind of asynchronous code that makes up the playful systems Ajeeb is designed for.\n\n### Quick Tour\n#### Main API\n```js\nconst signal = new Signal()\n\n// run coroutine up to its first yield then await the signal\ngo(function* () {\n   // ...\n\n   // *await* signal's emission, coroutine will not run\n   // until signal.emit is called\n   const x = yield signal\n})\n\n// ... elsewhere\n\n// *emit* the signal to unblock any awaiting coroutines\nsignal.emit(9) \n\n// *read* the most recent emitted value any any time\nsignal.value // => 9\n\n// subscribe to a signal with a conventional callback\nsignal.on(v => ...)\n```\n\n#### Update Loop\n```js\nconst frame = new Signal()\n\n// Ajeeb is agnostic to the graphics frameworks you use\nconst object = framework.makeObject(...)\n\ngo(function*(){\n   while(true) {\n      delta = yield frame\n      object.rotation.x += delta\n   }\n})\n\n// emit frame signal once per frame\nlet lastTime = 0\nfunction tick(time) {\n   requestAnimationFrame(tick)\n   frame.emit((time - lastTime) / 1000)\n   lastTime = time\n}\n```\n\n#### DOM Events\n```js\nconst mouse = Signal.event(document.body, \"mousepressed\")\n\ngo(function* () {\n   const eventData = yield mouse\n   console.log('clicked', eventData.clientX, eventData.clientY)\n})\n```\n\n#### Combinators\n```js\nconst timeout = new Signal()\nsetTimeout(timeout.emit, 5000)\n\nconst click = new Signal()\nconst button = document.createElement('button')\nbutton.value = \"five seconds to click me!\"\ndocument.body.appendChild(button)\nbutton.addEventListener('click', click.emit)\n\ngo(function* () {\n   yield* first(timeout, click)\n   // this code will not run until one of timeout or click has emitted\n   if(timeout.emitted) {\n      console.log(\"too slow!\")\n   } else if(click.emitted) {\n      console.log(\"you clicked me!\")\n   }\n   document.body.removeChild(button)\n})\n```\n\n## Other Systems\n### Ajeeb Coroutines and Input\nHistorically the Ajeeb system had a [coroutine][ajeeb-coro] system which provided a [Schedule][ajeeb-schedule] to contain and run coroutines and an [input][ajeeb-input] system to manage input. This was used to great effect in the many places Ajeeb was deployed but had its limitations. The semantics of the schedule were that `yield` would pause coroutines until the next `tick`, which meant to await long running asynchronous operations you were forced to poll a value once per frame. Also, managing and passing around a Schedule instance was messy.\n\nAjeeb Signals can seamlessly consume callback and [Promise][js-promises]-based APIs, which Ajeeb Coroutines could not. The lack of a schedule makes client code cleaner and certain edge cases much easier to implement.\n\n### Promises\nSignals could conceivably be built on [JavaScript's Promises][js-promises] but they are limited in two ways\n\n1. Little control over execution order\n   \n   We need *rock solid* guarantees of determinism and code execution order for these systems to be usable as foundational abstractions. Promises are scheduled by the JavaScript host event loop and the exact order of evaluation between scheduled promises is not defined. This is OK when you're waiting on things like network requests but intolerable for many of the interesting use cases Signals can address, namely waiting on coroutines and other asynchronous code.\n\n2. No way to cancel a Promise\n   \n   Being able to *cancel* a waiting coroutine and allow it to *clean up* after itself turns out to be a critical feature of asynchronous systems -- and is often missing. Ajeeb Signals leverage JavaScript's [`try`/`finally`][js-try-finally] machinery to enable this with minimal ceremony, but Promises simply cannot be cancelled.\n\n### Events\nJavaScript is built on the notion of \"events\" and C# even has them [built into the language][csharp-events]. They are similar so Ajeeb Signals in that you can emit values to through them but, critically, they cannot be awaited, making them less useful in coroutines or for the construction of state machines.\n\n### Godot Signals\n[Godot has signals][godot-signals] and since 4.0 they are awaitable in coroutines, making them very similar to Ajeeb's signals. A difference is that in Godot signals are part of a larger engine, with object-oriented semantics and other organizational ideas, where in Ajeeb signals and coroutines are meant to be the main organizational principles that everything else is built on.\n\n## Legal and Attribution\nAjeeb is free and unencumbered software released into the public domain under the terms of the [CC0 1.0 UNIVERSAL](https://creativecommons.org/publicdomain/zero/1.0/deed.en) license.\n\nAttribution is appreciated but not required. When attributing a mention to the effect of \"This project makes use of Ajeeb, originally by Ramsey Nasser\" and/or a link to `https://ajeeb.games/` is sufficient.\n\n## Author's Intent\n\nWhile this software may *legally* be used by anyone, it is not *for* everyone.\n\nThis software is the result of a great deal of joyful labor on the part of the author, and it is shared freely in the hope that individuals, students, artists, and worker cooperatives will benefit from its use and study in any capacity, commercial and otherwise. The author made this for you.\n\nIt is absolutely *not* intended for use in service of capital, in pursuit of profit for a minority off the labor of others, or by organizations that differentiate their owners and their workers.\n\nIf you own such an organization, know that the author believes that you are a bad person, and that you should step aside and allow your enterprise to be run democratically by your employees with equal stake and say in its management. The author did not make this for you, and holds you in contempt.\n\nIf you work for such an organization, know that the author believes that you are being exploited, and that you should start organizing towards converting your workplace into a cooperative or unionizing at the very least. The author is on your side, hopes this software lightens the burden of your labor.\n\nWe have nothing to lose but our chains and a better world is possible.\n\n---\n\n<sup>1</sup> There are plans for a fourth operation -- to subscribe to a signal using a conventional callback\n\n[generators]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*\n[ajeeb-input]: https://ajeeb.games/js/input/\n[ajeeb-coro]: https://ajeeb.games/js/coroutines/\n[godot-signals]: https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html\n[csharp-events]: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/\n[js-promises]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise\n[js-try-finally]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch\n[ajeeb-schedule]: https://ajeeb.games/js/coroutines/classes/Schedule.html","readmeFilename":"README.md"}