Skip to content

feat(ir)!: say what starts and ends a stream or a page walk - #1367

Open
OmarAlJarrah wants to merge 7 commits into
feat/ir-inbound-operationsfrom
feat/ir-stream-and-page-inputs
Open

OmarAlJarrah wants to merge 7 commits into
feat/ir-inbound-operationsfrom
feat/ir-stream-and-page-inputs

Conversation

@OmarAlJarrah

@OmarAlJarrah OmarAlJarrah commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Summary

Declared pagination and event-stream APIs state facts the IR had no field for, so every emitter would hard-code or guess them.

  • Pagination inputs in the body, a page count and a stop rule (ir: pagination can't address a request-body input, a page count, or a stop condition #665).
    • ParamPath becomes InputPath, whose Param is optional: nil roots the path in the request body, where a search endpoint takes its cursor or page size. Parameter identity was already a ParamID (ir: parameters are bound by name, so same-named parameters in different locations are ambiguous #596).
    • Pagination gains TotalPages, counting pages where TotalCount counts items. It also gains Stop, the conditions that end iteration, a closed empty | no_next | short | total, any one ending the loop. An empty Stop states no rule, and the plan guesses none.
    • pass.Validate holds a path naming a parameter to the operation's own, as it held ParamPath. irverify reports a path naming nothing (ir/input-path-empty) and an undeclared stop (ir/unknown-page-stop).
    • The plan's PagePlan carries the input path and the stop rules.
  • A stream's terminator (ir: an event stream's terminator value has no home #666). StreamDetail.Terminator holds the frame data that ends a stream verbatim, a sentinel that is no element, as an SSE data: [DONE] is. It sits beside Variant.Event.Terminal, which marks an event that is a variant. The stream plan compares each frame's data with it, and a frame matching it ends the stream undecoded.
  • A stream sent only when asked (ir: a response that streams only when a request field asks for it has no home #667). StreamDetail.Condition holds the request input and the value asking for the stream, as a body's stream: true does. The operation otherwise returns its non-streaming contents. The issue's second option was weighed and declined: lowering the operation to two operations sharing one route mints an operation no position names, and a selector type the document never wrote. The stream plan offers the pair as two methods of one operation, each setting the input itself.

docs/ir/interaction-patterns.md states each rule, and docs/compilers/planned-lowerings.md notes that TypeSpec's body-level pagination decorators fill the body form.

No compiler fills these yet. The OpenAPI compiler reads no pagination or stream extension, which a promotion would do. On the golden spec (spec @319dc756), one operation offers JSON beside an event stream, none selects the stream with a body flag, no stream ends on [DONE], and two operations carry a pagination extension. That is the measurement behind #665's P2 and the others' P3.

Closes #665
Closes #666
Closes #667

Breaking

IRVersion moves to 0.33.0, after #1366's 0.32.0. A consumer pinned to the generation before this one refuses a document that sets any of these, and finds a body input's param absent where it required one. In Go, ParamPath is now InputPath, and its Param is a *ParamID.

Stack

Base: #1366, on #1361 and the rest of the IR stack. Merge in order.

Test plan

  • go test on ir/..., pass/..., compilers/openapi/e2e, internal/... and cmd/.... The shape pin and the census were regenerated.
  • New tests:
    • irverify.TestVerify_AnInputPathNamesARequestValue: a parameter, a body member, and neither, as a cursor and as a stream's condition.
    • irverify.TestVerify_APaginationStopIsOneIRDeclares.
    • StreamCondition.Value joins the value-carrier cases, so the payload rules reach it.
    • PageStop is classified among the IR's named string types.
  • Push checklist: subject within 72 characters with the number, no artifact hits, skills_lint clean.
  • CI.

Declared pagination and event-stream APIs state facts the IR had no
field for, so each emitter would guess them.

- ParamPath becomes InputPath, whose Param is optional: nil roots the
  path in the request body, where a search endpoint takes its cursor or
  page size. pass.Validate holds a path naming a parameter to the
  operation's own, as it held ParamPath, and irverify reports one naming
  nothing (ir/input-path-empty).
- Pagination gains TotalPages, counting pages where TotalCount counts
  items, and Stop, the closed empty | no_next | short | total conditions
  that end iteration (ir/unknown-page-stop for another).
- StreamDetail gains Terminator, the frame data that ends a stream and
  is no element, as an SSE data: [DONE] is, and Condition, the request
  input and value that ask for a stream an operation sends only when
  asked. Lowering such an operation to two sharing one route was
  weighed and declined: it mints an operation no position names.
- The stream plan reads both: a terminator frame ends the stream
  undecoded, and a conditional stream is two methods of one operation.

No compiler fills these yet; they are the shape a declaring format or a
promotion fills.

BREAKING CHANGE: IRVersion moves to 0.31.0. A consumer pinned to 0.30.0
refuses a document that sets one of these, and finds a body input's
param absent where it required one. ParamPath is now InputPath in Go.
The validator reads the input a request or response stream is selected
by as a reference to the operation's own parameters, but no test built a
stream with a condition, so neither read was reached.
The JSON contract test kept the name and wording of ParamPath, the type
InputPath replaced.
… feat/ir-stream-and-page-inputs

# Conflicts:
#	ir/document.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant