UNPKG

workflow

Version:

Workflow SDK - Build durable, resilient, and observable workflows

316 lines (223 loc) • 12 kB
--- title: Analytics description: Metadata-only read APIs for runs, steps, events, hooks, waits, and attributes, backed by the observability pipeline. type: reference summary: "Interfaces: world.analytics.runs, .attributes, .steps, .events, .hooks, .waits. Metadata-only listings with plan-based lookback windows; filter runs by attribute key=value. Page limits are 1000 run-scoped, 100 cross-run." prerequisites: - /docs/api-reference/workflow-runtime/get-world related: - /docs/api-reference/workflow-runtime/world/storage - /docs/observability/attributes keywords: - world.analytics - analytics.runs - analytics.attributes - attribute filter - getMany - pagination limit - lookback window - observability-upgrade-required - pageInfo - metadata-only --- `world.analytics` is an optional, read-only namespace for observability surfaces: dashboards, command-line interface (CLI) tools, and admin tools that list large numbers of runs without touching payload data. Prefer this namespace for observability: listing, filtering, and inspecting workflow state. Use the [Storage](/docs/api-reference/workflow-runtime/world/storage) API for payload-bearing reads, and for anything operational that has to see the canonical, up-to-the-moment record. It differs from [Storage](/docs/api-reference/workflow-runtime/world/storage) in two ways: - **Metadata only**: Results never include run input/output, step data, or hook tokens. There is no `resolveData` option. - **Served from the observability pipeline**: On Vercel, queries are served from the Vercel observability data pipeline, so large listings do not compete with workflow execution. Data is ingested asynchronously and may trail the live state by a few seconds. The namespace is optional: worlds that don't implement it (such as the local development world) leave it `undefined`, so feature-detect before use: ```typescript lineNumbers import { getWorld } from "workflow/runtime"; const world = await getWorld(); if (world.analytics) { // [!code highlight] const page = await world.analytics.runs.list(); } ``` --- ## analytics.runs ### runs.list() List runs with metadata, current status, and attributes. Without an explicit time window, the listing defaults to the trailing 24 hours; pass `startTime`/`endTime` to reach older runs within your plan window. ```typescript lineNumbers const page = await world.analytics.runs.list({ workflowName: "orderWorkflow", status: "failed", attributes: { source: "checkout" }, // [!code highlight] pagination: { limit: 50, sortOrder: "desc" }, }); ``` | Parameter | Type | Description | |-----------|------|-------------| | `params.workflowName` | `string` | Filter to one workflow | | `params.status` | `string` | `pending`, `running`, `completed`, `failed`, or `cancelled` | | `params.startTime` / `params.endTime` | `string` | ISO 8601 window; must be provided together | | `params.attributes` | `Record<string, string>` | Only return runs whose latest attributes match every pair (up to 8) | | `params.pagination` | `PaginationOptions` | Cursor pagination | **Returns:** `PaginatedResponse<AnalyticsRun>`. Each run includes `runId`, `status`, `workflowName`, `deploymentId`, `attributes`, and lifecycle timestamps. Attribute matching is latest-write-wins: a run whose attribute moved from `"v1"` to `"v2"` no longer matches `{ key: "v1" }`. Reserved `$`-prefixed keys may be used in filters even though user code cannot write them. ### runs.get() Fetch one run by ID. Point lookups search the full plan window rather than only the trailing 24 hours. ```typescript lineNumbers const run = await world.analytics.runs.get(runId); ``` --- ## analytics.attributes Discover which [attributes](/docs/observability/attributes) exist on your runs, for example to build filter dropdowns over arbitrary user-defined keys. ### attributes.list() List the distinct attribute keys observed on runs in the window, ordered alphabetically. ```typescript lineNumbers const page = await world.analytics.attributes.list({ // [!code highlight] workflowName: "orderWorkflow", }); for (const { key, runCount, lastSeenAt } of page.data) { console.log(key, runCount, lastSeenAt); } ``` | Parameter | Type | Description | |-----------|------|-------------| | `params.workflowName` | `string` | Only count runs of one workflow | | `params.startTime` / `params.endTime` | `string` | ISO 8601 window; must be provided together | | `params.pagination` | `PaginationOptions` | Cursor pagination | **Returns:** `PaginatedResponse<AnalyticsAttributeKey>`: `{ key, runCount, firstSeenAt, lastSeenAt }` --- ## analytics.steps Run-scoped step listings mirroring their [Storage](/docs/api-reference/workflow-runtime/world/storage) counterparts, minus payload data. ### steps.list() ```typescript lineNumbers const steps = await world.analytics.steps.list({ runId, pagination: { limit: 200, sortOrder: "asc" }, }); ``` | Parameter | Type | Description | |-----------|------|-------------| | `params.runId` | `string` | Required. The run to list steps for | | `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 1000 | **Returns:** `PaginatedResponse<AnalyticsStep>`. Each step includes `stepId`, `stepName`, `status`, `attempt`, lifecycle timestamps, `errorCode`, and the `computeInstanceId` of the latest attempt. ### steps.get() ```typescript lineNumbers const step = await world.analytics.steps.get(runId, stepId); ``` **Returns:** `AnalyticsStep`. A step id is only unique within its run, so both arguments are required. --- ## analytics.events ### events.list() ```typescript lineNumbers const events = await world.analytics.events.list({ runId, eventType: "step_failed", // [!code highlight] pagination: { limit: 1000 }, }); ``` | Parameter | Type | Description | |-----------|------|-------------| | `params.runId` | `string` | Required. The run to list events for | | `params.eventType` | `string` | One event type, for example `run_failed` or `step_retrying` | | `params.correlationId` | `string` | Narrow to one entity: a step, hook, wait, or attribute id | | `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 1000 | **Returns:** `PaginatedResponse<AnalyticsEvent>`. Each event includes `eventId`, `eventType`, `correlationId`, `stepName`, `createdAt`, and provenance fields (`region`, `requestId`, `computeInstanceId`). Pass a step id as `correlationId` to build that step's timeline: `step_created` through `step_completed`, `step_failed`, or `step_retrying`. ### events.get() ```typescript lineNumbers const event = await world.analytics.events.get(runId, eventId); ``` **Returns:** `AnalyticsEvent`. ### events.getMany() Look up a bounded set of event ids in one run with a single request. ```typescript lineNumbers const events = await world.analytics.events.getMany(runId, eventIds); // [!code highlight] ``` **Returns:** `AnalyticsEvent[]` — not paginated, and no `pageInfo`. Duplicate ids are looked up once, and ids with no analytics row yet are **omitted rather than erroring**, since ingestion can trail canonical storage. Compare the returned length against your input to detect that. --- ## analytics.hooks ### hooks.list() ```typescript lineNumbers const hooks = await world.analytics.hooks.list({ runId }); ``` | Parameter | Type | Description | |-----------|------|-------------| | `params.runId` | `string` | Required. The run to list hooks for | | `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 100 | **Returns:** `PaginatedResponse<AnalyticsHook>`: `hookId`, `status` (`created`, `received`, `disposed`, or `conflict`), `receivedAt`, `disposedAt`, `isWebhook`, `isSystem`. ### hooks.get() ```typescript lineNumbers const hook = await world.analytics.hooks.get(hookId); ``` Unlike steps and waits, a hook id identifies one hook on its own, so no `runId` is needed. Pass `{ runId }` to scope the lookup when you already know it. <Callout> Hook listings never include the hook token. Resolve it separately through the runtime APIs if you need to deliver a payload. </Callout> --- ## analytics.waits ### waits.list() ```typescript lineNumbers const waits = await world.analytics.waits.list({ runId, status: "waiting", // [!code highlight] }); ``` | Parameter | Type | Description | |-----------|------|-------------| | `params.runId` | `string` | Required. The run to list waits for | | `params.status` | `string` | `waiting` or `completed` | | `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 1000 | **Returns:** `PaginatedResponse<AnalyticsWait>`: `waitId`, `status`, `resumeAt`, `completedAt`. ### waits.get() ```typescript lineNumbers const wait = await world.analytics.waits.get(runId, waitId); ``` **Returns:** `AnalyticsWait`. A wait id is only unique within its run, so both arguments are required. --- ## Limits and validation Arguments are validated in your process before a request goes out. An out-of-range or malformed argument throws a `RangeError` naming the bound it broke, rather than reaching the backend and coming back as a 400 — which matters because analytics is optional and callers commonly wrap it in a `try`/`catch`, where a rejected request is easy to mistake for "no data". ### Page limits `pagination.limit` defaults to 40 everywhere. The maximum depends on whether the listing scans within one run or across runs: | Method | Max `limit` | |--------|-------------| | `steps.list()`, `events.list()`, `waits.list()` | 1000 | | `runs.list()`, `attributes.list()`, `hooks.list()` | 100 | `events.getMany()` is not paginated; it accepts 1 to 100 event ids per call. <Callout type="warn"> The two page caps differ by a factor of ten, and `hooks.list()` takes the lower one despite being run-scoped. Reusing one page size across listings is the most common way to trip this. </Callout> ### Identifiers Every id is a prefix plus a ULID, and each is checked before the request: | Parameter | Shape | |-----------|-------| | `runId` | `wrun_` | | `stepId` | `step_` | | `eventId` | `evnt_` | | `hookId` | `hook_` | | `waitId` | `wait_` | | `correlationId` | `step_`, `hook_`, `wait_`, or `attr_` | Step, event, and wait ids are only unique **within** their run, so the methods that take them require a `runId` too. A hook id stands alone. ### Time windows `startTime` and `endTime` must be supplied **together** and be parseable ISO 8601 timestamps with `startTime` no later than `endTime`. Passing one without the other throws: it used to be dropped silently, which turned a listing you meant to bound into a scan of the whole retention window that looked like a successful answer. ### Attribute filters `runs.list({ attributes })` accepts 1 to 8 pairs. Keys are 1 to 256 characters; values are at most 256 UTF-8 bytes. Reserved `$`-prefixed keys are valid in a filter even though user code cannot write them. ### Pagination `cursor` is an opaque token from the previous response; do not construct or parse one. Branch on `hasMore`, not on `cursor` being non-null, and do not change `sortOrder` mid-walk — the cursor encodes the sort position, so reversing it can skip or repeat rows. --- ## Lookback windows and pageInfo Every paginated response carries `pageInfo` describing the window the query was allowed to scan: {/* @skip-typecheck: shape illustration, not runnable code */} ```typescript { currentLookbackDays: 2, // what your plan allows today maxLookbackDays: 30, // ceiling with Observability Plus on Vercel currentWindowStart: Date, maxWindowStart: Date, upgradeAvailable: true, // for Vercel deployed workflows } ``` Requests for a window older than `currentWindowStart` fail with an `observability-upgrade-required` error; windows older than `maxWindowStart` return not-found. Use `pageInfo` to size date pickers and to decide whether to surface an upgrade prompt.