Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/compile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,8 @@ jobs:
calcit calcit.cirru js
&& yarn vite build
&& calcit calcit.cirru --entry server js
&& node --check js-out/cumulo-reel.app.server.mjs
&& node --input-type=module -e 'await import("./js-out/cumulo-reel.app.server.mjs")'

- name: Exercise Node server runtime
run: node --test scripts/server-runtime.test.mjs
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

.compact-inc.cirru
.calcit-error.cirru
.calcit/

js-out/
node_modules/
Expand Down
2 changes: 2 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
nodeLinker: node-modules
npmPreapprovedPackages:
- "@calcit/procs@0.20.0"
296 changes: 249 additions & 47 deletions calcit.cirru

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion config/calcit-quality.cirru

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions deps.cirru
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@

{} (:calcit-version |0.19.1)
{} (:calcit-version |0.20.0)
:version |0.0.45
:dependencies $ {} (|Cumulo/cumulo-util.calcit |0.0.23)
|Respo/respo-message.calcit |0.0.28
|Respo/respo-ui.calcit |0.7.31
|Respo/respo.calcit |0.16.113
|calcit-lang/calcit-wss |0.2.32
|calcit-lang/calcit.std |0.2.35
|calcit-lang/js-ffi |0.1.36
|calcit-lang/recollect |0.0.53
|mvc-works/ws-edn.calcit |0.0.31
|mvc-works/ws-edn.calcit |0.0.32
151 changes: 151 additions & 0 deletions docs/architectures/js-server-host.cirru
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
{}
:schema-version 1
:feature 'js-server-host
:doc "|把 Cumulo Reel 的 server 宿主边界迁到 Node JS,保持 Number session ID、原始 Cirru EDN 消息、断线语义和本地日期备份命名。"
:roots $ #{} 'cumulo-reel.app.server/run-server!
:definitions $ {}
'cumulo-reel.app.server/run-server! $ {} (:mode :external) (:kind :fn)
:schema $ :: 'Fn $ {}
:args $ [] 'Number
:return 'ws-edn.server/NodeWebSocketServerHost
:features $ #{} :js-ffi
'cumulo-reel.app.server/get-backup-path! $ {} (:mode :external) (:kind :fn)
:schema $ :: 'Fn $ {}
:args $ []
:return 'String
'cumulo-reel.app.server/persist-db! $ {} (:mode :external) (:kind :fn)
:schema $ :: 'Fn $ {}
:args $ []
:return 'Unit
'cumulo-reel.app.server/main! $ {} (:mode :external) (:kind :fn)
:schema $ :: 'Fn $ {}
:args $ []
:return 'Unit
:features $ #{} :js-ffi
'cumulo-reel.app.server-ws/SocketEvent $ {}
:mode :ensure
:kind :data
:doc "|适配器向业务层报告的连接、原始文本消息、断线和二进制通知。"
:schema $ :: 'Enum
:code $ quote $ defenum SocketEvent (:connect 'Number) (:message 'Number 'String) (:disconnect 'Number) (:blob 'Number)
'cumulo-reel.app.server-ws/serve! $ {}
:mode :ensure
:kind :fn
:doc "|启动 Node WebSocket server,并把宿主事件转为保持原有 SID 语义的 SocketEvent。"
:params $ [] 'port 'on-event
:schema $ :: 'Fn $ {}
:args $ [] 'Number $ :: 'Fn $ {}
:args $ [] 'cumulo-reel.app.server-ws/SocketEvent
:return 'Unit
:return 'ws-edn.server/NodeWebSocketServerHost
:features $ #{} :js-ffi
'cumulo-reel.app.server-ws/send! $ {}
:mode :ensure
:kind :fn
:doc "|按 Number session ID 发送原始 Cirru EDN 文本。"
:params $ [] 'sid 'message
:schema $ :: 'Fn $ {}
:args $ [] 'Number 'String
:return 'Unit
:features $ #{} :js-ffi
'cumulo-reel.app.server-ws/close-session! $ {}
:mode :ensure
:kind :fn
:doc "|连接关闭或错误只通知业务层一次断线。"
:params $ [] 'sid 'on-event
:schema $ :: 'Fn $ {}
:args $ [] 'Number $ :: 'Fn $ {}
:args $ [] 'cumulo-reel.app.server-ws/SocketEvent
:return 'Unit
:return 'Unit
'cumulo-reel.app.server-ws/each! $ {}
:mode :ensure
:kind :fn
:doc "|只遍历当前仍连接的 Number session ID。"
:params $ [] 'handler
:schema $ :: 'Fn $ {}
:args $ [] $ :: 'Fn $ {}
:args $ [] 'Number
:return 'Unit
:return 'Unit
'cumulo-reel.app.server-host/LocalDateHost $ {}
:mode :ensure
:kind :data
:doc "|仅暴露备份命名所需的宿主本地月、日读取。"
:schema $ :: 'Trait
:code $ quote $ deftrait LocalDateHost
.get-month $ :: 'Fn $ {}
:args $ [] 'cumulo-reel.app.server-host/LocalDateHost
:return 'Number
.get-date $ :: 'Fn $ {}
:args $ [] 'cumulo-reel.app.server-host/LocalDateHost
:return 'Number
'cumulo-reel.app.server-host/NodeProcessHost $ {}
:mode :ensure
:kind :data
:doc "|仅用于订阅 Node 的 SIGINT 信号。"
:schema $ :: 'Trait
:code $ quote $ deftrait NodeProcessHost
.on $ :: 'Fn $ {}
:args $ [] 'cumulo-reel.app.server-host/NodeProcessHost 'String $ :: 'Fn $ {}
:args $ []
:return 'Unit
:return 'cumulo-reel.app.server-host/NodeProcessHost
'cumulo-reel.app.server-host/local-month-day! $ {}
:mode :ensure
:kind :fn
:doc "|读取 Node 宿主本地时区的月份与日期。"
:params $ []
:schema $ :: 'Fn $ {}
:args $ []
:return $ :: 'Map 'Tag 'Number
:features $ #{} :js-ffi
'cumulo-reel.app.server-host/check-write-text! $ {}
:mode :ensure
:kind :fn
:doc "|内容未变时跳过写入,按需建立一级备份目录。"
:params $ [] 'path 'content
:schema $ :: 'Fn $ {}
:args $ [] 'String 'String
:return 'Unit
:features $ #{} :js-ffi
'cumulo-reel.app.server-host/ensure-directory! $ {}
:mode :ensure
:kind :fn
:doc "|递归建立缺少的父目录,避免备份月份目录不存在时写入失败。"
:params $ [] 'directory
:schema $ :: 'Fn $ {}
:args $ [] 'String
:return 'Unit
:features $ #{} :js-ffi
'cumulo-reel.app.server-host/every! $ {}
:mode :ensure
:kind :fn
:doc "|由 Node 宿主重复调度 Unit 回调。"
:params $ [] 'delay 'callback
:schema $ :: 'Fn $ {}
:args $ [] 'Number $ :: 'Fn $ {}
:args $ []
:return 'Unit
:return 'Unit
:features $ #{} :js-ffi
'cumulo-reel.app.server-host/on-interrupt! $ {}
:mode :ensure
:kind :fn
:doc "|将 SIGINT 交给业务退出回调,而不依赖 native CLI 注入。"
:params $ [] 'callback
:schema $ :: 'Fn $ {}
:args $ [] $ :: 'Fn $ {}
:args $ []
:return 'Unit
:return 'Unit
:features $ #{} :js-ffi
:edges $ #{}
:: :call 'cumulo-reel.app.server/run-server! 'cumulo-reel.app.server-ws/serve!
:: :type 'cumulo-reel.app.server-ws/serve! 'cumulo-reel.app.server-ws/SocketEvent
:: :call 'cumulo-reel.app.server-ws/serve! 'cumulo-reel.app.server-ws/close-session!
:: :call 'cumulo-reel.app.server/get-backup-path! 'cumulo-reel.app.server-host/local-month-day!
:: :call 'cumulo-reel.app.server/persist-db! 'cumulo-reel.app.server-host/check-write-text!
:: :call 'cumulo-reel.app.server-host/check-write-text! 'cumulo-reel.app.server-host/ensure-directory!
:: :call 'cumulo-reel.app.server/main! 'cumulo-reel.app.server-host/on-interrupt!
:: :type 'cumulo-reel.app.server-host/on-interrupt! 'cumulo-reel.app.server-host/NodeProcessHost
26 changes: 26 additions & 0 deletions docs/js-server-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Cumulo Reel 的 Node JS server 迁移

服务端入口使用 Calcit 的 `:server` JS 模式。业务层继续使用数字 session ID、原始 Cirru EDN 消息和 `:connect`/`:message`/`:disconnect`/`:blob` 事件;WebSocket、文件、定时器和本地日期只在宿主适配层接触 Node API。密码 MD5 与随机颜色保留原调用语义,分别由现有 npm 包提供。

`cumulo-reel.app.server-ws/serve!` 只负责传输,不提前解析消息。业务层仍通过 `try-parse-cirru-edn-as` 验证客户端操作,并按原逻辑返回错误。连接关闭与错误复用 `close-session!`,只发送一次断线事件。数字 ID 从 1 起分配,与原 native WebSocket 服务一致。

持久化仍采用 Cirru EDN。`check-write-text!` 在内容相同时跳过写入,并在需要时建立备份目录;备份路径沿用宿主本地月份和日期,格式为 `backups/<month>/<day>-snapshot.cirru`。服务端时间戳仍是毫秒。

当前直接依赖 `js-ffi@0.1.36`,与现有上游模块的固定版本一致,保留 CI 的 `caps --strict --ci` 门禁。已确认迁移所需的 Node 文件、路径、时间及契约 API 在该版本存在;上游模块统一升级后再推进到更新的 `js-ffi` 版本。

质量基线仅增加四个宿主对象断言:一个 `Date` 到 `LocalDateHost`,一个 `process` 到 `NodeProcessHost`,以及 WebSocket server 与 socket 到对应的 host trait。外部 npm 返回值用 `expect-string` 检查,消息中的布尔值用 `expect-bool` 检查,不再增加 `unsafe-coerce`。这些断言应保持在适配层,不能向业务定义扩散。退出信号由 `process.on('SIGINT')` 注册,不再使用 native CLI 的 `on-control-c`。

验收命令:

```bash
caps --calcit-version 0.20.0 --strict --ci
corepack yarn install --immutable
calcit calcit.cirru --check-only
calcit --entry server calcit.cirru --check-only
calcit calcit.cirru test
calcit --entry server calcit.cirru analyze quality --baseline config/calcit-quality.cirru
calcit --entry server calcit.cirru js
node --check js-out/cumulo-reel.app.server.mjs
```

静态验收之后,还必须在 Node 进程中验证连接、数字 ID、文本与二进制消息、断线、EDN 非法输入、重连、广播、持久化和备份路径。`node --check` 只能证明生成文件的语法,不代表服务端已通过运行验收。
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"version": "0.0.45",
"dependencies": {
"@calcit/procs": "0.19.1"
"@calcit/procs": "0.20.0",
"md5": "^2.3.0",
"nanoid": "6.0.1",
"randomcolor": "^0.6.2",
"ws": "^8.13.0"
},
"scripts": {
"check-quality": "calcit calcit.cirru analyze quality --baseline config/calcit-quality.cirru",
Expand All @@ -11,11 +15,8 @@
"watch-page": "calcit calcit.cirru js -w"
},
"devDependencies": {
"md5": "^2.3.0",
"randomcolor": "^0.6.2",
"shortid": "^2.2.16",
"vite": "^8.2.2",
"ws": "^8.13.0"
"vite": "^8.2.2"
},
"packageManager": "yarn@4.18.0"
}
Loading
Loading