Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
3739e04
feat: add docs and test stubs for serializable AbortController/AbortS…
pranaygp Mar 9, 2026
42a3207
fix: use correct frontmatter type for error page
pranaygp Mar 9, 2026
97b0e47
docs: address review feedback on cancellation docs
pranaygp Mar 12, 2026
68a40dd
docs: document runtime change for processing abort queue items on com…
pranaygp Mar 12, 2026
f3b3ac4
docs: generalize queue processing on completion to all item types
pranaygp Mar 12, 2026
f766109
docs: abort errors in steps are automatically wrapped in FatalError
pranaygp Mar 12, 2026
589d63e
docs: remove contrived "aborting from within a step" example
pranaygp Mar 12, 2026
ae7454a
docs: add meaningful step-initiated abort example (quota monitor)
pranaygp Mar 12, 2026
78ecb59
docs: remove unnecessary "as const" from hook cancellation example
pranaygp Mar 12, 2026
47f36a9
feat: implement serializable AbortController/AbortSignal
pranaygp Mar 12, 2026
dc1981d
feat: wire up AbortController in workflow VM and process queue on com…
pranaygp Mar 12, 2026
90c8023
feat: implement tests and Request.signal serialization
pranaygp Mar 12, 2026
1f731b2
Merge remote-tracking branch 'origin/main' into pgp/serialize-abort-s…
pranaygp Mar 12, 2026
242e842
test: implement all remaining .todo test stubs
pranaygp Mar 12, 2026
aebd1a9
fix: address PR review comments + add changelog
pranaygp Mar 12, 2026
20b32ce
feat: show changelog in nav for preview deployments only
pranaygp Mar 12, 2026
8379677
feat: move preview badge from home page to navbar
pranaygp Mar 12, 2026
09c3e05
feat: consolidate preview tools into single Internal page
pranaygp Mar 12, 2026
aee200c
feat: use real deployment URLs on internal page + exclude from indexing
pranaygp Mar 12, 2026
3d76329
Merge remote-tracking branch 'origin/main' into pgp/serialize-abort-s…
pranaygp Mar 13, 2026
36b7826
fix: add missing type declarations for docs code sample typechecking
pranaygp Mar 13, 2026
48e85ac
fix: add missing type declarations for all docs code samples
pranaygp Mar 13, 2026
ef1aa08
fix: only suspend on completion for abort items, not all pending items
pranaygp Mar 13, 2026
264a6fd
fix: all pending queue items are fire-and-forget on completion
pranaygp Mar 13, 2026
670f461
fix: resolve docs typecheck errors in code samples
pranaygp Mar 13, 2026
bc45bb5
fix: abort() in workflow updates signal.aborted synchronously
pranaygp Mar 13, 2026
bb52d9b
docs: update how-it-works to reflect synchronous signal.aborted update
pranaygp Mar 13, 2026
6beb1f5
fix: ensure abort listeners fire at deterministic point across replays
pranaygp Mar 13, 2026
fd8dc12
test: add replay ordering tests for interleaved hook scenarios
pranaygp Mar 13, 2026
c490206
fix: signal.aborted stays false until abort() is called for determini…
pranaygp Mar 13, 2026
e154063
test: add abort+hook ordering matrix e2e tests (4 combinations)
pranaygp Mar 13, 2026
fbf9f0f
fix: simplify abort — event consumer calls _setAborted directly
pranaygp Mar 13, 2026
ad6d24b
test: skip abort+hook ordering e2e tests pending full integration
pranaygp Mar 13, 2026
97c635c
Merge branch 'main' of github.com:vercel/workflow into pgp/serialize-…
karthikscale3 Apr 7, 2026
6aae8bd
Merge branch 'main' into pgp/serialize-abort-signal
karthikscale3 Apr 21, 2026
ccb810e
handle dangling streams
karthikscale3 Apr 7, 2026
c10f891
fix postgres world
karthikscale3 Apr 7, 2026
42833d7
fix abort serialization bug
karthikscale3 Apr 7, 2026
7d37f00
refactors
karthikscale3 Apr 7, 2026
669fe9f
add drizzle migration file
karthikscale3 Apr 7, 2026
3f62695
fix tests
karthikscale3 Apr 7, 2026
d805a88
fix tests
karthikscale3 Apr 7, 2026
d786ebb
replace setTimeout probe and any casts with typed abort internals
karthikscale3 Apr 21, 2026
26d3b72
cover post-serialization abort and nested-in-Request reader cleanup
karthikscale3 Apr 21, 2026
c8da270
add v4/v5 docs switcher and pre-release gating
karthikscale3 Apr 21, 2026
5d876aa
fix version switcher URL when default locale is hidden
karthikscale3 Apr 21, 2026
ea90064
match ai-sdk pre-release banner styling
karthikscale3 Apr 21, 2026
380da85
match ai-sdk switcher icons and banner link color
karthikscale3 Apr 21, 2026
7ad0794
use exact ai-sdk icons and darker banner link
karthikscale3 Apr 21, 2026
e1f032c
fix(docs): correct dark-mode colors for pre-release banner and versio…
karthikscale3 Apr 22, 2026
e7c9726
Merge branch 'main' into pgp/serialize-abort-signal
karthikscale3 May 1, 2026
ee30ed7
Merge pgp/serialize-abort-signal into karthik/abort-signal-updates
karthikscale3 May 1, 2026
1bce60f
fix: add signal field to Request serializable type
pranaygp May 2, 2026
34f490d
refactor: address review feedback on abort serialization
pranaygp May 2, 2026
aaae686
Merge remote-tracking branch 'origin/main' into pgp/serialize-abort-s…
pranaygp May 2, 2026
100f067
Merge remote-tracking branch 'origin/karthik/abort-signal-updates' in…
pranaygp May 2, 2026
6426401
docs: correct cancellation docs against implementation
pranaygp May 2, 2026
6ef2604
Merge branch 'main' into pgp/serialize-abort-signal
pranaygp May 2, 2026
a953201
fix: DOMException serialization (replace broken isNativeError guard)
pranaygp May 3, 2026
0d025f1
fix: drain pending queue items on workflow completion
pranaygp May 4, 2026
9ce04f1
test: cover the deserialized AbortSignal listener path with an in-fli…
pranaygp May 4, 2026
5145fe4
test: extend abortFromStepWorkflow to verify in-flight sibling cancel…
pranaygp May 4, 2026
d70ade7
fix: dehydrate abort stream packets via the same machinery as hook ev…
pranaygp May 4, 2026
c157a4e
test: cover addEventListener, mid-flight throwIfAborted, and step-ini…
pranaygp May 4, 2026
5474595
test: drop signal.aborted shortcut in stepWaitingOnAbortListener
pranaygp May 4, 2026
092750f
Merge remote-tracking branch 'origin/main' into pgp/serialize-abort-s…
pranaygp May 4, 2026
7c07e34
fix: add DOMException reviver to observabilityRevivers so the o11y UI…
pranaygp May 4, 2026
45d1207
test: cover the external-signal-aborted-in-flight propagation path
pranaygp May 4, 2026
a08d0a0
fix(test): use httpbin.org/delay for abortFetchInFlightWorkflow
pranaygp May 4, 2026
6a89857
Merge remote-tracking branch 'origin/main' into pgp/serialize-abort-s…
pranaygp May 4, 2026
becdc0b
docs: fix serialization page — drop duplicate header, move AbortContr…
pranaygp May 4, 2026
e4882d1
docs: note that run.cancel() is the same as the observability Cancel …
pranaygp May 4, 2026
3ce720c
test: cover AbortSignal.any in both workflow VM and step contexts
pranaygp May 4, 2026
195c561
Update .changeset/fix-dom-exception-serialization.md
pranaygp May 4, 2026
4f312f1
Update .changeset/serializable-abort-controller.md
pranaygp May 4, 2026
f6dc2a6
Update .changeset/drain-pending-queue-on-completion.md
pranaygp May 4, 2026
400f96a
Merge branch 'main' into pgp/serialize-abort-signal
pranaygp May 4, 2026
6ec2b95
docs(errors): match slug-as-title convention + simplify the timeout e…
pranaygp May 4, 2026
409d9dc
fix: address review feedback on abort serialization
pranaygp May 5, 2026
34d2379
Merge remote-tracking branch 'origin/main' into pgp/serialize-abort-s…
pranaygp May 5, 2026
c6098ca
test: add getWorldLazy mock to serialization tests
pranaygp May 5, 2026
06f6654
fix: hydrate hook_received payload in abort events consumer
pranaygp May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/drain-pending-queue-on-completion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workflow/core": patch
---

