{"_id":"@ajeeb/ecs","_rev":"5-1b2ef0f3ae50414c41c218701626e379","name":"@ajeeb/ecs","dist-tags":{"latest":"0.1.4"},"versions":{"0.1.0":{"name":"@ajeeb/ecs","version":"0.1.0","keywords":["ecs","games","ajeeb","entity-component-system","entt"],"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","_id":"@ajeeb/ecs@0.1.0","maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"homepage":"https://git.sr.ht/~nasser/ajeeb#readme","dist":{"shasum":"9f3a1a04380879900564c1d7767740ecf212fca2","tarball":"https://registry.npmjs.org/@ajeeb/ecs/-/ecs-0.1.0.tgz","fileCount":8,"integrity":"sha512-ZTaBcKO72qocxVfqp9pIpl8mmnj/SU8o7AD3k7PDQpZ1Tt/pZRogpJJvBUZRnD1TWgfFxWXDr3QAim2/QZOLsQ==","signatures":[{"sig":"MEQCIFUdsCeWc0fYJJW/idglkDtZYRfcgjVbDw+iPVahmveCAiBEkUXeV+jzaWBXiefYXukrK2ByWvKz6D3yCQEbYp/tIQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":42795},"main":"index.js","type":"module","gitHead":"392e8cc76cd22eef8778d1a1fe9e1148fffad433","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/ecs --delete"},"_npmUser":{"name":"ramseynasser","email":"ram@nas.sr"},"repository":{"url":"https://git.sr.ht/~nasser/ajeeb.git","type":"git"},"_npmVersion":"10.2.3","description":"Entity Component System","directories":{},"_nodeVersion":"20.10.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ecs_0.1.0_1765554012399_0.44757791261293756","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@ajeeb/ecs","version":"0.1.1","keywords":["ecs","games","ajeeb","entity-component-system","entt"],"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","_id":"@ajeeb/ecs@0.1.1","maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"homepage":"https://git.sr.ht/~nasser/ajeeb#readme","dist":{"shasum":"e70fee00c490d48014a27341ba5a1664eb7e20d1","tarball":"https://registry.npmjs.org/@ajeeb/ecs/-/ecs-0.1.1.tgz","fileCount":8,"integrity":"sha512-JtY3F3/g+Bc2S7yJsHdVQ4pTmDYJW8BXSKyRWM+UWnRH5KF4SWXulBKZkIQy5j+TAmOx2QC+eNwNWj1HdN6NPg==","signatures":[{"sig":"MEQCIBhPq9EAT879KNr0KcYkkXDpC5IVPQLn6fijYH22pdmAAiBkK3H1pFbB9g3guQdIdXTZG5ELZwWSkaNEM7ElJbiMQw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":43296},"main":"index.js","type":"module","gitHead":"44f0d0feadaf68aaad9e5f9de8efbcdcda4c8fd2","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/ecs --delete"},"_npmUser":{"name":"ramseynasser","email":"ram@nas.sr"},"repository":{"url":"https://git.sr.ht/~nasser/ajeeb.git","type":"git"},"_npmVersion":"10.2.3","description":"Entity Component System","directories":{},"_nodeVersion":"20.10.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ecs_0.1.1_1765642445930_0.752842051573285","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@ajeeb/ecs","version":"0.1.2","keywords":["ecs","games","ajeeb","entity-component-system","entt"],"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","_id":"@ajeeb/ecs@0.1.2","maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"homepage":"https://git.sr.ht/~nasser/ajeeb#readme","dist":{"shasum":"97e4e724e0f3b5f2f6b36e643684e30efa02b278","tarball":"https://registry.npmjs.org/@ajeeb/ecs/-/ecs-0.1.2.tgz","fileCount":8,"integrity":"sha512-a+QXziPiO1UMIZSG6wMcNpbNYVi6Z54GPoZoKKdYnQ9w+fm+uRRfb5GPJgyFYsQbjIMXxTVms8pcjfsgRHNy5A==","signatures":[{"sig":"MEQCIAiDfa0KFc1GeBUrCF53dYn1iy7oMRoyyuB/g5r/I//zAiBX1XpALBVj8vH9drP8RIEwW1LHqkuVcEigUAiqLGc1vw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":50874},"main":"index.js","type":"module","gitHead":"59d9e9cfad0ae7f87dd3a122bbe9c1868ff216cf","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/ecs --delete"},"_npmUser":{"name":"ramseynasser","email":"ram@nas.sr"},"repository":{"url":"https://git.sr.ht/~nasser/ajeeb.git","type":"git"},"_npmVersion":"10.2.3","description":"Entity Component System","directories":{},"_nodeVersion":"20.10.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ecs_0.1.2_1765657414516_0.47650422225245603","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@ajeeb/ecs","version":"0.1.3","keywords":["ecs","games","ajeeb","entity-component-system","entt"],"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","_id":"@ajeeb/ecs@0.1.3","maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"homepage":"https://git.sr.ht/~nasser/ajeeb#readme","dist":{"shasum":"c01910676760e78085e1aa94d37bd5029168d4bc","tarball":"https://registry.npmjs.org/@ajeeb/ecs/-/ecs-0.1.3.tgz","fileCount":8,"integrity":"sha512-/NIIQwsJEseODfddU1Pkrj3d65XXtlH3I4pL636izAoEiucNzu4XoRoDJwViiry9m5eL85c5Bd6moHgCNji+ag==","signatures":[{"sig":"MEUCIQDWVQ1D1gEDhNUV9zfdirwCSleNOYCDOuUJMET3vuQAMQIgLARdTiL0Hk6cGL7MWzJjLWEcCdvPUYHaR231FgY6JSk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":51552},"main":"index.js","type":"module","gitHead":"59d9e9cfad0ae7f87dd3a122bbe9c1868ff216cf","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/ecs --delete"},"_npmUser":{"name":"ramseynasser","email":"ram@nas.sr"},"repository":{"url":"https://git.sr.ht/~nasser/ajeeb.git","type":"git"},"_npmVersion":"10.2.3","description":"Entity Component System","directories":{},"_nodeVersion":"20.10.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ecs_0.1.3_1765659732024_0.7331095356600703","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@ajeeb/ecs","type":"module","version":"0.1.4","description":"Entity Component System","main":"index.js","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/ecs --delete","test":"vitest"},"repository":{"type":"git","url":"https://git.sr.ht/~nasser/ajeeb.git"},"keywords":["ecs","games","ajeeb","entity-component-system","entt"],"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","homepage":"https://git.sr.ht/~nasser/ajeeb#readme","_id":"@ajeeb/ecs@0.1.4","gitHead":"1720dec016a49f9a97f8be60deed179bc9057666","_nodeVersion":"20.10.0","_npmVersion":"10.2.3","dist":{"integrity":"sha512-bd+4+uPGnvdXRFdo09uzkwLENQIgtg+4H6nq82IBlmXE0+wyS+6L2oiCkaC+T/bXFm1w1jvJpI0PgiWR1YNEQQ==","shasum":"aed25f12613bb86b9c5454feb8eaf01c69d367a1","tarball":"https://registry.npmjs.org/@ajeeb/ecs/-/ecs-0.1.4.tgz","fileCount":8,"unpackedSize":52954,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGrW7t9FnY7Bb5qL2EWt/osRD8b354tq86cbOmJyQgt7AiAFc7j0fcgee+K63Ztjfdu89YbDV8bLWnpG0e8fKKnZdA=="}]},"_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/ecs_0.1.4_1765660437076_0.5969304516029961"},"_hasShrinkwrap":false}},"time":{"created":"2025-12-12T15:40:12.287Z","modified":"2025-12-13T21:13:57.424Z","0.1.0":"2025-12-12T15:40:12.548Z","0.1.1":"2025-12-13T16:14:06.097Z","0.1.2":"2025-12-13T20:23:34.675Z","0.1.3":"2025-12-13T21:02:12.192Z","0.1.4":"2025-12-13T21:13:57.216Z"},"author":{"name":"Ramsey Nasser"},"license":"CC0-1.0","homepage":"https://git.sr.ht/~nasser/ajeeb#readme","keywords":["ecs","games","ajeeb","entity-component-system","entt"],"repository":{"type":"git","url":"https://git.sr.ht/~nasser/ajeeb.git"},"description":"Entity Component System","maintainers":[{"name":"ramseynasser","email":"ram@nas.sr"}],"readme":"# Entity Component System\n\nA simple [entity component system][ecs-wiki] inspired by [EnTT][entt-gh].\n\n## Entities\nAn entity is just an opaque identifier managed by the ECS system. You can create them with [`create`][doc-create], and under the hood they are just JavaScript integers.\n\n```js\nimport { Ecs } from '@ajeeb/ecs'\nconst ecs = new Ecs()\nconst player = ecs.create()\nconst enemy = ecs.create()\nconsole.log(player, enemy)\n// 0 1\n```\n\nEntities are *generational* meaning the system will recycle old values whenever possible and encode each entity's generation in the number itself. This is visible when destroying entities with [`destroy`][doc-destroy].\n\n```js\necs.destroy(enemy)\nconst enemy2 = ecs.create()\nconsole.log(player, enemy, enemy2)\n// 0 1 4194305\n```\n\nThe exact encoding of generations is an implementation detail you should not depend on -- treat entities as opaque identifiers. You can check the validity of an entity with [`isValid`][doc-isValid].\n\n```js\nconsole.log(ecs.isValid(player), ecs.isValid(enemy), ecs.isValid(enemy2))\n// true false true\n```\n\nThe entity stored in `enemy` is no longer valid because it was destroyed while the other entities remain valid. This is useful for situations where entity values get stored in different parts of your code and need to check if they've been destroyed.\n\n## Components\nEntities are just identifiers and contain no data or logic. Data is associated with entities by assigning *components*. A component is a plain JavaScript object with a constructor that can be used to identify it. Any object that meets that definition can be used, but ES6 classes are ideal.\n\nYou assign a component to an entity with [`assign`][doc-assign] which has two syntaxes. For simple components you can use a combination of the constructor value and a plain data object.\n\n```js\nclass Position { x = 0; y = 0 }\necs.assign(player, Position, { x: 100, y: 200 })\necs.assign(enemy2, Position, { x: 50 })\n```\n\nUnder the hood this calls the constructor with no arguments and uses [`Object.assign`][object-assign-mdn] to populate fields. The data object is optional making this syntax convenient for empty components that act as \"tags\".\n\n\n```js\nclass Enemy {}\necs.assign(enemy2, Enemy)\n```\n\nYou can also assign full instances, allowing you to take advantage of custom constructors or factory functions.\n\n```js\nclass Speed { \n    value = 0  \n    constructor(mode) {\n        this.value = mode === 'fast' ? 10 : 1\n    }\n}\necs.assign(player, new Speed('fast'))\n```\n\nEach entity can only have one component of a given type.\n\nYou get components with [`get`][doc-get] and check for their existence with [`has`][doc-has].\n\n```js\nconsole.log(ecs.get(player, Position))\n// Position { x: 100, y: 200 }\n\nconsole.log(ecs.has(player, Position), ecs.has(player, Enemy))\n// true false\n```\n\nDue to them being instances of classes you can write methods for your components if you like.\n\n```js\nclass Health {\n    value = 100\n\n    takeDamage(factor=1) {\n        this.value -= Math.random() * factor\n    }\n}\necs.assign(player, Health)\necs.get(player, Health).takeDamage()\n```\n\n### Lifecycle\nIt is sometimes useful to run logic when a component is assigned or removed to or from an entity. Component types can optionally implement the [`Component`][doc-component] interface which exposes the [`onAssign`][doc-component-onassign] and [`onRemove`][doc-component-onremove] methods. They will be called when the component instance is assigned to an entity or removed from an entity and can be used for setup, cleanup, and resource management.\n\n```js\nimport { PhysicsEngine } from 'your-physics-engine'\nclass Physics {\n    body\n    onAssign(e) {\n        PhysicsEngine.addBody(this.body)\n    }\n    onRemove(e) {\n        PhysicsEngine.removeBody(this.body)\n    }\n}\nconst physicsObject = ecs.create()\n\n// calls onAssign\necs.assign(physicsObject, Physics, { body: PhysicsEngine.newBody() })\n\n// calls onRemove\necs.remove(physicsObject, Physics)\n\n// would also call onRemove\necs.destroy(physicsObject)\n```\n\n## Systems\nYou can query for entities that has been assigned a set of components using [`each`][doc-each] which returns an optimized generator of matching entities usable in a [`for...of`][for-of-mdn] loop. The order of iteration is undefined and should not be relied on.\n\n```js\n// apply gravity to all enemies\nfor(const e of ecs.each(Position, Enemy)) {\n    // e is guaranteed to have at least Position and Enemy components\n     ecs.get(e, Position).y -= 0.1\n}\n```\n\nBecause its just normal iteration you can organize your logic however you want.\n\n```js\nfunction applyEnemyGravity(ecs, amount=0.1) {\n    for(const e of ecs.each(Position, Enemy)) {\n         ecs.get(e, Position).y -= amount\n    }\n}\n\napplyEnemyGravity(ecs, 0.5)\n```\n\n## Deferring Operations\nThree methods make visible mutations to the Ecs instance\n\n* [`assign`][doc-assign] -- assigns a component to an entity\n* [`remove`][doc-remove] -- removes a component from an entity\n* [`destroy`][doc-destroy] -- destroys an entity, removing all components assigned to it\n\nThese mutations are visible in iterations using [`each`][doc-each] and when\nreading with [`has`][doc-has] and [`get`][doc-get].\n\nBy default the changes are *immediately* visible which is simple and intuitive.\nThere are situations where you might want to *defer* these operations to take\nplace later, for example at the end of a frame so that all ECS operations during\na frame take place against a \"stable world\".\n\nThe three methods above take a `defer` boolean as their last argument. It is\noptional and defaults to the value passed into the [`Ecs`\nconstructor][doc-ctor], allowing you to configure your ECS to defer by default\nand/or determine deferral on a per-operation basis.\n\nDeferred operations take effect when you call [`commit`][doc-commit]\n\n```js\nconst engine = new GameEngine()\nconst ecs = new Ecs({ defer: true })\nengine.update = function() {\n    for(const e of ecs.each(Position, EnemySpawner)) {\n        if(Math.random() < 0.1) {\n            const newEnemy = ecs.create()\n            ecs.assign(newEnemy, Position, ecs.get(e, Position))\n            ecs.assign(newEnemy, Enemy)\n        }\n    }\n\n    for(const e of ecs.each(Position, Enemy)) {\n        // any enemies added by the spawner in the above loop\n        // *not visible here* until next frame\n    }\n\n    // end of frame\n    ecs.commit() // actually add enemies created in first loop\n}\n\n```\n\n[entt-gh]: https://github.com/skypjack/entt\n[ecs-wiki]: https://en.wikipedia.org/wiki/Entity_component_system\n[object-assign-mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign\n[for-of-mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of\n[doc-has]: classes/Ecs.html#has\n[doc-each]: classes/Ecs.html#each\n[doc-assign]: classes/Ecs.html#assign\n[doc-remove]: classes/Ecs.html#remove\n[doc-destroy]: classes/Ecs.html#destroy\n[doc-get]: classes/Ecs.html#get\n[doc-isValid]: classes/Ecs.html#isValid\n[doc-create]: classes/Ecs.html#create\n[doc-ctor]: classes/Ecs.html#constructor\n[doc-commit]: classes/Ecs.html#commit\n[doc-component]: /interfaces/Component.html\n[doc-component-onassign]: /interfaces/Component.html#onAssign\n[doc-component-onremove]: /interfaces/Component.html#onRemove","readmeFilename":"README.md"}