{"_id":"@aawwaaa/astralcore-syncer","_rev":"3-80d884efc0f648e87338bd497cfeace4","name":"@aawwaaa/astralcore-syncer","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@aawwaaa/astralcore-syncer","version":"1.0.0","author":{"name":"aawwaaa"},"license":"MIT","_id":"@aawwaaa/astralcore-syncer@1.0.0","maintainers":[{"name":"aawwaaa","email":"aawwaaa123456@outlook.com"}],"dist":{"shasum":"be505cba2537e52421bd560fbd8531d613b47633","tarball":"https://registry.npmjs.org/@aawwaaa/astralcore-syncer/-/astralcore-syncer-1.0.0.tgz","fileCount":30,"integrity":"sha512-2ahQFymF/ICTBovk0scDxqNtB5pCJMfWe/YJiKU5DSMJNm2YNlw9NR0nYC0ZlDfi1YW07Fh7Z//g5iB2S/KG0g==","signatures":[{"sig":"MEQCIDAteB37ePFhbxm3nJHP1CBielLnigEOSrdtAgErL1/eAiAGnqWBWmDOJywW4wHiRc7BO9kuaUWFjn7jD8ygnHIOAQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":153779},"exports":{".":{"types":"./src/index.ts","default":"./dist/index.js"},"./impl/server/resource-json":{"types":"./src/impl/server/resource-json.ts","default":"./dist/impl/server/resource-json.js"},"./impl/client/resource-remote":{"types":"./src/impl/client/resource-remote.ts","default":"./dist/impl/client/resource-remote.js"},"./impl/server/resource-memory":{"types":"./src/impl/server/resource-memory.ts","default":"./dist/impl/server/resource-memory.js"},"./impl/client/remote-websocket":{"types":"./src/impl/client/remote-websocket.ts","default":"./dist/impl/client/remote-websocket.js"},"./impl/server/remote-websocket":{"types":"./src/impl/server/remote-websocket.ts","default":"./dist/impl/server/remote-websocket.js"}},"gitHead":"8f73bd35bd9356847befef26b918619de4cbbd3b","scripts":{"build":"tsc"},"_npmUser":{"name":"aawwaaa","email":"aawwaaa123456@outlook.com"},"_npmVersion":"10.9.3","description":"A lightweight library to connect objects together","directories":{},"_nodeVersion":"22.19.0","dependencies":{"ws":"^8.21.0","@types/ws":"^8.18.1","@types/node":"^26.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^6.0.3"},"_npmOperationalInternal":{"tmp":"tmp/astralcore-syncer_1.0.0_1782009395380_0.3447745922642469","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@aawwaaa/astralcore-syncer","version":"1.0.1","author":{"name":"aawwaaa"},"license":"MIT","_id":"@aawwaaa/astralcore-syncer@1.0.1","maintainers":[{"name":"aawwaaa","email":"aawwaaa123456@outlook.com"}],"dist":{"shasum":"487a441b7cbd2ef67a4beba1d22848af66af0b1e","tarball":"https://registry.npmjs.org/@aawwaaa/astralcore-syncer/-/astralcore-syncer-1.0.1.tgz","fileCount":30,"integrity":"sha512-7yP6DruwZ5TVYdiyLkMwa0o4fCa3Eg4oPv9UaQn1KWSCABCs6PGVzeYyDbroxUIPkhb7qDiU3STkuplMtqv64g==","signatures":[{"sig":"MEQCIFBGcyepGuLQgmOY+CUfN92eLuKq+yr5WBi1TiVs97OeAiAzlK9l7by8Zt+j5ItsOsrVQ7Cg2XtO4sqxbhWeqyz8Bw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":153848},"exports":{".":{"types":"./src/index.ts","default":"./dist/index.js"},"./impl/server/resource-json":{"types":"./src/impl/server/resource-json.ts","default":"./dist/impl/server/resource-json.js"},"./impl/client/resource-remote":{"types":"./src/impl/client/resource-remote.ts","default":"./dist/impl/client/resource-remote.js"},"./impl/server/resource-memory":{"types":"./src/impl/server/resource-memory.ts","default":"./dist/impl/server/resource-memory.js"},"./impl/client/remote-websocket":{"types":"./src/impl/client/remote-websocket.ts","default":"./dist/impl/client/remote-websocket.js"},"./impl/server/remote-websocket":{"types":"./src/impl/server/remote-websocket.ts","default":"./dist/impl/server/remote-websocket.js"}},"gitHead":"2bdbeae49d255ed49706c2c5d779b5f8a5a9633d","scripts":{"build":"tsc"},"_npmUser":{"name":"aawwaaa","email":"aawwaaa123456@outlook.com"},"_npmVersion":"10.9.3","description":"A lightweight library to connect objects together","directories":{},"_nodeVersion":"22.19.0","dependencies":{"ws":"^8.21.0","@types/ws":"^8.18.1","@types/node":"^26.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^6.0.3"},"_npmOperationalInternal":{"tmp":"tmp/astralcore-syncer_1.0.1_1782031057416_0.22921461898294404","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@aawwaaa/astralcore-syncer","version":"1.0.2","description":"A lightweight library to connect objects together","scripts":{"build":"tsc"},"exports":{".":{"types":"./src/index.ts","default":"./dist/index.js"},"./impl/client/remote-websocket":{"types":"./src/impl/client/remote-websocket.ts","default":"./dist/impl/client/remote-websocket.js"},"./impl/client/resource-remote":{"types":"./src/impl/client/resource-remote.ts","default":"./dist/impl/client/resource-remote.js"},"./impl/server/remote-websocket":{"types":"./src/impl/server/remote-websocket.ts","default":"./dist/impl/server/remote-websocket.js"},"./impl/server/resource-json":{"types":"./src/impl/server/resource-json.ts","default":"./dist/impl/server/resource-json.js"},"./impl/server/resource-memory":{"types":"./src/impl/server/resource-memory.ts","default":"./dist/impl/server/resource-memory.js"}},"author":{"name":"aawwaaa"},"license":"MIT","devDependencies":{"typescript":"^6.0.3"},"dependencies":{"@types/node":"^26.0.0","@types/ws":"^8.18.1","ws":"^8.21.0"},"publishConfig":{"access":"public"},"_id":"@aawwaaa/astralcore-syncer@1.0.2","gitHead":"823bd6bad82742f98c48351d73c9149fa75cb84a","_nodeVersion":"22.19.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-f2hMt0+4nGhIqUJySCo4HOnB+JD3dcK35j91A8TKYCeI3z75mE8rupUEUdoVoO5qinzs+D6VKpkFb+PMKn50rg==","shasum":"98cbe995a9728e92b95117499ccd153bd9b128e0","tarball":"https://registry.npmjs.org/@aawwaaa/astralcore-syncer/-/astralcore-syncer-1.0.2.tgz","fileCount":30,"unpackedSize":154343,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIC99filjR6TQiSxEK3KgEBjePaVBNKP+RhxKRlVAjN+lAiEAsI908SR8OdxjiAYiqvvMZvfqkfgY+4VRGJ+pM0A/9a4="}]},"_npmUser":{"name":"aawwaaa","email":"aawwaaa123456@outlook.com"},"directories":{},"maintainers":[{"name":"aawwaaa","email":"aawwaaa123456@outlook.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/astralcore-syncer_1.0.2_1782031154038_0.549733075533634"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-21T02:36:35.175Z","modified":"2026-06-21T08:39:14.270Z","1.0.0":"2026-06-21T02:36:35.529Z","1.0.1":"2026-06-21T08:37:37.567Z","1.0.2":"2026-06-21T08:39:14.174Z"},"author":{"name":"aawwaaa"},"license":"MIT","description":"A lightweight library to connect objects together","maintainers":[{"name":"aawwaaa","email":"aawwaaa123456@outlook.com"}],"readme":"# Astralcore-Syncer\n\n> 不到 50 行业务代码，实现一个实时协同编辑器。\n\n`astralcore-syncer` 是一个**极轻量、可嵌入的分布式对象同步内核**。  \n它把 WebSocket、序列化、RPC、广播、资源生命周期管理全部封装成透明抽象，让你像操作本地对象一样构建实时协同应用。\n\n[![npm version](https://img.shields.io/npm/v/@aawwaaa/astralcore-syncer?style=flat-square)](https://www.npmjs.com/package/@aawwaaa/astralcore-syncer)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square)](https://www.typescriptlang.org/)\n\n---\n\n## 快速开始：实时协同编辑器（完整代码）\n\n### 效果展示\n\n![效果展示](https://raw.githubusercontent.com/aawwaaa/astralcore-syncer/main/readme/demo.gif)\n\n### 代码示例\n\n```js\n// shared.js\nimport { Resource } from \"@aawwaaa/astralcore-syncer\";\n\nexport class SharedDoc extends Resource {\n  async resCreate(text) { this.content = text }\n  resSave(client) { return { content: this.content } }\n  async resLoad({ content }) { this.content = content }\n  async modify(text) { this.content = text; this.resSync() }\n}\n\nexport const SharedDocDef = Resource.define(SharedDoc, \"shared-doc\").invoke(\"modify\");\n```\n```js\n// server.js\nimport { ResourceEnvironment, resourceEnvironmentSetImpl } from \"@aawwaaa/astralcore-syncer\";\nimport { ServerMemoryResourceManager } from \"@aawwaaa/astralcore-syncer/impl/server/resource-memory\";\nimport { ServerWebsocketRemoteManager } from \"@aawwaaa/astralcore-syncer/impl/server/remote-websocket\";\nimport express from \"express\";\nimport expressWs from \"express-ws\";\nimport { SharedDocDef } from \"./shared\";\n\nconst remote = new ServerWebsocketRemoteManager();\nconst impl = new ResourceEnvironment(remote, new ServerMemoryResourceManager());\nresourceEnvironmentSetImpl(() => impl);\n\n// 创建默认文档，生命周期到服务器停止\nconst doc = await SharedDocDef.loadOrCreate(\"main\", \"Hello, world!\");\ndoc.event.updater(\"sync\", () => console.log(\"Doc updated:\", doc.content));\n\n// 挂载 WebSocket\nconst { app } = expressWs(express());\napp.ws(\"/ws\", (ws) => remote.handleWebSocket(ws));\napp.listen(8000, () => console.log(\"Server on :8000\"));\n```\n```js\n// client.js\nimport { ResourceEnvironment, resourceEnvironmentSetImpl } from \"@aawwaaa/astralcore-syncer\";\nimport { ClientWebsocketRemoteManager } from \"@aawwaaa/astralcore-syncer/impl/client/remote-websocket\";\nimport { ClientRemoteResourceManager } from \"@aawwaaa/astralcore-syncer/impl/client/resource-remote\";\nimport { SharedDocDef } from \"./shared\";\n\nconst remote = new ClientWebsocketRemoteManager(\"/ws\").init();\nconst impl = new ResourceEnvironment(remote, new ClientRemoteResourceManager(remote));\nresourceEnvironmentSetImpl(() => impl);\n\nusing doc = await SharedDocDef.resolve(\"main\");\nconst textarea = document.getElementById(\"edit\");\ntextarea.addEventListener(\"input\", () => doc.modify(textarea.value));\ndoc.event.updater(\"sync\", () => (textarea.value = doc.content));\n// HTML: <textarea id=\"edit\" style=\"width:100%;height:200px;\"></textarea>\n```\n\n**就这样。** 多开几个浏览器窗口试试——你的编辑器已经支持多端实时同步了。\n\n---\n\n## 📦 安装\n\n```bash\nnpm install @aawwaaa/astralcore-syncer\n```\n\n---\n\n## 核心概念\n\n### Resource（资源）\n任何需要跨端同步的对象都可以定义为 `Resource`。你只需实现三个生命周期方法：\n\n| 方法 | 何时调用 | 用途 |\n|------|----------|------|\n| `resCreate(...args)` | 服务器首次创建 | 设置初始状态 |\n| `resSave(client)` | 保存/广播时调用 | 返回序列化数据（`client=true` 给客户端，可裁剪） |\n| `resLoad(data)` | 加载或收到同步时调用 | 用数据恢复状态 |\n\n`Resource` 中包含一个 `EventBus` 实例 `event`, 可用于本地或RPC事件。\n\n| 内置事件 | 时机 |\n| `sync` | 资源被修改并广播后触发 |\n| `remove` | 资源被删除时触发 |\n* `event` 支持 `on`, `updater`(初始执行一次并订阅变化, 只支持无参), `off`, `emit` 方法。\n\n### invoke & emit\n- **`invoke(\"method\")`**：把方法标记为远程可调用。客户端调用时自动 RPC 到服务器，服务器执行后结果广播给所有客户端。\n- **`emit(\"event\")`**：把自定义事件标记为可广播，任意端调用 `this.event.emit(\"event\", ...)` 即可全端同步。\n\n### 环境 (ResourceEnvironment)\n一个单例，组合了 `RemoteManager`（通信）和 `ResourceManager`（持久化）。通过 `resourceEnvironmentSetImpl` 初始化，服务端和客户端分别注入自己的实现。\n\n### 引用计数\n* 如果你只需要全局单例，你可以忽视引用计数系统。\n资源会被多个客户端和 RPC 参数持有。框架使用显式引用计数保证安全回收：\n```ts\nusing doc = await SharedDocDef.resolve(\"main\"); // 用 using 自动释放\n// 或手动 doc.refRemove()\n```\n忘记释放会导致内存泄漏。\n* `using` 需要 `typescript 5.2+` 且 `target` 为 `esnext`，也可用 `try/finally` + `doc.refRemove()` 替代。\n\n---\n\n## 扩展点（按需组合）\n\n框架只在核心同步上锁定，以下部分全部可替换：\n\n| 扩展点 | 接口 | 示例 |\n|--------|------|------|\n| **传输层** | `RemoteManager` | WebRTC、gRPC、Web Worker |\n| **持久化** | `ResourceManager` | 替换内存为 MongoDB、PostgreSQL、S3 |\n| **自定义序列化** | `SpecialObjectDefinition` | 传输 `Date`、`Buffer`、三维向量等特殊对象 |\n| **鉴权** | `callerExt` + `getCaller()` | 在连接时传入用户信息，方法内获取调用者身份 |\n| **冲突策略** | 在 `modify` 等方法内实现 | 最后写入胜出、CAS 版本检查、CRDT 算法均可组合 |\n| **离线支持** | 包装客户端 RPC 层 | 实现本地操作队列与重放 |\n\n> 这让 `astralcore-syncer` 成为一个**内核**，而非全功能平台。你可以自由嫁接任何生态工具（Yjs、JSON Patch、OT 库等）。\n\n---\n\n## 与同类方案对比\n\n| 特性 | astralcore-syncer | Meteor | Firebase | ShareDB | Yjs |\n|------|-------------------|--------|----------|---------|-----|\n| **抽象层级** | 对象方法 + 事件 | Publication/Sub | 查询订阅 | OT 文档 | CRDT 结构 |\n| **学习曲线** | 低（懂类即可） | 高（全栈平台） | 中（平台规则） | 高（OT 概念） | 中（CRDT 思维） |\n| **自托管** | ✅ 纯 Node.js | ✅ (重) | ❌ 有限 | ✅ | ✅ |\n| **冲突解决** | 留给开发者 | 乐观锁 | 安全规则 | 强制 OT | 强制 CRDT |\n| **适用场景** | 小型通用协同 | 大型实时应用 | 移动/网页应用 | 文档协作 | 离线优先文档 |\n\n---\n\n## FAQ\n\n<details>\n<summary><strong>生产环境能用吗？</strong></summary>\n可以，但需要你自己补充鉴权、冲突策略和持久化后端。项目本身被设计为稳定内核，但缺失部分由你组合。\n</details>\n\n<details>\n<summary><strong>为什么一定要手动释放引用？</strong></summary>\n分布式场景下，一个资源可能被多个客户端和回调同时引用，自动 GC 无法判断。通过显式引用计数，框架保证只在无人使用时安全卸载，避免内存泄漏。推荐使用 `using` 语法。\n</details>\n\n<details>\n<summary><strong>支持水平扩展吗？</strong></summary>\n内置的 `ServerMemoryResourceManager` 不能跨进程。你可以实现一个基于 Redis 或消息队列的 `RemoteManager` 和 `ResourceManager` 来扩展。\n</details>\n\n<details>\n<summary><strong>可以不用 WebSocket 吗？</strong></summary>\n完全可以。实现 `RemoteManager` 接口，你可以换成 WebRTC DataChannel、MQTT、甚至 HTTP 轮询。\n</details>\n\n---\n\n## 示例项目\n- [简易共享文档](https://github.com/aawwaaa/astralcore-syncer/tree/main/examples/shareddoc)\n\n---\n\n## 贡献\n欢迎提交 PR！请先打开 Issue 讨论你的想法，或直接提交 Draft PR。\n\n---\n\n## 许可证\nMIT © aawwaaa\n\n---\n\n<p align=\"center\">\n  <sub>less than 1000 lines of core. unlimited real-time possibilities.</sub>\n</p>\n","readmeFilename":"README.md"}