Transport
Use createJsonTransport for the simple JSON case:
import { createJsonTransport, mountForm } from "mlform";
mountForm(container, { transport: createJsonTransport({ endpoint: "/api/predict" }), schema,});When you need HTTP customization, build the transport explicitly:
import { createJsonTransport, mountForm } from "mlform";
mountForm(container, { schema, transport: createJsonTransport({ endpoint: "/api/predict", method: "PATCH", headers: { "x-client": "mlform" }, credentials: "include", }),});For full control, provide a transport:
mountForm(container, { schema, transport: { async submit(request) { return { reports: { prediction: await runLocalModel(request.serializedValues), }, }; }, },});Use createRoutingTransport when MLForm should pick one transport internally. Route names are internal policy ids, not part of form.submit():
import { createJsonTransport, createRoutingTransport, mountForm } from "mlform";
const transport = createRoutingTransport({ transports: { local: { async submit(request) { return { reports: { prediction: await runLocalModel(request.serializedValues) } }; }, }, remote: createJsonTransport({ endpoint: "/api/predict", }), }, selectTransport(request) { return request.serializedValues.mode === "offline" ? "local" : "remote"; },});
mountForm(container, { schema, transport,});Use createFanoutTransport when one submit should call every transport and merge the results:
import { createFanoutTransport, mountForm } from "mlform";
mountForm(container, { schema, transport: createFanoutTransport({ transports: [localModelTransport, remoteApiTransport], }),});Use createFallbackTransport when later transports should run only after earlier failures:
import { createFallbackTransport, mountForm } from "mlform";
mountForm(container, { schema, transport: createFallbackTransport({ transports: [primaryTransport, backupTransport], }),});Use middleware to compose auth, retries, circuit breaking, rate limiting, caching, deduplication, tracing, metrics, and request or response transforms:
import { createJsonTransport, mountForm, pipe, withAuth, withMetrics, withCircuitBreaker, withRateLimit, withRetry, withTracing,} from "mlform";
const transport = pipe( createJsonTransport({ endpoint: "/api/predict" }), withAuth({ type: "bearer", token: () => getAccessToken() }), withTracing({ traceparent: () => getTraceparent(), scope: "predict-form", }), withRetry({ attempts: 3 }), withCircuitBreaker({ failureThreshold: 5, resetTimeout: 60_000 }), withRateLimit({ maxConcurrent: 4, perSecond: 8 }), withMetrics({ emit(event) { console.log(event.kind, event.scope, event.durationMs); }, }),);
mountForm(container, { schema, transport,});Transport capabilities are normalized under transport.capabilities:
transport.capabilities = { modes: { submit: true, stream: false, session: false }, safety: { idempotent: false, retrySafe: false, cacheable: false, hedgeSafe: false }, limits: { maxPayloadBytes: 512_000 }, auth: { kinds: ["bearer", "transport-context"] }, delivery: { mode: "request-response", consistency: "best-effort", backpressure: "none" },};Use assertTransportCapabilities(transport, requirement, context) when orchestration or app code must reject transports that cannot satisfy a policy.
Streaming is optional. Any transport may expose stream(request) alongside submit(request).
Sessions are optional too. A transport may expose openSession(request) for long-lived channels such as WebSockets or custom RPC streams. Session transports surface progress to the engine through stream metadata and can publish incremental report-replace, report-patch, and field-update events.
Built-in protocol helpers:
createJsonTransportcreateGraphqlTransportcreateSseTransportcreateWebSocketSessionTransportcreateGrpcUnaryTransportcreateGrpcStreamTransportcreateGrpcSessionTransportcreateGrpcTransport
Shared policy adapters are pluggable. Use memory helpers in-process or bring your own distributed implementations for:
TransportCacheStoreSharedRateLimiterCircuitBreakerSharedStateTransportHealthState