Drain pending queue items at workflow completion instead of only logging warnings, and implicitly dispose any never-aborted system (abort) hooks at completion so unused `AbortController` instances don't leave abandoned rows in the hooks table for the run's TTL
5 changes: 5 additions & 0 deletions .changeset/fix-dom-exception-serialization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workflow/core": patch
---

Fix `DOMException` not serializing correctly
8 changes: 8 additions & 0 deletions .changeset/serializable-abort-controller.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@workflow/core": patch
"workflow": patch
---

Add serializable `AbortController` and `AbortSignal` support across workflow and step boundaries. Workflow code can now construct an `AbortController`, pass `signal` to steps, and call `abort()`.

**Behavior change:** `AbortError` thrown from inside a step is now wrapped as `FatalError` and skips retry semantics. As a result, custom timeouts on `fetch` inside steps are no longer re-tried by default, and now need to be wrapped in `RetryableError` to preserve the old behavior.
8 changes: 8 additions & 0 deletions docs/app/[lang]/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { notFound, permanentRedirect } from 'next/navigation';
import { rewriteCookbookUrl } from '@/lib/geistdocs/cookbook-source';
import { AgentTraces } from '@/components/custom/agent-traces';
import { FluidComputeCallout } from '@/components/custom/fluid-compute-callout';
import { PreviewInstallServer } from '@/components/preview-install-server';
import { AskAI } from '@/components/geistdocs/ask-ai';
import { CopyPage } from '@/components/geistdocs/copy-page';
import {
Expand Down Expand Up @@ -45,6 +46,12 @@ const Page = async ({ params }: PageProps<'/[lang]/docs/[[...slug]]'>) => {
notFound();
}

// preRelease pages are only reachable under /v5/docs/*. Block direct
// access via /docs/* so the v4 tree doesn't expose unreleased content.
if (page.data.preRelease) {
notFound();
}

const markdown = await getLLMText(page);
const MDX = page.data.body;

Expand Down Expand Up @@ -86,6 +93,7 @@ const Page = async ({ params }: PageProps<'/[lang]/docs/[[...slug]]'>) => {
...AccordionComponents,
Tabs,
Tab,
PreviewInstall: PreviewInstallServer,
// No-op for world MDX files (they redirect to /worlds/[id])
WorldTestingPerformance: WorldTestingPerformanceNoop,
})}
Expand Down
5 changes: 3 additions & 2 deletions docs/app/[lang]/docs/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { DocsLayout } from '@/components/geistdocs/docs-layout';
import { getDocsTreeWithoutCookbook } from '@/lib/geistdocs/cookbook-source';
import { getDocsTreeForVersion } from '@/lib/geistdocs/version-source';
import { LATEST_VERSION } from '@/lib/geistdocs/versions';

