PulseTrade.Comm.Spa
0.2.4-beta7
dotnet add package PulseTrade.Comm.Spa --version 0.2.4-beta7
NuGet\Install-Package PulseTrade.Comm.Spa -Version 0.2.4-beta7
<PackageReference Include="PulseTrade.Comm.Spa" Version="0.2.4-beta7" />
<PackageVersion Include="PulseTrade.Comm.Spa" Version="0.2.4-beta7" />
<PackageReference Include="PulseTrade.Comm.Spa" />
paket add PulseTrade.Comm.Spa --version 0.2.4-beta7
#r "nuget: PulseTrade.Comm.Spa, 0.2.4-beta7"
#:package PulseTrade.Comm.Spa@0.2.4-beta7
#addin nuget:?package=PulseTrade.Comm.Spa&version=0.2.4-beta7&prerelease
#tool nuget:?package=PulseTrade.Comm.Spa&version=0.2.4-beta7&prerelease
PulseTrade.Comm.Spa
英文版:README.en-us.md
PulseTrade.Comm.Spa 是從既有 PT.Comm web surface 萃取出的 Suave + WebSharper SPA POC package。目標是讓 FSI / NuGet #r 可以直接起一個最小通訊與資料互動框架,並保留 GitHub OAuth 與原本核心頁面。
縮寫與命名
| 名稱 | 正式專案 / package | 建議縮寫 | 說明 |
|---|---|---|---|
| PulseTrade.Comm | PT.Comm / PulseTrade.Comm |
PT.Comm / PTC |
MCP gateway、agent/user comm tools、upstream forwarding、PTFR tool surface 與部署邊界。PTC 是 PT.Comm 可接受且建議使用的短縮寫;避免再寫成 PTC.Comm。 |
| PulseTrade.Comm.Spa | PT.Comm.Spa / PulseTrade.Comm.Spa |
PTCS / spa |
本 package,負責 /chat、/sets、/actors UI semantics、Suave/WebSharper browser surface 與目前 package-provided ActorFabric。 |
| PulseTrade.fs.realworld | PT.fs.realworld / PulseTrade.fs.realworld |
PTFR |
真實世界交易研究 sandbox ledger;不是 comm gateway,也不是 SPA UI。 |
與 PT.Comm / PTC 的整合規劃
短期規劃:
PT.Comm直接透過 NuGet 參考本 package,不複製本 repo source,也不把 SPA UI route 搬回PT.Comm自己實作。- 同一個
PT.Commprocess 可以同時啟動 ASP.NET Core/Kestrel 的 MCP/GW listener,以及 Suave/WebSharper 的 SPA listener;兩者使用不同 port,不應互相搶 route。 /chat、/sets、/actors的 UI semantics 由本 package 擁有;/mcp、/.well-known/*、gateway diagnostics、PTFR/GitHub/FSharpDevKit forwarding 由PT.Comm擁有。- comm participant/message/thread/inbox 的 canonical runtime 應收斂到本 package 目前提供的
ActorFabric/CommHub,讓 MCPcomm_*tools 與 SPA UI 操作同一套通訊 fabric。
中長期規劃:
ActorFabric是 communication runtime concern,不是 UI concern;長期應從本 package 抽成獨立 fabric package,例如PT.Comm.Fabric。- 抽出後,本 package 只保留 browser UI、WebSharper/Suave surface、browser sync、human OAuth 與 read model presentation。
- 在 fabric package 尚未抽出前,文件用語應寫作「
PTCSpackage-provided fabric」,避免誤解為「SPA UI owns fabric」。
常見 FSI 與瀏覽器疑問請先看:Q&A.md。完整 RFC / SA / SD / WBS / Test / Runbook 文件鏈入口:doc/Traceability.md。
Package 分層與最小設計
這個 package 的 default path 必須保持最小:FSI user 應該能用少量 F# 啟動 /chat、/sets、/actors,再依需要 opt-in 進階能力。
Core
command envelope / stream key / SeqId / append page contracts / browser shell
Default local profile
in-memory Akka Journal / AutoLocal actor fabric / OAuth disabled / random port
Optional profiles
GitHub OAuth / SQL Server journal / PCSL writer node / cluster binding
Examples
Scripts/Demo/* / README / roadshow seed data
Tests
Playwright / PCSL repair / multi-instance / SQL integration
設計約束:
- Core 不要求 SQL Server、GitHub OAuth、固定 port、Fortigate、PCSL root、multi-node cluster 或 demo seed data。
CommHub/ HTTP / FSI helper 是人類好讀 facade;會改變狀態的操作應收斂到 command gateway。- PCSL 是 projection/cache,不是 canonical truth;canonical reality 是 Akka Journal。local POC 預設可以用 in-memory journal。
- GitHub OAuth、SQL Server journal、PCSL writer node、cluster/multi-instance 都是 explicit opt-in profile。
- Demo scripts 只屬 examples;demo-only helper 與 seed data 不進 runtime core。
目前 package boundary audit 見:doc/PackageBoundaryAudit.md。
保留路由
/chat/sets/actors- 透過
CommHub.RegisterAppendPage、Web page creator 或 sharded append-page intent 動態註冊的 append pages,例如/fcell-chat、/fcell-list、/fcell-grid、actor-address Argu fCell chat pages
OAuth
- GitHub browser OAuth 保留
/chat/login、/chat/oauth/callback、/chat/logout。 - OAuth secret 只用檔案路徑傳入,例如
--client-secret-path或ClientSecretPath;不要把 secret value 寫進 script、文件、log 或 repo。
從原始碼啟動
dotnet run --project .\PulseTrade.Comm.Spa.fsproj -c Release -- `
--port 8897 `
--pcsl-root .\.pcsl\run
FSI 基本形狀
#r "nuget: PulseTrade.Comm.Spa, 0.2.4-beta7"
open PulseTrade.Comm.Spa
let minimalApp = Server.startMinimal()
printfn "%s/chat" minimalApp.Url
// 需要指定 PCSL root 時,使用 seed-free hub + random local port。
let hub = CommHub.createEmptyWithPcslRoot @".\.pcsl\fsi"
let pcslApp =
hub
|> ServerOptions.localRandomWithHub
|> Server.start
printfn "%s/chat" pcslApp.Url
pcslApp.Hub.RegisterParticipant
{ ParticipantId = "agent.demo"
DisplayName = Some "Demo Agent"
Kind = Some "agent"
Labels = Some [ "demo" ] }
|> ignore
pcslApp.Hub.AppendSet
{ Keys = [ "user.github.alice"; "agent.demo" ]
SetName = "chat"
Value = "hello"
Tags = Some [ "fsi" ] }
|> ignore
// minimalApp.Dispose() / pcslApp.Dispose() 會停止 Suave。
Persistent Journal / Replay
預設 profile 使用 in-memory Akka Journal,適合 FSI POC 與單次 demo。需要 persistent journal 時,使用 journal profile 明確 opt-in;PCSL 仍是 projection/cache,actor recovery 會從 Akka Journal replay 後補回 fresh PCSL backend。Scripts/verify.sqlJournalReplay.fsx 與 Scripts/Demo/09-persistent-journal-replay.fsx 會用 SQL Server journal 實際寫入,然後換 fresh PCSL root 由 journal replay 補回 page/key/value projection。
#r "nuget: PulseTrade.Comm.Spa, 0.2.4-beta7"
open PulseTrade.Comm.Spa
let journal =
Journal.sqlServerLocal(dbName = "PulseTradeCommSpa")
// 只建立 database;journal/snapshot tables 由 Akka.Persistence.Sql 啟動時 auto-initialize。
let bootstrap = Journal.ensureSqlServerDatabase journal
printfn "journal db ready: %s created=%b" bootstrap.DatabaseName bootstrap.Created
let runtime = Journal.checkRuntimeHealth journal
printfn "journal open=%A query=%A error=%A" runtime.ConnectionOpenSucceeded runtime.QuerySucceeded runtime.LastError
let queryAdapter = Journal.sqlServerQueryHealthAdapter()
let fabricOptions =
CommSpaActorFabricOptions.defaults
|> CommSpaActorFabricOptions.withJournal journal
|> CommSpaActorFabricOptions.withJournalQueryAdapter queryAdapter
let persistentApp =
ServerOptions.localRandom()
|> ServerOptions.withActorFabricOptions fabricOptions
|> Server.start
printfn "%s/chat" persistentApp.Url
// Site 啟動後也可查詢:
// GET <app.Url>/healthz // cheap provider metadata
// GET <app.Url>/healthz.journal // explicit runtime + journal/projection reality probe
Durable ingress retry / dead-letter policy
DurableIngress 的 retry/dead-letter policy 是 runtime profile 設定,不代表 browser pending retry,也不宣稱 exactly-once。分層語意:
ResendIntervalMin/ResendIntervalMax:delivery protocol 的 resend 節奏。CommandMaxAge:ingress 邊界可接受的 command 年齡。CommandDeadline/DeadlineAtUtc:command 到期後應 reject 或進 failed/dead-letter task status。PoisonAttemptHint:提供 operator/diagnostics 的 poison threshold hint,不是唯一的重送次數保證。DeadLetterStreamKey:後續 durable profile 投影 failed/dead-letter 的 stream key。
let retry =
{ DurableDeliveryRetryOptions.defaults with
ResendIntervalMin = TimeSpan.FromMilliseconds 250.0
ResendIntervalMax = TimeSpan.FromSeconds 3.0
CommandMaxAge = Some(TimeSpan.FromMinutes 5.0)
CommandDeadline = Some(TimeSpan.FromSeconds 30.0)
PoisonAttemptHint = Some 7
DeadLetterStreamKey = "ptcs.dead-letter" }
let ingressOptions =
{ CommSpaDurableIngressOptions.volatileLocal() with
Mode = DurableIngressMode.DurableDelivery
ProfileId = "local-durable-policy"
Retry = retry }
|> CommSpaDurableIngressOptions.normalize
目前 CommSpaDurableIngress.createVolatile 會 expose normalized retry options,並在 ingress 邊界拒絕 expired CommandMaxAge / DeadlineAtUtc / CommandDeadline。SpawnAsync 建立的 task ticket 可由 CompleteAsync / FailAsync 轉成 completed/failed 狀態;ActorArgu.sendDurableAsync 已使用這個 boundary,讓 actor-address raw argu command 先 accepted,再依 reply/error 完成或失敗 ticket。
CommSpaMessageFabric.createDurable hub ingress 會建立 durable wrapper:RegisterParticipant、UpsertGroup、Send、Ack、Drain 先 accepted ticket,再執行既有 MessageFabric projection,成功後 CompleteAsync。Poll/Wait/List 仍讀 projection/fast path。既有 CommSpaMessageFabric.create hub 不變,適合 local/POC 或不需要 durable admission 的 caller。
真正 Akka.Delivery / Sharding.Delivery producer queue、restart retry 與 provider-specific dead-letter projection 仍屬後續 durable profile 切片。
RFC-0005 Dynamic PCSL / Task Result / Agent Task POC
RFC-SPA-UPSTREAM-0005 的 first-slice package consumer gate 是:
dotnet fsi --exec .\Scripts\verify.rfc0005PackageConsumer.fsx
這支腳本使用 defaultArgumentsText -> ParseLine -> Argu,可在 Visual Studio FSI 直接修改字串參數後框選執行。它覆蓋:
- journal namespace / persistence id prefix / projection id / projection epoch;
- same-journal projection rebuild plan;
- journal merge dry-run first-slice policy;
- volatile task result vault retention / max result bytes;
CommSpaDurableMessageFabric.SubmitAgentTaskDurableAsyncagent-task handoff;MessageFabricGatewayConsumerContract的 PTC.GW / PTCS ownership boundary。
它不啟動 web server、不做 OAuth、不宣稱 crash-durable result vault、runtime hot switch 或 PTC.GW 專案端已完成整合。
固定或隨機 Port
let minimalApp = Server.startMinimal()
printfn "%s/chat" minimalApp.Url
let randomOptions = ServerOptions.localRandom()
let randomApp = Server.start randomOptions
printfn "%s/chat" randomApp.Url
let fixedOptions =
ServerOptions.defaults
|> ServerOptions.withWebBinding (WebBinding.fixedPort 81)
let fixedApp = Server.start fixedOptions
printfn "%s/chat" fixedApp.Url
CQRS Snapshot 範例
let streamKey =
CommSpaStreamKey.forSet "chat" [ "agent.demo"; "user.github.alice" ]
let snapshot =
app.Hub.StreamSnapshot
{ StreamKey = streamKey
DesiredTailCount = 200
BrowserWatermark = None
IncludeMetadata = true }
snapshot.MissingTailEvents
|> List.iter (fun event -> printfn "%d %s" event.Sequence event.Payload)
目前 HTTP restart/reconnect POC surface:
POST /sync/api/snapshot- request body:
SnapshotRequest - response body:
SnapshotReply
Shared Site 模式
同一個 process 裡,多個 library 可以用相同 normalized options 共用同一個站台:
let sharedHub = CommHub.createWithPcslRoot @".\.pcsl\shared"
let options =
{ Host = "0.0.0.0"
Port = 81
Hub = sharedHub
OAuth = OAuth.disabled
ActorFabric = AutoLocal }
let appFromA = Server.startShared options
let appFromB = Server.startShared options
obj.ReferenceEquals(appFromA.Hub, appFromB.Hub) // true
appFromA.Stop()
appFromB.Stop()
fCell2 Key/Value 形狀
相關 source project:
G:\coldfar_py\sharftrade9\Libs5\KServer\FCell2\FAkka.FCell2.fsprojG:\coldfar_py\sharftrade9\Libs5\KServer\FCell2.WebSharper\FAkka.FCell2.WebSharper.fsproj
FSI / server 端使用 canonical fCell2<string>。Browser-side WebSharper 透過 FAkka.FCell2.WebSharper 共用 vocabulary;該 package 定義 JS,所以 D 在 JS 端編譯為 float,非 JS 端則保留 decimal。
Web 端可以直接在 /chat 頁面上方建立 fCell 系列頁面,不需要先在 FSI seed:
- Shape 選
FCell Chat、FCell List、FCell Grid或Actor Argu; - 填入 page id / title 後按 Add;
- 進入新 tab 後,左側用 Add 建立 key;
- chat/list/grid 的 append 會寫到目前 key;
Actor Argu的 key 是 actor address,輸入框內容是 Argu-style string,server 會 route 到該 actor,reply 會回到同 key 的 fCell chat history。
#r "nuget: PulseTrade.Comm.Spa, 0.2.4-beta7"
open PulseTrade.Comm.Spa
open PersistedConcurrentSortedList.Type
open FAkka.FCell2
let fcellHub = CommHub.createWithPcslRoot @".\.pcsl\fcell"
let app =
Server.start
{ Host = "127.0.0.1"
Port = 8897
Hub = fcellHub
OAuth = OAuth.disabled
ActorFabric = AutoLocal }
app.Hub.RegisterAppendPage(AppendPage.fCellChat "fcell-chat" "FCell Chat" "fcell chat") |> ignore
app.Hub.RegisterAppendPage(AppendPage.fCellList "fcell-list" "FCell List" "fcell list") |> ignore
app.Hub.RegisterAppendPage(AppendPage.fCellGrid "fcell-grid" "FCell Grid" "fcell grid") |> ignore
let s text = fCell2<string>.S text
let a values = fCell2<string>.A values
let t fields = fCell2<string>.T(Map.ofList fields)
app.Hub.AppendPageValue
{ PageId = "fcell-chat"
Keys = [ s "Aster" ]
Value = s "orz"
Direction = Some "inbound-message"
Tags = Some [ "fsi" ] }
|> ignore
app.Hub.AppendPageValue
{ PageId = "fcell-list"
Keys = [ s "Aster" ]
Value = a [| s "orz2"; s "orz3" |]
Direction = None
Tags = Some [ "fsi" ] }
|> ignore
app.Hub.AppendPageValue
{ PageId = "fcell-grid"
Keys = [ s "Aster" ]
Value =
a [| t [ "column3", s "orz"; "column1", s "orz3" ]
t [ "column1", s "orz"; "column2", s "orz3" ] |]
Direction = None
Tags = Some [ "fsi" ] }
|> ignore
Browser-side WebSharper 形狀
open PersistedConcurrentSortedList.Type
open FAkka.FCell2
let tab = fCell2<string>.S "tab.chat"
let value =
fCell2<string>.T(
Map.ofList
[ "body", fCell2<string>.S "hello from browser"
"score", fCell2<string>.D 12.34 ])
let keyText = FCell2Text.key tab
let valueText = value.toJsonString()
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Akka (>= 1.5.69)
- Akka.Cluster (>= 1.5.69)
- Akka.Cluster.Sharding (>= 1.5.69)
- Akka.Persistence (>= 1.5.69)
- Akka.Persistence.Sql (>= 1.5.67)
- FAkka.Argu (>= 10.1.301)
- FAkka.FCell2 (>= 10.1.301)
- FAkka.WebSocket (>= 1.569.101.301)
- FSharp.Core (>= 10.1.301)
- Microsoft.Data.SqlClient (>= 7.0.1)
- PersistedConcurrentSortedList (>= 10.1.301)
- Suave (>= 3.4.3)
- System.Data.SqlClient (>= 4.9.1)
- WebSharper (>= 10.1.5.674)
- WebSharper.FSharp (>= 10.1.5.674)
- WebSharper.UI (>= 10.1.4.674)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on PulseTrade.Comm.Spa:
| Package | Downloads |
|---|---|
|
PulseTrade.Comm.GW.PTCS
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.2.4-beta7 | 17 | 6/21/2026 |
| 0.2.4-beta6 | 17 | 6/21/2026 |
| 0.2.3-beta5 | 39 | 6/20/2026 |
| 0.1.0-preview6 | 46 | 6/20/2026 |
| 0.1.0-preview4 | 49 | 6/5/2026 |
| 0.1.0-preview3 | 47 | 6/5/2026 |
| 0.1.0-preview2 | 45 | 6/5/2026 |
| 0.1.0-preview1 | 53 | 6/4/2026 |