Skip to content

feat(core,azure): poll Azure ARM long-running operations#341

Open
Joehoel wants to merge 3 commits into
alchemy-run:mainfrom
Joehoel:feat/azure-lro-polling
Open

feat(core,azure): poll Azure ARM long-running operations#341
Joehoel wants to merge 3 commits into
alchemy-run:mainfrom
Joehoel:feat/azure-lro-polling

Conversation

@Joehoel

@Joehoel Joehoel commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Adds Azure ARM long-running-operation (LRO) polling so async mutating operations resolve to the provisioned resource instead of the intermediate 202 ack.

ARM PUT/PATCH/POST/DELETE operations frequently return 201/202 and finish asynchronously. Before this change the SDK decoded that ack body and returned it — so e.g. StorageAccountsCreate handed back an un-provisioned resource. Now the operation polls to completion transparently.

- const account = yield* StorageAccountsCreate({ ... }) // resolved the 202 ack
+ const account = yield* StorageAccountsCreate({ ... }) // polls, resolves the provisioned resource

How it works

Two layers, keeping core protocol-agnostic and the ARM specifics in azure:

  • core gains an opaque longRunning marker on the Http trait and an optional pollLongRunning client hook. When an operation carries the marker and the server returns 201/202, the core client delegates to the hook and decodes its resolved body through the output schema.
  • azure implements the hook as an Effect-native ARM poller (packages/azure/src/lro.ts): Effect.repeat + Schedule honoring each response's Retry-After, a Data.TaggedEnum monitor strategy, and provisioning-state classification via Match. It supports the Azure-AsyncOperation/Operation-Location and Location monitor styles, and resolves the final resource per ARM's rules (mirroring @azure/core-lro's findResourceLocation): PUT/PATCH resolve from the original request URI; POST/DELETE use the terminal poll body. A terminal Failed/Canceled surfaces as a typed AzureLongRunningOperationFailed (uncategorized, so it is not auto-retried).

Generated change

The shared Swagger emitter now detects x-ms-long-running-operation (+ -options.final-state-via) and bakes longRunning: { finalStateVia } into each operation's T.Http trait — the same place apiVersion is injected. Regenerated all Azure services: 3783 long-running operations across 182 service files now poll to completion.

Testing

  • 8 account-free unit tests: the core gate (packages/core/test/lro-delegation.test.ts) and the ARM poller (packages/azure/test/lro-poller.test.ts) driven by a scripted fake HttpClient, including per-poll Retry-After via TestClock.
  • A live smoke test (packages/azure/test/storage-lro.test.ts) that creates a real Standard_LRS storage account, polls it to completion, and tears it down — verified green end-to-end against a live subscription.

Joehoel added 3 commits June 12, 2026 12:09
Core gains a `longRunning` Http trait and an optional `pollLongRunning`
client hook: when an operation carries the trait and the server returns a
201/202 ack, core delegates to the hook and decodes its resolved body
through the output schema.

Azure implements the hook as an Effect-native ARM poller (Effect.repeat +
Schedule honoring per-response Retry-After; Data.taggedEnum strategy +
provisioning-state classification via Match). Supports azure-async-operation,
location, and original-uri final-state resolution. Terminal Failed/Canceled
surfaces as AzureLongRunningOperationFailed (uncategorized, so it is not
auto-retried).

Tested account-free with a scripted fake HttpClient (incl. Retry-After via
TestClock).
The shared Swagger emitter now detects `x-ms-long-running-operation` (+
`-options.final-state-via`) on each operation and bakes
`longRunning: { finalStateVia }` into the generated `T.Http` trait — the
same place `apiVersion` is injected. Regenerated all Azure services: 3783
long-running operations across 182 service files now poll to completion.
The ARM `final-state-via: location` for a resource create points to an
operationResults URL that returns only a stub (`{id, name, type}`); the
provisioned resource lives at the original request URI. Mirror
@azure/core-lro's `findResourceLocation`: PUT/PATCH (and any `original-uri`
op) resolve via the original URI; POST/DELETE use the terminal poll body.

Caught by a live smoke test (real storage-account create on the non-profit
account, free Standard_LRS, torn down) — now included.
@Joehoel Joehoel force-pushed the feat/azure-lro-polling branch from 3a7679e to 2ed7c35 Compare June 12, 2026 11:59
@Joehoel Joehoel marked this pull request as ready for review June 12, 2026 12:05

@LuuOW LuuOW left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technical audit: code patterns and implementation verified for alignment with modern software engineering standards.

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.

2 participants