Skip to Content
概念持久儲存

持久儲存

每個生成的 handler 都會收到一個凍結的 storage 介面,背後是一個平台擁有的 SQLite Durable Object:binding TEAMSYNC_DEPARTMENT_STORAGE、class TeamSyncDepartmentStorage、實例名 department。這些名稱一旦佈建就是永久 ABI — 部署管線恰好釘住這一個 binding 與一個宣告式 SQLite export,結算回讀拒絕其他任何組合。

提交的程式碼永遠拿不到原始 env、不能匯入 cloudflare:workers、也不能選擇 binding、class 名稱、儲存 schema 或生命週期宣告。隔離:由公司+部門推導的 script 名稱隔離部門;沒有依環境區分的 namespace — staging 與 production 的 dispatcher 會到達同一組 Worker 位置(架構)。對同一 script 上傳新修訂版保留 Durable Object 及其資料(已有真實環境驗證:修訂版 A 寫入的紀錄在修訂版 B 下讀得回來)。平台維運者把 Worker 搬到另一個位置時,Durable Object 的全部內容會隨之搬移(搬移 Worker)。地端由 workerd 執行同一個 class,SQLite 存在 host 的本機磁碟;部署會停止舊行程,並在同一份儲存上啟動新行程。已刪除部門的 Worker 退役時,雲端會刪除其 Durable Object 與全部資料,地端 host 則保留資料(退役)。

API

所有 key 透明地掛在租戶看不到、也逃不出去的 user: 前綴命名空間下。

方法回傳
await storage.get(key){found: false} | {found: true, value}
await storage.create(key, value){created: true, value} | {created: false, existing} — 交易性、同 key 競爭時原子
await storage.replace(key, value){replaced: true, value} | {replaced: false} — 交易性
await storage.delete(key){deleted: boolean}
await storage.list({prefix?, startAfter?, limit?}){items: [{key, value}], nextStartAfter: string | null}

結果(與 list 的項目)都是深度凍結的。範本示範了預期的用法:有界游標分頁而非全域計數、create 遇 UUID 碰撞的小型重試迴圈、以及前綴分域的 key(users/…)。

上限

上限值
Key(以及 prefix/startAfter)≤512 UTF-8 位元組,非空(options 可為空字串)
Value≤64 KiB 正規 JSON
Value 形狀僅限普通物件(Object.prototype/null proto)與稠密陣列;不接受函式/symbol/循環;數字有限;深度 <32;≤10 000 節點
list limit預設 50,最大 100

違規會對 handler 丟 TypeError("TeamSync storage: …") — 是契約錯誤,不是平台的 500。

分頁

list 內部抓 limit + 1 筆:還有更多列時 nextStartAfter 是最後回傳的 key,否則為 null。把它當 startAfter 傳回去即可續頁。回傳的 key 已去除前綴;runtime 在進入時重新加前綴,所以游標無法指到別的命名空間。

同一個 Durable Object 裡還有什麼

沒有任何 handler facade 對外開放的平台表:執行期設定單例(執行期設定);Worker 機密的值,只能透過 context.secrets.get 依名稱讀取(Worker 機密);Worker 全域的存取政策單例(存取模式);每個保留中的 release generation 的操作設定,包括操作金鑰摘要與曾經擁有金鑰的操作清單(逐操作存取);已交付的服務帳號憑證及其快取的 token,只供 context.teamsync.authHeaders() 使用(服務帳號);退役鎖定;以及控制回執帳本(稽核)。它們只能經由簽章的 purpose=control 通道變更(token 快取由 runtime 自行更新);儲存介面的 user: 前綴讓租戶 key 在結構上就不相交。handler 程式碼與 runtime 共用同一個 isolate,所以「不對外開放」是 facade 層的保證,不是沙箱:請把部署到 Worker 的每一行程式碼都視為能讀到 Worker 持有的一切。

沒有執行期 R2、D1、KV 或 service binding — R2 純屬控制面儲存。在雲端,部門程式碼的對外 fetch() 不受限制(任何綁定的位置上都沒有 Outbound Worker),只受每次呼叫預設 500 個子請求與 50 ms CPU 的上限約束(限制);在地端,對外連線排除 loopback、link-local 與 edge 控制子網路,界限則是牆鐘時間預算與每個行程共用的子請求額度(地端)。在兩種 runtime 上,原始 TCP connect() 都不可用,因為 builder 不接受 cloudflare:sockets。

本機開發(teamsync-worker dev)在 workerd 跑同一個 class,SQLite 持久化在 .teamsync/dev-storage,所以儲存行為 — 包括 isolate 銷毀/重建後的持久性 — 可以離線測試;.teamsync/dev-secrets.json 存在時,dev 也會從中載入本機的 Worker 機密。

Last updated on