Files
foundry-core/internal/platform/canvas_model_stream.go
T
codex-bot e38254821a
Foundry Go checks / check (push) Failing after 10m37s
Foundry Go checks / check (pull_request) Failing after 10m45s
Stream negotiated canonical model reads with snapshot framing
2026-11-09 02:01:46 +08:00

92 lines
2.7 KiB
Go

package platform
import (
"context"
"encoding/json"
"net/http"
)
const modelStreamType = "application/vnd.foundry.canvas-model+ndjson;version=1"
// The caller captures a coherent model while holding the workspace lock, then
// releases its transaction before writing. A slow reader must not hold a writer.
// This streams transfer/encoding; it does not stream database row acquisition.
func writeCanvasModelStream(ctx context.Context, w http.ResponseWriter, model canvasModel) {
token := newModelID()
if token == "" {
reject(w, 500, "model_stream_unavailable")
return
}
view := model.view(nil)
header := response{"streamVersion": 1, "kind": "header", "sequence": 0, "snapshotToken": token,
"documentFormatVersion": canvasModelFormat, "projectId": model.Root, "canvases": view.Canvases,
"versions": view.Versions, "tombstones": view.Tombstones, "removedCanvasIds": view.Removed, "elementCount": len(view.Elements)}
const maxLine = 8 * 1024 * 1024
const maxBytes = 64 * 1024 * 1024
sequence, total := 0, 0
started := false
write := func(value response) bool {
if ctx.Err() != nil {
return false
}
raw, err := json.Marshal(value)
if err != nil || len(raw) > maxLine || total+len(raw)+1 > maxBytes || sequence >= 4096 {
if !started {
reject(w, 413, "model_stream_budget")
}
return false
}
if !started {
w.Header().Set("Content-Type", modelStreamType)
w.Header().Set("Cache-Control", "no-store")
w.Header().Add("Vary", "Accept")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(http.StatusOK)
started = true
}
total += len(raw) + 1
if n, writeErr := w.Write(append(raw, '\n')); writeErr != nil || n != len(raw)+1 {
return false
}
sequence++
// A missing terminal record is an explicit incomplete stream at the client.
// Never append an unrelated JSON error after streaming has begun.
return http.NewResponseController(w).Flush() == nil
}
if !write(header) {
return
}
batch := []map[string]any{}
batchBytes := 0
flush := func() bool {
if len(batch) == 0 {
return true
}
if !write(response{"streamVersion": 1, "kind": "elements", "sequence": sequence, "snapshotToken": token, "elements": batch}) {
return false
}
batch, batchBytes = []map[string]any{}, 0
return true
}
for _, element := range view.Elements {
if ctx.Err() != nil {
return
}
raw, err := json.Marshal(element)
if err != nil {
return
}
if len(batch) > 0 && (len(batch) == 64 || batchBytes+len(raw) > 512*1024) {
if !flush() {
return
}
}
batch = append(batch, element)
batchBytes += len(raw)
}
if !flush() {
return
}
write(response{"streamVersion": 1, "kind": "complete", "sequence": sequence, "snapshotToken": token, "elementCount": len(view.Elements)})
}