{"_id":"@alife-sdk/persistence","_rev":"5-79ad323b85ab42868ffbbc9ee4930e9d","name":"@alife-sdk/persistence","dist-tags":{"latest":"0.4.1"},"versions":{"0.1.0":{"name":"@alife-sdk/persistence","version":"0.1.0","keywords":["alife","game-ai","persistence","save","load","storage"],"author":{"name":"Eugene Rusakov"},"license":"MIT","_id":"@alife-sdk/persistence@0.1.0","maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"homepage":"https://github.com/eurusik/alife-sdk#readme","bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"dist":{"shasum":"c8f7633021dbe10ea346a2a566e48969edbd8a6b","tarball":"https://registry.npmjs.org/@alife-sdk/persistence/-/persistence-0.1.0.tgz","fileCount":35,"integrity":"sha512-EndRzpHua8w8YR9i116PlKvMKAhkeQstyYWnBNAQYxK44v2CqeUyOkapZhTof/4QDdlVp/efBvcY/8OzidpkAw==","signatures":[{"sig":"MEYCIQDb5+EUiqFYZ36ApfyCAXQ7iqNje41ZBcOoFjqnQX2NzgIhAKBgJ+OuXcB5IuzOhyc1ATPDhBob4JEbfHZaeVe32GqZ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":28956},"main":"dist/index.js","type":"module","_from":"file:alife-sdk-persistence-0.1.0.tgz","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ports":{"types":"./dist/ports/index.d.ts","import":"./dist/ports/index.js"},"./plugin":{"types":"./dist/plugin/index.d.ts","import":"./dist/plugin/index.js"},"./providers":{"types":"./dist/providers/index.d.ts","import":"./dist/providers/index.js"}},"scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"eurusik","email":"john.rusakov@gmail.com"},"_resolved":"/private/var/folders/jv/cpf6x8mj1g11mppj8rpdhlmr0000gn/T/0056fb73ad6028f3a9207f4b84d86f3a/alife-sdk-persistence-0.1.0.tgz","_integrity":"sha512-EndRzpHua8w8YR9i116PlKvMKAhkeQstyYWnBNAQYxK44v2CqeUyOkapZhTof/4QDdlVp/efBvcY/8OzidpkAw==","repository":{"url":"git+https://github.com/eurusik/alife-sdk.git","type":"git","directory":"packages/alife-persistence"},"_npmVersion":"10.8.1","description":"Framework-agnostic save/load pipeline for A-Life kernel — pluggable storage backends","directories":{},"sideEffects":false,"_nodeVersion":"20.16.0","dependencies":{"@alife-sdk/core":"0.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/persistence_0.1.0_1772739947033_0.3215261741169666","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@alife-sdk/persistence","version":"0.1.1","keywords":["alife","game-ai","persistence","save","load","storage"],"author":{"name":"Eugene Rusakov"},"license":"MIT","_id":"@alife-sdk/persistence@0.1.1","maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"homepage":"https://github.com/eurusik/alife-sdk#readme","bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"dist":{"shasum":"df513072b0828bda8c0e7b0ec0af5ae11165b5a1","tarball":"https://registry.npmjs.org/@alife-sdk/persistence/-/persistence-0.1.1.tgz","fileCount":35,"integrity":"sha512-UMwJIF/9UvAh2/clOF086SfScwdqAqA6O6haS6vf57XeKP08p7Sm5ip8WhxMpUNgnkKX/fFLRWxe5U32RkXInw==","signatures":[{"sig":"MEQCIEC7ngbk5Fs19/O7hawhenFC121dAXB9a0imHG8UL3J6AiAjWWMJ3oEEzMHZFeXFYjpMkRihSO9FE9l8YvV7ExtAeQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31626},"main":"dist/index.js","type":"module","_from":"file:alife-sdk-persistence-0.1.1.tgz","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ports":{"types":"./dist/ports/index.d.ts","import":"./dist/ports/index.js"},"./plugin":{"types":"./dist/plugin/index.d.ts","import":"./dist/plugin/index.js"},"./providers":{"types":"./dist/providers/index.d.ts","import":"./dist/providers/index.js"}},"scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"eurusik","email":"john.rusakov@gmail.com"},"_resolved":"/tmp/93a761f7ce1ae8c4c5eb4b696f3eaa80/alife-sdk-persistence-0.1.1.tgz","_integrity":"sha512-UMwJIF/9UvAh2/clOF086SfScwdqAqA6O6haS6vf57XeKP08p7Sm5ip8WhxMpUNgnkKX/fFLRWxe5U32RkXInw==","repository":{"url":"git+https://github.com/eurusik/alife-sdk.git","type":"git","directory":"packages/alife-persistence"},"_npmVersion":"10.8.2","description":"Framework-agnostic save/load pipeline for A-Life kernel — pluggable storage backends","directories":{},"sideEffects":false,"_nodeVersion":"20.20.0","dependencies":{"@alife-sdk/core":"0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/persistence_0.1.1_1772742735389_0.3615868543158771","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@alife-sdk/persistence","version":"0.3.0","keywords":["alife","game-ai","persistence","save","load","storage"],"author":{"name":"Eugene Rusakov"},"license":"MIT","_id":"@alife-sdk/persistence@0.3.0","maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"homepage":"https://github.com/eurusik/alife-sdk#readme","bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"dist":{"shasum":"75197c7ca367c742a1d587efc21945e119e40016","tarball":"https://registry.npmjs.org/@alife-sdk/persistence/-/persistence-0.3.0.tgz","fileCount":35,"integrity":"sha512-RZj1nA0Fqkozy388woQKYclprxrq7jiiJtSOn2KzpLm3gblI5/mw02q1slP4DIxazUwDBKMYs82XsT1XDz85XQ==","signatures":[{"sig":"MEUCIQDyhnkYODHiDvJhUvaV80bXhBZiGLUk9IYvCIsXYw+N3AIgAeGretJzkC6zILiPvT5f6EmxJYHeNi5lJdwYvSDL1eo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31626},"main":"dist/index.js","type":"module","_from":"file:alife-sdk-persistence-0.3.0.tgz","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ports":{"types":"./dist/ports/index.d.ts","import":"./dist/ports/index.js"},"./plugin":{"types":"./dist/plugin/index.d.ts","import":"./dist/plugin/index.js"},"./providers":{"types":"./dist/providers/index.d.ts","import":"./dist/providers/index.js"}},"scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"eurusik","email":"john.rusakov@gmail.com"},"_resolved":"/tmp/d7d5ddfc5c9d2fdbc4d21d9affeeb0d2/alife-sdk-persistence-0.3.0.tgz","_integrity":"sha512-RZj1nA0Fqkozy388woQKYclprxrq7jiiJtSOn2KzpLm3gblI5/mw02q1slP4DIxazUwDBKMYs82XsT1XDz85XQ==","repository":{"url":"git+https://github.com/eurusik/alife-sdk.git","type":"git","directory":"packages/alife-persistence"},"_npmVersion":"10.8.2","description":"Framework-agnostic save/load pipeline for A-Life kernel — pluggable storage backends","directories":{},"sideEffects":false,"_nodeVersion":"20.20.0","dependencies":{"@alife-sdk/core":"0.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/persistence_0.3.0_1772789682010_0.43326416033900794","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@alife-sdk/persistence","version":"0.4.0","keywords":["alife","game-ai","persistence","save","load","storage"],"author":{"name":"Eugene Rusakov"},"license":"MIT","_id":"@alife-sdk/persistence@0.4.0","maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"homepage":"https://github.com/eurusik/alife-sdk#readme","bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"dist":{"shasum":"ab65a12b2084ee85336178164735d2f9a0dda2dd","tarball":"https://registry.npmjs.org/@alife-sdk/persistence/-/persistence-0.4.0.tgz","fileCount":35,"integrity":"sha512-8nRkt7MKNEgfeTCnS0UjilIZzbMRRrovC8HFuYyRuh24p7K1hSl86WEiMPI5kq7H8mvEMCv3wSTQgiyLWztu9g==","signatures":[{"sig":"MEQCIFw1oJQYzZcM4TMglQVROfCNxrG6cuIoK8kYgWc0YiUzAiA/gBWfFL7VqL+RiVfL5rb3MSgUCyTxF/KdagHG+rD6Hw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31626},"main":"dist/index.js","type":"module","_from":"file:alife-sdk-persistence-0.4.0.tgz","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ports":{"types":"./dist/ports/index.d.ts","import":"./dist/ports/index.js"},"./plugin":{"types":"./dist/plugin/index.d.ts","import":"./dist/plugin/index.js"},"./providers":{"types":"./dist/providers/index.d.ts","import":"./dist/providers/index.js"}},"scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"eurusik","email":"john.rusakov@gmail.com"},"_resolved":"/tmp/12297a4d0511843daf4375b7ada638b1/alife-sdk-persistence-0.4.0.tgz","_integrity":"sha512-8nRkt7MKNEgfeTCnS0UjilIZzbMRRrovC8HFuYyRuh24p7K1hSl86WEiMPI5kq7H8mvEMCv3wSTQgiyLWztu9g==","repository":{"url":"git+https://github.com/eurusik/alife-sdk.git","type":"git","directory":"packages/alife-persistence"},"_npmVersion":"10.8.2","description":"Framework-agnostic save/load pipeline for A-Life kernel — pluggable storage backends","directories":{},"sideEffects":false,"_nodeVersion":"20.20.0","dependencies":{"@alife-sdk/core":"0.4.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/persistence_0.4.0_1772823546429_0.47570961246242893","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@alife-sdk/persistence","version":"0.4.1","description":"Framework-agnostic save/load pipeline for A-Life kernel — pluggable storage backends","type":"module","sideEffects":false,"main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ports":{"types":"./dist/ports/index.d.ts","import":"./dist/ports/index.js"},"./providers":{"types":"./dist/providers/index.d.ts","import":"./dist/providers/index.js"},"./plugin":{"types":"./dist/plugin/index.d.ts","import":"./dist/plugin/index.js"}},"keywords":["alife","game-ai","persistence","save","load","storage"],"license":"MIT","author":{"name":"Eugene Rusakov"},"repository":{"type":"git","url":"git+https://github.com/eurusik/alife-sdk.git","directory":"packages/alife-persistence"},"homepage":"https://github.com/eurusik/alife-sdk#readme","bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"engines":{"node":">=20.0.0"},"publishConfig":{"access":"public"},"dependencies":{"@alife-sdk/core":"0.4.1"},"devDependencies":{"typescript":"^5.9.3","vitest":"^4.0.18"},"scripts":{"build":"tsc","dev":"tsc --watch","test":"vitest run","test:watch":"vitest"},"_id":"@alife-sdk/persistence@0.4.1","_integrity":"sha512-66kO89ZnEOjCiFnbRWy161w5yqMLgpbpu0xVV77qEFv+YQNyjewEQzbyZLMXHLtxcUvN541LgGK8M4h4CCDXCQ==","_resolved":"/tmp/18ad1026bc5e2c910016b4b7527aef87/alife-sdk-persistence-0.4.1.tgz","_from":"file:alife-sdk-persistence-0.4.1.tgz","_nodeVersion":"20.20.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-66kO89ZnEOjCiFnbRWy161w5yqMLgpbpu0xVV77qEFv+YQNyjewEQzbyZLMXHLtxcUvN541LgGK8M4h4CCDXCQ==","shasum":"3a91b547f166c41a8b69d43c6617d792d0acb19a","tarball":"https://registry.npmjs.org/@alife-sdk/persistence/-/persistence-0.4.1.tgz","fileCount":35,"unpackedSize":31626,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDaOoX0gi8AWBgY+8NWqsoTLbWCv1rCbzs4U97nW5AogwIgDDp8dFXf1MMlWKcl/Ely6qtFFwY3ORXOYSXUtUVEsSQ="}]},"_npmUser":{"name":"eurusik","email":"john.rusakov@gmail.com"},"directories":{},"maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/persistence_0.4.1_1773837144611_0.7229459760745247"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-05T19:45:46.626Z","modified":"2026-03-18T12:32:24.882Z","0.1.0":"2026-03-05T19:45:47.242Z","0.1.1":"2026-03-05T20:32:15.538Z","0.3.0":"2026-03-06T09:34:42.178Z","0.4.0":"2026-03-06T18:59:06.583Z","0.4.1":"2026-03-18T12:32:24.761Z"},"bugs":{"url":"https://github.com/eurusik/alife-sdk/issues"},"author":{"name":"Eugene Rusakov"},"license":"MIT","homepage":"https://github.com/eurusik/alife-sdk#readme","keywords":["alife","game-ai","persistence","save","load","storage"],"repository":{"type":"git","url":"git+https://github.com/eurusik/alife-sdk.git","directory":"packages/alife-persistence"},"description":"Framework-agnostic save/load pipeline for A-Life kernel — pluggable storage backends","maintainers":[{"name":"eurusik","email":"john.rusakov@gmail.com"}],"readme":"# @alife-sdk/persistence\n\nSave/load pipeline for `ALifeKernel` — pluggable storage backends, typed error\ncodes, zero platform dependencies.\n\n```ts\nimport { PersistencePlugin, createDefaultPersistenceConfig } from '@alife-sdk/persistence/plugin';\n```\n\nEngine-agnostic. Works in the browser (`localStorage`), Electron (file system),\nNode.js, or any other runtime — you supply the backend, the SDK handles\nserialisation.\n\n---\n\n## Quick start (5 steps)\n\n```ts\nimport { ALifeKernel }                                         from '@alife-sdk/core';\nimport { PersistencePlugin, createDefaultPersistenceConfig }   from '@alife-sdk/persistence/plugin';\nimport type { IStorageBackend }                                from '@alife-sdk/persistence/ports';\n\n// 1. Implement IStorageBackend once for your platform\nclass LocalStorageBackend implements IStorageBackend {\n  save(key: string, data: string): void { localStorage.setItem(key, data); }\n  load(key: string): string | null      { return localStorage.getItem(key); }\n  has(key: string):  boolean            { return localStorage.getItem(key) !== null; }\n  remove(key: string): void             { localStorage.removeItem(key); }\n}\n\n// 2. Create the plugin\nconst persistence = new PersistencePlugin(\n  createDefaultPersistenceConfig(new LocalStorageBackend()),\n);\n\n// 3. Register with kernel\nconst kernel = new ALifeKernel({ /* … */ });\nkernel.use(persistence);\nkernel.init();\n\n// 4. Save (F5 / autosave)\nconst save = persistence.save();\nif (!save.ok) console.error(`[${save.reason}] ${save.message}`);\n\n// 5. Load (F9 / \"Continue\" button)\nif (persistence.hasSave()) {\n  const load = persistence.load();\n  if (!load.ok) console.warn(`[${load.reason}] ${load.message}`);\n}\n```\n\n---\n\n## Sub-path imports\n\n| Import | What you get |\n|--------|-------------|\n| `@alife-sdk/persistence/plugin` | `PersistencePlugin`, `PersistencePluginToken`, `createDefaultPersistenceConfig`, `SaveResult`, `LoadResult` |\n| `@alife-sdk/persistence/ports` | `IStorageBackend` — the interface you implement |\n| `@alife-sdk/persistence/providers` | `MemoryStorageProvider` — built-in for tests and Node.js |\n\n---\n\n## Architecture\n\n```\n   Your code                  PersistencePlugin               IStorageBackend\n  ────────────               ───────────────────             ──────────────────\n  persistence.save()\n    │                          kernel.serialize()\n    │                          JSON.stringify(state)\n    │                          backend.save(key, json)  ───→  localStorage / file / …\n    │\n  persistence.load()\n    │                          backend.load(key)        ←───  raw JSON string\n    │                          JSON.parse(raw)\n    │                          kernel.restoreState(state)\n    │\n  persistence.hasSave()        backend.has(key)         ←───  boolean\n  persistence.deleteSave()     backend.remove(key)      ───→  delete entry\n```\n\nThe plugin owns serialisation and deserialisation. Your backend only moves raw\nstrings — it never inspects JSON content.\n\n---\n\n## Key concepts\n\n### Pluggable backend\n\n`IStorageBackend` is a 4-method synchronous interface — implement it once for\nyour target platform:\n\n```ts\ninterface IStorageBackend {\n  save(key: string, data: string): void;\n  load(key: string): string | null;\n  has(key: string):  boolean;\n  remove(key: string): void;\n}\n```\n\nThe SDK ships `MemoryStorageProvider` for tests. Everything else you write.\n\n### Discriminated union results\n\n`save()` and `load()` never throw on normal failure paths — they return a\ntyped result you pattern-match on:\n\n```ts\nconst result = persistence.save();\n\nif (result.ok) {\n  ui.showToast('Збережено');\n} else {\n  // result.reason: 'serialize_failed' | 'write_failed'\n  // result.message: human-readable detail\n  logger.error(`Save failed [${result.reason}]: ${result.message}`);\n}\n```\n\n| `SaveResult.reason` | When |\n|--------------------|------|\n| `serialize_failed` | `kernel.serialize()` or `JSON.stringify` threw |\n| `write_failed` | `backend.save()` threw (storage full, permissions) |\n\n| `LoadResult.reason` | When |\n|--------------------|------|\n| `not_found` | No save exists at this key |\n| `parse_failed` | Stored data is corrupted / not valid JSON |\n| `restore_failed` | `kernel.restoreState()` threw (incompatible save version, corrupted state) |\n\n> **Version field requirement:** save data must be a JSON object with a `version: number` field.\n> This is enforced by `kernel.serialize()` — if the field is absent or not a number, `load()`\n> returns `{ ok: false, reason: 'parse_failed' }` before attempting to restore.\n\n### Why synchronous?\n\n`IStorageBackend` is intentionally synchronous. Keeping the interface sync makes\nserialisation deterministic and predictable — save and load complete in a single\ncall frame with no hidden state changes between ticks.\n\nFrom the source comment in `IStorageBackend.ts`:\n\n> NOTE: This interface is intentionally synchronous. For async backends\n> (IndexedDB, remote API), wrap with a sync cache layer or use a separate\n> async persistence strategy outside of PersistencePlugin.\n\nIn practice this means:\n- `localStorage`, `sessionStorage`, and `fs.writeFileSync` work directly.\n- For **IndexedDB or remote APIs**, maintain an in-memory cache that is written\n  asynchronously in the background, and expose the cached values through the sync\n  interface. Alternatively, handle that persistence layer entirely outside\n  `PersistencePlugin`.\n\n### Error recovery\n\nBecause `save()` and `load()` return typed result objects instead of throwing,\nyou can handle each failure mode explicitly:\n\n| Failure reason | Likely cause | Recommended action |\n|---|---|---|\n| `write_failed` | Storage quota exceeded or permission denied | Show a UI prompt (e.g. \"Storage full — free space and try again\"), then retry `save()` with a smaller data set or a different save slot |\n| `parse_failed` | Corrupted save data or missing `version` field | Offer the player a \"New Game\" option or a fallback save slot; do not attempt to restore partial state |\n| `restore_failed` | Incompatible save version or corrupted state rejected by `kernel.restoreState()` | Log `result.message` for debugging, then offer \"New Game\"; the save file can be deleted with `deleteSave()` |\n\n```ts\nconst load = persistence.load();\nif (!load.ok) {\n  switch (load.reason) {\n    case 'write_failed':\n      ui.showPrompt('Storage full — free space and retry.');\n      break;\n    case 'parse_failed':\n      ui.offerNewGame('Save data is corrupted.');\n      break;\n    case 'restore_failed':\n      logger.error(`restore_failed: ${load.message}`);\n      ui.offerNewGame('Incompatible save version.');\n      break;\n  }\n}\n```\n\n### Multiple save slots\n\nPass a custom `saveKey` per plugin instance:\n\n```ts\nconst manual   = new PersistencePlugin({ backend, saveKey: 'save_manual' });\nconst autosave = new PersistencePlugin({ backend, saveKey: 'save_auto' });\n```\n\n---\n\n## Lifecycle\n\n```\nnew PersistencePlugin(config)\n\nkernel.use(persistence)   ← stores kernel reference (install)\nkernel.init()\n\npersistence.hasSave()     ← check before \"Continue\" button\npersistence.save()        ← F5 / autosave timer\npersistence.load()        ← F9 / \"Continue\"\npersistence.deleteSave()  ← \"New Game\"\n```\n\n`save()` and `load()` throw only if called before `kernel.use()` —\nall other failures are returned as result objects.\n\n---\n\n## Tests\n\n32 tests, 0 failures:\n\n```\npnpm --filter @alife-sdk/persistence test\n```\n\nCovers: unit tests for all result codes, integration round-trips with a real\n`ALifeKernel` (save → restart → load → verify state), multiple save slots,\ncorrupted JSON handling, and tick counter preservation.\n\n---\n\n## Module map\n\n| Module | README |\n|--------|--------|\n| `plugin/` | [`plugin/README.md`](src/plugin/README.md) — entry point, full API reference |\n| `ports/` | [`ports/README.md`](src/ports/README.md) — IStorageBackend, platform implementation examples |\n| `providers/` | [`providers/README.md`](src/providers/README.md) — MemoryStorageProvider for tests |\n\n## See also\n\n- [`@alife-sdk/simulation`](../alife-simulation/README.md) — primary consumer: saves NPC brain state, terrain assignments, morale\n- [`@alife-sdk/economy`](../alife-economy/README.md) — saves inventory, trade history, and quest progress\n- [`@alife-sdk/hazards`](../alife-hazards/README.md) — saves hazard zone state and spawned artefact positions\n","readmeFilename":"README.md"}