{"_id":"@caspian-vega/nano-rune-binding","name":"@caspian-vega/nano-rune-binding","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@caspian-vega/nano-rune-binding","version":"0.1.0","description":"Two way binding between nanostores and Svelte 5 runes: toRune mirrors a store into a deep proxy and writes edits back as a labelled, loggable action.","type":"module","license":"MIT","sideEffects":false,"main":"./dist/index.svelte.js","module":"./dist/index.svelte.js","svelte":"./dist/index.svelte.js","types":"./dist/index.svelte.d.ts","exports":{".":{"types":"./dist/index.svelte.d.ts","svelte":"./dist/index.svelte.js","import":"./dist/index.svelte.js"},"./package.json":"./package.json"},"keywords":["svelte","runes","nanostores","state","binding"],"author":"","peerDependencies":{"@nanostores/logger":">=0.3.0","nanostores":">=0.10.0","svelte":"^5.0.0"},"peerDependenciesMeta":{"@nanostores/logger":{"optional":true}},"devDependencies":{"@nanostores/logger":"^1.1.0","@types/node":"^25.9.1","nanostores":"^1.3.0","svelte":"^5.56.3","typescript":"^5.4.0","vite":"^8.2.1","vite-plugin-dts":"^5.0.3","vitest":"^4.1.10"},"publishConfig":{"access":"public"},"scripts":{"build":"vite build","test":"vitest run --passWithNoTests","typecheck":"tsc --noEmit"},"_nodeVersion":"24.15.0","_id":"@caspian-vega/nano-rune-binding@0.1.0","dist":{"integrity":"sha512-hU4mWSuACZ+y8aH0aTKJ7xlI16hEgLfcrOtCSd45nS3fUdc+IxQTtO/PLMB2bQQv1mQcvX60S94dbEK7sDhOXQ==","shasum":"6fc550762967bf8f940ee033e9ecfdb9b6c28cb5","tarball":"https://registry.npmjs.org/@caspian-vega/nano-rune-binding/-/nano-rune-binding-0.1.0.tgz","fileCount":6,"unpackedSize":20996,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGb0rGz1JUUv88XAZgfOspWOnNdxYgbusvA8qIJFZwJIAiAMPRdqpF0Iafs8poTx2LUfLa6CcOQyWqBh/+FBmIZW3g=="}]},"_npmUser":{"name":"caspianvega","email":"dom.konstantin@tutanota.com"},"directories":{},"maintainers":[{"name":"caspianvega","email":"dom.konstantin@tutanota.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nano-rune-binding_0.1.0_1787411856937_0.5497669131724048"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-22T15:17:36.737Z","0.1.0":"2026-08-22T15:17:37.184Z","modified":"2026-08-22T15:17:37.387Z"},"maintainers":[{"name":"caspianvega","email":"dom.konstantin@tutanota.com"}],"description":"Two way binding between nanostores and Svelte 5 runes: toRune mirrors a store into a deep proxy and writes edits back as a labelled, loggable action.","keywords":["svelte","runes","nanostores","state","binding"],"license":"MIT","readme":"# @caspian-vega/nano-rune-binding\n\nTwo way binding between a nanostore and a Svelte 5 rune, with every write back traceable through a\nnamed `@nanostores/logger` action.\n\nPart of [caspian-vega-astro-libs](https://codeberg.org/caspian-vega/caspian-vega-astro-libs).\n\n## Read only interop\n\nIf you ONLY reading a store inside a component, use the official\n[`@nanostores/svelte-runes`](https://github.com/nanostores/svelte-runes) package:\n\n```bash\npnpm add @nanostores/svelte-runes\n```\n\n```svelte\n<script lang=\"ts\">\n    import { useStore } from '@nanostores/svelte-runes';\n\n    import { postCount } from './post.store.ts';\n\n    const count = useStore(postCount);\n</script>\n\n<p>{count.current} posts</p>\n```\n\nThis rune binding is read-only, by default, but ships code you may actually never need.  \n\n## Install\n\n```bash\nnpm install @caspian-vega/nano-rune-binding\n# or\npnpm add @caspian-vega/nano-rune-binding\n```\n\nPeer dependencies: `svelte` 5, `nanostores`. `@nanostores/logger` is optional and only needed for\n`bindLogging`.\n\n## Usage\n\n```svelte\n<script lang=\"ts\">\n    import { toRune } from '@caspian-vega/nano-rune-binding';\n\n    import { filterStore } from './post.store.ts';\n\n    // Edits to `filter` are pushed back into the store, each one logged as\n    // the action `RuneProxy_post-filters`.\n    const filter = toRune(filterStore, true, 'post-filters').rune;\n</script>\n\n<input bind:value={filter.search} />\n```\n\n## Traceable write back\n\nA `bind:` on a rune is otherwise an anonymous mutation: the store changes and nothing says which\ncomponent did it. Passing a label to `bindLogging` wraps the write back in a `@nanostores/logger`\naction named `RuneProxy_<label>`, so every store change coming from a binding is attributed to that\nlabel in the logger output, and a change without such an entry came from somewhere else.\n\n```mermaid\nsequenceDiagram\n  participant U as User input\n  participant R as Rune proxy\n  participant A as Action RuneProxy_label\n  participant S as Store\n  participant L as Logger\n  U->>R: bind: writes a nested value\n  R->>R: Snapshot differs from last synced value\n  R->>A: Call with the cloned snapshot\n  A->>S: set(value)\n  A-->>L: action RuneProxy_label\n  S-->>R: Emit, marked as synced, no echo back\n```\n\nGive each binding its own label. Two bindings sharing one label are indistinguishable in the log.\n\n## Why the package depends on peer `.svelte.js`\n\nThe entry is a rune module. It is published uncompiled, so the runes are compiled by the consumer's\n`vite-plugin-svelte` and share that app's reactivity. The package therefore declares a `svelte` export\ncondition, and any bundler must run `.svelte.js` files from `node_modules` through the Svelte compiler.\nAstro projects using `@astrojs/svelte` get this by default.\n\n## API\n\n### toRune(store, bidirectional?, bindLogging?)\n\nSubscribes to `store` and mirrors its value into a `$state` proxy of the same shape.\n\n- `store` (`StoreContract<T>`, required): anything with `subscribe`, and `set` for write back.\n- `bidirectional` (boolean, default `false`): push rune edits back into the store. Requires `store.set`.\n- `bindLogging` (`false | string`, default `false`): wrap the write back in a `@nanostores/logger`\n  action named `RuneProxy_<value>`. Needs `@nanostores/logger` installed and a logger attached to the\n  store.\n- Returns: `RuneProxy<T>` with a `rune` property holding the proxy and a `destroy()` method.\n- Registers `onDestroy`, so it must be called during component initialization.\n\n```ts\nconst proxy = toRune(draftStore, true, 'draft-editor');\nproxy.rune.title = 'new title';\n```\n\nWrite back is guarded, which is what keeps a store update from bouncing back as a fresh object:\n\n```mermaid\nflowchart TD\n  S[Store emits value] --> D[deepAssign into rune proxy]\n  D --> M[Mark value as synced]\n  E[Rune mutated in a component] --> F{Snapshot equals synced value?}\n  F -->|yes| G[Skip, the store already holds it]\n  F -->|no| H[Clone, mark synced, store.set]\n  M --> F\n```\n\n## Types\n\n### StoreContract\n\n- `subscribe(run: (value: T) => void, invalidate?: () => void): () => void` (required)\n- `set(value: T): void` (optional): required for `bidirectional`\n\n## Constraints\n\n- The store value must be an object or an array. `toRune` mirrors into a proxy of that shape.\n- The shape should stay stable. Values are merged with a deep assign that preserves proxy identity,\n  arrays sync by index and are truncated to the source length.\n- Write back clones with `structuredClone`, so values must be structured cloneable.\n- `toRune` uses `onDestroy` and belongs in component initialization, not in a store or a plain module.\n\n## License\n\nMIT\n","readmeFilename":"","_rev":"1-037d34fca8691c30f814fff74f46f5ca"}