const Layout = async ({ children, params }: LayoutProps<'/[lang]/docs'>) => {
const { lang } = await params;

return (
<div className="bg-background-100">
<DocsLayout tree={getDocsTreeWithoutCookbook(lang)}>
<DocsLayout tree={getDocsTreeForVersion(lang, LATEST_VERSION)}>
{children}
</DocsLayout>
</div>
Expand Down
5 changes: 4 additions & 1 deletion docs/app/[lang]/llms.mdx/[[...slug]]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,8 @@ export const generateStaticParams = async ({
}: RouteContext<'/[lang]/llms.mdx/[[...slug]]'>) => {
const { lang } = await params;

return source.generateParams(lang);
// Exclude internal/preview-only pages from LLM scraping
return source
.generateParams(lang)
.filter((p) => !p.slug?.includes('internal'));
};
3 changes: 3 additions & 0 deletions docs/app/[lang]/sitemap.md/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export async function GET(
const indent = ' '.repeat(depth);

if ('type' in node) {
// Exclude internal/preview-only pages from sitemap
if (node.type === 'page' && node.url.includes('/internal')) return;
if (node.type === 'folder' && node.name === 'Internal') return;
if (node.type === 'page') {
mdText += `${indent}- [${node.name}](${rewriteCookbookUrl(node.url)})\n`;
} else if (node.type === 'folder') {
Expand Down
127 changes: 127 additions & 0 deletions docs/app/[lang]/v5/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { createRelativeLink } from 'fumadocs-ui/mdx';
import type { Metadata } from 'next';
import { notFound, permanentRedirect } from 'next/navigation';
import { AgentTraces } from '@/components/custom/agent-traces';
import { FluidComputeCallout } from '@/components/custom/fluid-compute-callout';
import { AskAI } from '@/components/geistdocs/ask-ai';
import { CopyPage } from '@/components/geistdocs/copy-page';
import {
DocsBody,
DocsDescription,
DocsPage,
DocsTitle,
} from '@/components/geistdocs/docs-page';
import { EditSource } from '@/components/geistdocs/edit-source';
import { Feedback } from '@/components/geistdocs/feedback';
import { getMDXComponents } from '@/components/geistdocs/mdx-components';
import { MobileDocsBar } from '@/components/geistdocs/mobile-docs-bar';
import { OpenInChat } from '@/components/geistdocs/open-in-chat';
import { ScrollTop } from '@/components/geistdocs/scroll-top';
import { PreviewInstallServer } from '@/components/preview-install-server';
import * as AccordionComponents from '@/components/ui/accordion';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { rewriteCookbookUrl } from '@/lib/geistdocs/cookbook-source';
import { getLLMText, getPageImage, source } from '@/lib/geistdocs/source';
import { TSDoc } from '@/lib/tsdoc';

const WorldTestingPerformanceNoop = () => null;

const Page = async ({ params }: PageProps<'/[lang]/v5/docs/[[...slug]]'>) => {
const { slug, lang } = await params;

if (Array.isArray(slug) && slug[0] === 'cookbook') {
const rest = slug.slice(1).join('/');
const legacyPath = `/docs/cookbook${rest ? `/${rest}` : ''}`;
permanentRedirect(`/${lang}${rewriteCookbookUrl(legacyPath)}`);
}

const page = source.getPage(slug, lang);
if (!page) {
notFound();
}

const markdown = await getLLMText(page);
const MDX = page.data.body;

return (
<DocsPage
full={page.data.full}
tableOfContent={{
style: 'clerk',
footer: (
<div className="my-3 space-y-3">
<Separator />
<EditSource path={page.path} />
<ScrollTop />
<Feedback />
<CopyPage text={markdown} />
<AskAI href={page.url} />
<OpenInChat href={page.url} />
</div>
),
}}
tableOfContentPopover={{ enabled: false }}
toc={page.data.toc}
>
<MobileDocsBar toc={page.data.toc} />
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDX
components={getMDXComponents({
a: createRelativeLink(source, page),
AgentTraces,
FluidComputeCallout,
Badge,
TSDoc,
Step,
Steps,
...AccordionComponents,
Tabs,
Tab,
PreviewInstall: PreviewInstallServer,
WorldTestingPerformance: WorldTestingPerformanceNoop,
})}
/>
</DocsBody>
</DocsPage>
);
};

export const generateStaticParams = () =>
source
.generateParams()
.filter(
(params) => !(Array.isArray(params.slug) && params.slug[0] === 'cookbook')
);

export const generateMetadata = async ({
params,
}: PageProps<'/[lang]/v5/docs/[[...slug]]'>): Promise<Metadata> => {
const { slug, lang } = await params;
const page = source.getPage(slug, lang);
if (!page) notFound();
return {
title: `${page.data.title} · Pre-release`,
description: page.data.description,
openGraph: {
images: getPageImage(page).url,
},
// Pre-release pages are not canonical; point search engines at the
// latest URL (or self if this page is v5-only).
alternates: {
canonical: page.data.preRelease
? `/${lang}/v5${page.url}`
: `/${lang}${page.url}`,
},
robots: {
index: false,
follow: true,
},
};
};

export default Page;
18 changes: 18 additions & 0 deletions docs/app/[lang]/v5/docs/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DocsLayout } from '@/components/geistdocs/docs-layout';
import { PreReleaseBanner } from '@/components/geistdocs/pre-release-banner';
import { getDocsTreeForVersion } from '@/lib/geistdocs/version-source';
import { PRE_RELEASE_VERSION } from '@/lib/geistdocs/versions';

const Layout = async ({ children, params }: LayoutProps<'/[lang]/v5/docs'>) => {
const { lang } = await params;
return (
<div className="bg-background-100">
<PreReleaseBanner pathname={`/${lang}/v5/docs`} />
<DocsLayout tree={getDocsTreeForVersion(lang, PRE_RELEASE_VERSION)}>
{children}
</DocsLayout>
</div>
);
};

export default Layout;
1 change: 1 addition & 0 deletions docs/app/robots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default function robots(): MetadataRoute.Robots {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/*/docs/internal/', '/docs/internal/'],
},
sitemap: `${baseUrl}/sitemap.xml`,
};
Expand Down
2 changes: 2 additions & 0 deletions docs/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export default function sitemap(): MetadataRoute.Sitemap {
const pages: MetadataRoute.Sitemap = [];

for (const page of source.getPages()) {
// Exclude internal/preview-only pages from sitemap
if (page.url.includes('/internal')) continue;
pages.push({
changeFrequency: 'weekly' as const,
lastModified: undefined,
Expand Down
49 changes: 49 additions & 0 deletions docs/components/geistdocs/pre-release-banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Link from 'next/link';
import {
buildVersionUrl,
LATEST_VERSION,
PRE_RELEASE_VERSION,
} from '@/lib/geistdocs/versions';

interface PreReleaseBannerProps {
pathname: string;
}

const SparklesFilled = ({ className }: { className?: string }) => (
<svg
aria-hidden="true"
className={className}
fill="currentColor"
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M2.5 0.5V0H3.5V0.5C3.5 1.60457 4.39543 2.5 5.5 2.5H6V3V3.5H5.5C4.39543 3.5 3.5 4.39543 3.5 5.5V6H3H2.5V5.5C2.5 4.39543 1.60457 3.5 0.5 3.5H0V3V2.5H0.5C1.60457 2.5 2.5 1.60457 2.5 0.5Z" />
<path d="M14.5 4.5V5H13.5V4.5C13.5 3.94772 13.0523 3.5 12.5 3.5H12V3V2.5H12.5C13.0523 2.5 13.5 2.05228 13.5 1.5V1H14H14.5V1.5C14.5 2.05228 14.9477 2.5 15.5 2.5H16V3V3.5H15.5C14.9477 3.5 14.5 3.94772 14.5 4.5Z" />
<path d="M8.40706 4.92939L8.5 4H9.5L9.59294 4.92939C9.82973 7.29734 11.7027 9.17027 14.0706 9.40706L15 9.5V10.5L14.0706 10.5929C11.7027 10.8297 9.82973 12.7027 9.59294 15.0706L9.5 16H8.5L8.40706 15.0706C8.17027 12.7027 6.29734 10.8297 3.92939 10.5929L3 10.5V9.5L3.92939 9.40706C6.29734 9.17027 8.17027 7.29734 8.40706 4.92939Z" />
</svg>
);

export const PreReleaseBanner = ({ pathname }: PreReleaseBannerProps) => {
const latestHref = buildVersionUrl(pathname, LATEST_VERSION);
return (
<div className="border-b bg-blue-50 px-4 py-2 text-center text-sm dark:bg-blue-950/40">
<div className="mx-auto flex max-w-[1448px] flex-wrap items-center justify-center gap-x-3 gap-y-1">
<div className="flex items-center gap-2 text-blue-700 dark:text-blue-900">
<SparklesFilled className="size-4 shrink-0" />
<span>
Viewing Workflow {PRE_RELEASE_VERSION.id.replace(/^v/, '')}{' '}
(Pre-release) Documentation.
</span>
</div>
<Link
className="font-medium text-blue-700 underline underline-offset-4 decoration-blue-700/40 transition-colors hover:decoration-blue-700 dark:text-blue-900 dark:decoration-blue-900/40 dark:hover:decoration-blue-900"
href={latestHref}
>
Go to Workflow {LATEST_VERSION.id.replace(/^v/, '')} (Latest)
</Link>
</div>
</div>
);
};
3 changes: 3 additions & 0 deletions docs/components/geistdocs/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { Badge } from '@/components/ui/badge';
import { useSidebarContext } from '@/hooks/geistdocs/use-sidebar';
import { SearchButton } from './search';
import { VersionSwitcher } from './version-switcher';

// Map of URL suffixes to badges shown inline next to the sidebar item name.
const SIDEBAR_ITEM_BADGES: Array<{ suffix: string; label: string }> = [
Expand Down Expand Up @@ -70,6 +71,7 @@ export const Sidebar = () => {
data-sidebar-placeholder
>
<div className="h-full overflow-y-auto px-4 pt-12 pb-4">
<VersionSwitcher />
<Fragment key={root.$id}>{renderSidebarList(root.children)}</Fragment>
</div>
<Sheet onOpenChange={setIsOpen} open={isOpen}>
Expand All @@ -82,6 +84,7 @@ export const Sidebar = () => {
<SearchButton onClick={() => setIsOpen(false)} />
</SheetHeader>
<div className="flex-1 overflow-y-auto px-4 pb-4">
<VersionSwitcher />
{renderSidebarList(root.children)}
</div>
</SheetContent>
Expand Down
Loading
Loading