diff --git a/.env.example b/.env.example
index 95a93dd..bd1523e 100644
--- a/.env.example
+++ b/.env.example
@@ -1,5 +1,5 @@
-# `full` contains the default embedding model; `slim` downloads it only when
-# a user first indexes a custom corpus.
+# `full` contains the default embedding and reranker models. `slim` keeps BM25
+# available and offers separate explicit model downloads in Settings.
LAB_IMAGE_VARIANT=full
TINY_RAG_LAB_PORT=8000
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..48e6248
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,85 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ python:
+ name: Python tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Install uv
+ uses: astral-sh/setup-uv@v6
+ with:
+ enable-cache: true
+ - name: Set up Python
+ run: uv python install 3.12
+ - name: Check lockfile
+ run: uv lock --check
+ - name: Create test environment
+ run: uv venv --python 3.12
+ - name: Install CPU-only test dependencies
+ run: >-
+ uv pip install
+ --index https://download.pytorch.org/whl/cpu
+ --default-index https://pypi.org/simple
+ --index-strategy unsafe-best-match
+ 'torch==2.7.1+cpu'
+ '.[qdrant]'
+ 'pytest>=8.0'
+ - name: Run Python tests
+ run: uv run --no-sync pytest --tb=short -q
+
+ web:
+ name: Web tests and build
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: web
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: npm
+ cache-dependency-path: web/package-lock.json
+ - name: Install dependencies
+ run: npm ci
+ - name: Run tests
+ run: npm test
+ - name: Build production app
+ run: npm run build
+
+ guides:
+ name: Learning Guides build and links
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: learning_materials
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: npm
+ cache-dependency-path: learning_materials/package-lock.json
+ - name: Install dependencies
+ run: npm ci
+ - name: Build guides and validate links
+ run: npm run build
diff --git a/.gitignore b/.gitignore
index 2cc1763..b90bea4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ __pycache__/
.Python
*.egg-info/
dist/
-build/
+/build/
.eggs/
# Virtual environments
diff --git a/Dockerfile b/Dockerfile
index 9c75cae..a9dc755 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -17,6 +17,7 @@ ARG LAB_IMAGE_VARIANT=full
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
TINY_RAG_LAB_DATA_DIR=/data \
+ HF_HOME=/opt/tiny-rag-models \
LAB_IMAGE_VARIANT=${LAB_IMAGE_VARIANT}
WORKDIR /app
COPY pyproject.toml README.md ./
@@ -24,17 +25,19 @@ COPY pyproject.toml README.md ./
# wheel first so sentence-transformers cannot resolve a CUDA/NVIDIA runtime
# transitively on Linux. Keeping it before application source preserves this
# expensive, CPU-only layer while the lab code is refined.
-RUN pip install --no-cache-dir --index-url https://download.pytorch.org/whl/cpu 'torch==2.7.1+cpu'
+RUN --mount=type=cache,target=/root/.cache/pip \
+ pip install --index-url https://download.pytorch.org/whl/cpu 'torch==2.7.1+cpu'
COPY tiny_rag_lab ./tiny_rag_lab
+RUN --mount=type=cache,target=/root/.cache/pip pip install '.[qdrant]'
+# Full prepares both exact CPU-only model snapshots; slim defers each one to
+# its own explicit Settings action.
+RUN if [ "$LAB_IMAGE_VARIANT" = "full" ]; then python -c "from tiny_rag_lab.embeddings import SentenceTransformerEmbedder; from tiny_rag_lab.reranker import CrossEncoderReranker; SentenceTransformerEmbedder(); CrossEncoderReranker.ensure_default_model(local_files_only=False)"; fi
COPY scripts ./scripts
-COPY assets/seed/v1 /opt/tiny-rag-lab/seeds/v1
-COPY docker-entrypoint.sh /usr/local/bin/tiny-rag-lab-entrypoint
-RUN chmod +x /usr/local/bin/tiny-rag-lab-entrypoint \
- && pip install --no-cache-dir '.[qdrant]'
+COPY assets/seed/v2 /opt/tiny-rag-lab/seeds/v2
COPY --from=web-build /web/dist /app/web-dist
COPY --from=guides-build /guides/.vitepress/dist /app/web-dist/docs
-# Full prepares the existing default embedder at build time; slim defers it.
-RUN if [ "$LAB_IMAGE_VARIANT" = "full" ]; then SENTENCE_TRANSFORMERS_HOME=/opt/tiny-rag-models python -c "from tiny_rag_lab.embeddings import SentenceTransformerEmbedder; SentenceTransformerEmbedder()"; fi
+COPY docker-entrypoint.sh /usr/local/bin/tiny-rag-lab-entrypoint
+RUN chmod +x /usr/local/bin/tiny-rag-lab-entrypoint
EXPOSE 8000
ENTRYPOINT ["tiny-rag-lab-entrypoint"]
CMD ["uvicorn", "tiny_rag_lab.web_api:create_packaged_app", "--factory", "--host", "0.0.0.0", "--port", "8000"]
diff --git a/README.md b/README.md
index 65700bf..f361cc5 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@
[简体中文](README.zh-CN.md) · [Project site](https://jameswei.github.io/tiny-rag-lab/)
> A learning-first, inspectable classic RAG lab with readable Python, a rich
-> browser Studio, direct CLI experiments, real-corpus traces, and bilingual
-> Learning Guides.
+> browser Studio, an interactive retrieval course, direct CLI experiments,
+> real-corpus traces, and bilingual Learning Guides.
`tiny-rag-lab` makes the path between a question, a document corpus, retrieved
evidence, packed context, and a cited answer visible and inspectable. Readable
@@ -13,6 +13,12 @@ artifacts into guided replays and hands-on experiments; a direct CLI supports
repeatable inspection. Searchable English and Simplified Chinese Learning
Guides open beside the lab when a concept deserves quieter, deeper reading.
+The Studio also teaches the retrieval stack as a live course: inspect BM25
+term contributions, dense cosine math, the same vectors in NumPy and optional
+Qdrant, hybrid RRF fusion, cross-encoder rank movement, and
+two-configuration evaluation over 16 reviewed real-corpus questions. None of
+this requires an LLM provider.
+
It is a learning tool, not a production RAG platform. The project favors
visible mechanics over framework magic, evaluation before optimization, and
failure analysis before advanced features.
@@ -103,17 +109,21 @@ A useful first visit follows this path:
pinned 40-document Cloudflare State & Coordination corpus.
2. **Learn:** step through corpus, chunks, embedding vector, retrieved
candidates, selected context, grounded answer, and citations.
-3. **Explore:** ask a catalog or free-form question, compare Dense, BM25, and
- Hybrid retrieval, then inspect the returned trace. Add a tested
+3. **Retrieval:** follow six live modules from lexical and dense mechanics
+ through NumPy/Qdrant comparison, hybrid fusion, reranking, and browser A/B
+ evaluation over 16 reviewed questions.
+4. **Explore:** ask a catalog or free-form question, compare Dense, BM25, and
+ Hybrid retrieval, optionally rerank a larger candidate pool, then inspect
+ the returned trace. Add a tested
OpenAI-compatible provider only when you want Live Ask generation.
-4. **Build & Inspect:** build an index from a bundled corpus or a small
+5. **Build & Inspect:** build an index from a bundled corpus or a small
Markdown/plain-text upload, then inspect documents, chunks, vectors, and
provenance.
-5. **Failure Lab:** compare curated failure scenarios with their interventions.
+6. **Failure Lab:** compare curated failure scenarios with their interventions.
-Open **Read the learning guide** from Learn, Explore, or Failure Lab whenever
-you want the corresponding concept in a quieter reading format. It opens in a
-new tab, preserving the current experiment.
+Open **Read the learning guide** from Learn, Retrieval, Explore, or Failure Lab
+whenever you want the corresponding concept in a quieter reading format. It
+opens in a new tab, preserving the current experiment.
The interface is available in English and Simplified Chinese. Bundled corpus
content, questions, recorded answers, and citations keep their original
@@ -122,6 +132,9 @@ language.
### What the lab includes
- Four provider-free Guided Learn replays with complete, saved artifacts.
+- Six live Retrieval modules covering lexical and dense scoring, local vectors
+ versus optional Qdrant, hybrid fusion, cross-encoder reranking, and A/B
+ evaluation over 16 reviewed Cloudflare questions.
- A pinned Cloudflare learning corpus with ready structural and
fixed-character NumPy indexes.
- Bundled watsonxDocsQA source data and all 75 catalog questions after its
@@ -136,16 +149,18 @@ language.
- Curated failure lessons, raw-artifact inspection, source provenance,
candidate-versus-context selection, and reduced-motion-safe playback.
-The default `full` image includes the local embedding model. It runs on CPU;
-no GPU or CUDA runtime is required. To try the smaller image:
+The default `full` image includes pinned local embedding and cross-encoder
+reranker snapshots. It runs on CPU; no GPU or CUDA runtime is required. To try
+the smaller image:
```bash
LAB_IMAGE_VARIANT=slim docker compose up --build
```
Guided Learn replay and BM25 retrieval remain available in the slim image. The
-Settings page makes the embedding-model download explicit before it enables
-Dense/Hybrid retrieval or index building.
+Settings page provides separate explicit downloads for the embedding model and
+reranker. Dense/Hybrid retrieval and index building require the embedding
+model; cross-encoder experiments require the reranker.
To use the optional Qdrant comparison backend:
@@ -186,6 +201,7 @@ rag index --corpus PATH --index-dir .tiny-rag/index --chunking-strategy semantic
rag retrieve "question text" --index-dir .tiny-rag/index --top-k 5 --retriever dense
rag retrieve "question text" --index-dir .tiny-rag/index --top-k 5 --retriever bm25
rag retrieve "question text" --index-dir .tiny-rag/index --top-k 5 --retriever hybrid
+rag retrieve "question text" --index-dir .tiny-rag/index --top-k 5 --retriever hybrid --reranker cross-encoder --rerank-top-n 20
rag ask "question text" --index-dir .tiny-rag/index --top-k 5
rag ask "question text" --index-dir .tiny-rag/index --context-budget 8192 --output-format json
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 6e31579..fc4caf1 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -3,13 +3,17 @@
[English](README.md) · [项目主页](https://jameswei.github.io/tiny-rag-lab/)
> 一个以学习为先、可检查的经典 RAG 实验室:通过易读 Python、丰富的浏览器 Studio、
-> 直接的 CLI 实验、真实语料 trace 与中英双语学习指南理解 RAG。
+> 交互式检索课程、直接的 CLI 实验、真实语料 trace 与中英双语学习指南理解 RAG。
`tiny-rag-lab` 让用户问题、文档语料、检索证据、打包后的上下文与带引用答案之间的
完整路径清晰可见、可以检查。易读的 Python 直接呈现 RAG 机制;丰富的浏览器 Studio
将中间产物转化为引导回放和动手实验;直接的 CLI 支持可重复检查。当某个概念需要更
安静、深入的阅读时,可搜索的中英双语学习指南会在实验旁打开。
+Studio 还把检索栈变成一套实时课程:检查 BM25 逐词贡献、稠密余弦计算、NumPy 与
+可选 Qdrant 中的同一组向量、混合 RRF 融合、交叉编码器排名移动,以及在 16 道已
+审核真实语料问题上的双配置评估。整个过程不需要 LLM 服务商。
+
它是学习工具,而不是生产级 RAG 平台。项目优先选择可见的机制,而非框架魔法;先评估再优化;先分析失败再引入高级特性。

@@ -61,18 +65,24 @@ http://127.0.0.1:8000/docs/,也可以从实验室中的相关阶段直接打
1. **Home → Start guided lesson:** 从固定的 40 篇 Cloudflare State & Coordination 文档中,选择四个已保存课程之一进行回放。
2. **Learn:** 逐步查看语料、文本块、查询嵌入向量、检索候选、选入上下文的证据、基于证据的答案和引用。
-3. **Explore:** 提出题库问题或自由问题,比较稠密检索、BM25 和混合检索,并检查返回的 trace。只有希望进行 Live Ask 生成时,才需要配置并测试 OpenAI 兼容的 LLM 服务商。
-4. **Build & Inspect:** 使用内置语料或小型 Markdown/纯文本上传构建索引,然后检查文档、文本块、向量和来源信息。
-5. **Failure Lab:** 对比精心设计的失败场景及其改进方案。
+3. **Retrieval:** 通过六个实时模块,从词法与稠密检索机制一路学习 NumPy/Qdrant
+ 对比、混合融合、重排序,以及在 16 道已审核问题上的浏览器 A/B 评估。
+4. **Explore:** 提出题库问题或自由问题,比较稠密检索、BM25 和混合检索,可选地
+ 重排更大的候选池,并检查返回的 trace。只有希望进行 Live Ask 生成时,才需要
+ 配置并测试 OpenAI 兼容的 LLM 服务商。
+5. **Build & Inspect:** 使用内置语料或小型 Markdown/纯文本上传构建索引,然后检查文档、文本块、向量和来源信息。
+6. **Failure Lab:** 对比精心设计的失败场景及其改进方案。
-当你希望在更安静的阅读环境中理解相应概念时,可以从 Learn、Explore 或 Failure Lab
-打开**阅读学习指南**。它会在新标签页打开,并保留当前实验状态。
+当你希望在更安静的阅读环境中理解相应概念时,可以从 Learn、Retrieval、Explore 或
+Failure Lab 打开**阅读学习指南**。它会在新标签页打开,并保留当前实验状态。
界面提供英文和简体中文。内置语料内容、问题、已记录答案和引用会保留其原始语言。
### 实验室包含什么
- 四个不依赖 LLM 服务商、带完整已保存产物的 Guided Learn 回放课程。
+- 六个实时 Retrieval 模块,覆盖词法与稠密评分、本地向量与可选 Qdrant、混合融合、
+ 交叉编码器重排序,以及对 16 道已审核 Cloudflare 问题的 A/B 评估。
- 固定的 Cloudflare 学习语料,以及可直接使用的结构化分块和固定字符分块 NumPy 索引。
- 内置 watsonxDocsQA 源数据;完成显式的后台索引构建后,可以使用全部 75 个题库问题。
- 不配置 LLM 服务商也可以进行纯检索探索;通过连接测试后,可使用任何 OpenAI 兼容 Chat Completions 服务进行 Live Ask。
@@ -80,13 +90,16 @@ http://127.0.0.1:8000/docs/,也可以从实验室中的相关阶段直接打
- 默认使用 NumPy/文件索引;可选的本地 Qdrant 后端只改变存储和向量搜索的执行方式,不改变本项目要讲解的文本块、嵌入、检索、上下文、引用和 trace 概念。
- 精心设计的失败课程、原始产物检查、来源溯源、候选证据与上下文选择的对比,以及支持减少动画偏好的回放体验。
-默认的 `full` 镜像包含本地嵌入模型,只使用 CPU,不需要 GPU 或 CUDA 运行时。若想体验更小的镜像:
+默认的 `full` 镜像包含固定版本的本地嵌入模型和交叉编码器重排序模型,只使用 CPU,
+不需要 GPU 或 CUDA 运行时。若想体验更小的镜像:
```bash
LAB_IMAGE_VARIANT=slim docker compose up --build
```
-在 slim 镜像中,Guided Learn 回放和 BM25 检索仍然可用。设置页会明确提示下载嵌入模型;下载完成后才会解锁稠密/混合检索和索引构建。
+在 slim 镜像中,Guided Learn 回放和 BM25 检索仍然可用。设置页分别提供嵌入模型与
+重排序模型的显式下载。稠密/混合检索和索引构建需要嵌入模型;交叉编码器实验需要
+重排序模型。
如需使用可选的 Qdrant 对比后端:
@@ -121,6 +134,7 @@ rag index --corpus PATH --index-dir .tiny-rag/index --chunking-strategy semantic
rag retrieve "question text" --index-dir .tiny-rag/index --top-k 5 --retriever dense
rag retrieve "question text" --index-dir .tiny-rag/index --top-k 5 --retriever bm25
rag retrieve "question text" --index-dir .tiny-rag/index --top-k 5 --retriever hybrid
+rag retrieve "question text" --index-dir .tiny-rag/index --top-k 5 --retriever hybrid --reranker cross-encoder --rerank-top-n 20
rag ask "question text" --index-dir .tiny-rag/index --top-k 5
rag ask "question text" --index-dir .tiny-rag/index --context-budget 8192 --output-format json
diff --git a/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/rules-of-workflows.md b/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/rules-of-workflows.md
new file mode 100644
index 0000000..19f6e7c
--- /dev/null
+++ b/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/rules-of-workflows.md
@@ -0,0 +1,658 @@
+---
+title: Rules of Workflows
+description: Best practices for building resilient Workflows, including idempotency, state management, and error handling.
+pcx_content_type: concept
+sidebar:
+ order: 10
+products:
+ - workflows
+---
+
+import { WranglerConfig, TypeScriptExample } from "~/components";
+
+A Workflow contains one or more steps. Each step is a self-contained, individually retryable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue.
+
+This is a small guidebook on how to build more resilient and correct Workflows.
+
+### Ensure API/Binding calls are idempotent
+
+Because a step might be retried multiple times, your steps should (ideally) be idempotent. For context, idempotency is a logical property where the operation (in this case a step),
+can be applied multiple times without changing the result beyond the initial application.
+
+As an example, let us assume you have a Workflow that charges your customers, and you really do not want to charge them twice by accident. Before charging them, you should
+check if they were already charged:
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ const customer_id = 123456;
+ // ✅ Good: Non-idempotent API/Binding calls are always done **after** checking if the operation is
+ // still needed.
+ await step.do(
+ `charge ${customer_id} for its monthly subscription`,
+ async () => {
+ // API call to check if customer was already charged
+ const subscription = await fetch(
+ `https://payment.processor/subscriptions/${customer_id}`,
+ ).then((res) => res.json());
+
+ // return early if the customer was already charged, this can happen if the destination service dies
+ // in the middle of the request but still commits it, or if the Workflows Engine restarts.
+ if (subscription.charged) {
+ return;
+ }
+
+ // non-idempotent call, this operation can fail and retry but still commit in the payment
+ // processor - which means that, on retry, it would mischarge the customer again if the above checks
+ // were not in place.
+ return await fetch(
+ `https://payment.processor/subscriptions/${customer_id}`,
+ {
+ method: "POST",
+ body: JSON.stringify({ amount: 10.0 }),
+ },
+ );
+ },
+ );
+ }
+}
+```
+
+
+
+:::note
+
+Guaranteeing idempotency might be optional in your specific use-case and implementation, but we recommend that you always try to guarantee it.
+
+:::
+
+### Make your steps granular
+
+Steps should be as self-contained as possible. This allows your own logic to be more durable in case of failures in third-party APIs, network errors, and so on.
+
+You can also think of it as a transaction, or a unit of work.
+
+- ✅ Minimize the number of API/binding calls per step (unless you need multiple calls to prove idempotency).
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // ✅ Good: Unrelated API/Binding calls are self-contained, so that in case one of them fails
+ // it can retry them individually. It also has an extra advantage: you can control retry or
+ // timeout policies for each granular step - you might not to want to overload http.cat in
+ // case of it being down.
+ const httpCat = await step.do("get cutest cat from KV", async () => {
+ return await this.env.KV.get("cutest-http-cat");
+ });
+
+ const image = await step.do("fetch cat image from http.cat", async () => {
+ return await fetch(`https://http.cat/${httpCat}`);
+ });
+ }
+}
+```
+
+
+
+Otherwise, your entire Workflow might not be as durable as you might think, and you may encounter some undefined behaviour. You can avoid them by following the rules below:
+
+- 🔴 Do not encapsulate your entire logic in one single step.
+- 🔴 Do not call separate services in the same step (unless you need it to prove idempotency).
+- 🔴 Do not make too many service calls in the same step (unless you need it to prove idempotency).
+- 🔴 Do not do too much CPU-intensive work inside a single step - sometimes the engine may have to restart, and it will start over from the beginning of that step.
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: you are calling two separate services from within the same step. This might cause
+ // some extra calls to the first service in case the second one fails, and in some cases, makes
+ // the step non-idempotent altogether
+ const image = await step.do("get cutest cat from KV", async () => {
+ const httpCat = await this.env.KV.get("cutest-http-cat");
+ return fetch(`https://http.cat/${httpCat}`);
+ });
+ }
+}
+```
+
+
+
+### Do not rely on state outside of a step
+
+Workflows may hibernate and lose all in-memory state. This will happen when engine detects that there is no pending work and can hibernate until it needs to wake-up (because of a sleep, retry, or event).
+
+This means that you should not store state outside of a step:
+
+
+
+```ts
+function getRandomInt(min, max) {
+ const minCeiled = Math.ceil(min);
+ const maxFloored = Math.floor(max);
+ return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive
+}
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: `imageList` will be not persisted across engine's lifetimes. Which means that after hibernation,
+ // `imageList` will be empty again, even though the following two steps have already ran.
+ const imageList: string[] = [];
+
+ await step.do("get first cutest cat from KV", async () => {
+ const httpCat = await this.env.KV.get("cutest-http-cat-1");
+
+ imageList.push(httpCat);
+ });
+
+ await step.do("get second cutest cat from KV", async () => {
+ const httpCat = await this.env.KV.get("cutest-http-cat-2");
+
+ imageList.push(httpCat);
+ });
+
+ // A long sleep can (and probably will) hibernate the engine which means that the first engine lifetime ends here
+ await step.sleep("💤💤💤💤", "3 hours");
+
+ // When this runs, it will be on the second engine lifetime - which means `imageList` will be empty.
+ await step.do(
+ "choose a random cat from the list and download it",
+ async () => {
+ const randomCat = imageList.at(getRandomInt(0, imageList.length));
+ // this will fail since `randomCat` is undefined because `imageList` is empty
+ return await fetch(`https://http.cat/${randomCat}`);
+ },
+ );
+ }
+}
+```
+
+
+
+Instead, you should build top-level state exclusively comprised of `step.do` returns:
+
+
+
+```ts
+function getRandomInt(min, max) {
+ const minCeiled = Math.ceil(min);
+ const maxFloored = Math.floor(max);
+ return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive
+}
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // ✅ Good: imageList state is exclusively comprised of step returns - this means that in the event of
+ // multiple engine lifetimes, imageList will be built accordingly
+ const imageList: string[] = await Promise.all([
+ step.do("get first cutest cat from KV", async () => {
+ return await this.env.KV.get("cutest-http-cat-1");
+ }),
+
+ step.do("get second cutest cat from KV", async () => {
+ return await this.env.KV.get("cutest-http-cat-2");
+ }),
+ ]);
+
+ // A long sleep can (and probably will) hibernate the engine which means that the first engine lifetime ends here
+ await step.sleep("💤💤💤💤", "3 hours");
+
+ // When this runs, it will be on the second engine lifetime - but this time, imageList will contain
+ // the two most cutest cats
+ await step.do(
+ "choose a random cat from the list and download it",
+ async () => {
+ const randomCat = imageList.at(getRandomInt(0, imageList.length));
+ // this will eventually succeed since `randomCat` is defined
+ return await fetch(`https://http.cat/${randomCat}`);
+ },
+ );
+ }
+}
+```
+
+
+
+### Avoid doing side effects outside of a `step.do`
+
+It is not recommended to write code with any side effects outside of steps, unless you would like it to be repeated, because the Workflow engine may restart while an instance is running. If the engine restarts, the step logic will be preserved, but logic outside of the steps may be duplicated.
+
+For example, a `console.log()` outside of workflow steps may cause the logs to print twice when the engine restarts.
+
+However, logic involving non-serializable resources, like a database connection, should be executed outside of steps. Operations outside of a `step.do` might be repeated more than once, due to the nature of the Workflows' instance lifecycle.
+
+:::note
+If you use [Hyperdrive](/hyperdrive/) in a Workflow, create a new connection inside each `step.do()` and run your queries in that same step. Do not reuse a Hyperdrive-backed connection across steps.
+:::
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: creating instances outside of steps
+ // This might get called more than once creating more instances than expected
+ const badInstance = await this.env.ANOTHER_WORKFLOW.create();
+
+ // 🔴 Bad: using non-deterministic functions outside of steps
+ // this will produce different results if the instance has to restart, different runs of the same instance
+ // might go through different paths
+ const badRandom = Math.random();
+
+ if (badRandom > 0) {
+ // do some stuff
+ }
+
+ // ⚠️ Warning: This log may happen many times
+ console.log("This might be logged more than once");
+
+ await step.do("do some stuff and have a log for when it runs", async () => {
+ // do some stuff
+
+ // this log will only appear once
+ console.log("successfully did stuff");
+ });
+
+ // ✅ Good: wrap non-deterministic function in a step
+ // after running successfully will not run again
+ const goodRandom = await step.do("create a random number", async () => {
+ return Math.random();
+ });
+
+ // ✅ Good: calls that have no side effects can be done outside of steps
+ // For Hyperdrive, create the connection inside each step instead of here.
+ const db = createDBConnection(this.env.DB_URL, this.env.DB_TOKEN);
+
+ // ✅ Good: run functions with side effects inside of a step
+ // after running successfully will not run again
+ const goodInstance = await step.do(
+ "good step that returns state",
+ async () => {
+ const instance = await this.env.ANOTHER_WORKFLOW.create();
+
+ return instance;
+ },
+ );
+ }
+}
+```
+
+
+
+### Do not mutate your incoming events
+
+The `event` passed to your Workflow's `run` method is immutable: changes you make to the event are not persisted across steps and/or Workflow restarts.
+
+
+
+```ts
+interface MyEvent {
+ user: string;
+ data: string;
+}
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: Mutating the event
+ // This will not be persisted across steps and `event.payload` will
+ // take on its original value.
+ await step.do("bad step that mutates the incoming event", async () => {
+ let userData = await this.env.KV.get(event.payload.user);
+ event.payload = userData;
+ });
+
+ // ✅ Good: persist data by returning it as state from your step
+ // Use that state in subsequent steps
+ let userData = await step.do("good step that returns state", async () => {
+ return await this.env.KV.get(event.payload.user);
+ });
+
+ let someOtherData = await step.do(
+ "following step that uses that state",
+ async () => {
+ // Access to userData here
+ // Will always be the same if this step is retried
+ },
+ );
+ }
+}
+```
+
+
+
+### Name steps deterministically
+
+Steps should be named deterministically (that is, not using the current date/time, randomness, etc). This ensures that their state is cached, and prevents the step from being rerun unnecessarily. Step names act as the "cache key" in your Workflow.
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: Naming the step non-deterministically prevents it from being cached
+ // This will cause the step to be re-run if subsequent steps fail.
+ await step.do(`step #1 running at: ${Date.now()}`, async () => {
+ let userData = await this.env.KV.get(event.payload.user);
+ // Do not mutate event.payload
+ event.payload = userData;
+ });
+
+ // ✅ Good: give steps a deterministic name.
+ // Return dynamic values in your state, or log them instead.
+ let state = await step.do("fetch user data from KV", async () => {
+ let userData = await this.env.KV.get(event.payload.user);
+ console.log(`fetched at ${Date.now()}`);
+ return userData;
+ });
+
+ // ✅ Good: steps that are dynamically named are constructed in a deterministic way.
+ // In this case, `catList` is a step output, which is stable, and `catList` is
+ // traversed in a deterministic fashion (no shuffles or random accesses) so,
+ // it's fine to dynamically name steps (e.g: create a step per list entry).
+ let catList = await step.do("get cat list from KV", async () => {
+ return await this.env.KV.get("cat-list");
+ });
+
+ for (const cat of catList) {
+ await step.do(`get cat: ${cat}`, async () => {
+ return await this.env.KV.get(cat);
+ });
+ }
+ }
+}
+```
+
+
+
+### Take care with `Promise.race()` and `Promise.any()`
+
+Workflows allows the usage steps within the `Promise.race()` or `Promise.any()` methods as a way to achieve concurrent steps execution. However, some considerations must be taken.
+
+Due to the nature of Workflows' instance lifecycle, and given that a step inside a Promise will run until it finishes, the step that is returned during the first passage may not be the actual cached step, as [steps are cached by their names](#name-steps-deterministically).
+
+
+
+```ts
+// helper sleep method
+const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: The `Promise.race` is not surrounded by a `step.do`, which may cause undeterministic caching behavior.
+ const race_return = await Promise.race([
+ step.do("Promise first race", async () => {
+ await sleep(1000);
+ return "first";
+ }),
+ step.do("Promise second race", async () => {
+ return "second";
+ }),
+ ]);
+
+ await step.sleep("Sleep step", "2 hours");
+
+ return await step.do("Another step", async () => {
+ // This step will return `first`, even though the `Promise.race` first returned `second`.
+ return race_return;
+ });
+ }
+}
+```
+
+
+
+To ensure consistency, we suggest to surround the `Promise.race()` or `Promise.any()` within a `step.do()`, as this will ensure caching consistency across multiple passages.
+
+
+
+```ts
+// helper sleep method
+const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // ✅ Good: The `Promise.race` is surrounded by a `step.do`, ensuring deterministic caching behavior.
+ const race_return = await step.do("Promise step", async () => {
+ return await Promise.race([
+ step.do("Promise first race", async () => {
+ await sleep(1000);
+ return "first";
+ }),
+ step.do("Promise second race", async () => {
+ return "second";
+ }),
+ ]);
+ });
+
+ await step.sleep("Sleep step", "2 hours");
+
+ return await step.do("Another step", async () => {
+ // This step will return `second` because the `Promise.race` was surround by the `step.do` method.
+ return race_return;
+ });
+ }
+}
+```
+
+
+
+### Instance IDs are unique
+
+Workflow [instance IDs](/workflows/build/workers-api/#workflowinstance) are unique per Workflow. The ID is the unique identifier that associates logs, metrics, state and status of a run to a specific instance, even after completion. Allowing ID re-use would make it hard to understand if a Workflow instance ID referred to an instance that run yesterday, last week or today.
+
+It would also present a problem if you wanted to run multiple different Workflow instances with different [input parameters](/workflows/build/events-and-parameters/) for the same user ID, as you would immediately need to determine a new ID mapping.
+
+If you need to associate multiple instances with a specific user, merchant or other "customer" ID in your system, consider using a composite ID or using randomly generated IDs and storing the mapping in a database like [D1](/d1/).
+
+
+
+```ts
+// This is in the same file as your Workflow definition
+export default {
+ async fetch(req: Request, env: Env): Promise {
+ // 🔴 Bad: Use an ID that isn't unique across future Workflow invocations
+ let userId = getUserId(req); // Returns the userId
+ let badInstance = await env.MY_WORKFLOW.create({
+ id: userId,
+ params: payload,
+ });
+
+ // ✅ Good: use an ID that is unique
+ // e.g. a transaction ID, order ID, or task ID are good options
+ let instanceId = getTransactionId(); // e.g. assuming transaction IDs are unique
+ // or: compose a composite ID and store it in your database
+ // so that you can track all instances associated with a specific user or merchant.
+ instanceId = `${getUserId(req)}-${crypto.randomUUID().slice(0, 6)}`;
+ let { result } = await addNewInstanceToDB(userId, instanceId);
+ let goodInstance = await env.MY_WORKFLOW.create({
+ id: instanceId,
+ params: payload,
+ });
+
+ return Response.json({
+ id: goodInstance.id,
+ details: await goodInstance.status(),
+ });
+ },
+};
+```
+
+
+
+### `await` your steps
+
+When calling `step.do` or `step.sleep`, use `await` to avoid introducing bugs and race conditions into your Workflow code.
+
+If you don't call `await step.do` or `await step.sleep`, you create a dangling Promise. This occurs when a Promise is created but not properly `await`ed, leading to potential bugs and race conditions.
+
+This happens when you do not use the `await` keyword or fail to chain `.then()` methods to handle the result of a Promise. For example, calling `fetch(GITHUB_URL)` without awaiting its response will cause subsequent code to execute immediately, regardless of whether the fetch completed. This can cause issues like premature logging, exceptions being swallowed (and not terminating the Workflow), and lost return values (state).
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: The step isn't await'ed, and any state or errors is swallowed before it returns.
+ const badIssues = step.do(`fetch issues from GitHub`, async () => {
+ // The step will return before this call is done
+ let issues = await getIssues(event.payload.repoName);
+ return issues;
+ });
+
+ // ✅ Good: The step is correctly await'ed.
+ const goodIssues = await step.do(`fetch issues from GitHub`, async () => {
+ let issues = await getIssues(event.payload.repoName);
+ return issues;
+ });
+
+ // Rest of your Workflow goes here!
+ }
+}
+```
+
+
+
+### Use conditional logic carefully
+
+You can use `if` statements, loops, and other control flow outside of steps. However, conditions must be based on **deterministic values** — either values from `event.payload` or return values from previous steps. Non-deterministic conditions (such as `Math.random()` or `Date.now()`) outside of steps can cause unexpected behavior if the Workflow restarts.
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ const config = await step.do("fetch config", async () => {
+ return await this.env.KV.get("feature-flags", { type: "json" });
+ });
+
+ // ✅ Good: Condition based on step output (deterministic)
+ if (config.enableEmailNotifications) {
+ await step.do("send email", async () => {
+ // Send email logic
+ });
+ }
+
+ // ✅ Good: Condition based on event payload (deterministic)
+ if (event.payload.userType === "premium") {
+ await step.do("premium processing", async () => {
+ // Premium-only logic
+ });
+ }
+
+ // 🔴 Bad: Condition based on non-deterministic value outside a step
+ // This could behave differently if the Workflow restarts
+ if (Math.random() > 0.5) {
+ await step.do("maybe do something", async () => {});
+ }
+
+ // ✅ Good: Wrap non-deterministic values in a step
+ const shouldProcess = await step.do("decide randomly", async () => {
+ return Math.random() > 0.5;
+ });
+ if (shouldProcess) {
+ await step.do("conditionally do something", async () => {});
+ }
+ }
+}
+```
+
+
+
+### Batch multiple Workflow invocations
+
+When creating multiple Workflow instances, use the [`createBatch`](/workflows/build/workers-api/#createBatch) method to batch the invocations together. This allows you to create multiple Workflow instances in a single request, which will reduce the number of requests made to the Workflows API. However, each individual instance in the batch will still count towards the [creation rate limit](/workflows/reference/limits/). Unlike `create`, `createBatch` is idempotent: if an existing instance with the same ID is still within its [retention limit](/workflows/reference/limits/), it will be skipped and excluded from the returned array.
+
+
+
+```ts
+export default {
+ async fetch(req: Request, env: Env): Promise {
+ let instances = [
+ { id: "user1", params: { name: "John" } },
+ { id: "user2", params: { name: "Jane" } },
+ { id: "user3", params: { name: "Alice" } },
+ { id: "user4", params: { name: "Bob" } },
+ ];
+
+ // 🔴 Bad: Create them one by one, which is more likely to hit creation rate limits.
+ for (let instance of instances) {
+ await env.MY_WORKFLOW.create({
+ id: instance.id,
+ params: instance.params,
+ });
+ }
+
+ // ✅ Good: Batch calls together
+ // This improves throughput.
+ let createdInstances = await env.MY_WORKFLOW.createBatch(instances);
+ return Response.json({ instances: createdInstances });
+ },
+};
+```
+
+
+
+### Limit timeouts to 30 minutes or less
+
+When setting a [WorkflowStep timeout](/workflows/build/workers-api/#workflowstep), ensure that its duration is 30 minutes or less. If your use case requires a timeout greater than 30 minutes, consider using `step.waitForEvent()` instead.
+
+### Keep non-stream step return values under 1 MiB
+
+A non-stream `step.do()` return value can persist up to 1 MiB (2^20 bytes). If your step returns structured data exceeding this limit, the step will fail. This is a common issue when fetching large API responses or processing large files.
+
+In JavaScript Workflows, `ReadableStream` is a supported serializable return type for larger binary output. When persisting this kind of output, you should:
+
+- Return a new stream from the step callback.
+- Keep individual chunks under 16 MB.
+- Do not return a locked stream or a stream that has already been read.
+- Rely only on streams returned from steps.
+ :::note
+ Only byte streams are supported - use `ReadableStream`.
+
+BYOB streams and BYOB readers are not supported.
+:::
+
+Note that streamed outputs are still considered part of the Workflow instance storage limit.
+
+If these storage limits still do not work for you, consider storing your step outputs externally (for example, in [R2](/r2)) and saving a reference to it.
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: Returning a large response that may exceed 1 MiB
+ const largeData = await step.do("fetch large dataset", async () => {
+ const response = await fetch("https://api.example.com/large-dataset");
+ return await response.json(); // Could exceed 1 MiB
+ });
+
+ // ✅ Good: Store large structured data externally and return a reference
+ const dataRef = await step.do("fetch and store large dataset", async () => {
+ const response = await fetch("https://api.example.com/large-dataset");
+ const data = await response.json();
+ // Store in R2 and return a reference
+ await this.env.MY_BUCKET.put("dataset-123", JSON.stringify(data));
+ return { key: "dataset-123" };
+ });
+
+ // Retrieve the data in a later step when needed
+ const data = await step.do("process dataset", async () => {
+ const stored = await this.env.MY_BUCKET.get(dataRef.key);
+ return processData(await stored.json());
+ });
+ }
+}
+```
+
+
+
+## Related resources
+
+- [Workers Best Practices](/workers/best-practices/workers-best-practices/): code patterns for request handling, observability, and security that apply to the Workers triggering your Workflows.
+- [Rules of Durable Objects](/durable-objects/best-practices/rules-of-durable-objects/): best practices for stateful, coordinated applications — useful when combining Durable Objects with Workflows.
diff --git a/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/sleeping-and-retrying.md b/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/sleeping-and-retrying.md
new file mode 100644
index 0000000..580d207
--- /dev/null
+++ b/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/sleeping-and-retrying.md
@@ -0,0 +1,253 @@
+---
+title: Sleeping and retrying
+description: Configure sleep durations and retry logic for Workflows steps, including relative and absolute sleep timers.
+pcx_content_type: concept
+sidebar:
+ order: 4
+products:
+ - workflows
+---
+
+import { TypeScriptExample } from "~/components";
+
+This guide details how to sleep a Workflow and/or configure retries for a Workflow step.
+
+## Sleep a Workflow
+
+You can set a Workflow to sleep as an explicit step, which can be useful when you want a Workflow to wait, schedule work ahead, or pause until an input or other external state is ready.
+
+:::note
+
+A Workflow instance that is resuming from sleep will take priority over newly scheduled (queued) instances. This helps ensure that older Workflow instances can run to completion and are not blocked by newer instances.
+
+:::
+
+### Sleep for a relative period
+
+Use `step.sleep` to have a Workflow sleep for a relative period of time:
+
+```ts
+await step.sleep("sleep for a bit", "1 hour");
+```
+
+The second argument to `step.sleep` accepts both `number` (milliseconds) or a human-readable format, such as "1 minute" or "26 hours". The accepted units for `step.sleep` when used this way are as follows:
+
+```ts
+| "second"
+| "minute"
+| "hour"
+| "day"
+| "week"
+| "month"
+| "year"
+```
+
+### Sleep until a fixed date
+
+Use `step.sleepUntil` to have a Workflow sleep to a specific `Date`: this can be useful when you have a timestamp from another system or want to "schedule" work to occur at a specific time (e.g. Sunday, 9AM UTC).
+
+```ts
+// sleepUntil accepts a Date object as its second argument
+const workflowsLaunchDate = Date.parse("24 Oct 2024 13:00:00 UTC");
+await step.sleepUntil("sleep until X times out", workflowsLaunchDate);
+```
+
+You can also provide a UNIX timestamp (milliseconds since the UNIX epoch) directly to `sleepUntil`.
+
+## Retry steps
+
+Each call to `step.do` in a Workflow accepts an optional `StepConfig`, which allows you define the retry behaviour for that step.
+
+If you do not provide your own retry configuration, Workflows applies the following defaults:
+
+```ts
+const defaultConfig: WorkflowStepConfig = {
+ retries: {
+ limit: 5,
+ delay: 10000,
+ backoff: "exponential",
+ },
+ timeout: "10 minutes",
+};
+```
+
+When providing your own `StepConfig`, you can configure:
+
+- The total number of attempts to make for a step (limited to 10,000 retries per step)
+- The delay between attempts. Use a fixed duration as a `number` in milliseconds or a human-readable string, or use a function that returns the next delay.
+- What backoff algorithm to apply between each attempt: any of `constant`, `linear`, or `exponential`
+- When to timeout (in duration) before considering the step as failed (including during a retry attempt, as the timeout is set per attempt)
+
+For example, to limit a step to 10 retries and have it apply an exponential delay (starting at 10 seconds) between each attempt, you would pass the following configuration as an optional object to `step.do`:
+
+```ts
+let someState = await step.do(
+ "call an API",
+ {
+ retries: {
+ limit: 10, // The total number of attempts
+ delay: "10 seconds", // Delay between each retry
+ backoff: "exponential", // Any of "constant" | "linear" | "exponential";
+ },
+ timeout: "30 minutes",
+ },
+ async () => {
+ /* Step code goes here */
+ },
+);
+```
+
+### Set a dynamic retry delay
+
+Use a delay function when the next retry delay should depend on the failed attempt or the thrown error. This gives you more control than a fixed delay with `constant`, `linear`, or `exponential` backoff. It is useful for rate limits, downstream provider recovery, and short network failures.
+
+The delay function receives an object with:
+
+- `ctx` - the current [`WorkflowStepContext`](/workflows/build/step-context/), including `ctx.attempt`.
+- `error` - the error that caused the retry.
+
+Return a duration string, a number in milliseconds, or a promise that resolves to either value.
+
+
+
+```ts
+await step.do(
+ "sync customer",
+ {
+ retries: {
+ limit: 5,
+ delay: ({ ctx, error }) => {
+ if (error.message.includes("rate limit")) {
+ return `${ctx.attempt * 30} seconds`;
+ }
+
+ return "10 seconds";
+ },
+ },
+ },
+ async () => {
+ await syncCustomer();
+ },
+);
+```
+
+
+
+## Force a Workflow instance to fail
+
+You can also force a Workflow instance to fail and _not_ retry by throwing a `NonRetryableError` from within the step.
+
+This can be useful when you detect a terminal (permanent) error from an upstream system (such as an authentication failure) or other errors where retrying would not help.
+
+```ts
+// Import the NonRetryableError definition
+import {
+ WorkflowEntrypoint,
+ WorkflowStep,
+ WorkflowEvent,
+} from "cloudflare:workers";
+import { NonRetryableError } from "cloudflare:workflows";
+
+// In your step code:
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ await step.do("some step", async () => {
+ if (!event.payload.data) {
+ throw new NonRetryableError(
+ "event.payload.data did not contain the expected payload",
+ );
+ }
+ });
+ }
+}
+```
+
+The Workflow instance itself will fail immediately, no further steps will be invoked, and the Workflow will not be retried.
+
+If earlier steps registered rollback handlers, those handlers will still run before the instance settles into its terminal state.
+
+## Register rollback handlers
+
+You can attach a rollback handler to `step.do()` to implement saga-style compensation. When the Workflow later fails, Workflows runs registered rollback handlers in reverse `step-start` order.
+
+A failed step with rollback options can also participate in rollback alongside any completed steps which have a rollback handler registered. For example, if a steps throws a `NonRetryableError` after registering rollback, its rollback handler runs with `output` set to `undefined`.
+
+
+
+```ts
+import {
+ WorkflowEntrypoint,
+ type WorkflowEvent,
+ type WorkflowStep,
+} from "cloudflare:workers";
+import { NonRetryableError } from "cloudflare:workflows";
+
+export class OrderWorkflow extends WorkflowEntrypoint {
+ async run(_event: WorkflowEvent, step: WorkflowStep) {
+ await step.do(
+ "reserve inventory",
+ async () => {
+ const reservation = await reserveInventory();
+ return { reservationId: reservation.id };
+ },
+ {
+ rollback: async ({ output }) => {
+ const { reservationId } = output as { reservationId: string };
+ await releaseInventory(reservationId);
+ },
+ rollbackConfig: {
+ retries: { limit: 3, delay: "10 seconds", backoff: "linear" },
+ timeout: "2 minutes",
+ },
+ },
+ );
+
+ await step.do("charge card", async () => {
+ throw new NonRetryableError("payment processor rejected the charge");
+ });
+ }
+}
+```
+
+
+
+Rollback handlers receive:
+
+- `error` - the error that caused the Workflow to fail.
+- `output` - the value returned by the forward step, or `undefined` if the step failed before returning
+
+You can use `rollbackConfig` to control retry behavior for the rollback handler. Throw a `NonRetryableError` from the rollback handler to stop retrying it immediately.
+
+## Catch Workflow errors
+
+Any uncaught exceptions that propagate to the top level, or any steps that reach their retry limit, will cause the Workflow to end execution in an `Errored` state.
+
+If you want to avoid this, you can catch exceptions emitted by a `step`. This can be useful if you need to trigger clean-up tasks or have conditional logic that triggers additional steps.
+
+To allow the Workflow to continue its execution, surround the intended steps that are allowed to fail with a `try...catch` block.
+
+```ts
+...
+await step.do('task', async () => {
+ // work to be done
+});
+
+try {
+ await step.do('non-retryable-task', async () => {
+ // work not to be retried
+ throw new NonRetryableError('oh no');
+ });
+} catch (e) {
+ console.log(`Step failed: ${e.message}`);
+ await step.do('clean-up-task', async () => {
+ // Clean up code here
+ });
+}
+
+// the Workflow will not fail and will continue its execution
+
+await step.do('next-task', async() => {
+ // more work to be done
+});
+...
+```
diff --git a/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/step-context.md b/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/step-context.md
new file mode 100644
index 0000000..18bd00f
--- /dev/null
+++ b/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/step-context.md
@@ -0,0 +1,114 @@
+---
+title: Step context
+description: Access runtime information in Workflows steps using the WorkflowStepContext object, including step name and retry attempt.
+pcx_content_type: concept
+sidebar:
+ order: 5
+products:
+ - workflows
+---
+
+Every `step.do` callback receives a **context object** (`WorkflowStepContext`) as its first argument. The context gives your step code runtime information about the step itself, the current retry attempt, and the resolved configuration for that step.
+
+## WorkflowStepContext
+
+```ts
+type WorkflowStepContext = {
+ step: {
+ name: string;
+ count: number;
+ };
+ attempt: number;
+ config: WorkflowStepConfig;
+};
+```
+
+### Properties
+
+| Property | Type | Description |
+| ------------ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
+| `step.name` | `string` | The name you passed to `step.do`. |
+| `step.count` | `number` | How many times `step.do` has been called with this name so far in the current Workflow run. Starts at `1` for the first call with a given name. |
+| `attempt` | `number` | The current attempt number (1-indexed). `1` on the first try, `2` on the first retry, and so on. |
+| `config` | [`WorkflowStepConfig`](/workflows/build/workers-api/#workflowstepconfig) | The resolved retry and timeout configuration for this step, including any defaults applied by the runtime. |
+
+If a step config's `retries.delay` is a function, the dynamic delay is not exposed on `ctx.config.retries.delay`. The delay function receives its own context object with the current step context and the error that caused the retry.
+
+## Access the context
+
+Pass a parameter to your `step.do` callback to receive the context object:
+
+```ts
+await step.do("my-step", async (ctx) => {
+ console.log(ctx.step.name); // "my-step"
+ console.log(ctx.step.count); // 1
+ console.log(ctx.attempt); // 1 on first try, 2 on first retry, etc.
+ console.log(ctx.config); // { retries: { limit: 5, ... }, timeout: "10 minutes" }
+});
+```
+
+The context is also available when you pass a custom `WorkflowStepConfig`:
+
+```ts
+await step.do(
+ "call an API",
+ {
+ retries: {
+ limit: 10,
+ delay: "10 seconds",
+ backoff: "exponential",
+ },
+ timeout: "30 minutes",
+ },
+ async (ctx) => {
+ console.log(ctx.config.retries.limit); // 10
+ console.log(ctx.config.timeout); // "30 minutes"
+ },
+);
+```
+
+To configure delay functions, refer to [Set a dynamic retry delay](/workflows/build/sleeping-and-retrying/#set-a-dynamic-retry-delay).
+
+## Examples
+
+### Adjust behavior based on retry attempt
+
+Use `ctx.attempt` to change how your step behaves on retries. For example, you might use a fallback endpoint after a certain number of retries:
+
+```ts
+await step.do(
+ "fetch data",
+ { retries: { limit: 5, delay: "5 seconds", backoff: "linear" } },
+ async (ctx) => {
+ const url =
+ ctx.attempt <= 3
+ ? "https://api.example.com/primary"
+ : "https://api.example.com/fallback";
+
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(`Request failed with status ${response.status}`);
+ }
+ return await response.json();
+ },
+);
+```
+
+### Log step metadata for observability
+
+Use `ctx.step` to add structured metadata to your logs:
+
+```ts
+await step.do("process-order", async (ctx) => {
+ console.log(
+ JSON.stringify({
+ step: ctx.step.name,
+ stepCount: ctx.step.count,
+ attempt: ctx.attempt,
+ retryLimit: ctx.config.retries?.limit,
+ }),
+ );
+
+ // Your step logic here
+});
+```
diff --git a/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/trigger-workflows.md b/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/trigger-workflows.md
new file mode 100644
index 0000000..4ce0805
--- /dev/null
+++ b/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/trigger-workflows.md
@@ -0,0 +1,293 @@
+---
+title: Trigger Workflows
+description: Trigger Workflows from Workers bindings, the REST API, or the Wrangler CLI.
+pcx_content_type: concept
+tags:
+ - Bindings
+sidebar:
+ order: 3
+products:
+ - workflows
+---
+
+import { TypeScriptExample, WranglerConfig } from "~/components";
+
+You can trigger Workflows both programmatically and via the Workflows APIs, including:
+
+1. With [Workers](/workers) via HTTP requests in a `fetch` handler, or bindings from a `queue` or `scheduled` handler
+2. On a recurring interval by defining `schedules` on a Workflow binding in your Wrangler configuration
+3. Using the [Workflows REST API](/api/resources/workflows/methods/list/)
+4. Via the [wrangler CLI](/workers/wrangler/commands/workflows/#workflows) in your terminal
+
+## Workers API (Bindings)
+
+You can interact with Workflows programmatically from any Worker script by creating a binding to a Workflow. A Worker can bind to multiple Workflows, including Workflows defined in other Workers projects (scripts) within your account.
+
+You can trigger a Workflow:
+
+- Directly over HTTP via the [`fetch`](/workers/runtime-apis/handlers/fetch/) handler
+- From a [Queue consumer](/queues/configuration/javascript-apis/#consumer) inside a `queue` handler
+- On a recurring schedule by defining `schedules` on the Workflow binding in `wrangler.jsonc`
+- From a [Cron Trigger](/workers/configuration/cron-triggers/) inside a `scheduled` handler
+- Within a [Durable Object](/durable-objects/)
+
+:::note
+
+New to Workflows? Start with the [Workflows tutorial](/workflows/get-started/guide/) to deploy your first Workflow and familiarize yourself with Workflows concepts.
+
+:::
+
+To bind to a Workflow from your Workers code, you need to define a [binding](/workers/wrangler/configuration/) to a specific Workflow. For example, to bind to the Workflow defined in the [get started guide](/workflows/get-started/guide/), you would configure the [Wrangler configuration file](/workers/wrangler/configuration/) with the below:
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "workflows-tutorial",
+ "main": "src/index.ts",
+ "compatibility_date": "$today",
+ "workflows": [
+ {
+ // The name of the Workflow
+ "name": "workflows-tutorial",
+ // The binding name, which must be a valid JavaScript variable name. This will
+ // be how you call (run) your Workflow from your other Workers handlers or
+ // scripts.
+ "binding": "MY_WORKFLOW",
+ // Must match the class defined in your code that extends the Workflow class
+ "class_name": "MyWorkflow"
+ }
+ ]
+}
+```
+
+
+
+The `binding = "MY_WORKFLOW"` line defines the JavaScript variable that our Workflow methods are accessible on, including `create` (which triggers a new instance) or `get` (which returns the status of an existing instance).
+
+### Schedule a Workflow directly
+
+If you want to create Workflow instances on a recurring interval, add a `schedules` array (up to 100 cron expressions per account) to the Workflow binding in your Wrangler configuration:
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "workflows-tutorial",
+ "main": "src/index.ts",
+ "compatibility_date": "$today",
+ "workflows": [
+ {
+ "name": "workflows-tutorial",
+ "binding": "MY_WORKFLOW",
+ "class_name": "MyWorkflow",
+ "schedules": ["0 * * * *"]
+ }
+ ]
+}
+```
+
+
+
+Each matching cron expression creates a new Workflow instance automatically. Use this when you want to run a Workflow on a schedule without defining top-level `triggers.crons` and a separate `scheduled` handler.
+
+Scheduled instances include the matching cron expression and scheduled trigger time on `event.schedule`:
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ if (event.schedule) {
+ console.log(event.schedule.cron);
+ console.log(new Date(event.schedule.scheduledTime));
+ }
+ }
+}
+```
+
+On [Workers Paid](/workers/platform/pricing/#workers), Workflow instances created by `schedules` can run for up to one hour per cron firing without consuming a Workflow concurrency slot. If the instance pauses or sleeps after that window, the instance yields and enters the normal concurrency queue upon resume. It resumes when a concurrency slot is available.
+
+Use the latest Wrangler release when configuring Workflow schedules. If your local Wrangler schema does not recognize `schedules` yet, update Wrangler before deploying.
+
+The following example shows how you can manage Workflows from within a Worker, including:
+
+- Retrieving the status of an existing Workflow instance by its ID
+- Creating (triggering) a new Workflow instance
+- Returning the status of a given instance ID
+
+```ts title="src/index.ts"
+interface Env {
+ MY_WORKFLOW: Workflow;
+}
+
+export default {
+ async fetch(req: Request, env: Env) {
+ // Get instanceId from query parameters
+ const instanceId = new URL(req.url).searchParams.get("instanceId");
+
+ // If an ?instanceId= query parameter is provided, fetch the status
+ // of an existing Workflow by its ID.
+ if (instanceId) {
+ let instance = await env.MY_WORKFLOW.get(instanceId);
+ return Response.json({
+ status: await instance.status(),
+ });
+ }
+
+ // Else, create a new instance of our Workflow, passing in any (optional)
+ // params and return the ID.
+ const newId = crypto.randomUUID();
+ let instance = await env.MY_WORKFLOW.create({ id: newId });
+ return Response.json({
+ id: instance.id,
+ details: await instance.status(),
+ });
+ },
+};
+```
+
+### Inspect a Workflow's status
+
+You can inspect the status of any running Workflow instance by calling `status` against a specific instance ID. This allows you to programmatically inspect whether an instance is queued (waiting to be scheduled), actively running, paused, or errored.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+let status = await instance.status(); // Returns an InstanceStatus
+```
+
+The possible values of status are as follows:
+
+```ts
+ status:
+ | "queued" // means that instance is waiting to be started (see concurrency limits)
+ | "running"
+ | "paused"
+ | "errored"
+ | "terminated" // user terminated the instance while it was running
+ | "complete"
+ | "waiting" // instance is hibernating and waiting for sleep or event to finish
+ | "waitingForPause" // instance is finishing the current work to pause
+ | "unknown";
+ error?: {
+ name: string,
+ message: string
+ };
+ output?: unknown;
+ rollback:
+ | {
+ outcome: "complete" | "failed";
+ error: {
+ name: string,
+ message: string,
+ } | null,
+ }
+ | null;
+```
+
+If your Workflow registers rollback handlers on `step.do()`, inspect `rollback` after the instance finishes to see whether the compensating steps completed successfully. While rollback is actively running, the Workers API continues to return `status: "running"`.
+
+### Explicitly pause a Workflow
+
+You can explicitly pause a Workflow instance (and later resume it) by calling `pause` against a specific instance ID.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+await instance.pause(); // Returns Promise
+```
+
+### Resume a Workflow
+
+You can resume a paused Workflow instance by calling `resume` against a specific instance ID.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+await instance.resume(); // Returns Promise
+```
+
+Calling `resume` on an instance that is not currently paused will have no effect.
+
+:::caution
+If you have reached the maximum concurrent instances for your Workflow, resuming an instance may not restart it immediately. The instance will be queued until a concurrency slot becomes available.
+:::
+
+### Stop a Workflow
+
+You can stop/terminate a Workflow instance by calling `terminate` against a specific instance ID.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+await instance.terminate(); // Returns Promise
+```
+
+To run registered rollback handlers before terminating, pass `rollback: true`:
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+await instance.terminate({ rollback: true }); // Returns Promise
+```
+
+You can also run rollback handlers from Wrangler:
+
+```sh
+npx wrangler workflows instances terminate --rollback
+# For a local Workflows instance during wrangler dev:
+npx wrangler workflows instances terminate --local --rollback
+```
+
+Once stopped/terminated, the Workflow instance _cannot_ be resumed.
+
+### Restart a Workflow
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+await instance.restart(); // Returns Promise
+```
+
+Restarting an instance will immediately cancel any in-progress steps, erase any intermediate state, and treat the Workflow as if it was run for the first time.
+
+To restart an instance from a specific step instead of the beginning, refer to [`restart`](/workflows/build/workers-api/#restart) in the Workers API reference.
+
+### Trigger a Workflow from another Workflow
+
+You can create a new Workflow instance from within a step of another Workflow. The parent Workflow will not block waiting for the child Workflow to complete — it continues execution immediately after the child instance is successfully created.
+
+
+
+```ts
+export class ParentWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // Perform initial work
+ const result = await step.do("initial processing", async () => {
+ // ... processing logic
+ return { fileKey: "output.pdf" };
+ });
+
+ // Trigger a child workflow for additional processing
+ const childInstance = await step.do("trigger child workflow", async () => {
+ return await this.env.CHILD_WORKFLOW.create({
+ id: `child-${event.instanceId}`,
+ params: { fileKey: result.fileKey },
+ });
+ });
+
+ // Parent continues immediately - not blocked by child workflow
+ await step.do("continue with other work", async () => {
+ console.log(`Started child workflow: ${childInstance.id}`);
+ // This runs right away, regardless of child workflow status
+ });
+ }
+}
+```
+
+
+
+If the child Workflow fails to start, the step will fail and be retried according to your retry configuration. Once the child instance is successfully created, it runs independently from the parent.
+
+## REST API (HTTP)
+
+Refer to the [Workflows REST API documentation](/api/resources/workflows/subresources/instances/methods/create/).
+
+## Command line (CLI)
+
+Refer to the [CLI quick start](/workflows/get-started/guide/) to learn more about how to manage and trigger Workflows via the command-line.
diff --git a/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/workers-api.md b/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/workers-api.md
new file mode 100644
index 0000000..4988c27
--- /dev/null
+++ b/assets/seed/v1/corpora/cloudflare-state-v1/files/workflows/build/workers-api.md
@@ -0,0 +1,796 @@
+---
+title: Workers API
+description: Reference for the Workflows Workers API, including WorkflowEntrypoint, step methods, and instance management.
+pcx_content_type: concept
+sidebar:
+ order: 2
+products:
+ - workflows
+---
+
+import {
+ MetaInfo,
+ Render,
+ Type,
+ TypeScriptExample,
+ WranglerConfig,
+} from "~/components";
+
+This guide details the Workflows API within Cloudflare Workers, including methods, types, and usage examples.
+
+## WorkflowEntrypoint
+
+The `WorkflowEntrypoint` class is the core element of a Workflow definition. A Workflow must extend this class and define a `run` method with at least one `step` call to be considered a valid Workflow.
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // Steps here
+ }
+}
+```
+
+### run
+
+- run(event: WorkflowEvent<T>, step: WorkflowStep): Promise<T>
+ - `event` - the event passed to the Workflow, including an optional `payload` containing data (parameters)
+ - `step` - the `WorkflowStep` type that provides the step methods for your Workflow
+
+The `run` method can optionally return data, which is available when querying the instance status via the [Workers API](/workflows/build/workers-api/#instancestatus), [REST API](/api/resources/workflows/subresources/instances/subresources/status/) and the Workflows dashboard. This can be useful if your Workflow is computing a result, returning the key to data stored in object storage, or generating some kind of identifier you need to act on.
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // Steps here
+ let someComputedState = await step.do("my step", async () => {});
+
+ // Optional: return state from our run() method
+ return someComputedState;
+ }
+}
+```
+
+The `WorkflowEvent` type accepts an optional [type parameter](https://www.typescriptlang.org/docs/handbook/2/generics.html#working-with-generic-type-variables) that allows you to provide a type for the `payload` property within the `WorkflowEvent`.
+
+Refer to the [events and parameters](/workflows/build/events-and-parameters/) documentation for how to handle events within your Workflow code.
+
+Finally, any JS control-flow primitive (if conditions, loops, `try...catch` blocks, promises, and more) can be used to manage steps inside the `run` method.
+
+## WorkflowEvent
+
+```ts
+export type WorkflowCronSchedule = {
+ /** Cron expression that triggered this event. */
+ cron: string;
+ /** Timestamp of the scheduled trigger, in milliseconds since the Unix epoch. */
+ scheduledTime: number;
+};
+
+export type WorkflowEvent = {
+ payload: Readonly;
+ timestamp: Date;
+ instanceId: string;
+ workflowName: string;
+ schedule?: WorkflowCronSchedule;
+};
+```
+
+- The `WorkflowEvent` is the first argument to a Workflow's `run` method.
+ - `payload` - a default type of `any` or type `T` if a type parameter is provided.
+ - `timestamp` - a `Date` object set to the time the Workflow instance was created (triggered).
+ - `instanceId` - the ID of the associated instance.
+ - `workflowName` - the name of the associated Workflow.
+ - `schedule` - metadata for Workflow instances created by a cron schedule, including the `cron` expression and `scheduledTime` in milliseconds since the Unix epoch.
+
+Refer to the [events and parameters](/workflows/build/events-and-parameters/) documentation for how to handle events within your Workflow code.
+
+## WorkflowStep
+
+### step
+
+{/* prettier-ignore */}
+- step.do(name: string, callback: (ctx: WorkflowStepContext): RpcSerializable): Promise<T>
+- step.do(name: string, callback: (ctx: WorkflowStepContext): RpcSerializable, rollbackOptions?: WorkflowStepRollbackOptions<T>): Promise<T>
+- step.do(name: string, config?: WorkflowStepConfig, callback: (ctx: WorkflowStepContext):
+ RpcSerializable): Promise<T>
+ - `name` - the name of the step, up to 256 characters.
+ - `config` (optional) - an optional `WorkflowStepConfig` for configuring [step specific retry behaviour](/workflows/build/sleeping-and-retrying/).
+ - `callback` - an asynchronous function that receives a [`WorkflowStepContext`](/workflows/build/step-context/) and optionally returns serializable state for the Workflow to persist. In JavaScript Workflows, this includes a fresh, unlocked `ReadableStream` for large binary output.
+- step.do(name: string, config?: WorkflowStepConfig, callback: (ctx: WorkflowStepContext):
+ RpcSerializable, rollbackOptions?: WorkflowStepRollbackOptions<T>): Promise<T>
+ - `name` - the name of the step, up to 256 characters.
+ - `config` (optional) - an optional `WorkflowStepConfig` for configuring [step specific retry behaviour](/workflows/build/sleeping-and-retrying/).
+ - `callback` - an asynchronous function that receives a [`WorkflowStepContext`](/workflows/build/step-context/) and optionally returns serializable state for the Workflow to persist. In JavaScript Workflows, this includes a fresh, unlocked `ReadableStream` for large binary output.
+ - `rollbackOptions` (optional) - register rollback logic for the step. If the Workflow later fails, registered rollbacks run in reverse step-start order.
+
+:::note[Returning state]
+
+When returning state from a `step`, ensure that the object you return is _serializable_.
+
+Primitive types like `string`, `number`, and `boolean`, along with composite structures such as `Array` and `Object` (provided they only contain serializable values), can be serialized. Any [structured-cloneable](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) type can be serialized, as long it is no longer than 1 MB.
+
+On the other hand, objects that include `Function` or `Symbol` types, and objects with circular references, cannot be serialized. The Workflow instance will throw an error if objects with those types is returned.
+
+In JavaScript Workflows, `ReadableStream` is a supported serializable return type when a step needs to persist larger binary output than the normal 1 MiB non-stream step-result limit.
+
+Return a new stream from the callback.
+
+:::caution
+Do not return a locked stream or a stream that has already been read. BYOB streams and BYOB readers are not supported.
+:::
+
+After a `ReadableStream` object has been persisted within a step, it should not be reused - rely on the new fresh stream that gets returned from step. The bytes are preserved from the original stream, but the implementation might differ.
+
+:::
+
+
+
+```ts
+type Env = {
+ MY_BUCKET: R2Bucket;
+};
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(_event: WorkflowEvent, step: WorkflowStep) {
+ const reportStream = await step.do("read report from R2", async () => {
+ const object = await this.env.MY_BUCKET.get("reports/latest.csv");
+
+ if (!object?.body) {
+ throw new Error("Could not read reports/latest.csv from R2.");
+ }
+
+ return object.body;
+ });
+
+ const preview = await new Response(reportStream).text();
+ return { preview };
+ }
+}
+```
+
+
+
+- step.sleep(name: string, duration: WorkflowDuration): Promise<void>
+ - `name` - the name of the step.
+ - `duration` - the duration to sleep until, in either seconds or as a `WorkflowDuration` compatible string.
+ - Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how Workflows are retried.
+
+- step.sleepUntil(name: string, timestamp: Date | number): Promise<void>
+ - `name` - the name of the step.
+ - `timestamp` - a JavaScript `Date` object or milliseconds from the Unix epoch to sleep the Workflow instance until.
+
+:::note
+
+`step.sleep` and `step.sleepUntil` methods do not count towards the maximum Workflow steps limit.
+
+More information about the limits imposed on Workflow can be found in the [Workflows limits documentation](/workflows/reference/limits/).
+
+:::
+
+- step.waitForEvent(name: string, options: ): Promise<void>-
+ `name` - the name of the step. - `options` - an object with properties for
+ `type` (up to 100 characters [^1]), which determines which event type this
+ `waitForEvent` call will match on when calling `instance.sendEvent`, and an
+ optional `timeout` property, which defines how long the `waitForEvent` call
+ will block for before throwing a timeout exception. The default timeout is 24
+ hours.
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // Other steps in your Workflow
+ let stripeEvent = await step.waitForEvent(
+ "receive invoice paid webhook from Stripe",
+ { type: "stripe-webhook", timeout: "1 hour" },
+ );
+ // Rest of your Workflow
+ }
+}
+```
+
+
+
+Review the documentation on [events and parameters](/workflows/build/events-and-parameters/) to learn how to send events to a running Workflow instance.
+
+## WorkflowStepConfig
+
+```ts
+export type WorkflowDynamicDelayContext = {
+ ctx: WorkflowStepContext;
+ error: Error;
+};
+
+export type WorkflowDelayFunction = (
+ input: WorkflowDynamicDelayContext,
+) => string | number | Promise;
+
+export type WorkflowStepConfig = {
+ retries?: {
+ limit: number;
+ delay: string | number | WorkflowDelayFunction;
+ backoff?: WorkflowBackoff;
+ };
+ timeout?: string | number;
+};
+```
+
+- A `WorkflowStepConfig` is an optional argument to the `do` method of a `WorkflowStep` and defines properties that allow you to configure the retry behaviour of that step.
+- Set `retries.delay` to a fixed duration, or pass a `WorkflowDelayFunction` to calculate the next retry delay from the current step context and thrown error.
+
+Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how Workflows are retried.
+
+## Rollback options
+
+```ts
+type WorkflowRollbackContext = {
+ ctx: WorkflowStepContext;
+ error: Error;
+ output: T | undefined;
+};
+
+type WorkflowRollbackHandler = (
+ ctx: WorkflowRollbackContext,
+) => Promise;
+
+type WorkflowStepRollbackConfig = Pick<
+ WorkflowStepConfig,
+ "retries" | "timeout"
+>;
+
+type WorkflowStepRollbackOptions = {
+ rollback: WorkflowRollbackHandler;
+ rollbackConfig?: WorkflowStepRollbackConfig;
+};
+```
+
+- Pass this `WorkflowStepRollbackOptions` object as the final argument to `step.do()` to register a compensating action for a successful step.
+- `rollback` receives the original step context, the error that caused the Workflow to fail, and the step output returned by the forward step.
+- `rollbackConfig` applies retry and timeout settings to the rollback handler itself.
+
+
+
+```ts
+export class BillingWorkflow extends WorkflowEntrypoint {
+ async run(_event: WorkflowEvent, step: WorkflowStep) {
+ await step.do(
+ "create charge",
+ async () => {
+ const charge = await createCharge();
+ return { chargeId: charge.id };
+ },
+ {
+ rollback: async ({ ctx, output, error }) => {
+ const { chargeId } = output as { chargeId: string };
+ await refundCharge(chargeId, {
+ reason: `${ctx.step.name}: ${error.message}`,
+ });
+ },
+ rollbackConfig: {
+ retries: {
+ limit: 3,
+ delay: "30 seconds",
+ backoff: "linear",
+ },
+ timeout: "5 minutes",
+ },
+ },
+ );
+ }
+}
+```
+
+
+
+## WorkflowStepContext
+
+```ts
+export type WorkflowStepContext = {
+ step: {
+ name: string;
+ count: number;
+ };
+ attempt: number;
+ config: WorkflowStepConfig;
+};
+```
+
+- The `WorkflowStepContext` is passed as the first argument to the `step.do` callback function. It provides runtime information about the current step.
+ - `step.name` - the name of the step as passed to `step.do`.
+ - `step.count` - how many times `step.do` has been called with this name in the current Workflow run (1-indexed).
+ - `attempt` - the current attempt number (1-indexed). `1` on the first try, `2` on the first retry, and so on.
+ - `config` - the resolved `WorkflowStepConfig` for this step, including any defaults applied by the runtime.
+
+Refer to the [step context documentation](/workflows/build/step-context/) for usage examples.
+
+## Workflow step limits
+
+Each workflow on Workers Paid supports 10,000 steps by default. You can increase this up to 25,000 steps by configuring `steps` within the `limits` property of your Workflow definition in your Wrangler configuration:
+
+
+
+```toml
+[[workflows]]
+name = "my-workflow"
+binding = "MY_WORKFLOW"
+class_name = "MyWorkflow"
+
+[workflows.limits]
+steps = 25_000
+```
+
+
+
+`step.sleep` does not count towards the maximum steps limit.
+
+Note that Workflows on Workers Free have a limit of 1,024 steps. Refer to [Workflow limits](/workflows/reference/limits/) for more information.
+
+## NonRetryableError
+
+- throw new NonRetryableError(message: , name ):
+ - When thrown inside [`step.do()`](/workflows/build/workers-api/#step), this error stops step retries, propagating the error to the top level (the [run](/workflows/build/workers-api/#run) function). Any error not handled at this top level will cause the Workflow instance to fail.
+ - Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how Workflows steps are retried.
+
+## Call Workflows from Workers
+
+Workflows exposes an API directly to your Workers scripts via the [bindings](/workers/runtime-apis/bindings/#what-is-a-binding) concept. Bindings allow you to securely call a Workflow without having to manage API keys or clients.
+
+You can bind to a Workflow by defining a `[[workflows]]` binding within your Wrangler configuration.
+
+For example, to bind to a Workflow called `workflows-starter` and to make it available on the `MY_WORKFLOW` variable to your Worker script, you would configure the following fields within the `[[workflows]]` binding definition:
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "workflows-starter",
+ "main": "src/index.ts",
+ "compatibility_date": "$today",
+ "workflows": [
+ {
+ // name of your workflow
+ "name": "workflows-starter",
+ // binding name env.MY_WORKFLOW
+ "binding": "MY_WORKFLOW",
+ // this is class that extends the Workflow class in src/index.ts
+ "class_name": "MyWorkflow",
+ },
+ ],
+}
+```
+
+
+
+### Bind from Pages
+
+You can bind and trigger Workflows from [Pages Functions](/pages/functions/) by deploying a Workers project with your Workflow definition and then invoking that Worker using [service bindings](/pages/functions/bindings/#service-bindings) or a standard `fetch()` call.
+
+Visit the documentation on [calling Workflows from Pages](/workflows/build/call-workflows-from-pages/) for examples.
+
+### Cross-script calls
+
+You can also bind to a Workflow that is defined in a different Worker script from the script your Workflow definition is in. To do this, provide the `script_name` key with the name of the script to the `[[workflows]]` binding definition in your Wrangler configuration.
+
+For example, if your Workflow is defined in a Worker script named `billing-worker`, but you are calling it from your `web-api-worker` script, your [Wrangler configuration file](/workers/wrangler/configuration/) would resemble the following:
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "web-api-worker",
+ "main": "src/index.ts",
+ "compatibility_date": "$today",
+ "workflows": [
+ {
+ // name of your workflow
+ "name": "billing-workflow",
+ // binding name env.MY_WORKFLOW
+ "binding": "MY_WORKFLOW",
+ // this is class that extends the Workflow class in src/index.ts
+ "class_name": "MyWorkflow",
+ // the script name where the Workflow is defined.
+ // required if the Workflow is defined in another script.
+ "script_name": "billing-worker",
+ },
+ ],
+}
+```
+
+
+
+
+
+## Workflow
+
+:::note
+
+Ensure you have a compatibility date `2024-10-22` or later installed when binding to Workflows from within a Workers project.
+
+:::
+
+The `Workflow` type provides methods that allow you to create, inspect the status, and manage running Workflow instances from within a Worker script.
+It is part of the generated types produced by [`wrangler types`](/workers/wrangler/commands/general/#types).
+
+```ts title="./worker-configuration.d.ts"
+interface Env {
+ // The 'MY_WORKFLOW' variable should match the "binding" value set in the Wrangler config file
+ MY_WORKFLOW: Workflow;
+}
+```
+
+The `Workflow` type exports the following methods:
+
+### create
+
+Create (trigger) a new instance of the given Workflow.
+
+- create(options?: WorkflowInstanceCreateOptions): Promise<WorkflowInstance>
+ - `options` - optional properties to pass when creating an instance, including a user-provided ID and payload parameters.
+
+An ID is automatically generated, but a user-provided ID can be specified (up to 100 characters [^1]). This can be useful when mapping Workflows to users, merchants or other identifiers in your system. You can also provide a JSON object as the `params` property, allowing you to pass data for the Workflow instance to act on as its [`WorkflowEvent`](/workflows/build/events-and-parameters/).
+
+```ts
+// Create a new Workflow instance with your own ID and pass params to the Workflow instance
+let instance = await env.MY_WORKFLOW.create({
+ id: myIdDefinedFromOtherSystem,
+ params: { hello: "world" },
+});
+return Response.json({
+ id: instance.id,
+ details: await instance.status(),
+});
+```
+
+Returns a `WorkflowInstance`.
+
+Throws an error if the provided ID is already used by an existing instance that has not yet passed its [retention limit](/workflows/reference/limits/). To re-run a workflow with the same ID, you can [`restart`](/workflows/build/trigger-workflows/#restart-a-workflow) the existing instance.
+
+
+
+To provide an optional type parameter to the `Workflow`, pass a type argument with your type when defining your Workflow bindings:
+
+```ts
+interface User {
+ email: string;
+ createdTimestamp: number;
+}
+
+interface Env {
+ // Pass our User type as the type parameter to the Workflow definition
+ MY_WORKFLOW: Workflow;
+}
+
+export default {
+ async fetch(request, env, ctx) {
+ // More likely to come from your database or via the request body!
+ const user: User = {
+ email: user@example.com,
+ createdTimestamp: Date.now()
+ }
+
+ let instance = await env.MY_WORKFLOW.create({
+ // params expects the type User
+ params: user
+ })
+
+ return Response.json({
+ id: instance.id,
+ details: await instance.status(),
+ });
+ }
+}
+```
+
+### createBatch
+
+Create (trigger) a batch of new instance of the given Workflow, up to 100 instances at a time.
+
+This is useful when you are scheduling multiple instances at once. A call to `createBatch` is treated the same as a call to `create` (for a single instance) and allows you to work within the [instance creation limit](/workflows/reference/limits/).
+
+- createBatch(batch: WorkflowInstanceCreateOptions[]): Promise<WorkflowInstance[]>
+ - `batch` - list of Options to pass when creating an instance, including a user-provided ID and payload parameters.
+
+Each element of the `batch` list is expected to include both `id` and `params` properties:
+
+```ts
+// Create a new batch of 3 Workflow instances, each with its own ID and pass params to the Workflow instances
+const listOfInstances = [
+ { id: "id-abc123", params: { hello: "world-0" } },
+ { id: "id-def456", params: { hello: "world-1" } },
+ { id: "id-ghi789", params: { hello: "world-2" } },
+];
+let instances = await env.MY_WORKFLOW.createBatch(listOfInstances);
+```
+
+Returns an array of `WorkflowInstance`.
+
+Unlike [`create`](/workflows/build/workers-api/#create), this operation is idempotent and will not fail if an ID is already in use. If an existing instance with the same ID is still within its [retention limit](/workflows/reference/limits/), it will be skipped and excluded from the returned array.
+
+### get
+
+Get a specific Workflow instance by ID.
+
+- get(id: string): Promise<WorkflowInstance>- `id` - the ID
+ of the Workflow instance.
+
+Returns a `WorkflowInstance`. Throws an exception if the instance ID does not exist.
+
+```ts
+// Fetch an existing Workflow instance by ID:
+try {
+ let instance = await env.MY_WORKFLOW.get(id);
+ return Response.json({
+ id: instance.id,
+ details: await instance.status(),
+ });
+} catch (e: any) {
+ // Handle errors
+ // .get will throw an exception if the ID doesn't exist or is invalid.
+ const msg = `failed to get instance ${id}: ${e.message}`;
+ console.error(msg);
+ return Response.json({ error: msg }, { status: 400 });
+}
+```
+
+## WorkflowInstanceCreateOptions
+
+Optional properties to pass when creating an instance.
+
+```ts
+interface WorkflowInstanceCreateOptions {
+ /**
+ * An id for your Workflow instance. Must be unique within the Workflow.
+ */
+ id?: string;
+ /**
+ * The event payload the Workflow instance is triggered with
+ */
+ params?: unknown;
+ /**
+ * The retention policy for the Workflow instance.
+ * Defaults to the maximum retention period available for the owner's account.
+ */
+ retention?: {
+ /**
+ * How long to retain instance state after the Workflow completes successfully.
+ */
+ successRetention?: WorkflowRetentionDuration;
+ /**
+ * How long to retain instance state after the Workflow ends in an errored or terminated state.
+ */
+ errorRetention?: WorkflowRetentionDuration;
+ };
+}
+
+type WorkflowRetentionDuration = WorkflowSleepDuration;
+```
+
+If `retention` is not set, instance state is retained for the maximum retention period available on your account (3 days on the Workers Free plan, 30 days on the Workers Paid plan). Refer to the [retention limit](/workflows/reference/limits/) for more information.
+
+The following example creates an instance that retains state for 1 day after success and 7 days after an error:
+
+```ts
+let instance = await env.MY_WORKFLOW.create({
+ id: myIdDefinedFromOtherSystem,
+ params: { hello: "world" },
+ retention: {
+ successRetention: "1 day",
+ errorRetention: "7 days",
+ },
+});
+```
+
+## WorkflowInstance
+
+Represents a specific instance of a Workflow, and provides methods to manage the instance.
+
+```ts
+declare abstract class WorkflowInstance {
+ public id: string;
+ /**
+ * Pause the instance.
+ */
+ public pause(): Promise;
+ /**
+ * Resume the instance. If it is already running, an error will be thrown.
+ */
+ public resume(): Promise;
+ /**
+ * Terminate the instance. If it is errored, terminated or complete, an error will be thrown.
+ */
+ public terminate(options?: WorkflowInstanceTerminateOptions): Promise;
+ /**
+ * Restart the instance from the beginning, or from a specific step.
+ */
+ public restart(options?: WorkflowInstanceRestartOptions): Promise;
+ /**
+ * Returns the current status of the instance.
+ */
+ public status(): Promise;
+}
+```
+
+### id
+
+Return the id of a Workflow.
+
+- id: string
+
+### status
+
+Return the status of a running Workflow instance.
+
+- status(): Promise<InstanceStatus>
+
+### pause
+
+Pause a running Workflow instance.
+
+- pause(): Promise<void>
+
+### resume
+
+Resume a paused Workflow instance.
+
+- resume(): Promise<void>
+
+### restart
+
+Restart a Workflow instance from the beginning, or from a specific step.
+
+- restart(options?: WorkflowInstanceRestartOptions): Promise<void>
+ - `options` - optional properties that control from where the instance restarts.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+
+// Restart the instance from the beginning.
+await instance.restart();
+
+// Restart the instance from the step named "aggregate".
+await instance.restart({ from: { name: "aggregate" } });
+
+// Restart the instance from the third call to a step named "process".
+await instance.restart({ from: { name: "process", count: 3 } });
+```
+
+When restarting from a specific step, the cached results of every earlier step are reused, while the target step and any steps that follow it run again. The call throws an error if no step matching `from` is found in the instance's execution history.
+
+#### WorkflowInstanceRestartOptions
+
+```ts
+interface WorkflowInstanceRestartOptions {
+ /**
+ * The step to restart the instance from.
+ * If omitted, the instance restarts from the beginning.
+ */
+ from?: {
+ /**
+ * The name of the step.
+ */
+ name: string;
+ /**
+ * The 1-based index of the step, used when multiple steps share the same name and type. Defaults to 1 (the first occurrence).
+ */
+ count?: number;
+ /**
+ * The step type. Use this to disambiguate when the same name is shared across step types. Defaults to "do".
+ */
+ type?: "do" | "sleep" | "waitForEvent";
+ };
+}
+```
+
+The `from` object identifies the step to restart from. Only `name` is required; `count` and `type` are only needed when the same step name appears more than once in the run.
+
+- `name` - the name of the step.
+- `count` - the 1-based index of the step, used when multiple steps share the same name and type (for example, inside a loop). Defaults to `1` (the first occurrence). Corresponds to `step.count` in the [step context](/workflows/build/step-context/).
+- `type` - the step type (`"do"`, `"sleep"`, or `"waitForEvent"`). Defaults to `"do"`. Use this when the same name is shared across different step types.
+
+### terminate
+
+Terminate a Workflow instance.
+
+- terminate(options?: WorkflowInstanceTerminateOptions): Promise<void>
+ - `options` - optional properties that control how the instance is terminated.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+
+// Terminate without running rollback handlers.
+await instance.terminate();
+
+// Run registered rollback handlers before terminating.
+await instance.terminate({ rollback: true });
+```
+
+If `rollback` is `true`, Workflows runs the rollback handlers registered by completed or eligible steps before the instance reaches the `terminated` state. Steps without rollback handlers are skipped.
+
+#### WorkflowInstanceTerminateOptions
+
+```ts
+interface WorkflowInstanceTerminateOptions {
+ /**
+ * If true, run registered rollback handlers before terminating the instance.
+ */
+ rollback?: boolean;
+}
+```
+
+### sendEvent
+
+[Send an event](/workflows/build/events-and-parameters/) to a running Workflow instance.
+
+- sendEvent(): Promise<void>- `options` - the event `type`
+ (up to 100 characters [^1]) and `payload` to send to the Workflow instance.
+ The `type` must match the `type` in the corresponding `waitForEvent` call in
+ your Workflow.
+
+Return `void` on success; throws an exception if the Workflow is not running or is an errored state.
+
+
+
+```ts
+export default {
+ async fetch(req: Request, env: Env) {
+ const instanceId = new URL(req.url).searchParams.get("instanceId");
+ const webhookPayload = await req.json();
+
+ let instance = await env.MY_WORKFLOW.get(instanceId);
+ // Send our event, with `type` matching the event type defined in
+ // our step.waitForEvent call
+ await instance.sendEvent({
+ type: "stripe-webhook",
+ payload: webhookPayload,
+ });
+
+ return Response.json({
+ status: await instance.status(),
+ });
+ },
+};
+```
+
+
+
+You can call `sendEvent` multiple times, setting the value of the `type` property to match the specific `waitForEvent` calls in your Workflow.
+
+This allows you to wait for multiple events at once, or use `Promise.race` to wait for multiple events and allow the first event to progress the Workflow.
+
+### InstanceStatus
+
+Details the status of a Workflow instance.
+
+```ts
+type InstanceStatus = {
+ status:
+ | "queued" // means that instance is waiting to be started (see concurrency limits)
+ | "running"
+ | "paused"
+ | "errored"
+ | "terminated" // user terminated the instance while it was running
+ | "complete"
+ | "waiting" // instance is hibernating and waiting for sleep or event to finish
+ | "waitingForPause" // instance is finishing the current work to pause
+ | "unknown";
+ error?: {
+ name: string;
+ message: string;
+ };
+ output?: unknown;
+ rollback: {
+ outcome: "complete" | "failed";
+ error: {
+ name: string;
+ message: string;
+ } | null;
+ } | null;
+};
+```
+
+If a Workflow enters rollback, the Workers API continues to report `status: "running"` for compatibility while the rollback is executing. After the instance reaches a terminal state, inspect `rollback` to determine whether compensating steps completed successfully or failed.
+
+[^1]: Match pattern: `^[a-zA-Z0-9_][a-zA-Z0-9-_]*$`
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/corpus.json b/assets/seed/v2/corpora/cloudflare-state-v1/corpus.json
new file mode 100644
index 0000000..310d92b
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/corpus.json
@@ -0,0 +1,6 @@
+{
+ "id": "cloudflare-state-v1",
+ "name": "Cloudflare State & Coordination",
+ "kind": "catalog",
+ "file_count": 40
+}
\ No newline at end of file
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/evaluation-manifest.json b/assets/seed/v2/corpora/cloudflare-state-v1/evaluation-manifest.json
new file mode 100644
index 0000000..eefa8e4
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/evaluation-manifest.json
@@ -0,0 +1,16 @@
+{
+ "schema_version": 1,
+ "questions_sha256": "53dcbd1bd6eb14cc87510ad482032e357b53b6e61e9141d11af1722531981a36",
+ "chunks_sha256": "11960c4f72360fdb4dd7fea1f43fbec4dd36a9214e3256bdcffc36ad3aee1f41",
+ "embeddings_sha256": "c944c09db6e42fbdac3ec3e25dc74c6e6ea8b23802d612705ec6be512fa29604",
+ "source_vector_fingerprint": "b4e7cd9f2a4dff45661dfea67ab4dfe265cce119fe731b39ab01ff8065965ff5",
+ "document_count": 40,
+ "chunk_count": 537,
+ "distance_metric": "cosine",
+ "embedding_dimension": 384,
+ "embedding_model": "sentence-transformers/all-MiniLM-L6-v2",
+ "embedding_revision": "1110a243fdf4706b3f48f1d95db1a4f5529b4d41",
+ "reranker_model": "cross-encoder/ms-marco-MiniLM-L-6-v2",
+ "reranker_revision": "c5ee24cb16019beea0893ab7796b1df96625c6b8",
+ "question_count": 16
+}
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/api/alarms.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/api/alarms.md
new file mode 100644
index 0000000..4d576fb
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/api/alarms.md
@@ -0,0 +1,252 @@
+---
+title: Alarms
+description: Schedule future wake-ups for Durable Objects using the Alarms API with guaranteed at-least-once execution.
+pcx_content_type: concept
+sidebar:
+ order: 8
+products:
+ - durable-objects
+---
+
+import { Type, GlossaryTooltip, Tabs, TabItem } from "~/components";
+
+## Background
+
+Durable Objects alarms allow you to schedule the Durable Object to be woken up at a time in the future. When the alarm's scheduled time comes, the `alarm()` handler method will be called. Alarms are modified using the Storage API, and alarm operations follow the same rules as other storage operations.
+
+Notably:
+
+- Each Durable Object is able to schedule a single alarm at a time by calling `setAlarm()`.
+- Alarms have guaranteed at-least-once execution and are retried automatically when the `alarm()` handler throws.
+- Retries are performed using exponential backoff starting at a 2 second delay from the first failure with up to 6 retries allowed.
+
+:::note[How are alarms different from Cron Triggers?]
+
+Alarms are more fine grained than [Cron Triggers](/workers/configuration/cron-triggers/). A Worker can have up to three Cron Triggers configured at once, but it can have an unlimited amount of Durable Objects, each of which can have an alarm set.
+
+Alarms are directly scheduled from within your Durable Object. Cron Triggers, on the other hand, are not programmatic. [Cron Triggers](/workers/configuration/cron-triggers/) execute based on their schedules, which have to be configured through the Cloudflare dashboard or API.
+
+:::
+
+Alarms can be used to build distributed primitives, like queues or batching of work atop Durable Objects. Alarms also provide a mechanism to guarantee that operations within a Durable Object will complete without relying on incoming requests to keep the Durable Object alive. For a complete example, refer to [Use the Alarms API](/durable-objects/examples/alarms-api/).
+
+## Scheduling multiple events with a single alarm
+
+Although each Durable Object can only have one alarm set at a time, you can manage many scheduled and recurring events by storing your event schedule in storage and having the `alarm()` handler process due events, then reschedule itself for the next one.
+
+```js
+import { DurableObject } from "cloudflare:workers";
+
+export class AgentServer extends DurableObject {
+ // Schedule a one-time or recurring event
+ async scheduleEvent(id, runAt, repeatMs = null) {
+ await this.ctx.storage.put(`event:${id}`, { id, runAt, repeatMs });
+ const currentAlarm = await this.ctx.storage.getAlarm();
+ if (!currentAlarm || runAt < currentAlarm) {
+ await this.ctx.storage.setAlarm(runAt);
+ }
+ }
+
+ async alarm() {
+ const now = Date.now();
+ const events = await this.ctx.storage.list({ prefix: "event:" });
+ let nextAlarm = null;
+
+ for (const [key, event] of events) {
+ if (event.runAt <= now) {
+ await this.processEvent(event);
+ if (event.repeatMs) {
+ event.runAt = now + event.repeatMs;
+ await this.ctx.storage.put(key, event);
+ } else {
+ await this.ctx.storage.delete(key);
+ }
+ }
+ // Track the next event time
+ if (event.runAt > now && (!nextAlarm || event.runAt < nextAlarm)) {
+ nextAlarm = event.runAt;
+ }
+ }
+
+ if (nextAlarm) await this.ctx.storage.setAlarm(nextAlarm);
+ }
+
+ async processEvent(event) {
+ // Your event handling logic here
+ }
+}
+```
+
+## Storage methods
+
+### `getAlarm`
+
+- getAlarm():
+
+ - If there is an alarm set, then return the currently set alarm time as the number of milliseconds elapsed since the UNIX epoch. Otherwise, return `null`.
+
+ - If `getAlarm` is called while an [`alarm`](/durable-objects/api/alarms/#alarm) is already running, it returns `null` unless `setAlarm` has also been called since the alarm handler started running.
+
+### `setAlarm`
+
+- setAlarm(scheduledTimeMs ) :
+
+ - Set the time for the alarm to run. Specify the time as the number of milliseconds elapsed since the UNIX epoch.
+ - If you call `setAlarm` when there is already one scheduled, it will override the existing alarm.
+
+:::caution[Calling `setAlarm` inside the constructor]
+If you wish to call `setAlarm` inside the constructor of a Durable Object, ensure that you are first checking whether an alarm has already been set.
+
+This is due to the fact that, if the Durable Object wakes up after being inactive, the constructor is invoked before the [`alarm` handler](/durable-objects/api/alarms/#alarm). Therefore, if the constructor calls `setAlarm`, it could interfere with the next alarm which has already been set.
+:::
+
+### `deleteAlarm`
+
+- `deleteAlarm()`:
+
+ - Unset the alarm if there is a currently set alarm.
+
+ - Calling `deleteAlarm()` inside the `alarm()` handler may prevent retries on a best-effort basis, but is not guaranteed.
+
+## Handler methods
+
+### `alarm`
+
+- alarm(alarmInfo ):
+
+ - Called by the system when a scheduled alarm time is reached.
+
+ - The optional parameter `alarmInfo` object has two properties:
+
+ - `retryCount` : The number of times this alarm event has been retried.
+ - `isRetry` : A boolean value to indicate if the alarm has been retried. This value is `true` if this alarm event is a retry.
+
+ - Only one instance of `alarm()` will ever run at a given time per Durable Object instance.
+ - The `alarm()` handler has guaranteed at-least-once execution and will be retried upon failure using exponential backoff, starting at 2 second delays for up to 6 retries. This only applies to the most recent `setAlarm()` call. Retries will be performed if the method fails with an uncaught exception.
+
+ - This method can be `async`.
+
+:::note[Catching exceptions in alarm handlers]
+
+Because alarms are only retried up to 6 times on error, it's recommended to catch any exceptions inside your `alarm()` handler and schedule a new alarm before returning if you want to make sure your alarm handler will be retried indefinitely. Otherwise, a sufficiently long outage in a downstream service that you depend on or a bug in your code that goes unfixed for hours can exhaust the limited number of retries, causing the alarm to not be re-run in the future until the next time you call `setAlarm`.
+
+:::
+
+## Example
+
+This example shows how to both set alarms with the `setAlarm(timestamp)` method and handle alarms with the `alarm()` handler within your Durable Object.
+
+- The `alarm()` handler will be called once every time an alarm fires.
+- If an unexpected error terminates the Durable Object, the `alarm()` handler may be re-instantiated on another machine.
+- Following a short delay, the `alarm()` handler will run from the beginning on the other machine.
+
+
+
+
+
+```js
+import { DurableObject } from "cloudflare:workers";
+
+export default {
+ async fetch(request, env) {
+ return await env.ALARM_EXAMPLE.getByName("foo").fetch(request);
+ },
+};
+
+const SECONDS = 1000;
+
+export class AlarmExample extends DurableObject {
+ constructor(ctx, env) {
+ super(ctx, env);
+ this.storage = ctx.storage;
+ }
+ async fetch(request) {
+ // If there is no alarm currently set, set one for 10 seconds from now
+ let currentAlarm = await this.storage.getAlarm();
+ if (currentAlarm == null) {
+ this.storage.setAlarm(Date.now() + 10 * SECONDS);
+ }
+ }
+ async alarm() {
+ // The alarm handler will be invoked whenever an alarm fires.
+ // You can use this to do work, read from the Storage API, make HTTP calls
+ // and set future alarms to run using this.storage.setAlarm() from within this handler.
+ }
+}
+```
+
+
+
+
+
+```python
+import time
+
+from workers import DurableObject, WorkerEntrypoint
+
+class Default(WorkerEntrypoint):
+ async def fetch(self, request):
+ return await self.env.ALARM_EXAMPLE.getByName("foo").fetch(request)
+
+SECONDS = 1000
+
+class AlarmExample(DurableObject):
+ def __init__(self, ctx, env):
+ super().__init__(ctx, env)
+ self.storage = ctx.storage
+
+ async def fetch(self, request):
+ # If there is no alarm currently set, set one for 10 seconds from now
+ current_alarm = await self.storage.getAlarm()
+ if current_alarm is None:
+ self.storage.setAlarm(int(time.time() * 1000) + 10 * SECONDS)
+
+ async def alarm(self):
+ # The alarm handler will be invoked whenever an alarm fires.
+ # You can use this to do work, read from the Storage API, make HTTP calls
+ # and set future alarms to run using self.storage.setAlarm() from within this handler.
+ pass
+```
+
+
+
+
+
+The following example shows how to use the `alarmInfo` property to identify if the alarm event has been attempted before.
+
+
+
+
+
+```js
+class MyDurableObject extends DurableObject {
+ async alarm(alarmInfo) {
+ if (alarmInfo?.retryCount != 0) {
+ console.log(
+ "This alarm event has been attempted ${alarmInfo?.retryCount} times before.",
+ );
+ }
+ }
+}
+```
+
+
+
+
+
+```python
+class MyDurableObject(DurableObject):
+ async def alarm(self, alarm_info):
+ if alarm_info and alarm_info.get('retryCount', 0) != 0:
+ print(f"This alarm event has been attempted {alarm_info.get('retryCount')} times before.")
+```
+
+
+
+
+
+## Related resources
+
+- Understand how to [use the Alarms API](/durable-objects/examples/alarms-api/) in an end-to-end example.
+- Read the [Durable Objects alarms announcement blog post](https://blog.cloudflare.com/durable-objects-alarms/).
+- Review the [Storage API](/durable-objects/api/sqlite-storage-api/) documentation for Durable Objects.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/api/state.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/api/state.md
new file mode 100644
index 0000000..2c9675b
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/api/state.md
@@ -0,0 +1,369 @@
+---
+title: Durable Object State
+description: API reference for DurableObjectState, which controls concurrency, WebSocket attachment, and storage access.
+pcx_content_type: concept
+sidebar:
+ order: 5
+products:
+ - durable-objects
+---
+
+import { Tabs, TabItem, GlossaryTooltip, Type, MetaInfo } from "~/components";
+
+## Description
+
+The `DurableObjectState` interface is accessible as an instance property on the Durable Object class. This interface encapsulates methods that modify the state of a Durable Object, for example which WebSockets are attached to a Durable Object or how the runtime should handle concurrent Durable Object requests.
+
+The `DurableObjectState` interface is different from the Storage API in that it does not have top-level methods which manipulate persistent application data. These methods are instead encapsulated in the [`DurableObjectStorage`](/durable-objects/api/sqlite-storage-api/) interface and accessed by [`DurableObjectState::storage`](/durable-objects/api/state/#storage).
+
+
+
+```js
+import { DurableObject } from "cloudflare:workers";
+
+// Durable Object
+export class MyDurableObject extends DurableObject {
+ // DurableObjectState is accessible via the ctx instance property
+ constructor(ctx, env) {
+ super(ctx, env);
+ }
+ ...
+}
+```
+
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ MY_DURABLE_OBJECT: DurableObjectNamespace;
+}
+
+// Durable Object
+export class MyDurableObject extends DurableObject {
+ // DurableObjectState is accessible via the ctx instance property
+ constructor(ctx: DurableObjectState, env: Env) {
+ super(ctx, env);
+ }
+ ...
+}
+```
+
+
+
+
+
+```python
+from workers import DurableObject
+
+# Durable Object
+class MyDurableObject(DurableObject):
+ # DurableObjectState is accessible via the ctx instance property
+ def __init__(self, ctx, env):
+ super().__init__(ctx, env)
+ # ...
+```
+
+
+
+
+
+## Methods and Properties
+
+### `exports`
+
+Contains loopback bindings to the Worker's own top-level exports. This has exactly the same meaning as [`ExecutionContext`'s `ctx.exports`](/workers/runtime-apis/context/#exports).
+
+### `waitUntil`
+
+`waitUntil` is available on `DurableObjectState` for API compatibility with [Workers Runtime APIs](/workers/runtime-apis/context/#waituntil).
+
+:::note[`waitUntil` has no effect in Durable Objects]
+
+Unlike in Workers, `waitUntil` has no effect in Durable Objects. It does not extend the lifetime of a Durable Object or affect when a request or RPC completes.
+
+Durable Objects automatically remain active as long as there is ongoing work or pending I/O, so `waitUntil` is not needed. Refer to [Lifecycle of a Durable Object](/durable-objects/concepts/durable-object-lifecycle/) for more information.
+:::
+
+#### Parameters
+
+- A required promise of any type.
+
+#### Return values
+
+- None.
+
+### `blockConcurrencyWhile`
+
+`blockConcurrencyWhile` executes an async callback while blocking any other events from being delivered to the Durable Object until the callback completes. This method guarantees ordering and prevents concurrent requests. All events that were not explicitly initiated as part of the callback itself will be blocked. Once the callback completes, all other events will be delivered.
+
+- `blockConcurrencyWhile` is commonly used within the constructor of the Durable Object class to enforce initialization to occur before any requests are delivered.
+- Another use case is executing `async` operations based on the current state of the Durable Object and using `blockConcurrencyWhile` to prevent that state from changing while yielding the event loop.
+- If the callback throws an exception, the object will be terminated and reset. This ensures that the object cannot be left stuck in an uninitialized state if something fails unexpectedly.
+- To avoid this behavior, enclose the body of your callback in a `try...catch` block to ensure it cannot throw an exception.
+
+To help mitigate deadlocks there is a 30 second timeout applied when executing the callback. If this timeout is exceeded, the Durable Object will be reset. It is best practice to have the callback do as little work as possible to improve overall request throughput to the Durable Object.
+
+:::note[When to use `blockConcurrencyWhile`]
+
+Use `blockConcurrencyWhile` in the constructor to run schema migrations or initialize state before any requests are processed. This ensures your Durable Object is fully ready before handling traffic.
+
+For regular request handling, you rarely need `blockConcurrencyWhile`. SQLite storage operations are synchronous and do not yield the event loop, so they execute atomically without it. For asynchronous KV storage operations, input gates already prevent other requests from interleaving during storage calls.
+
+Reserve `blockConcurrencyWhile` outside the constructor for cases where you make external async calls (such as `fetch()`) and cannot tolerate state changes while the event loop yields.
+
+:::
+
+
+
+
+
+```js
+// Durable Object
+export class MyDurableObject extends DurableObject {
+ initialized = false;
+
+ constructor(ctx, env) {
+ super(ctx, env);
+
+ // blockConcurrencyWhile will ensure that initialized will always be true
+ this.ctx.blockConcurrencyWhile(async () => {
+ this.initialized = true;
+ });
+ }
+ ...
+}
+```
+
+
+
+
+
+```python
+# Durable Object
+class MyDurableObject(DurableObject):
+ def __init__(self, ctx, env):
+ super().__init__(ctx, env)
+ self.initialized = False
+
+ # blockConcurrencyWhile will ensure that initialized will always be true
+ async def set_initialized():
+ self.initialized = True
+ self.ctx.blockConcurrencyWhile(set_initialized)
+ # ...
+```
+
+
+
+
+
+#### Parameters
+
+- A required callback which returns a `Promise`.
+
+#### Return values
+
+- A `Promise` returned by the callback.
+
+### `acceptWebSocket`
+
+`acceptWebSocket` is part of the [WebSocket Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api), which allows a Durable Object to be removed from memory to save costs while keeping its WebSockets connected.
+
+`acceptWebSocket` adds a WebSocket to the set of WebSockets attached to the Durable Object. Once called, any incoming messages will be delivered by calling the Durable Object's `webSocketMessage` handler, and `webSocketClose` will be invoked upon disconnect. After calling `acceptWebSocket`, the WebSocket is accepted and its `send` and `close` methods can be used.
+
+The [WebSocket Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api) takes the place of the standard [WebSockets API](/workers/runtime-apis/websockets/). Therefore, `ws.accept` must not have been called separately and `ws.addEventListener` method will not receive events as they will instead be delivered to the Durable Object.
+
+The WebSocket Hibernation API permits a maximum of 32,768 WebSocket connections per Durable Object, but the CPU and memory usage of a given workload may further limit the practical number of simultaneous connections.
+
+#### Parameters
+
+- A required `WebSocket` with name `ws`.
+- An optional `Array` of associated tags. Tags can be used to retrieve WebSockets via [`DurableObjectState::getWebSockets`](/durable-objects/api/state/#getwebsockets). Each tag is a maximum of 256 characters and there can be at most 10 tags associated with a WebSocket.
+
+#### Return values
+
+- None.
+
+### `getWebSockets`
+
+`getWebSockets` is part of the [WebSocket Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api), which allows a Durable Object to be removed from memory to save costs while keeping its WebSockets connected.
+
+`getWebSockets` returns an `Array` which is the set of WebSockets attached to the Durable Object. An optional tag argument can be used to filter the list according to tags supplied when calling [`DurableObjectState::acceptWebSocket`](/durable-objects/api/state/#acceptwebsocket).
+
+:::note[`waitUntil` is not necessary]
+
+Disconnected WebSockets are not returned by this method, but `getWebSockets` may still return WebSockets even after `ws.close` has been called. For example, if the server-side WebSocket sends a close, but does not receive one back (and has not detected a disconnect from the client), then the connection is in the `CLOSING` readyState. The client might send more messages, so the WebSocket is technically not disconnected.
+
+With the [`web_socket_auto_reply_to_close`](/workers/configuration/compatibility-flags/#websocket-auto-reply-to-close) compatibility flag (enabled by default on compatibility dates on or after `2026-04-07`), the runtime automatically completes the close handshake, so WebSockets transition from `CLOSING` to `CLOSED` much faster and are less likely to be observed in the `CLOSING` state.
+
+:::
+
+#### Parameters
+
+- An optional tag of type `string`.
+
+#### Return values
+
+- An `Array`.
+
+### `setWebSocketAutoResponse`
+
+`setWebSocketAutoResponse` is part of the [WebSocket Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api), which allows a Durable Object to be removed from memory to save costs while keeping its WebSockets connected.
+
+`setWebSocketAutoResponse` sets an automatic response, auto-response, for the request provided for all WebSockets attached to the Durable Object. If a request is received matching the provided request then the auto-response will be returned without waking WebSockets in hibernation and incurring billable duration charges.
+
+`setWebSocketAutoResponse` is a common alternative to setting up a server for static ping/pong messages because this can be handled without waking hibernating WebSockets.
+
+#### Parameters
+
+- An optional `WebSocketRequestResponsePair(request string, response string)` enabling any WebSocket accepted via [`DurableObjectState::acceptWebSocket`](/durable-objects/api/state/#acceptwebsocket) to automatically reply to the provided response when it receives the provided request. Both request and response are limited to 2,048 characters each. If the parameter is omitted, any previously set auto-response configuration will be removed. [`DurableObjectState::getWebSocketAutoResponseTimestamp`](/durable-objects/api/state/#getwebsocketautoresponsetimestamp) will still reflect the last timestamp that an auto-response was sent.
+
+#### Return values
+
+- None.
+
+### `getWebSocketAutoResponse`
+
+`getWebSocketAutoResponse` returns the `WebSocketRequestResponsePair` object last set by [`DurableObjectState::setWebSocketAutoResponse`](/durable-objects/api/state/#setwebsocketautoresponse), or null if not auto-response has been set.
+
+:::note[inspect `WebSocketRequestResponsePair`]
+
+`WebSocketRequestResponsePair` can be inspected further by calling `getRequest` and `getResponse` methods.
+
+:::
+
+#### Parameters
+
+- None.
+
+#### Return values
+
+- A `WebSocketRequestResponsePair` or null.
+
+### `getWebSocketAutoResponseTimestamp`
+
+`getWebSocketAutoResponseTimestamp` is part of the [WebSocket Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api), which allows a Durable Object to be removed from memory to save costs while keeping its WebSockets connected.
+
+`getWebSocketAutoResponseTimestamp` gets the most recent `Date` on which the given WebSocket sent an auto-response, or null if the given WebSocket never sent an auto-response.
+
+#### Parameters
+
+- A required `WebSocket`.
+
+#### Return values
+
+- A `Date` or null.
+
+### `setHibernatableWebSocketEventTimeout`
+
+`setHibernatableWebSocketEventTimeout` is part of the [WebSocket Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api), which allows a Durable Object to be removed from memory to save costs while keeping its WebSockets connected.
+
+`setHibernatableWebSocketEventTimeout` sets the maximum amount of time in milliseconds that a WebSocket event can run for.
+
+If no parameter or a parameter of `0` is provided and a timeout has been previously set, then the timeout will be unset. The maximum value of timeout is 604,800,000 ms (7 days).
+
+#### Parameters
+
+- An optional `number`.
+
+#### Return values
+
+- None.
+
+### `getHibernatableWebSocketEventTimeout`
+
+`getHibernatableWebSocketEventTimeout` is part of the [WebSocket Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api), which allows a Durable Object to be removed from memory to save costs while keeping its WebSockets connected.
+
+`getHibernatableWebSocketEventTimeout` gets the currently set hibernatable WebSocket event timeout if one has been set via [`DurableObjectState::setHibernatableWebSocketEventTimeout`](/durable-objects/api/state/#sethibernatablewebsocketeventtimeout).
+
+#### Parameters
+
+- None.
+
+#### Return values
+
+- A number, or null if the timeout has not been set.
+
+### `getTags`
+
+`getTags` is part of the [WebSocket Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api), which allows a Durable Object to be removed from memory to save costs while keeping its WebSockets connected.
+
+`getTags` returns tags associated with a given WebSocket. This method throws an exception if the WebSocket has not been associated with the Durable Object via [`DurableObjectState::acceptWebSocket`](/durable-objects/api/state/#acceptwebsocket).
+
+#### Parameters
+
+- A required `WebSocket`.
+
+#### Return values
+
+- An `Array` of tags.
+
+### `abort`
+
+`abort` is used to forcibly reset a Durable Object. A JavaScript `Error` with the message passed as a parameter will be logged. This error is not able to be caught within the application code.
+
+
+
+
+
+```js
+// Durable Object
+export class MyDurableObject extends DurableObject {
+ constructor(ctx: DurableObjectState, env: Env) {
+ super(ctx, env);
+ }
+
+ async sayHello() {
+ // Error: Hello, World! will be logged
+ this.ctx.abort("Hello, World!");
+ }
+}
+```
+
+
+
+
+
+```python
+# Durable Object
+class MyDurableObject(DurableObject):
+ def __init__(self, ctx, env):
+ super().__init__(ctx, env)
+
+ async def say_hello(self):
+ # Error: Hello, World! will be logged
+ self.ctx.abort("Hello, World!")
+```
+
+
+
+
+
+:::caution[Not available in local development]
+
+`abort` is not available in local development with the `wrangler dev` CLI command.
+
+:::
+
+#### Parameters
+
+- An optional `string` .
+
+#### Return values
+
+- None.
+
+## Properties
+
+### `id`
+
+`id` is a readonly property of type `DurableObjectId` corresponding to the [`DurableObjectId`](/durable-objects/api/id) of the Durable Object.
+
+### `storage`
+
+`storage` is a readonly property of type `DurableObjectStorage` encapsulating the [Storage API](/durable-objects/api/sqlite-storage-api/).
+
+## Related resources
+
+- [Durable Objects: Easy, Fast, Correct - Choose Three](https://blog.cloudflare.com/durable-objects-easy-fast-correct-choose-three/).
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/best-practices/create-durable-object-stubs-and-send-requests.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/best-practices/create-durable-object-stubs-and-send-requests.md
new file mode 100644
index 0000000..cc4c0f5
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/best-practices/create-durable-object-stubs-and-send-requests.md
@@ -0,0 +1,217 @@
+---
+title: Invoke methods
+description: Call RPC methods or send fetch requests to Durable Objects using stubs from a Worker.
+pcx_content_type: concept
+tags:
+ - RPC
+sidebar:
+ order: 2
+products:
+ - durable-objects
+---
+
+import { Render, Tabs, TabItem, GlossaryTooltip } from "~/components";
+
+## Invoking methods on a Durable Object
+
+All new projects and existing projects with a compatibility date greater than or equal to [`2024-04-03`](/workers/configuration/compatibility-flags/#durable-object-stubs-and-service-bindings-support-rpc) should prefer to invoke [Remote Procedure Call (RPC)](/workers/runtime-apis/rpc/) methods defined on a Durable Object class.
+
+Projects requiring HTTP request/response flows or legacy projects can continue to invoke the `fetch()` handler on the Durable Object class.
+
+### Invoke RPC methods
+
+By writing a Durable Object class which inherits from the built-in type `DurableObject`, public methods on the Durable Objects class are exposed as [RPC methods](/workers/runtime-apis/rpc/), which you can call using a [DurableObjectStub](/durable-objects/api/stub) from a Worker.
+
+All RPC calls are [asynchronous](/workers/runtime-apis/rpc/lifecycle/), accept and return [serializable types](/workers/runtime-apis/rpc/), and [propagate exceptions](/workers/runtime-apis/rpc/error-handling/) to the caller without a stack trace. Refer to [Workers RPC](/workers/runtime-apis/rpc/) for complete details.
+
+
+
+:::note
+
+With RPC, the `DurableObject` superclass defines `ctx` and `env` as class properties. What was previously called `state` is now called `ctx` when you extend the `DurableObject` class. The name `ctx` is adopted rather than `state` for the `DurableObjectState` interface to be consistent between `DurableObject` and `WorkerEntrypoint` objects.
+
+:::
+
+Refer to [Build a Counter](/durable-objects/examples/build-a-counter/) for a complete example.
+
+### Invoking the `fetch` handler
+
+If your project is stuck on a compatibility date before [`2024-04-03`](/workers/configuration/compatibility-flags/#durable-object-stubs-and-service-bindings-support-rpc), or has the need to send a [`Request`](/workers/runtime-apis/request/) object and return a `Response` object, then you should send requests to a Durable Object via the fetch handler.
+
+
+
+```js
+import { DurableObject } from "cloudflare:workers";
+
+// Durable Object
+export class MyDurableObject extends DurableObject {
+ constructor(ctx, env) {
+ super(ctx, env);
+ }
+
+ async fetch(request) {
+ return new Response("Hello, World!");
+ }
+}
+
+// Worker
+export default {
+ async fetch(request, env) {
+ // A stub is a client used to invoke methods on the Durable Object
+ const stub = env.MY_DURABLE_OBJECT.getByName("foo");
+
+ // Methods on the Durable Object are invoked via the stub
+ const response = await stub.fetch(request);
+
+ return response;
+ },
+};
+```
+
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ MY_DURABLE_OBJECT: DurableObjectNamespace;
+}
+
+// Durable Object
+export class MyDurableObject extends DurableObject {
+ constructor(ctx: DurableObjectState, env: Env) {
+ super(ctx, env);
+ }
+
+ async fetch(request: Request): Promise {
+ return new Response("Hello, World!");
+ }
+}
+
+// Worker
+export default {
+ async fetch(request, env) {
+ // A stub is a client used to invoke methods on the Durable Object
+ const stub = env.MY_DURABLE_OBJECT.getByName("foo");
+
+ // Methods on the Durable Object are invoked via the stub
+ const response = await stub.fetch(request);
+
+ return response;
+ },
+} satisfies ExportedHandler;
+```
+
+
+
+The `URL` associated with the [`Request`](/workers/runtime-apis/request/) object passed to the `fetch()` handler of your Durable Object must be a well-formed URL, but does not have to be a publicly-resolvable hostname.
+
+Without RPC, customers frequently construct requests which corresponded to private methods on the Durable Object and dispatch requests from the `fetch` handler. RPC is obviously more ergonomic in this example.
+
+
+
+```js
+import { DurableObject } from "cloudflare:workers";
+
+// Durable Object
+export class MyDurableObject extends DurableObject {
+ constructor(ctx: DurableObjectState, env: Env) {
+ super(ctx, env);
+ }
+
+ private hello(name) {
+ return new Response(`Hello, ${name}!`);
+ }
+
+ private goodbye(name) {
+ return new Response(`Goodbye, ${name}!`);
+ }
+
+ async fetch(request) {
+ const url = new URL(request.url);
+ let name = url.searchParams.get("name");
+ if (!name) {
+ name = "World";
+ }
+
+ switch (url.pathname) {
+ case "/hello":
+ return this.hello(name);
+ case "/goodbye":
+ return this.goodbye(name);
+ default:
+ return new Response("Bad Request", { status: 400 });
+ }
+ }
+}
+
+// Worker
+export default {
+ async fetch(_request, env, _ctx) {
+ // A stub is a client used to invoke methods on the Durable Object
+ const stub = env.MY_DURABLE_OBJECT.getByName("foo");
+
+ // Invoke the fetch handler on the Durable Object stub
+ let response = await stub.fetch("http://do/hello?name=World");
+
+ return response;
+ },
+};
+```
+
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ MY_DURABLE_OBJECT: DurableObjectNamespace;
+}
+
+// Durable Object
+export class MyDurableObject extends DurableObject {
+ constructor(ctx: DurableObjectState, env: Env) {
+ super(ctx, env);
+ }
+
+ private hello(name: string) {
+ return new Response(`Hello, ${name}!`);
+ }
+
+ private goodbye(name: string) {
+ return new Response(`Goodbye, ${name}!`);
+ }
+
+ async fetch(request: Request): Promise {
+ const url = new URL(request.url);
+ let name = url.searchParams.get("name");
+ if (!name) {
+ name = "World";
+ }
+
+ switch (url.pathname) {
+ case "/hello":
+ return this.hello(name);
+ case "/goodbye":
+ return this.goodbye(name);
+ default:
+ return new Response("Bad Request", { status: 400 });
+ }
+ }
+}
+
+// Worker
+export default {
+ async fetch(_request, env, _ctx) {
+ // A stub is a client used to invoke methods on the Durable Object
+ const stub = env.MY_DURABLE_OBJECT.getByName("foo");
+
+ // Invoke the fetch handler on the Durable Object stub
+ let response = await stub.fetch("http://do/hello?name=World");
+
+ return response;
+ },
+} satisfies ExportedHandler;
+```
+
+
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/best-practices/rules-of-durable-objects.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/best-practices/rules-of-durable-objects.md
new file mode 100644
index 0000000..6a0f731
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/best-practices/rules-of-durable-objects.md
@@ -0,0 +1,1581 @@
+---
+title: Rules of Durable Objects
+description: Design guidelines for building correct and effective Durable Objects applications, covering when and how to use them.
+pcx_content_type: concept
+sidebar:
+ order: 1
+products:
+ - durable-objects
+---
+
+import { WranglerConfig, TypeScriptExample, Render } from "~/components";
+
+Durable Objects provide a powerful primitive for building stateful, coordinated applications. Each Durable Object is a single-threaded, globally-unique instance with its own persistent storage. Understanding how to design around these properties is essential for building effective applications.
+
+This is a guidebook on how to build more effective and correct Durable Object applications.
+
+## When to use Durable Objects
+
+### Use Durable Objects for stateful coordination, not stateless request handling
+
+Workers are stateless functions: each request may run on a different instance, in a different location, with no shared memory between requests. Durable Objects are stateful compute: each instance has a unique identity, runs in a single location, and maintains state across requests.
+
+Use Durable Objects when you need:
+
+- **Coordination** — Multiple clients need to interact with shared state (chat rooms, multiplayer games, collaborative documents)
+- **Strong consistency** — Operations must be serialized to avoid race conditions (inventory management, booking systems, turn-based games)
+- **Per-entity storage** — Each user, tenant, or resource needs its own isolated database (multi-tenant SaaS, per-user data)
+- **Persistent connections** — Long-lived WebSocket connections that survive across requests (real-time notifications, live updates)
+- **Scheduled work per entity** — Each entity needs its own timer or scheduled task (subscription renewals, game timeouts)
+
+Use plain Workers when you need:
+
+- **Stateless request handling** — API endpoints, proxies, or transformations with no shared state
+- **Maximum global distribution** — Requests should be handled at the nearest edge location
+- **High fan-out** — Each request is independent and can be processed in parallel
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ BOOKING: DurableObjectNamespace;
+}
+
+// ✅ Good use of Durable Objects: Seat booking requires coordination
+// All booking requests for a venue must be serialized to prevent double-booking
+export class SeatBooking extends DurableObject {
+async bookSeat(
+seatId: string,
+userId: string
+): Promise<{ success: boolean; message: string }> {
+// Check if seat is already booked
+const existing = this.ctx.storage.sql
+.exec<{ user_id: string }>(
+"SELECT user_id FROM bookings WHERE seat_id = ?",
+seatId
+)
+.toArray();
+
+ if (existing.length > 0) {
+ return { success: false, message: "Seat already booked" };
+ }
+
+ // Book the seat - this is safe because Durable Objects are single-threaded
+ this.ctx.storage.sql.exec(
+ "INSERT INTO bookings (seat_id, user_id, booked_at) VALUES (?, ?, ?)",
+ seatId,
+ userId,
+ Date.now()
+ );
+
+ return { success: true, message: "Seat booked successfully" };
+ }
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ const url = new URL(request.url);
+ const eventId = url.searchParams.get("event") ?? "default";
+
+ // Route to a Durable Object by event ID
+ // All bookings for the same event go to the same instance
+ const id = env.BOOKING.idFromName(eventId);
+ const booking = env.BOOKING.get(id);
+
+ const { seatId, userId } = await request.json<{
+ seatId: string;
+ userId: string;
+ }>();
+ const result = await booking.bookSeat(seatId, userId);
+
+ return Response.json(result, {
+ status: result.success ? 200 : 409,
+ });
+ },
+};
+```
+
+
+A common pattern is to use Workers as the stateless entry point that routes requests to Durable Objects when coordination is needed. The Worker handles authentication, validation, and response formatting, while the Durable Object handles the stateful logic.
+
+## Design and sharding
+
+### Model your Durable Objects around your "atom" of coordination
+
+The most important design decision is choosing what each Durable Object represents. Create one Durable Object per logical unit that needs coordination: a chat room, a game session, a document, a user's data, or a tenant's workspace.
+
+This is the key insight that makes Durable Objects powerful. Instead of a shared database with locks, each "atom" of your application gets its own single-threaded execution environment with private storage.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+// Each chat room is its own Durable Object instance
+export class ChatRoom extends DurableObject {
+ async sendMessage(userId: string, message: string) {
+ // All messages to this room are processed sequentially by this single instance.
+ // No race conditions, no distributed locks needed.
+ this.ctx.storage.sql.exec(
+ "INSERT INTO messages (user_id, content, created_at) VALUES (?, ?, ?)",
+ userId,
+ message,
+ Date.now()
+ );
+ }
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ const url = new URL(request.url);
+ const roomId = url.searchParams.get("room") ?? "lobby";
+
+ // Each room ID maps to exactly one Durable Object instance globally
+ const id = env.CHAT_ROOM.idFromName(roomId);
+ const stub = env.CHAT_ROOM.get(id);
+
+ await stub.sendMessage("user-123", "Hello, room!");
+ return new Response("Message sent");
+ },
+};
+```
+
+
+
+:::note
+
+If you have global application or user configuration that you need to access frequently (on every request), consider using [Workers KV](/kv/) instead.
+
+:::
+
+Do not create a single "global" Durable Object that handles all requests:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+// 🔴 Bad: A single Durable Object handling ALL chat rooms
+export class ChatRoom extends DurableObject {
+async sendMessage(roomId: string, userId: string, message: string) {
+// All messages for ALL rooms go through this single instance.
+// This becomes a bottleneck as traffic grows.
+this.ctx.storage.sql.exec(
+"INSERT INTO messages (room_id, user_id, content) VALUES (?, ?, ?)",
+roomId,
+userId,
+message
+);
+}
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ // 🔴 Bad: Always using the same ID means one global instance
+ const id = env.CHAT_ROOM.idFromName("global");
+ const stub = env.CHAT_ROOM.get(id);
+
+ await stub.sendMessage("room-123", "user-456", "Hello!");
+ return new Response("Sent");
+ },
+};
+
+```
+
+
+### Message throughput limits
+
+A single Durable Object can handle approximately **500-1,000 requests per second** for simple operations. This limit varies based on the work performed per request:
+
+| Operation type | Throughput |
+|----------------|------------|
+| Simple pass-through (minimal parsing) | ~1,000 req/sec |
+| Moderate processing (JSON parsing, validation) | ~500-750 req/sec |
+| Complex operations (transformation, storage writes) | ~200-500 req/sec |
+
+When modeling your "atom," factor in the expected request rate. If your use case exceeds these limits, shard your workload across multiple Durable Objects.
+
+For example, consider a real-time game with 50,000 concurrent players sending 10 updates per second. This generates 500,000 requests per second total. You would need 500-1,000 game session Durable Objects—not one global coordinator.
+
+Calculate your sharding requirements:
+
+```
+
+Required DOs = (Total requests/second) / (Requests per DO capacity)
+
+```
+
+### Use deterministic IDs for predictable routing
+
+Use `getByName()` with meaningful, deterministic strings for consistent routing. The same input always produces the same Durable Object ID, ensuring requests for the same logical entity always reach the same instance.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ GAME_SESSION: DurableObjectNamespace;
+}
+
+export class GameSession extends DurableObject {
+ async join(playerId: string) {
+ // Game logic here
+ }
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ const url = new URL(request.url);
+ const gameId = url.searchParams.get("game");
+
+ if (!gameId) {
+ return new Response("Missing game ID", { status: 400 });
+ }
+
+ // ✅ Good: Deterministic ID from a meaningful string
+ // All requests for "game-abc123" go to the same Durable Object
+ const stub = env.GAME_SESSION.getByName(gameId);
+
+ await stub.join("player-xyz");
+ return new Response("Joined game");
+ },
+};
+```
+
+
+
+Creating a stub does not instantiate or wake up the Durable Object. The Durable Object is only activated when you call a method on the stub.
+
+Use `newUniqueId()` only when you need a new, random instance and will store the mapping externally:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ GAME_SESSION: DurableObjectNamespace;
+}
+
+export class GameSession extends DurableObject {
+ async join(playerId: string) {
+ // Game logic here
+ }
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ // newUniqueId() creates a random ID - useful when creating new instances
+ // You must store this ID somewhere (e.g., D1) to find it again later
+ const id = env.GAME_SESSION.newUniqueId();
+ const stub = env.GAME_SESSION.get(id);
+
+ // Store the mapping: gameCode -> id.toString()
+ // await env.DB.prepare("INSERT INTO games (code, do_id) VALUES (?, ?)").bind(gameCode, id.toString()).run();
+
+ return Response.json({ gameId: id.toString() });
+ },
+};
+
+```
+
+
+### Use parent-child relationships for related entities
+
+Do not put all your data in a single Durable Object. When you have hierarchical data (workspaces containing projects, game servers managing matches), create separate child Durable Objects for each entity. The parent coordinates and tracks children, while children handle their own state independently.
+
+This enables parallelism: operations on different children can happen concurrently, while each child maintains its own single-threaded consistency ([read more about this pattern](/reference-architecture/diagrams/storage/durable-object-control-data-plane-pattern/)).
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ GAME_SERVER: DurableObjectNamespace;
+ GAME_MATCH: DurableObjectNamespace;
+}
+
+// Parent: Coordinates matches, but doesn't store match data
+export class GameServer extends DurableObject {
+ async createMatch(matchName: string): Promise {
+ const matchId = crypto.randomUUID();
+
+ // Store reference to the child in parent's database
+ this.ctx.storage.sql.exec(
+ "INSERT INTO matches (id, name, created_at) VALUES (?, ?, ?)",
+ matchId,
+ matchName,
+ Date.now()
+ );
+
+ // Initialize the child Durable Object
+ const childId = this.env.GAME_MATCH.idFromName(matchId);
+ const childStub = this.env.GAME_MATCH.get(childId);
+ await childStub.init(matchId, matchName);
+
+ return matchId;
+ }
+
+ async listMatches(): Promise<{ id: string; name: string }[]> {
+ // Parent knows about all matches without waking up each child
+ const cursor = this.ctx.storage.sql.exec<{ id: string; name: string }>(
+ "SELECT id, name FROM matches ORDER BY created_at DESC"
+ );
+ return cursor.toArray();
+ }
+}
+
+// Child: Handles its own game state independently
+export class GameMatch extends DurableObject {
+ async init(matchId: string, matchName: string) {
+ await this.ctx.storage.put("matchId", matchId);
+ await this.ctx.storage.put("matchName", matchName);
+ this.ctx.storage.sql.exec(`
+ CREATE TABLE IF NOT EXISTS players (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ score INTEGER DEFAULT 0
+ )
+ `);
+ }
+
+ async addPlayer(playerId: string, playerName: string) {
+ this.ctx.storage.sql.exec(
+ "INSERT INTO players (id, name, score) VALUES (?, ?, 0)",
+ playerId,
+ playerName
+ );
+ }
+
+ async updateScore(playerId: string, score: number) {
+ this.ctx.storage.sql.exec(
+ "UPDATE players SET score = ? WHERE id = ?",
+ score,
+ playerId
+ );
+ }
+}
+```
+
+
+
+With this pattern:
+
+- Listing matches only queries the parent (children stay hibernated)
+- Different matches process player actions in parallel
+- Each match has its own SQLite database for player data
+
+### Consider location hints for latency-sensitive applications
+
+By default, a Durable Object is created near the location of the first request it receives. For most applications, this works well. However, you can provide a location hint to influence where the Durable Object is created.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ GAME_SESSION: DurableObjectNamespace;
+}
+
+export class GameSession extends DurableObject {
+ // Game session logic
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ const url = new URL(request.url);
+ const gameId = url.searchParams.get("game") ?? "default";
+ const region = url.searchParams.get("region") ?? "wnam"; // Western North America
+
+ // Provide a location hint for where this Durable Object should be created
+ const id = env.GAME_SESSION.idFromName(gameId);
+ const stub = env.GAME_SESSION.get(id, { locationHint: region });
+
+ return new Response("Connected to game session");
+ },
+};
+
+```
+
+
+Location hints are suggestions, not guarantees. Refer to [Data location](/durable-objects/reference/data-location/) for available regions and details.
+
+## Storage and state
+
+### Use SQLite-backed Durable Objects
+
+[SQLite storage](/durable-objects/api/sqlite-storage-api/) is the recommended storage backend for new Durable Objects. It provides a familiar SQL API for relational queries, indexes, transactions, and better performance than the legacy key-value storage backed Durable Objects. SQLite Durable Objects also support the KV API in synchronous and asynchronous versions.
+
+Configure your Durable Object class to use SQLite storage in your Wrangler configuration:
+
+
+```jsonc
+{
+ "migrations": [
+ { "tag": "v1", "new_sqlite_classes": ["ChatRoom"] }
+ ]
+}
+```
+
+
+
+Then use the SQL API in your Durable Object:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+type Message = {
+id: number;
+user_id: string;
+content: string;
+created_at: number;
+};
+
+export class ChatRoom extends DurableObject {
+ constructor(ctx: DurableObjectState, env: Env) {
+ super(ctx, env);
+
+ // Create tables on first instantiation
+ this.ctx.storage.sql.exec(`
+ CREATE TABLE IF NOT EXISTS messages (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id TEXT NOT NULL,
+ content TEXT NOT NULL,
+ created_at INTEGER NOT NULL
+ )
+ `);
+ }
+
+ async addMessage(userId: string, content: string) {
+ this.ctx.storage.sql.exec(
+ "INSERT INTO messages (user_id, content, created_at) VALUES (?, ?, ?)",
+ userId,
+ content,
+ Date.now()
+ );
+ }
+
+ async getRecentMessages(limit: number = 50): Promise {
+ // Use type parameter for typed results
+ const cursor = this.ctx.storage.sql.exec(
+ "SELECT * FROM messages ORDER BY created_at DESC LIMIT ?",
+ limit
+ );
+ return cursor.toArray();
+ }
+}
+
+```
+
+
+Refer to [Access Durable Objects storage](/durable-objects/best-practices/access-durable-objects-storage/) for more details on the SQL API.
+
+### Initialize storage and run migrations in the constructor
+
+Use `blockConcurrencyWhile()` in the constructor to run migrations and initialize state before any requests are processed. This ensures your schema is ready and prevents race conditions during initialization.
+
+:::note
+`PRAGMA user_version` is not supported by Durable Objects SQLite storage. You must use an alternative approach to track your schema version.
+:::
+
+For production applications, use a migration library that handles version tracking and execution automatically:
+
+- [`durable-utils`](https://github.com/lambrospetrou/durable-utils#sqlite-schema-migrations) — provides a `SQLSchemaMigrations` class that tracks executed migrations both in memory and in storage.
+- [`@cloudflare/actors` storage utilities](https://github.com/cloudflare/actors/blob/main/packages/storage/src/sql-schema-migrations.ts) — a reference implementation of the same pattern used by the Cloudflare Actors framework.
+
+If you prefer not to use a library, you can track schema versions manually using a `_sql_schema_migrations` table. The following example demonstrates this approach:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+export class ChatRoom extends DurableObject {
+ constructor(ctx: DurableObjectState, env: Env) {
+ super(ctx, env);
+
+ // blockConcurrencyWhile() ensures no requests are processed until this completes
+ ctx.blockConcurrencyWhile(async () => {
+ await this.migrate();
+ });
+ }
+
+ private async migrate() {
+ // Create the migrations tracking table if it does not exist
+ this.ctx.storage.sql.exec(`
+ CREATE TABLE IF NOT EXISTS _sql_schema_migrations (
+ id INTEGER PRIMARY KEY,
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+ `);
+
+ // Determine the current schema version
+ const version =
+ this.ctx.storage.sql
+ .exec<{ version: number }>(
+ "SELECT COALESCE(MAX(id), 0) as version FROM _sql_schema_migrations",
+ )
+ .one().version;
+
+ if (version < 1) {
+ this.ctx.storage.sql.exec(`
+ CREATE TABLE IF NOT EXISTS messages (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id TEXT NOT NULL,
+ content TEXT NOT NULL,
+ created_at INTEGER NOT NULL
+ );
+ CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at);
+ INSERT INTO _sql_schema_migrations (id) VALUES (1);
+ `);
+ }
+
+ if (version < 2) {
+ // Future migration: add a new column
+ this.ctx.storage.sql.exec(`
+ ALTER TABLE messages ADD COLUMN edited_at INTEGER;
+ INSERT INTO _sql_schema_migrations (id) VALUES (2);
+ `);
+ }
+ }
+}
+```
+
+
+
+### Understand the difference between in-memory state and persistent storage
+
+Durable Objects provide multiple state management layers, each with different characteristics:
+
+| Type | Speed | Persistence | Use Case |
+| ---------------------------- | -------- | ---------------------------- | --------------------------- |
+| In-memory (class properties) | Fastest | Lost on eviction or crash | Caching, active connections |
+| SQLite storage | Fast | Durable across restarts | Primary data storage |
+| External (R2, D1) | Variable | Durable, cross-DO accessible | Large files, shared data |
+
+In-memory state is **not preserved** if the Durable Object is evicted from memory due to inactivity, or if it crashes from an uncaught exception. Always persist important state to SQLite storage.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+type Message = {
+id: number;
+user_id: string;
+content: string;
+created_at: number;
+};
+
+export class ChatRoom extends DurableObject {
+ // In-memory cache - fast but NOT preserved across evictions or crashes
+ private messageCache: Message[] | null = null;
+
+ async getRecentMessages(): Promise {
+ // Return from cache if available (only valid while DO is in memory)
+ if (this.messageCache !== null) {
+ return this.messageCache;
+ }
+
+ // Otherwise, load from durable storage
+ const cursor = this.ctx.storage.sql.exec(
+ "SELECT * FROM messages ORDER BY created_at DESC LIMIT 100"
+ );
+ this.messageCache = cursor.toArray();
+ return this.messageCache;
+ }
+
+ async addMessage(userId: string, content: string) {
+ // ✅ Always persist to durable storage first
+ this.ctx.storage.sql.exec(
+ "INSERT INTO messages (user_id, content, created_at) VALUES (?, ?, ?)",
+ userId,
+ content,
+ Date.now()
+ );
+
+ // Then update the cache (if it exists)
+ // If the DO crashes here, the message is still saved in SQLite
+ this.messageCache = null; // Invalidate cache
+ }
+}
+
+```
+
+
+:::caution
+
+If an uncaught exception occurs in your Durable Object, the runtime may terminate the instance. Any in-memory state will be lost, but SQLite storage remains intact. Always persist critical state to storage before performing operations that might fail.
+
+:::
+
+### Create indexes for frequently-queried columns
+
+Just like any database, indexes dramatically improve read performance for frequently-filtered columns. The cost is slightly more storage and marginally slower writes.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+export class ChatRoom extends DurableObject {
+ constructor(ctx: DurableObjectState, env: Env) {
+ super(ctx, env);
+
+ ctx.blockConcurrencyWhile(async () => {
+ this.ctx.storage.sql.exec(`
+ CREATE TABLE IF NOT EXISTS messages (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id TEXT NOT NULL,
+ content TEXT NOT NULL,
+ created_at INTEGER NOT NULL
+ );
+
+ -- Index for queries filtering by user
+ CREATE INDEX IF NOT EXISTS idx_messages_user_id ON messages(user_id);
+
+ -- Index for time-based queries (recent messages)
+ CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at);
+
+ -- Composite index for user + time queries
+ CREATE INDEX IF NOT EXISTS idx_messages_user_time ON messages(user_id, created_at);
+ `);
+ });
+ }
+
+ // This query benefits from idx_messages_user_time
+ async getUserMessages(userId: string, since: number) {
+ return this.ctx.storage.sql
+ .exec(
+ "SELECT * FROM messages WHERE user_id = ? AND created_at > ? ORDER BY created_at",
+ userId,
+ since
+ )
+ .toArray();
+ }
+}
+```
+
+
+
+### Understand how input and output gates work
+
+While Durable Objects are single-threaded, JavaScript's `async`/`await` can allow multiple requests to interleave execution while a request waits for the result of an asynchronous operation. Cloudflare's runtime uses **input gates** and **output gates** to prevent data races and ensure correctness by default.
+
+**Input gates** block new events (incoming requests, fetch responses) while synchronous JavaScript execution is in progress. Awaiting async operations like `fetch()` or KV storage methods opens the input gate, allowing other requests to interleave. However, storage operations provide special protection:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ COUNTER: DurableObjectNamespace;
+}
+
+export class Counter extends DurableObject {
+ // This code is safe due to input gates
+ async increment(): Promise {
+ // While these storage operations execute, no other requests
+ // can interleave - input gate blocks new events
+ const value = (await this.ctx.storage.get("count")) ?? 0;
+ await this.ctx.storage.put("count", value + 1);
+ return value + 1;
+ }
+}
+```
+
+
+**Output gates** hold outgoing network messages (responses, fetch requests) until pending storage writes complete. This ensures clients never see confirmation of data that has not been persisted:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+export class ChatRoom extends DurableObject {
+ async sendMessage(userId: string, content: string): Promise {
+ // Write to storage - don't need to await for correctness
+ this.ctx.storage.sql.exec(
+ "INSERT INTO messages (user_id, content, created_at) VALUES (?, ?, ?)",
+ userId,
+ content,
+ Date.now()
+ );
+
+ // This response is held by the output gate until the write completes.
+ // The client only receives "Message sent" after data is safely persisted.
+ return "Message sent";
+ }
+}
+
+```
+
+
+**Write coalescing:** Multiple storage writes without intervening `await` calls are automatically batched into a single atomic implicit transaction:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ ACCOUNT: DurableObjectNamespace;
+}
+
+export class Account extends DurableObject {
+ async transfer(fromId: string, toId: string, amount: number) {
+ // ✅ Good: These writes are coalesced into one atomic transaction
+ this.ctx.storage.sql.exec(
+ "UPDATE accounts SET balance = balance - ? WHERE id = ?",
+ amount,
+ fromId
+ );
+ this.ctx.storage.sql.exec(
+ "UPDATE accounts SET balance = balance + ? WHERE id = ?",
+ amount,
+ toId
+ );
+ this.ctx.storage.sql.exec(
+ "INSERT INTO transfers (from_id, to_id, amount, created_at) VALUES (?, ?, ?, ?)",
+ fromId,
+ toId,
+ amount,
+ Date.now()
+ );
+ // All three writes commit together atomically
+ }
+
+ // 🔴 Bad: await on KV operations breaks coalescing
+ async transferBrokenKV(fromId: string, toId: string, amount: number) {
+ const fromBalance = (await this.ctx.storage.get(`balance:${fromId}`)) ?? 0;
+ await this.ctx.storage.put(`balance:${fromId}`, fromBalance - amount);
+ // If the next write fails, the debit already committed!
+ const toBalance = (await this.ctx.storage.get(`balance:${toId}`)) ?? 0;
+ await this.ctx.storage.put(`balance:${toId}`, toBalance + amount);
+ }
+}
+```
+
+
+
+For more details, see [Durable Objects: Easy, Fast, Correct — Choose three](https://blog.cloudflare.com/durable-objects-easy-fast-correct-choose-three/) and the [glossary](/durable-objects/reference/glossary/).
+
+### Avoid race conditions with non-storage I/O
+
+Input gates only protect during storage operations. Non-storage I/O like `fetch()` or writing to R2 allows other requests to interleave, which can cause race conditions:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ PROCESSOR: DurableObjectNamespace;
+}
+
+export class Processor extends DurableObject {
+ // ⚠️ Potential race condition: fetch() allows interleaving
+ async processItem(id: string) {
+ const item = await this.ctx.storage.get<{ status: string }>(`item:${id}`);
+
+ if (item?.status === "pending") {
+ // During this fetch, other requests CAN execute and modify storage
+ const result = await fetch("https://api.example.com/process");
+
+ // Another request may have already processed this item!
+ await this.ctx.storage.put(`item:${id}`, { status: "completed" });
+ }
+ }
+}
+
+```
+
+
+To handle this, use optimistic locking (check-and-set) patterns: read a version number before the external call, then verify it has not changed before writing.
+
+:::note
+
+With the legacy KV storage backend, use the [`transaction()`](/durable-objects/api/sqlite-storage-api/#transaction) method for atomic read-modify-write operations across async boundaries.
+
+:::
+
+### Use `blockConcurrencyWhile()` sparingly
+
+The [`blockConcurrencyWhile()`](/durable-objects/api/state/#blockconcurrencywhile) method guarantees that no other events are processed until the provided callback completes, even if the callback performs asynchronous I/O. This is useful for operations that must be atomic, such as state initialization from storage in the constructor:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+export class ChatRoom extends DurableObject {
+ constructor(ctx: DurableObjectState, env: Env) {
+ super(ctx, env);
+
+ // ✅ Good: Use blockConcurrencyWhile for one-time initialization
+ ctx.blockConcurrencyWhile(async () => {
+ this.ctx.storage.sql.exec(`
+ CREATE TABLE IF NOT EXISTS messages (
+ id INTEGER PRIMARY KEY,
+ content TEXT
+ )
+ `);
+ });
+ }
+
+ // 🔴 Bad: Don't use blockConcurrencyWhile on every request
+ async sendMessageSlow(content: string) {
+ await this.ctx.blockConcurrencyWhile(async () => {
+ this.ctx.storage.sql.exec(
+ "INSERT INTO messages (content) VALUES (?)",
+ content
+ );
+ });
+ // If this takes ~5ms, you're limited to ~200 requests/second
+ }
+
+ // ✅ Good: Let output gates handle consistency
+ async sendMessageFast(content: string) {
+ this.ctx.storage.sql.exec(
+ "INSERT INTO messages (content) VALUES (?)",
+ content
+ );
+ // Output gate ensures write completes before response is sent
+ // Other requests can be processed concurrently
+ }
+}
+```
+
+
+
+Because `blockConcurrencyWhile()` blocks _all_ concurrency unconditionally, it significantly reduces throughput. If each call takes ~5ms, that individual Durable Object is limited to approximately 200 requests/second. Reserve it for initialization and migrations, not regular request handling. For normal operations, rely on input/output gates and write coalescing instead.
+
+For atomic read-modify-write operations during request handling, prefer [`transaction()`](/durable-objects/api/sqlite-storage-api/#transaction) over `blockConcurrencyWhile()`. Transactions provide atomicity for storage operations without blocking unrelated concurrent requests.
+
+:::caution
+
+Using `blockConcurrencyWhile()` across I/O operations (such as `fetch()`, KV, R2, or other external API calls) is an anti-pattern. This is equivalent to holding a lock across I/O in other languages or concurrency frameworks — it blocks all other requests while waiting for slow external operations, severely degrading throughput. Keep `blockConcurrencyWhile()` callbacks fast and limited to local storage operations.
+
+:::
+
+## Communication and API design
+
+### Use RPC methods instead of the `fetch()` handler
+
+Projects with a [compatibility date](/workers/configuration/compatibility-flags/) of `2024-04-03` or later should use RPC methods. RPC is more ergonomic, provides better type safety, and eliminates manual request/response parsing.
+
+Define public methods on your Durable Object class, and call them directly from stubs with full TypeScript support:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ // Type parameter provides typed method calls on the stub
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+type Message = {
+id: number;
+userId: string;
+content: string;
+createdAt: number;
+};
+
+export class ChatRoom extends DurableObject {
+ // Public methods are automatically exposed as RPC endpoints
+ async sendMessage(userId: string, content: string): Promise {
+ const createdAt = Date.now();
+ const result = this.ctx.storage.sql.exec<{ id: number }>(
+ "INSERT INTO messages (user_id, content, created_at) VALUES (?, ?, ?) RETURNING id",
+ userId,
+ content,
+ createdAt
+ );
+ const { id } = result.one();
+ return { id, userId, content, createdAt };
+ }
+
+ async getMessages(limit: number = 50): Promise {
+ const cursor = this.ctx.storage.sql.exec<{
+ id: number;
+ user_id: string;
+ content: string;
+ created_at: number;
+ }>("SELECT * FROM messages ORDER BY created_at DESC LIMIT ?", limit);
+
+ return cursor.toArray().map((row) => ({
+ id: row.id,
+ userId: row.user_id,
+ content: row.content,
+ createdAt: row.created_at,
+ }));
+ }
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ const url = new URL(request.url);
+ const roomId = url.searchParams.get("room") ?? "lobby";
+
+ const id = env.CHAT_ROOM.idFromName(roomId);
+ // stub is typed as DurableObjectStub
+ const stub = env.CHAT_ROOM.get(id);
+
+ if (request.method === "POST") {
+ const { userId, content } = await request.json<{
+ userId: string;
+ content: string;
+ }>();
+ // Direct method call with full type checking
+ const message = await stub.sendMessage(userId, content);
+ return Response.json(message);
+ }
+
+ // TypeScript knows getMessages() returns Promise
+ const messages = await stub.getMessages(100);
+ return Response.json(messages);
+ },
+};
+
+```
+
+
+Refer to [Invoke methods](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) for more details on RPC and the legacy `fetch()` handler.
+
+### Initialize Durable Objects explicitly with an `init()` method
+
+Durable Objects do not know their own name or ID from within. If your Durable Object needs to know its identity (for example, to store a reference to itself or to communicate with related objects), you must explicitly initialize it.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+export class ChatRoom extends DurableObject {
+ private roomId: string | null = null;
+
+ // Call this after creating the Durable Object for the first time
+ async init(roomId: string, createdBy: string) {
+ // Check if already initialized
+ const existing = await this.ctx.storage.get("roomId");
+ if (existing) {
+ return; // Already initialized
+ }
+
+ // Store the identity
+ await this.ctx.storage.put("roomId", roomId);
+ await this.ctx.storage.put("createdBy", createdBy);
+ await this.ctx.storage.put("createdAt", Date.now());
+
+ // Cache in memory for this session
+ this.roomId = roomId;
+ }
+
+ async getRoomId(): Promise {
+ if (this.roomId) {
+ return this.roomId;
+ }
+
+ const stored = await this.ctx.storage.get("roomId");
+ if (!stored) {
+ throw new Error("ChatRoom not initialized. Call init() first.");
+ }
+
+ this.roomId = stored;
+ return stored;
+ }
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ const url = new URL(request.url);
+ const roomId = url.searchParams.get("room") ?? "lobby";
+
+ const id = env.CHAT_ROOM.idFromName(roomId);
+ const stub = env.CHAT_ROOM.get(id);
+
+ // Initialize on first access
+ await stub.init(roomId, "system");
+
+ return new Response(`Room ${await stub.getRoomId()} ready`);
+ },
+};
+```
+
+
+
+### Always `await` RPC calls
+
+When calling methods on a Durable Object stub, always use `await`. Unawaited calls create dangling promises, causing errors to be swallowed and return values to be lost.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+export class ChatRoom extends DurableObject {
+ async sendMessage(userId: string, content: string): Promise {
+ const result = this.ctx.storage.sql.exec<{ id: number }>(
+ "INSERT INTO messages (user_id, content, created_at) VALUES (?, ?, ?) RETURNING id",
+ userId,
+ content,
+ Date.now()
+ );
+ return result.one().id;
+ }
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ const id = env.CHAT_ROOM.idFromName("lobby");
+ const stub = env.CHAT_ROOM.get(id);
+
+ // 🔴 Bad: Not awaiting the call
+ // The message ID is lost, and any errors are swallowed
+ stub.sendMessage("user-123", "Hello");
+
+ // ✅ Good: Properly awaited
+ const messageId = await stub.sendMessage("user-123", "Hello");
+
+ return Response.json({ messageId });
+ },
+};
+
+```
+
+
+## Error handling
+
+### Handle errors and use exception boundaries
+
+Uncaught exceptions in a Durable Object can leave it in an unknown state and may cause the runtime to terminate the instance. Wrap risky operations in `try...catch` blocks, and handle errors appropriately.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+export class ChatRoom extends DurableObject {
+ async processMessage(userId: string, content: string) {
+ // ✅ Good: Wrap risky operations in try...catch
+ try {
+ // Validate input before processing
+ if (!content || content.length > 10000) {
+ throw new Error("Invalid message content");
+ }
+
+ this.ctx.storage.sql.exec(
+ "INSERT INTO messages (user_id, content, created_at) VALUES (?, ?, ?)",
+ userId,
+ content,
+ Date.now()
+ );
+
+ // External call that might fail
+ await this.notifySubscribers(content);
+ } catch (error) {
+ // Log the error for debugging
+ console.error("Failed to process message:", error);
+
+ // Re-throw if it's a validation error (don't retry)
+ if (error instanceof Error && error.message.includes("Invalid")) {
+ throw error;
+ }
+
+ // For transient errors, you might want to handle differently
+ throw error;
+ }
+ }
+
+ private async notifySubscribers(content: string) {
+ // External notification logic
+ }
+}
+```
+
+
+
+When calling Durable Objects from a Worker, errors may include `.retryable` and `.overloaded` properties indicating whether the operation can be retried. For transient failures, implement exponential backoff to avoid overwhelming the system.
+
+Refer to [Error handling](/durable-objects/best-practices/error-handling/) for details on error properties, retry strategies, and exponential backoff patterns.
+
+## WebSockets and real-time
+
+### Use the Hibernatable WebSockets API for cost efficiency
+
+The Hibernatable WebSockets API allows Durable Objects to sleep while maintaining WebSocket connections. This significantly reduces costs for applications with many idle connections.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+export class ChatRoom extends DurableObject {
+ async fetch(request: Request): Promise {
+ const url = new URL(request.url);
+
+ if (url.pathname === "/websocket") {
+ // Check for WebSocket upgrade
+ if (request.headers.get("Upgrade") !== "websocket") {
+ return new Response("Expected WebSocket", { status: 400 });
+ }
+
+ const pair = new WebSocketPair();
+ const [client, server] = Object.values(pair);
+
+ // Accept the WebSocket with Hibernation API
+ this.ctx.acceptWebSocket(server);
+
+ return new Response(null, { status: 101, webSocket: client });
+ }
+
+ return new Response("Not found", { status: 404 });
+ }
+
+ // Called when a message is received (even after hibernation)
+ async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
+ const data = typeof message === "string" ? message : "binary data";
+
+ // Broadcast to all connected clients
+ for (const client of this.ctx.getWebSockets()) {
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
+ client.send(data);
+ }
+ }
+ }
+
+ // Called when a WebSocket is closed
+ async webSocketClose(
+ ws: WebSocket,
+ code: number,
+ reason: string,
+ wasClean: boolean
+ ) {
+ // With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
+ // auto-replies to Close frames. Calling close() is safe but no longer required.
+ ws.close(code, reason);
+ console.log(`WebSocket closed: ${code} ${reason}`);
+ }
+
+ // Called when a WebSocket error occurs
+ async webSocketError(ws: WebSocket, error: unknown) {
+ console.error("WebSocket error:", error);
+ }
+}
+
+```
+
+
+With the Hibernation API, your Durable Object can go to sleep when there is no active JavaScript execution, but WebSocket connections remain open. When a message arrives, the Durable Object wakes up automatically.
+
+Best practices:
+
+- The [WebSocket Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api) exposes `webSocketError`, `webSocketMessage`, and `webSocketClose` handlers for their respective WebSocket events.
+- With the [`web_socket_auto_reply_to_close`](/workers/configuration/compatibility-flags/#websocket-auto-reply-to-close) compatibility flag (enabled by default on compatibility dates on or after `2026-04-07`), the runtime automatically completes the close handshake. Calling `ws.close()` in `webSocketClose` is still safe but no longer required. On older compatibility dates, you **must** call `ws.close()` to avoid `1006` abnormal closure errors.
+
+Refer to [WebSockets](/durable-objects/best-practices/websockets/) for more details.
+
+### Use `serializeAttachment()` to persist per-connection state
+
+WebSocket attachments let you store metadata for each connection that survives hibernation. Use this for user IDs, session tokens, or other per-connection data.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+type ConnectionState = {
+ userId: string;
+ username: string;
+ joinedAt: number;
+};
+
+export class ChatRoom extends DurableObject {
+ async fetch(request: Request): Promise {
+ const url = new URL(request.url);
+
+ if (url.pathname === "/websocket") {
+ if (request.headers.get("Upgrade") !== "websocket") {
+ return new Response("Expected WebSocket", { status: 400 });
+ }
+
+ const userId = url.searchParams.get("userId") ?? "anonymous";
+ const username = url.searchParams.get("username") ?? "Anonymous";
+
+ const pair = new WebSocketPair();
+ const [client, server] = Object.values(pair);
+
+ this.ctx.acceptWebSocket(server);
+
+ // Store per-connection state that survives hibernation
+ const state: ConnectionState = {
+ userId,
+ username,
+ joinedAt: Date.now(),
+ };
+ server.serializeAttachment(state);
+
+ // Broadcast join message
+ this.broadcast(`${username} joined the chat`);
+
+ return new Response(null, { status: 101, webSocket: client });
+ }
+
+ return new Response("Not found", { status: 404 });
+ }
+
+ async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
+ // Retrieve the connection state (works even after hibernation)
+ const state = ws.deserializeAttachment() as ConnectionState;
+
+ const chatMessage = JSON.stringify({
+ userId: state.userId,
+ username: state.username,
+ content: message,
+ timestamp: Date.now(),
+ });
+
+ this.broadcast(chatMessage);
+ }
+
+ async webSocketClose(ws: WebSocket, code: number, reason: string) {
+ // With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
+ // auto-replies to Close frames. Calling close() is safe but no longer required.
+ ws.close(code, reason);
+ const state = ws.deserializeAttachment() as ConnectionState;
+ this.broadcast(`${state.username} left the chat`);
+ }
+
+ private broadcast(message: string) {
+ for (const client of this.ctx.getWebSockets()) {
+ if (client.readyState === WebSocket.OPEN) {
+ client.send(message);
+ }
+ }
+ }
+}
+```
+
+
+
+## Scheduling and lifecycle
+
+### Use alarms for per-entity scheduled tasks
+
+Each Durable Object can schedule its own future work using the [Alarms API](/durable-objects/api/alarms/), allowing a Durable Object to execute background tasks on any interval without an incoming request, RPC call, or WebSocket message.
+
+Key points about alarms:
+
+- **`setAlarm(timestamp)`** schedules the `alarm()` handler to run at any time in the future (millisecond precision)
+- **Alarms do not repeat automatically** — you must call `setAlarm()` again to schedule the next execution
+- **Only schedule alarms when there is work to do** — avoid waking up every Durable Object on short intervals (seconds), as each alarm invocation incurs costs
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ GAME_MATCH: DurableObjectNamespace;
+}
+
+export class GameMatch extends DurableObject {
+ async startGame(durationMs: number = 60000) {
+ await this.ctx.storage.put("gameStarted", Date.now());
+ await this.ctx.storage.put("gameActive", true);
+
+ // Schedule the game to end after the duration
+ await this.ctx.storage.setAlarm(Date.now() + durationMs);
+ }
+
+ // Called when the alarm fires
+ async alarm(alarmInfo?: AlarmInvocationInfo) {
+ const isActive = await this.ctx.storage.get("gameActive");
+
+ if (!isActive) {
+ return; // Game was already ended
+ }
+
+ // End the game
+ await this.ctx.storage.put("gameActive", false);
+ await this.ctx.storage.put("gameEnded", Date.now());
+
+ // Calculate final scores, notify players, etc.
+ try {
+ await this.calculateFinalScores();
+ } catch (err) {
+ // If we're almost out of retries but still have work to do, schedule a new alarm
+ // rather than letting our retries run out to ensure we keep getting invoked.
+ if (alarmInfo && alarmInfo.retryCount >= 5) {
+ await this.ctx.storage.setAlarm(Date.now() + 30 * 1000);
+ return;
+ }
+ throw err;
+ }
+
+ // Schedule the next alarm only if there's more work to do
+ // In this case, schedule cleanup in 24 hours
+ await this.ctx.storage.setAlarm(Date.now() + 24 * 60 * 60 * 1000);
+ }
+
+ private async calculateFinalScores() {
+ // Game ending logic
+ }
+}
+
+```
+
+
+### Make alarm handlers idempotent
+
+In rare cases, alarms may fire more than once. Your `alarm()` handler should be safe to run multiple times without causing issues.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ SUBSCRIPTION: DurableObjectNamespace;
+}
+
+export class Subscription extends DurableObject {
+ async alarm() {
+ // ✅ Good: Check state before performing the action
+ const lastRenewal = await this.ctx.storage.get("lastRenewal");
+ const renewalPeriod = 30 * 24 * 60 * 60 * 1000; // 30 days
+
+ // If we already renewed recently, don't do it again
+ if (lastRenewal && Date.now() - lastRenewal < renewalPeriod - 60000) {
+ console.log("Already renewed recently, skipping");
+ return;
+ }
+
+ // Perform the renewal
+ const success = await this.processRenewal();
+
+ if (success) {
+ // Record the renewal time
+ await this.ctx.storage.put("lastRenewal", Date.now());
+
+ // Schedule the next renewal
+ await this.ctx.storage.setAlarm(Date.now() + renewalPeriod);
+ } else {
+ // Retry in 1 hour
+ await this.ctx.storage.setAlarm(Date.now() + 60 * 60 * 1000);
+ }
+ }
+
+ private async processRenewal(): Promise {
+ // Payment processing logic
+ return true;
+ }
+}
+```
+
+
+
+### Clean up storage with `deleteAll()`
+
+To fully clear a Durable Object's storage, call `deleteAll()`. Simply deleting individual keys or dropping tables is not sufficient, as some internal metadata may remain. Workers with a compatibility date before [2026-02-24](/workers/configuration/compatibility-flags/#durable-object-deleteall-deletes-alarms) and an alarm set should delete the alarm first with `deleteAlarm()`.
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ CHAT_ROOM: DurableObjectNamespace;
+}
+
+export class ChatRoom extends DurableObject {
+ async clearStorage() {
+
+ // Delete all storage, including any set alarm
+ await this.ctx.storage.deleteAll();
+
+ // The Durable Object instance still exists, but with empty storage
+ // A subsequent request will find no data
+ }
+}
+
+```
+
+
+### Design for unexpected shutdowns
+
+
+
+## Anti-patterns to avoid
+
+### Do not use a single Durable Object as a global singleton
+
+A single Durable Object handling all traffic becomes a bottleneck. While async operations allow request interleaving, all synchronous JavaScript execution is single-threaded, and storage operations provide serialization guarantees that limit throughput.
+
+A common mistake is using a Durable Object for global rate limiting or global counters. This funnels all traffic through a single instance:
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ RATE_LIMITER: DurableObjectNamespace;
+}
+
+// 🔴 Bad: Global rate limiter - ALL requests go through one instance
+export class RateLimiter extends DurableObject {
+ async checkLimit(ip: string): Promise {
+ const key = `rate:${ip}`;
+ const count = (await this.ctx.storage.get(key)) ?? 0;
+ await this.ctx.storage.put(key, count + 1);
+ return count < 100;
+ }
+}
+
+// 🔴 Bad: Always using the same ID creates a global bottleneck
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ // Every single request to your application goes through this one DO
+ const limiter = env.RATE_LIMITER.get(
+ env.RATE_LIMITER.idFromName("global")
+ );
+
+ const ip = request.headers.get("CF-Connecting-IP") ?? "unknown";
+ const allowed = await limiter.checkLimit(ip);
+
+ if (!allowed) {
+ return new Response("Rate limited", { status: 429 });
+ }
+
+ return new Response("OK");
+ },
+};
+```
+
+
+
+This pattern does not scale. As traffic increases, the single Durable Object becomes a chokepoint. Instead, identify natural coordination boundaries in your application (per user, per room, per document) and create separate Durable Objects for each.
+
+## Testing and migrations
+
+### Test with Vitest and plan for class migrations
+
+Use `@cloudflare/vitest-pool-workers` for testing Durable Objects. The integration provides utilities for direct instance access.
+
+
+```ts
+import { env } from "cloudflare:workers";
+import {
+ runInDurableObject,
+ runDurableObjectAlarm,
+} from "cloudflare:test";
+import { describe, it, expect } from "vitest";
+
+describe("ChatRoom", () => {
+
+it("should send and retrieve messages", async () => {
+const id = env.CHAT_ROOM.idFromName("test-room");
+const stub = env.CHAT_ROOM.get(id);
+
+ // Call RPC methods directly on the stub
+ await stub.sendMessage("user-1", "Hello!");
+ await stub.sendMessage("user-2", "Hi there!");
+
+ const messages = await stub.getMessages(10);
+ expect(messages).toHaveLength(2);
+ });
+
+ it("can access instance internals and trigger alarms", async () => {
+ const id = env.CHAT_ROOM.idFromName("test-room");
+ const stub = env.CHAT_ROOM.get(id);
+
+ // Access storage directly for verification
+ await runInDurableObject(stub, async (instance, state) => {
+ const count = state.storage.sql
+ .exec<{ count: number }>("SELECT COUNT(*) as count FROM messages")
+ .one();
+ expect(count.count).toBe(2);
+ });
+
+ // Trigger alarms immediately without waiting
+ const alarmRan = await runDurableObjectAlarm(stub);
+ expect(alarmRan).toBe(false); // No alarm was scheduled
+ });
+});
+
+```
+
+
+Configure Vitest in your `vitest.config.ts`:
+
+```ts
+import { cloudflareTest } from "@cloudflare/vitest-pool-workers";
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ plugins: [
+ cloudflareTest({
+ wrangler: { configPath: "./wrangler.jsonc" },
+ }),
+ ],
+});
+```
+
+For schema changes, run migrations in the constructor using `blockConcurrencyWhile()`. For class renames or deletions, use Wrangler migrations:
+
+
+```jsonc
+{
+ "migrations": [
+ // Rename a class
+ { "tag": "v2", "renamed_classes": [{ "from": "OldChatRoom", "to": "ChatRoom" }] },
+ // Delete a class (removes all data!)
+ { "tag": "v3", "deleted_classes": ["DeprecatedRoom"] }
+ ]
+}
+```
+
+
+Refer to [Durable Objects migrations](/durable-objects/reference/durable-objects-migrations/) for more details on class migrations, and [Testing with Durable Objects](/durable-objects/examples/testing-with-durable-objects/) for comprehensive testing patterns including SQLite queries and alarm testing.
+
+## Related resources
+
+- [Workers Best Practices](/workers/best-practices/workers-best-practices/): code patterns for request handling, observability, and security that apply to the Workers calling your Durable Objects.
+- [Rules of Workflows](/workflows/build/rules-of-workflows/): best practices for durable, multi-step Workflows — useful when combining Workflows with Durable Objects for long-running orchestration.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/concepts/durable-object-lifecycle.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/concepts/durable-object-lifecycle.md
new file mode 100644
index 0000000..3f26619
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/concepts/durable-object-lifecycle.md
@@ -0,0 +1,108 @@
+---
+title: Lifecycle of a Durable Object
+description: Understand how a Durable Object is created, activated, handles requests, and is eventually evicted.
+pcx_content_type: concept
+sidebar:
+ order: 3
+products:
+ - durable-objects
+---
+
+import { Render } from "~/components";
+
+This section describes the lifecycle of a [Durable Object](/durable-objects/concepts/what-are-durable-objects/).
+
+To use a Durable Object you need to create a [Durable Object Stub](/durable-objects/api/stub/).
+Simply creating the Durable Object Stub does not send a request to the Durable Object, and therefore the Durable Object is not yet instantiated.
+A request is sent to the Durable Object and its lifecycle begins only once a method is invoked on the Durable Object Stub.
+
+```js
+const stub = env.MY_DURABLE_OBJECT.getByName("foo");
+// Now the request is sent to the remote Durable Object.
+const rpcResponse = await stub.sayHello();
+```
+
+## Durable Object Lifecycle state transitions
+
+A Durable Object can be in one of the following states at any moment:
+
+| State | Description |
+| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Active, in-memory** | The Durable Object runs, in memory, and handles incoming requests. |
+| **Idle, in-memory non-hibernateable** | The Durable Object waits for the next incoming request/event, but does not satisfy the criteria for hibernation. |
+| **Idle, in-memory hibernateable** | The Durable Object waits for the next incoming request/event and satisfies the criteria for hibernation. It is up to the runtime to decide when to hibernate the Durable Object. Currently, it is after 10 seconds of inactivity while in this state. |
+| **Hibernated** | The Durable Object is removed from memory. Hibernated WebSocket connections stay connected. |
+| **Inactive** | The Durable Object is completely removed from the host process and might need to cold start. This is the initial state of all Durable Objects. |
+
+This is how a Durable Object transitions among these states (each state is in a rounded rectangle).
+
+
+
+Assuming a Durable Object does not run, the first incoming request or event (like an alarm) will execute the `constructor()` of the Durable Object class, then run the corresponding function invoked.
+
+At this point the Durable Object is in the **active in-memory state**.
+
+Once all incoming requests or events have been processed, the Durable Object remains idle in-memory for a few seconds either in a hibernateable state or in a non-hibernateable state.
+
+Hibernation can only occur if **all** of the conditions below are true:
+
+- No `setTimeout`/`setInterval` scheduled callbacks are set, since there would be no way to recreate the callback after hibernating.
+- No in-progress awaited `fetch()` exists, since it is considered to be waiting for I/O.
+- No WebSocket standard API is used.
+- No request/event is still being processed, because hibernating would mean losing track of the async function which is eventually supposed to return a response to that request.
+- No active outbound TCP socket (`connect()`) or outbound WebSocket connection exists.
+
+After 10 seconds of no incoming request or event, and all the above conditions satisfied, the Durable Object will transition into the **hibernated** state.
+
+:::caution
+When hibernated, the in-memory state is discarded, so ensure you persist all important information in the Durable Object's storage.
+:::
+
+If any of the above conditions is false, the Durable Object remains in-memory, in the **idle, in-memory, non-hibernateable** state.
+
+In case of an incoming request or event while in the **hibernated** state, the `constructor()` will run again, and the Durable Object will transition to the **active, in-memory** state and execute the invoked function.
+
+While in the **idle, in-memory, non-hibernateable** state, after 70-140 seconds of inactivity (no incoming requests or events), the Durable Object will be evicted entirely from memory and potentially from the Cloudflare host and transition to the **inactive** state.
+
+:::note[Outbound connections keep Durable Objects alive]
+Active outbound connections created via [`connect()`](/workers/runtime-apis/tcp-sockets/) (TCP) or an outbound WebSocket prevent the Durable Object from being evicted. Eviction is deferred until both conditions are met: all outbound connections have closed, **and** the standard 70-140 second inactivity window has elapsed with no incoming requests or events.
+
+While kept alive by an outbound connection, the Durable Object remains in memory in the **idle, in-memory, non-hibernateable** state and continues to [incur duration charges](/durable-objects/platform/pricing/#when-does-a-durable-object-incur-duration-charges).
+
+Each outbound connection keeps the Durable Object alive for a maximum of 15 minutes. After 15 minutes, the connection stops preventing eviction (the connection itself continues operating), and the [standard eviction rules](/durable-objects/concepts/durable-object-lifecycle/#durable-object-lifecycle-state-transitions) resume.
+
+This applies to outbound TCP sockets and outbound WebSockets (including a `fetch()` request upgraded to a WebSocket via `Upgrade: websocket`). It does not apply to plain `fetch()` subrequests. Those never keep the Durable Object alive, even while the response body is still streaming.
+:::
+
+Objects in the **hibernated** state keep their Websocket clients connected, and the runtime decides if and when to transition the object to the **inactive** state (for example deciding to move the object to a different host) thus restarting the lifecycle.
+
+The next incoming request or event starts the cycle again.
+
+:::note[Lifecycle states incurring duration charges]
+A Durable Object incurs charges only when it is **actively running in-memory**, or when it is **idle in-memory and non-hibernateable** (indicated as green rectangles in the diagram).
+:::
+
+## Shutdown behavior
+
+Durable Objects will occasionally shut down and objects are restarted, which will run your Durable Object class constructor. This can happen for various reasons, including:
+
+- New Worker [deployments](/workers/versions-and-deployments/) with code updates
+- Lack of requests to an object following the state transitions documented above
+- Cloudflare updates to the Workers runtime system
+- Workers runtime decisions on where to host objects
+
+When a Durable Object is shut down, the object instance is automatically restarted and new requests are routed to the new instance. In-flight requests are handled as follows:
+
+- **HTTP & RPC requests**: In-flight requests are allowed to finish if they do not access a Durable Object's storage. If a request attempts to access a Durable Object's storage, it will be stopped immediately and return an error to maintain Durable Objects global uniqueness property. When the Worker runtime system is being updated, in-flight requests have up to 30 seconds to complete.
+- **WebSocket connections**: WebSocket requests are terminated automatically during shutdown. This is so that the new instance can take over the connection as soon as possible.
+- **Other invocations (email, cron)**: Other invocations are treated similarly to HTTP requests.
+
+It is important to ensure that any services using Durable Objects are designed to handle the possibility of a Durable Object being shut down.
+
+### Code updates
+
+When your Durable Object code is updated, your Worker and Durable Objects are released globally in an eventually consistent manner. This will cause a Durable Object to shut down, with the behavior described above. Updates can also create a situation where a request reaches a new version of your Worker in one location, and calls to a Durable Object still running a previous version elsewhere. Refer to [Code updates](/durable-objects/platform/known-issues/#code-updates) for more information about handling this scenario.
+
+### Working without shutdown hooks
+
+
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/concepts/what-are-durable-objects.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/concepts/what-are-durable-objects.md
new file mode 100644
index 0000000..200c20c
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/concepts/what-are-durable-objects.md
@@ -0,0 +1,115 @@
+---
+title: What are Durable Objects?
+description: Durable Objects provide globally unique, single-threaded compute instances with persistent storage on Cloudflare.
+pcx_content_type: concept
+sidebar:
+ order: 2
+products:
+ - durable-objects
+---
+
+import { Render } from "~/components";
+
+
+
+## Durable Objects highlights
+
+Durable Objects have properties that make them a great fit for distributed stateful scalable applications.
+
+**Serverless compute, zero infrastructure management**
+
+- Durable Objects are built on-top of the Workers runtime, so they support exactly the same code (JavaScript and WASM), and similar memory and CPU limits.
+- Each Durable Object is [implicitly created on first access](/durable-objects/api/namespace/#get). User applications are not concerned with their lifecycle, creating them or destroying them. Durable Objects migrate among healthy servers, and therefore applications never have to worry about managing them.
+- Each Durable Object stays alive as long as requests are being processed, and remains alive for several seconds after being idle before hibernating, allowing applications to [exploit in-memory caching](/durable-objects/reference/in-memory-state/) while handling many consecutive requests and boosting their performance.
+
+**Storage colocated with compute**
+
+- Each Durable Object has its own [durable, transactional, and strongly consistent storage](/durable-objects/api/sqlite-storage-api/) (up to 10 GB[^1]), persisted across requests, and accessible only within that object.
+
+**Single-threaded concurrency**
+
+- Each [Durable Object instance has an identifier](/durable-objects/api/id/), either randomly-generated or user-generated, which allows you to globally address which Durable Object should handle a specific action or request.
+- Durable Objects are single-threaded and cooperatively multi-tasked, just like code running in a web browser. For more details on how safety and correctness are achieved, refer to the blog post ["Durable Objects: Easy, Fast, Correct — Choose three"](https://blog.cloudflare.com/durable-objects-easy-fast-correct-choose-three/).
+
+**Elastic horizontal scaling across Cloudflare's global network**
+
+- Durable Objects can be spread around the world, and you can [optionally influence where each instance should be located](/durable-objects/reference/data-location/#provide-a-location-hint). Durable Objects are not yet available in every Cloudflare data center; refer to the [where.durableobjects.live](https://where.durableobjects.live/) project for live locations.
+- Each Durable Object type (or ["Namespace binding"](/durable-objects/api/namespace/) in Cloudflare terms) corresponds to a JavaScript class implementing the actual logic. There is no hard limit on how many Durable Objects can be created for each namespace.
+- Durable Objects scale elastically as your application creates millions of objects. There is no need for applications to manage infrastructure or plan ahead for capacity.
+
+## Durable Objects features
+
+### In-memory state
+
+Each Durable Object has its own [in-memory state](/durable-objects/reference/in-memory-state/). Applications can use this in-memory state to optimize the performance of their applications by keeping important information in-memory, thereby avoiding the need to access the durable storage at all.
+
+Useful cases for in-memory state include batching and aggregating information before persisting it to storage, or for immediately rejecting/handling incoming requests meeting certain criteria, and more.
+
+In-memory state is reset when the Durable Object hibernates after being idle for some time. Therefore, it is important to persist any in-memory data to the durable storage if that data will be needed at a later time when the Durable Object receives another request.
+
+### Storage API
+
+The [Durable Object Storage API](/durable-objects/api/sqlite-storage-api/) allows Durable Objects to access fast, transactional, and strongly consistent storage. A Durable Object's attached storage is private to its unique instance and cannot be accessed by other objects.
+
+There are two flavors of the storage API, a [key-value (KV) API](/durable-objects/api/legacy-kv-storage-api/) and an [SQL API](/durable-objects/api/sqlite-storage-api/).
+
+When using the [new SQLite in Durable Objects storage backend](/durable-objects/reference/durable-objects-migrations/#create-migration), you have access to both the APIs. However, if you use the previous storage backend you only have access to the key-value API.
+
+### Alarms API
+
+Durable Objects provide an [Alarms API](/durable-objects/api/alarms/) which allows you to schedule the Durable Object to be woken up at a time in the future. This is useful when you want to do certain work periodically, or at some specific point in time, without having to manually manage infrastructure such as job scheduling runners on your own.
+
+You can combine Alarms with in-memory state and the durable storage API to build batch and aggregation applications such as queues, workflows, or advanced data pipelines.
+
+### WebSockets
+
+WebSockets are long-lived TCP connections that enable bi-directional, real-time communication between client and server. Because WebSocket sessions are long-lived, applications commonly use Durable Objects to accept either the client or server connection.
+
+Because Durable Objects provide a single-point-of-coordination between Cloudflare Workers, a single Durable Object instance can be used in parallel with WebSockets to coordinate between multiple clients, such as participants in a chat room or a multiplayer game.
+
+Durable Objects support the [WebSocket Standard API](/durable-objects/best-practices/websockets/#websocket-standard-api), as well as the [WebSockets Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api) which extends the Web Standard WebSocket API to reduce costs by not incurring billing charges during periods of inactivity.
+
+### RPC
+
+Durable Objects support Workers [Remote-Procedure-Call (RPC)](/workers/runtime-apis/rpc/) which allows applications to use JavaScript-native methods and objects to communicate between Workers and Durable Objects.
+
+Using RPC for communication makes application development easier and simpler to reason about, and more efficient.
+
+## Actor programming model
+
+Another way to describe and think about Durable Objects is through the lens of the [Actor programming model](https://en.wikipedia.org/wiki/Actor_model). There are several popular examples of the Actor model supported at the programming language level through runtimes or library frameworks, like [Erlang](https://www.erlang.org/), [Elixir](https://elixir-lang.org/), [Akka](https://akka.io/), or [Microsoft Orleans for .NET](https://learn.microsoft.com/en-us/dotnet/orleans/overview).
+
+The Actor model simplifies a lot of problems in distributed systems by abstracting away the communication between actors using RPC calls (or message sending) that could be implemented on-top of any transport protocol, and it avoids most of the concurrency pitfalls you get when doing concurrency through shared memory such as race conditions when multiple processes/threads access the same data in-memory.
+
+Each Durable Object instance can be seen as an Actor instance, receiving messages (incoming HTTP/RPC requests), executing some logic in its own single-threaded context using its attached durable storage or in-memory state, and finally sending messages to the outside world (outgoing HTTP/RPC requests or responses), even to another Durable Object instance.
+
+Each Durable Object has certain capabilities in terms of [how much work it can do](/durable-objects/platform/limits/#how-much-work-can-a-single-durable-object-do), which should influence the application's [architecture to fully take advantage of the platform](/reference-architecture/diagrams/storage/durable-object-control-data-plane-pattern/).
+
+Durable Objects are natively integrated into Cloudflare's infrastructure, giving you the ultimate serverless platform to build distributed stateful applications exploiting the entirety of Cloudflare's network.
+
+## Durable Objects in Cloudflare
+
+Many of Cloudflare's products use Durable Objects. Some of our technical blog posts showcase real-world applications and use-cases where Durable Objects make building applications easier and simpler.
+
+These blog posts may also serve as inspiration on how to architect scalable applications using Durable Objects, and how to integrate them with the rest of Cloudflare Developer Platform.
+
+- [Durable Objects aren't just durable, they're fast: a 10x speedup for Cloudflare Queues](https://blog.cloudflare.com/how-we-built-cloudflare-queues/)
+- [Behind the scenes with Stream Live, Cloudflare's live streaming service](https://blog.cloudflare.com/behind-the-scenes-with-stream-live-cloudflares-live-streaming-service/)
+- [DO it again: how we used Durable Objects to add WebSockets support and authentication to AI Gateway](https://blog.cloudflare.com/do-it-again/)
+- [Workers Builds: integrated CI/CD built on the Workers platform](https://blog.cloudflare.com/workers-builds-integrated-ci-cd-built-on-the-workers-platform/)
+- [Build durable applications on Cloudflare Workers: you write the Workflows, we take care of the rest](https://blog.cloudflare.com/building-workflows-durable-execution-on-workers/)
+- [Building D1: a Global Database](https://blog.cloudflare.com/building-d1-a-global-database/)
+- [Billions and billions (of logs): scaling AI Gateway with the Cloudflare Developer Platform](https://blog.cloudflare.com/billions-and-billions-of-logs-scaling-ai-gateway-with-the-cloudflare/)
+- [Indexing millions of HTTP requests using Durable Objects](https://blog.cloudflare.com/r2-rayid-retrieval/)
+
+Finally, the following blog posts may help you learn some of the technical implementation aspects of Durable Objects, and how they work.
+
+- [Durable Objects: Easy, Fast, Correct — Choose three](https://blog.cloudflare.com/durable-objects-easy-fast-correct-choose-three/)
+- [Zero-latency SQLite storage in every Durable Object](https://blog.cloudflare.com/sqlite-in-durable-objects/)
+- [Workers Durable Objects Beta: A New Approach to Stateful Serverless](https://blog.cloudflare.com/introducing-workers-durable-objects/)
+
+## Get started
+
+Get started now by following the ["Get started" guide](/durable-objects/get-started/) to create your first application using Durable Objects.
+
+[^1]: Storage per Durable Object with SQLite is currently 1 GB. This will be raised to 10 GB for general availability.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/examples/build-a-counter.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/examples/build-a-counter.md
new file mode 100644
index 0000000..86eb133
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/examples/build-a-counter.md
@@ -0,0 +1,252 @@
+---
+summary: Build a counter using Durable Objects and Workers with RPC methods.
+pcx_content_type: example
+title: Build a counter
+sidebar:
+ order: 3
+description: Build a counter using Durable Objects and Workers with RPC methods.
+reviewed: 2023-08-04
+products:
+ - durable-objects
+ - workers
+---
+
+import { TabItem, Tabs, WranglerConfig } from "~/components";
+
+This example shows how to build a counter using Durable Objects and Workers with [RPC methods](/workers/runtime-apis/rpc) that can print, increment, and decrement a `name` provided by the URL query string parameter, for example, `?name=A`.
+
+
+
+```js
+import { DurableObject } from "cloudflare:workers";
+
+// Worker
+export default {
+ async fetch(request, env) {
+ let url = new URL(request.url);
+ let name = url.searchParams.get("name");
+ if (!name) {
+ return new Response(
+ "Select a Durable Object to contact by using" +
+ " the `name` URL query string parameter, for example, ?name=A",
+ );
+ }
+
+ // A stub is a client Object used to send messages to the Durable Object.
+ let stub = env.COUNTERS.getByName(name);
+
+ // Send a request to the Durable Object using RPC methods, then await its response.
+ let count = null;
+ switch (url.pathname) {
+ case "/increment":
+ count = await stub.increment();
+ break;
+ case "/decrement":
+ count = await stub.decrement();
+ break;
+ case "/":
+ // Serves the current value.
+ count = await stub.getCounterValue();
+ break;
+ default:
+ return new Response("Not found", { status: 404 });
+ }
+
+ return new Response(`Durable Object '${name}' count: ${count}`);
+ },
+};
+
+// Durable Object
+export class Counter extends DurableObject {
+ async getCounterValue() {
+ let value = (await this.ctx.storage.get("value")) || 0;
+ return value;
+ }
+
+ async increment(amount = 1) {
+ let value = (await this.ctx.storage.get("value")) || 0;
+ value += amount;
+ // You do not have to worry about a concurrent request having modified the value in storage.
+ // "input gates" will automatically protect against unwanted concurrency.
+ // Read-modify-write is safe.
+ await this.ctx.storage.put("value", value);
+ return value;
+ }
+
+ async decrement(amount = 1) {
+ let value = (await this.ctx.storage.get("value")) || 0;
+ value -= amount;
+ await this.ctx.storage.put("value", value);
+ return value;
+ }
+}
+```
+
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export interface Env {
+ COUNTERS: DurableObjectNamespace;
+}
+
+// Worker
+export default {
+ async fetch(request, env) {
+ let url = new URL(request.url);
+ let name = url.searchParams.get("name");
+ if (!name) {
+ return new Response(
+ "Select a Durable Object to contact by using" +
+ " the `name` URL query string parameter, for example, ?name=A",
+ );
+ }
+
+ // A stub is a client Object used to send messages to the Durable Object.
+ let stub = env.COUNTERS.get(name);
+
+ let count = null;
+ switch (url.pathname) {
+ case "/increment":
+ count = await stub.increment();
+ break;
+ case "/decrement":
+ count = await stub.decrement();
+ break;
+ case "/":
+ // Serves the current value.
+ count = await stub.getCounterValue();
+ break;
+ default:
+ return new Response("Not found", { status: 404 });
+ }
+
+ return new Response(`Durable Object '${name}' count: ${count}`);
+ },
+} satisfies ExportedHandler;
+
+// Durable Object
+export class Counter extends DurableObject {
+ async getCounterValue() {
+ let value = (await this.ctx.storage.get("value")) || 0;
+ return value;
+ }
+
+ async increment(amount = 1) {
+ let value: number = (await this.ctx.storage.get("value")) || 0;
+ value += amount;
+ // You do not have to worry about a concurrent request having modified the value in storage.
+ // "input gates" will automatically protect against unwanted concurrency.
+ // Read-modify-write is safe.
+ await this.ctx.storage.put("value", value);
+ return value;
+ }
+
+ async decrement(amount = 1) {
+ let value: number = (await this.ctx.storage.get("value")) || 0;
+ value -= amount;
+ await this.ctx.storage.put("value", value);
+ return value;
+ }
+}
+```
+
+
+
+```py
+from workers import DurableObject, Response, WorkerEntrypoint
+from urllib.parse import urlparse, parse_qs
+
+# Worker
+class Default(WorkerEntrypoint):
+ async def fetch(self, request):
+ parsed_url = urlparse(request.url)
+ query_params = parse_qs(parsed_url.query)
+ name = query_params.get('name', [None])[0]
+
+ if not name:
+ return Response(
+ "Select a Durable Object to contact by using"
+ + " the `name` URL query string parameter, for example, ?name=A"
+ )
+
+ # A stub is a client Object used to send messages to the Durable Object.
+ stub = self.env.COUNTERS.getByName(name)
+
+ # Send a request to the Durable Object using RPC methods, then await its response.
+ count = None
+
+ if parsed_url.path == "/increment":
+ count = await stub.increment()
+ elif parsed_url.path == "/decrement":
+ count = await stub.decrement()
+ elif parsed_url.path == "" or parsed_url.path == "/":
+ # Serves the current value.
+ count = await stub.getCounterValue()
+ else:
+ return Response("Not found", status=404)
+
+ return Response(f"Durable Object '{name}' count: {count}")
+
+# Durable Object
+class Counter(DurableObject):
+ def __init__(self, ctx, env):
+ super().__init__(ctx, env)
+
+ async def getCounterValue(self):
+ value = await self.ctx.storage.get("value")
+ return value if value is not None else 0
+
+ async def increment(self, amount=1):
+ value = await self.ctx.storage.get("value")
+ value = (value if value is not None else 0) + amount
+ # You do not have to worry about a concurrent request having modified the value in storage.
+ # "input gates" will automatically protect against unwanted concurrency.
+ # Read-modify-write is safe.
+ await self.ctx.storage.put("value", value)
+ return value
+
+ async def decrement(self, amount=1):
+ value = await self.ctx.storage.get("value")
+ value = (value if value is not None else 0) - amount
+ await self.ctx.storage.put("value", value)
+ return value
+```
+
+
+
+Finally, configure your Wrangler file to include a Durable Object [binding](/durable-objects/get-started/#4-configure-durable-object-bindings) and [migration](/durable-objects/reference/durable-objects-migrations/) based on the namespace and class name chosen previously.
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "my-counter",
+ "main": "src/index.ts",
+ "durable_objects": {
+ "bindings": [
+ {
+ "name": "COUNTERS",
+ "class_name": "Counter"
+ }
+ ]
+ },
+ "migrations": [
+ {
+ "tag": "v1",
+ "new_sqlite_classes": [
+ "Counter"
+ ]
+ }
+ ]
+}
+```
+
+
+
+### Related resources
+
+- [Workers RPC](/workers/runtime-apis/rpc/)
+- [Durable Objects: Easy, Fast, Correct — Choose three](https://blog.cloudflare.com/durable-objects-easy-fast-correct-choose-three/).
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/platform/storage-options.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/platform/storage-options.md
new file mode 100644
index 0000000..3fb19f9
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/durable-objects/platform/storage-options.md
@@ -0,0 +1,10 @@
+---
+pcx_content_type: navigation
+title: Choose a data or storage product
+description: Compare Cloudflare storage and data products to find the best option for your Durable Objects use case.
+external_link: /workers/platform/storage-options/
+sidebar:
+ order: 3
+products:
+ - durable-objects
+---
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/concepts/how-kv-works.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/concepts/how-kv-works.md
new file mode 100644
index 0000000..15370b3
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/concepts/how-kv-works.md
@@ -0,0 +1,81 @@
+---
+pcx_content_type: concept
+title: How KV works
+description: Workers KV stores data centrally and caches it globally, optimizing for high-read, low-latency workloads.
+sidebar:
+ order: 6
+products:
+ - kv
+---
+
+KV is a global, low-latency, key-value data store. It stores data in a small number of centralized data centers, then caches that data in Cloudflare's data centers after access.
+
+KV supports exceptionally high read volumes with low latency, making it possible to build dynamic APIs that scale thanks to KV's built-in caching and global distribution.
+Requests which are not in cache and need to access the central stores can experience higher latencies.
+
+## Write data to KV and read data from KV
+
+When you write to KV, your data is written to central data stores. Your data is not sent automatically to every location's cache.
+
+
+
+Initial reads from a location do not have a cached value. Data must be read from the nearest regional tier, followed by a central tier, degrading finally to the central stores for a truly cold global read. While the first access is slow globally, subsequent requests are faster, especially if requests are concentrated in a single region.
+
+:::note[Hot and cold read]
+
+A hot read means that the data is cached on Cloudflare's edge network using the [CDN](https://developers.cloudflare.com/cache/), whether it is in a local cache or a regional cache. A cold read means that the data is not cached, so the data must be fetched from the central stores.
+:::
+
+
+
+Frequent reads from the same location return the cached value without reading from anywhere else, resulting in the fastest response times. KV operates diligently to update the cached values by refreshing from upper tier caches and central data stores before cache expires in the background.
+
+Refreshing from upper tiers and the central data stores in the background is done carefully so that assets that are being accessed continue to be kept served from the cache without any stalls.
+
+
+
+KV is optimized for high-read applications. It stores data centrally and uses a hybrid push/pull-based replication to store data in cache. KV is suitable for use cases where you need to write relatively infrequently, but read quickly and frequently. Infrequently read values are pulled from other data centers or the central stores, while more popular values are cached in the data centers they are requested from.
+
+## Performance
+
+To improve KV performance, increase the [`cacheTtl` parameter](/kv/api/read-key-value-pairs/#cachettl-parameter) up from its default 60 seconds.
+
+KV achieves high performance by [caching](https://www.cloudflare.com/en-gb/learning/cdn/what-is-caching/) which makes reads eventually-consistent with writes.
+
+Changes are usually immediately visible in the Cloudflare global network location at which they are made. Changes may take up to 60 seconds or more to be visible in other global network locations as their cached versions of the data time out.
+
+Negative lookups indicating that the key does not exist are also cached, so the same delay exists noticing a value is created as when a value is changed.
+
+## Consistency
+
+KV achieves high performance by being eventually-consistent. At the Cloudflare global network location at which changes are made, these changes are usually immediately visible. However, this is not guaranteed and therefore it is not advised to rely on this behaviour. In other global network locations changes may take up to 60 seconds or more to be visible as their cached versions of the data time-out.
+
+Visibility of changes takes longer in locations which have recently read a previous version of a given key (including reads that indicated the key did not exist, which are also cached locally).
+
+:::note
+
+KV is not ideal for applications where you need support for atomic operations or where values must be read and written in a single transaction.
+If you need stronger consistency guarantees, consider using [Durable Objects](/durable-objects/).
+:::
+
+An approach to achieve write-after-write consistency is to send all of your writes for a given KV key through a corresponding instance of a Durable Object, and then read that value from KV in other Workers. This is useful if you need more control over writes, but are satisfied with KV's read characteristics described above.
+
+## Guidance
+
+Workers KV is an eventually-consistent edge key-value store. That makes it ideal for **read-heavy**, highly cacheable workloads such as:
+
+- Serving static assets
+- Storing application configuration
+- Storing user preferences
+- Implementing allow-lists/deny-lists
+- Caching
+
+In these scenarios, Workers are invoked in a data center closest to the user and Workers KV data will be cached in that region for subsequent requests to minimize latency.
+
+If you have a **write-heavy** [Redis](https://redis.io)-type workload where you are updating the same key tens or hundreds of times per second, KV will not be an ideal fit.
+If you can revisit how your application writes to single key-value pairs and spread your writes across several discrete keys, Workers KV can suit your needs.
+Alternatively, [Durable Objects](/durable-objects/) provides a key-value API with higher writes per key rate limits.
+
+## Security
+
+Refer to [Data security documentation](/kv/reference/data-security/) to understand how Workers KV secures data.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/concepts/kv-bindings.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/concepts/kv-bindings.md
new file mode 100644
index 0000000..4af37dd
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/concepts/kv-bindings.md
@@ -0,0 +1,109 @@
+---
+pcx_content_type: concept
+title: KV bindings
+description: KV bindings connect a Cloudflare Worker to a KV namespace for reading and writing data.
+tags:
+ - Bindings
+sidebar:
+ order: 7
+products:
+ - kv
+---
+
+import { WranglerConfig } from "~/components";
+
+KV [bindings](/workers/runtime-apis/bindings/) allow for communication between a Worker and a KV namespace.
+
+Configure KV bindings in the [Wrangler configuration file](/workers/wrangler/configuration/).
+
+## Access KV from Workers
+
+A [KV namespace](/kv/concepts/kv-namespaces/) is a key-value database replicated to Cloudflare's global network.
+
+To connect to a KV namespace from within a Worker, you must define a binding that points to the namespace's ID.
+
+The name of your binding does not need to match the KV namespace's name. Instead, the binding should be a valid JavaScript identifier, because the identifier will exist as a global variable within your Worker.
+
+A KV namespace will have a name you choose (for example, `My tasks`), and an assigned ID (for example, `06779da6940b431db6e566b4846d64db`).
+
+To execute your Worker, define the binding.
+
+In the following example, the binding is called `TODO`. In the `kv_namespaces` portion of your Wrangler configuration file, add:
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "worker",
+ // ...
+ "kv_namespaces": [
+ {
+ "binding": "TODO",
+ "id": "06779da6940b431db6e566b4846d64db"
+ }
+ ]
+}
+```
+
+
+
+With this, the deployed Worker will have a `TODO` field in their environment object (the second parameter of the `fetch()` request handler). Any methods on the `TODO` binding will map to the KV namespace with an ID of `06779da6940b431db6e566b4846d64db` – which you called `My Tasks` earlier.
+
+```js
+export default {
+ async fetch(request, env, ctx) {
+ // Get the value for the "to-do:123" key
+ // NOTE: Relies on the `TODO` KV binding that maps to the "My Tasks" namespace.
+ let value = await env.TODO.get("to-do:123");
+
+ // Return the value, as is, for the Response
+ return new Response(value);
+ },
+};
+```
+
+## Use KV bindings when developing locally
+
+When you use Wrangler to develop locally with the `wrangler dev` command, Wrangler will default to using a local version of KV to avoid interfering with any of your live production data in KV. This means that reading keys that you have not written locally will return `null`.
+
+To have `wrangler dev` connect to your Workers KV namespace running on Cloudflare's global network, set `"remote" : true` in the KV binding configuration. Refer to the [remote bindings documentation](/workers/local-development/#remote-bindings) for more information.
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "worker",
+ // ...
+ "kv_namespaces": [
+ {
+ "binding": "TODO",
+ "id": "06779da6940b431db6e566b4846d64db"
+ }
+ ]
+}
+```
+
+
+
+## Access KV from Durable Objects and Workers using ES modules format
+
+[Durable Objects](/durable-objects/) use ES modules format. Instead of a global variable, bindings are available as properties of the `env` parameter [passed to the constructor](/durable-objects/get-started/#2-write-a-durable-object-class).
+
+An example might look like:
+
+```js
+import { DurableObject } from "cloudflare:workers";
+
+export class MyDurableObject extends DurableObject {
+ constructor(ctx, env) {
+ super(ctx, env);
+ }
+
+ async fetch(request) {
+ const valueFromKV = await this.env.NAMESPACE.get("someKey");
+ return new Response(valueFromKV);
+ }
+}
+```
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/concepts/kv-namespaces.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/concepts/kv-namespaces.md
new file mode 100644
index 0000000..7867efd
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/concepts/kv-namespaces.md
@@ -0,0 +1,68 @@
+---
+pcx_content_type: concept
+title: KV namespaces
+description: A KV namespace is a key-value database replicated across Cloudflare's global network.
+sidebar:
+ order: 7
+products:
+ - kv
+---
+import { Type, MetaInfo, WranglerConfig, DashButton } from "~/components";
+
+A KV namespace is a key-value database replicated to Cloudflare’s global network.
+
+Bind your KV namespaces through Wrangler or via the Cloudflare dashboard.
+
+:::note
+
+KV namespace IDs are public and bound to your account.
+
+:::
+
+## Bind your KV namespace through Wrangler
+
+To bind KV namespaces to your Worker, assign an array of the below object to the `kv_namespaces` key.
+
+* `binding`
+
+ * The binding name used to refer to the KV namespace.
+
+* `id`
+
+ * The ID of the KV namespace.
+
+* `preview_id`
+
+ * The ID of the KV namespace used during `wrangler dev`.
+
+Example:
+
+
+
+```jsonc
+{
+ "kv_namespaces": [
+ {
+ "binding": "",
+ "id": ""
+ }
+ ]
+}
+```
+
+
+
+## Bind your KV namespace via the dashboard
+
+To bind the namespace to your Worker in the Cloudflare dashboard:
+
+1. In the Cloudflare dashboard, go to the **Workers & Pages** page.
+
+
+2. Select your **Worker**.
+3. Select **Settings** > **Bindings**.
+4. Select **Add**.
+5. Select **KV Namespace**.
+6. Enter your desired variable name (the name of the binding).
+7. Select the KV namespace you wish to bind the Worker to.
+8. Select **Deploy**.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/examples/cache-data-with-workers-kv.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/examples/cache-data-with-workers-kv.md
new file mode 100644
index 0000000..0265f4e
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/examples/cache-data-with-workers-kv.md
@@ -0,0 +1,145 @@
+---
+summary: Cache data or API responses in Workers KV to improve application performance
+pcx_content_type: example
+title: Cache data with Workers KV
+sidebar:
+ order: 5
+description: Example of how to use Workers KV to build a distributed application configuration store.
+reviewed: 2025-03-27
+products:
+ - kv
+---
+
+import { Render, PackageManagers, Tabs, TabItem } from "~/components";
+
+Workers KV can be used as a persistent, single, global cache accessible from Cloudflare Workers to speed up your application.
+Data cached in Workers KV is accessible from all other Cloudflare locations as well, and persists until expiry or deletion.
+
+After fetching data from external resources in your Workers application, you can write the data to Workers KV.
+On subsequent Worker requests (in the same region or in other regions), you can read the cached data from Workers KV instead of calling the external API.
+This improves your Worker application's performance and resilience while reducing load on external resources.
+
+This example shows how you can cache data in Workers KV and read cached data from Workers KV in a Worker application.
+
+:::note[Note]
+
+You can also cache data in Workers with the [Cache API](/workers/runtime-apis/cache/). With the Cache API,
+the contents of the cache do not replicate outside of the originating data center and the cache is ephemeral (can be evicted).
+
+With Workers KV, the data is persisted by default to [central stores](/kv/concepts/how-kv-works/) (or can be set to [expire](/kv/api/write-key-value-pairs/#expiring-keys), and can be accessed from other Cloudflare locations.
+:::
+
+## Cache data in Workers KV from your Worker application
+
+In the following `index.ts` file, the Worker fetches data from an external server and caches the response in Workers KV. If the data is already cached in Workers KV, the Worker reads the cached data from Workers KV instead of calling the external API.
+
+
+
+```js title="index.ts" collapse={42-1000}
+interface Env {
+ CACHE_KV: KVNamespace;
+}
+
+export default {
+ async fetch(request, env, ctx): Promise {
+
+ const EXPIRATION_TTL = 30; // Cache expiration in seconds
+ const url = 'https://example.com';
+ const cacheKey = "cache-json-example";
+
+ // Try to get data from KV cache first
+ let data = await env.CACHE_KV.get(cacheKey, { type: 'json' });
+ let fromCache = true;
+
+ // If data is not in cache, fetch it from example.com
+ if (!data) {
+ console.log('Cache miss. Fetching fresh data from example.com');
+ fromCache = false;
+
+ // In this example, we are fetching HTML content but it can also be API responses or any other data
+ const response = await fetch(url);
+ const htmlData = await response.text();
+
+ // In this example, we are converting HTML to JSON to demonstrate caching JSON data with Workers KV
+ // You could cache any type of data, or even cache the HTML data directly
+ data = helperConvertToJSON(htmlData);
+ // The expirationTtl option is used to set the expiration time for the cache entry (in seconds), otherwise it will be stored indefinitely
+ await env.CACHE_KV.put(cacheKey, JSON.stringify(data), { expirationTtl: EXPIRATION_TTL });
+ }
+
+ // Return the appropriate response format
+ return new Response(JSON.stringify({
+ data,
+ fromCache
+ }), {
+ headers: { 'Content-Type': 'application/json' }
+ });
+
+}
+} satisfies ExportedHandler;
+
+// Helper function to convert HTML to JSON
+function helperConvertToJSON(html: string) {
+// Parse HTML and extract relevant data
+const title = helperExtractTitle(html);
+const content = helperExtractContent(html);
+const lastUpdated = new Date().toISOString();
+
+ return { title, content, lastUpdated };
+
+}
+
+// Helper function to extract title from HTML
+function helperExtractTitle(html: string) {
+const titleMatch = html.match(/(.\*?)<\/title>/i);
+return titleMatch ? titleMatch[1] : 'No title found';
+}
+
+// Helper function to extract content from HTML
+function helperExtractContent(html: string) {
+const bodyMatch = html.match(/(.\*?)<\/body>/is);
+if (!bodyMatch) return 'No content found';
+
+ // Strip HTML tags for a simple text representation
+ const textContent = bodyMatch[1].replace(/<[^>]*>/g, ' ')
+ .replace(/\s+/g, ' ')
+ .trim();
+
+ return textContent;
+
+}
+
+```
+
+
+```json
+{
+ "$schema": "node_modules/wrangler/config-schema.json",
+ "name": "",
+ "main": "src/index.ts",
+ "compatibility_date": "2025-03-03",
+ "observability": {
+ "enabled": true
+ },
+ "kv_namespaces": [
+ {
+ "binding": "CACHE_KV",
+ "id": ""
+ }
+ ]
+}
+```
+
+
+
+
+This code snippet demonstrates how to read and update cached data in Workers KV from your Worker.
+If the data is not in the Workers KV cache, the Worker fetches the data from an external server and caches it in Workers KV.
+
+In this example, we convert HTML to JSON to demonstrate how to cache JSON data with Workers KV, but any type of data
+can be cached in Workers KV. For instance, you could cache API responses, HTML content, or any other data that you want to persist across requests.
+
+## Related resources
+
+- [Rust support in Workers](/workers/languages/rust/).
+- [Using KV in Workers](/kv/get-started/).
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/examples/distributed-configuration-with-workers-kv.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/examples/distributed-configuration-with-workers-kv.md
new file mode 100644
index 0000000..2ef7b10
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/examples/distributed-configuration-with-workers-kv.md
@@ -0,0 +1,237 @@
+---
+summary: Use Workers KV to as a geo-distributed, low-latency configuration store for your Workers application
+pcx_content_type: example
+title: Build a distributed configuration store
+sidebar:
+ order: 5
+description: Example of how to use Workers KV to build a distributed application configuration store.
+reviewed: 2025-03-27
+products:
+ - kv
+---
+
+import { Render, PackageManagers, Tabs, TabItem } from "~/components";
+
+Storing application configuration data is an ideal use case for Workers KV. Configuration data can include data to personalize an application for each user or tenant, enable features for user groups, restrict access with allow-lists/deny-lists, etc. These use-cases can have high read volumes that are highly cacheable by Workers KV, which can ensure low-latency reads from your Workers application.
+
+In this example, application configuration data is used to personalize the Workers application for each user. The configuration data is stored in an external application and database, and written to Workers KV using the REST API.
+
+## Write your configuration from your external application to Workers KV
+
+In some cases, your source-of-truth for your configuration data may be stored elsewhere than Workers KV.
+If this is the case, use the Workers KV REST API to write the configuration data to your Workers KV namespace.
+
+The following external Node.js application demonstrates a simple scripts that reads user data from a database and writes it to Workers KV using the REST API library.
+
+
+
+```js title="index.js"
+const postgres = require('postgres');
+const { Cloudflare } = require('cloudflare');
+const { backOff } = require('exponential-backoff');
+
+if(!process.env.DATABASE_CONNECTION_STRING || !process.env.CLOUDFLARE_EMAIL || !process.env.CLOUDFLARE_API_KEY || !process.env.CLOUDFLARE_WORKERS_KV_NAMESPACE_ID || !process.env.CLOUDFLARE_ACCOUNT_ID) {
+console.error('Missing required environment variables.');
+process.exit(1);
+}
+
+// Setup Postgres connection
+const sql = postgres(process.env.DATABASE_CONNECTION_STRING);
+
+// Setup Cloudflare REST API client
+const client = new Cloudflare({
+apiEmail: process.env.CLOUDFLARE_EMAIL,
+apiKey: process.env.CLOUDFLARE_API_KEY,
+});
+
+// Function to sync Postgres data to Workers KV
+async function syncPreviewStatus() {
+console.log('Starting sync of user preview status...');
+
+ try {
+ // Get all users and their preview status
+ const users = await sql`SELECT id, preview_features_enabled FROM users`;
+
+ console.log(users);
+
+ // Create the bulk update body
+ const bulkUpdateBody = users.map(user => ({
+ key: user.id,
+ value: JSON.stringify({
+ preview_features_enabled: user.preview_features_enabled
+ })
+ }));
+
+ const response = await backOff(async () => {
+ console.log("trying to update")
+ try{
+ const response = await client.kv.namespaces.bulkUpdate(process.env.CLOUDFLARE_WORKERS_KV_NAMESPACE_ID, {
+ account_id: process.env.CLOUDFLARE_ACCOUNT_ID,
+ body: bulkUpdateBody
+ });
+ }
+ catch(e){
+ // Implement your error handling and logging here
+ console.log(e);
+ throw e; // Rethrow the error to retry
+ }
+ });
+
+ console.log(`Sync complete. Updated ${users.length} users.`);
+ } catch (error) {
+ console.error('Error syncing preview status:', error);
+ }
+
+}
+
+// Run the sync function
+syncPreviewStatus()
+.catch(console.error)
+.finally(() => process.exit(0));
+
+```
+
+
+```md title=".env"
+DATABASE_CONNECTION_STRING =
+CLOUDFLARE_EMAIL =
+CLOUDFLARE_API_KEY =
+CLOUDFLARE_ACCOUNT_ID =
+CLOUDFLARE_WORKERS_KV_NAMESPACE_ID =
+```
+
+
+
+```sql title="db.sql"
+-- Create users table with preview_features_enabled flag
+CREATE TABLE users (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ username VARCHAR(100) NOT NULL,
+ email VARCHAR(255) NOT NULL,
+ preview_features_enabled BOOLEAN DEFAULT false
+);
+
+-- Insert sample users
+INSERT INTO users (username, email, preview_features_enabled) VALUES
+('alice', 'alice@example.com', true),
+('bob', 'bob@example.com', false),
+('charlie', 'charlie@example.com', true);
+
+```
+
+
+
+In this code snippet, the Node.js application reads user data from a Postgres database and writes the user data to be used for configuration in our Workers application to Workers KV using the Cloudflare REST API Node.js library.
+The application also uses exponential backoff to handle retries in case of errors.
+
+## Use configuration data from Workers KV in your Worker application
+
+With the configuration data now in the Workers KV namespace, we can use it in our Workers application to personalize the application for each user.
+
+
+
+```js title="index.ts"
+// Example configuration data stored in Workers KV:
+// Key: "user-id-abc" | Value: {"preview_features_enabled": false}
+// Key: "user-id-def" | Value: {"preview_features_enabled": true}
+
+interface Env {
+ USER_CONFIGURATION: KVNamespace;
+}
+
+export default {
+ async fetch(request, env) {
+ // Get user ID from query parameter
+ const url = new URL(request.url);
+ const userId = url.searchParams.get('userId');
+
+ if (!userId) {
+ return new Response('Please provide a userId query parameter', {
+ status: 400,
+ headers: { 'Content-Type': 'text/plain' }
+ });
+ }
+
+
+ const userConfiguration = await env.USER_CONFIGURATION.get<{
+ preview_features_enabled: boolean;
+ }>(userId, {type: "json"});
+
+ console.log(userConfiguration);
+
+ // Build HTML response
+ const html = `
+
+
+
+ My App
+
+
+
+ ${userConfiguration?.preview_features_enabled ? `
+
+ 🎉 You have early access to preview features! 🎉
+
+ ` : ''}
+
Welcome to My App
+
This is the regular content everyone sees.
+
+
+ `;
+
+ return new Response(html, {
+ headers: { "Content-Type": "text/html; charset=utf-8" }
+ });
+ }
+} satisfies ExportedHandler;
+
+```
+
+
+```json
+{
+ "$schema": "node_modules/wrangler/config-schema.json",
+ "name": "",
+ "main": "src/index.ts",
+ "compatibility_date": "2025-03-03",
+ "observability": {
+ "enabled": true
+ },
+ "kv_namespaces": [
+ {
+ "binding": "USER_CONFIGURATION",
+ "id": ""
+ }
+ ]
+}
+```
+
+
+
+
+This code will use the path within the URL and find the file associated to the path within the KV store. It also sets the proper MIME type in the response to inform the browser how to handle the response. To retrieve the value from the KV store, this code uses `arrayBuffer` to properly handle binary data such as images, documents, and video/audio files.
+
+## Optimize performance for configuration
+
+To optimize performance, you may opt to consolidate values in fewer key-value pairs. By doing so, you may benefit from higher caching efficiency and lower latency.
+
+For example, instead of storing each user's configuration in a separate key-value pair, you may store all users' configurations in a single key-value pair. This approach may be suitable for use-cases where the configuration data is small and can be easily managed in a single key-value pair (the [size limit for a Workers KV value is 25 MiB](/kv/platform/limits/)).
+
+## Related resources
+
+- [Rust support in Workers](/workers/languages/rust/)
+- [Using KV in Workers](/kv/get-started/)
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/index.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/index.md
new file mode 100644
index 0000000..c0a8458
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/kv/index.md
@@ -0,0 +1,225 @@
+---
+title: Cloudflare Workers KV
+description: Workers KV is a global, low-latency, key-value data store for building dynamic and performant APIs and websites.
+pcx_content_type: overview
+sidebar:
+ order: 1
+products:
+ - kv
+---
+
+import {
+ CardGrid,
+ Description,
+ Feature,
+ LinkTitleCard,
+ Plan,
+ RelatedProduct,
+ Tabs,
+ TabItem,
+ LinkButton,
+} from "~/components";
+
+
+
+Create a global, low-latency, key-value data storage.
+
+
+
+
+
+Workers KV is a data storage that allows you to store and retrieve data globally. With Workers KV, you can build dynamic and performant APIs and websites that support high read volumes with low latency.
+
+For example, you can use Workers KV for:
+
+- Caching API responses.
+- Storing user configurations / preferences.
+- Storing user authentication details.
+
+Access your Workers KV namespace from Cloudflare Workers using [Workers Bindings](/workers/runtime-apis/bindings/) or from your external application using the REST API:
+
+
+
+
+
+```ts
+export default {
+ async fetch(request, env, ctx): Promise {
+ // write a key-value pair
+ await env.KV.put('KEY', 'VALUE');
+
+ // read a key-value pair
+ const value = await env.KV.get('KEY');
+
+ // list all key-value pairs
+ const allKeys = await env.KV.list();
+
+ // delete a key-value pair
+ await env.KV.delete('KEY');
+
+ // return a Workers response
+ return new Response(
+ JSON.stringify({
+ value: value,
+ allKeys: allKeys,
+ }),
+ );
+ },
+
+} satisfies ExportedHandler<{ KV: KVNamespace }>;
+
+ ```
+
+
+
+```json
+{
+ "$schema": "node_modules/wrangler/config-schema.json",
+ "name": "",
+ "main": "src/index.ts",
+ "compatibility_date": "2025-02-04",
+ "observability": {
+ "enabled": true
+ },
+
+ "kv_namespaces": [
+ {
+ "binding": "KV",
+ "id": ""
+ }
+ ]
+}
+```
+
+
+
+
+See the full [Workers KV binding API reference](/kv/api/read-key-value-pairs/).
+
+
+
+
+
+
+ ```
+ curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/storage/kv/namespaces/$NAMESPACE_ID/values/$KEY_NAME \
+ -X PUT \
+ -H 'Content-Type: multipart/form-data' \
+ -H "X-Auth-Email: $CLOUDFLARE_EMAIL" \
+ -H "X-Auth-Key: $CLOUDFLARE_API_KEY" \
+ -d '{
+ "value": "Some Value"
+ }'
+
+ curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/storage/kv/namespaces/$NAMESPACE_ID/values/$KEY_NAME \
+ -H "X-Auth-Email: $CLOUDFLARE_EMAIL" \
+ -H "X-Auth-Key: $CLOUDFLARE_API_KEY"
+ ```
+
+
+ ```ts
+ const client = new Cloudflare({
+ apiEmail: process.env['CLOUDFLARE_EMAIL'], // This is the default and can be omitted
+ apiKey: process.env['CLOUDFLARE_API_KEY'], // This is the default and can be omitted
+ });
+
+ const value = await client.kv.namespaces.values.update('', 'KEY', {
+ account_id: '',
+ value: 'VALUE',
+ });
+
+ const value = await client.kv.namespaces.values.get('', 'KEY', {
+ account_id: '',
+ });
+
+ const value = await client.kv.namespaces.values.delete('', 'KEY', {
+ account_id: '',
+ });
+
+ // Automatically fetches more pages as needed.
+ for await (const namespace of client.kv.namespaces.list({ account_id: '' })) {
+ console.log(namespace.id);
+ }
+
+ ```
+
+
+
+See the full Workers KV [REST API and SDK reference](/api/resources/kv/) for details on using REST API from external applications, with pre-generated SDK's for external TypeScript, Python, or Go applications.
+
+
+
+
+Get started
+
+---
+
+## Features
+
+
+ Learn how Workers KV stores and retrieves data.
+
+
+
+
+The Workers command-line interface, Wrangler, allows you to [create](/workers/wrangler/commands/general/#init), [test](/workers/wrangler/commands/general/#dev), and [deploy](/workers/wrangler/commands/pages/#pages-deploy) your Workers projects.
+
+
+
+
+
+Bindings allow your Workers to interact with resources on the Cloudflare developer platform, including [R2](/r2/), [Durable Objects](/durable-objects/), and [D1](/d1/).
+
+
+
+---
+
+## Related products
+
+
+
+Cloudflare R2 Storage allows developers to store large amounts of unstructured data without the costly egress bandwidth fees associated with typical cloud storage services.
+
+
+
+
+
+Cloudflare Durable Objects allows developers to access scalable compute and permanent, consistent storage.
+
+
+
+
+
+Built on SQLite, D1 is Cloudflare’s first queryable relational database. Create an entire database by importing data or defining your tables and writing your queries within a Worker or through the API.
+
+
+
+---
+
+### More resources
+
+
+
+
+ Learn about KV limits.
+
+
+
+ Learn about KV pricing.
+
+
+
+ Ask questions, show off what you are building, and discuss the platform
+ with other developers.
+
+
+
+ Learn about product announcements, new tutorials, and what is new in
+ Cloudflare Developer Platform.
+
+
+
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/configuration/batching-retries.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/configuration/batching-retries.md
new file mode 100644
index 0000000..dc80213
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/configuration/batching-retries.md
@@ -0,0 +1,393 @@
+---
+title: Batching, Retries and Delays
+description: Configure message batching, retry behavior, and delivery delays for Cloudflare Queues.
+pcx_content_type: concept
+sidebar:
+ order: 2
+products:
+ - queues
+---
+
+import { WranglerConfig, TypeScriptExample, Tabs, TabItem } from "~/components";
+
+## Batching
+
+When configuring a [consumer Worker](/queues/reference/how-queues-works#consumers) for a queue, you can also define how messages are batched as they are delivered.
+
+Batching can:
+
+1. Reduce the total number of times your consumer Worker needs to be invoked (which can reduce costs).
+2. Allow you to batch messages when writing to an external API or service (reducing writes).
+3. Disperse load over time, especially if your producer Workers are associated with user-facing activity.
+
+There are two ways to configure how messages are batched. You configure batching when connecting your consumer Worker to a queue.
+
+- `max_batch_size` - The maximum size of a batch delivered to a consumer (defaults to 10 messages).
+- `max_batch_timeout` - the _maximum_ amount of time the queue will wait before delivering a batch to a consumer (defaults to 5 seconds)
+
+:::note[Batch size configuration]
+
+Both `max_batch_size` and `max_batch_timeout` work together. Whichever limit is reached first will trigger the delivery of a batch.
+
+:::
+
+For example, a `max_batch_size = 30` and a `max_batch_timeout = 10` means that if 30 messages are written to the queue, the consumer will receive a batch of 30 messages. However, if it takes longer than 10 seconds for those 30 messages to be written to the queue, then the consumer will get a batch of messages that contains however many messages were on the queue at the time (somewhere between 1 and 29, in this case).
+
+:::note[Empty queues]
+
+When a queue is empty, a push-based (Worker) consumer's `queue` handler will not be invoked until there are messages to deliver. A queue does not attempt to push empty batches to a consumer and thus does not invoke unnecessary reads.
+
+[Pull-based consumers](/queues/configuration/pull-consumers/) that attempt to pull from a queue, even when empty, will incur a read operation.
+
+:::
+
+When determining what size and timeout settings to configure, you will want to consider latency (how long can you wait to receive messages?), overall batch size (when writing to external systems), and cost (fewer-but-larger batches).
+
+### Batch settings
+
+The following batch-level settings can be configured to adjust how Queues delivers batches to your configured consumer.
+
+
+
+| Setting | Default | Minimum | Maximum |
+| ----------------------------------------- | ----------- | --------- | ------------ |
+| Maximum Batch Size `max_batch_size` | 10 messages | 1 message | 100 messages |
+| Maximum Batch Timeout `max_batch_timeout` | 5 seconds | 0 seconds | 60 seconds |
+
+
+
+## Explicit acknowledgement and retries
+
+You can acknowledge individual messages within a batch by explicitly acknowledging each message as it is processed. Messages that are explicitly acknowledged will not be re-delivered, even if your queue consumer fails on a subsequent message and/or fails to return successfully when processing a batch.
+
+- Each message can be acknowledged as you process it within a batch, and avoids the entire batch from being re-delivered if your consumer throws an error during batch processing.
+- Acknowledging individual messages is useful when you are calling external APIs, writing messages to a database, or otherwise performing non-idempotent (state changing) actions on individual messages.
+
+To explicitly acknowledge a message as delivered, call the `ack()` method on the message.
+
+
+
+```ts
+export default {
+ async queue(batch, env, ctx): Promise {
+ for (const msg of batch.messages) {
+ // TODO: do something with the message
+ // Explicitly acknowledge the message as delivered
+ msg.ack();
+ }
+ },
+} satisfies ExportedHandler;
+```
+
+
+```python
+from workers import WorkerEntrypoint
+
+class Default(WorkerEntrypoint):
+ async def queue(self, batch):
+ for msg in batch.messages:
+ # TODO: do something with the message
+ # Explicitly acknowledge the message as delivered
+ msg.ack()
+```
+
+
+
+You can also call `retry()` to explicitly force a message to be redelivered in a subsequent batch. This is referred to as "negative acknowledgement". This can be particularly useful when you want to process the rest of the messages in that batch without throwing an error that would force the entire batch to be redelivered.
+
+
+
+```ts
+export default {
+ async queue(batch, env, ctx): Promise {
+ for (const msg of batch.messages) {
+ // TODO: do something with the message that fails
+ msg.retry();
+ }
+ },
+} satisfies ExportedHandler;
+```
+
+
+```python
+from workers import WorkerEntrypoint
+
+class Default(WorkerEntrypoint):
+ async def queue(self, batch):
+ for msg in batch.messages:
+ # TODO: do something with the message that fails
+ msg.retry()
+```
+
+
+
+You can also acknowledge or negatively acknowledge messages at a batch level with `ackAll()` and `retryAll()`. Calling `ackAll()` on the batch of messages (`MessageBatch`) delivered to your consumer Worker has the same behaviour as a consumer Worker that successfully returns (does not throw an error).
+
+Note that calls to `ack()`, `retry()` and their `ackAll()` / `retryAll()` equivalents follow the below precedence rules:
+
+- If you call `ack()` on a message, subsequent calls to `ack()` or `retry()` are silently ignored.
+- If you call `retry()` on a message and then call `ack()`: the `ack()` is ignored. The first method call wins in all cases.
+- If you call either `ack()` or `retry()` on a single message, and then either/any of `ackAll()` or `retryAll()` on the batch, the call on the single message takes precedence. That is, the batch-level call does not apply to that message (or messages, if multiple calls were made).
+
+## Delivery failure
+
+When a message is failed to be delivered, the default behaviour is to retry delivery three times before marking the delivery as failed. You can set `max_retries` (defaults to 3) when configuring your consumer, but in most cases we recommend leaving this as the default.
+
+Messages that reach the configured maximum retries will be deleted from the queue, or if a [dead-letter queue](/queues/configuration/dead-letter-queues/) (DLQ) is configured, written to the DLQ instead.
+
+:::note
+
+Each retry counts as an additional read operation per [Queues pricing](/queues/platform/pricing/).
+
+:::
+
+When a single message within a batch fails to be delivered, the entire batch is retried, unless you have [explicitly acknowledged](#explicit-acknowledgement-and-retries) a message (or messages) within that batch. For example, if a batch of 10 messages is delivered, but the 8th message fails to be delivered, all 10 messages will be retried and thus redelivered to your consumer in full.
+
+:::caution[Retried messages and consumer concurrency]
+
+Retrying messages with `retry()` or calling `retryAll()` on a batch will **not** cause the consumer to autoscale down if consumer concurrency is enabled. Refer to [Consumer concurrency](/queues/configuration/consumer-concurrency/) to learn more.
+
+:::
+
+## Delay messages
+
+When publishing messages to a queue, or when [marking a message or batch for retry](#explicit-acknowledgement-and-retries), you can choose to delay messages from being processed for a period of time.
+
+Delaying messages allows you to defer tasks until later, and/or respond to backpressure when consuming from a queue. For example, if an upstream API you are calling to returns a `HTTP 429: Too Many Requests`, you can delay messages to slow down how quickly you are consuming them before they are re-processed.
+
+Messages can be delayed by up to 24 hours.
+
+:::note
+
+Configuring delivery and retry delays via the `wrangler` CLI or when [developing locally](/queues/configuration/local-development/) requires `wrangler` version `3.38.0` or greater. Use `npx wrangler@latest` to always use the latest version of `wrangler`.
+
+:::
+
+### Delay on send
+
+To delay a message or batch of messages when sending to a queue, you can provide a `delaySeconds` parameter when sending a message.
+
+
+
+```ts
+// Delay a singular message by 600 seconds (10 minutes)
+await env.YOUR_QUEUE.send(message, { delaySeconds: 600 });
+
+// Delay a batch of messages by 300 seconds (5 minutes)
+await env.YOUR_QUEUE.sendBatch(messages, { delaySeconds: 300 });
+
+// Do not delay this message.
+// If there is a global delay configured on the queue, ignore it.
+await env.YOUR_QUEUE.sendBatch(messages, { delaySeconds: 0 });
+```
+
+
+```python
+# Delay a singular message by 600 seconds (10 minutes)
+await env.YOUR_QUEUE.send(message, delaySeconds=600)
+
+# Delay a batch of messages by 300 seconds (5 minutes)
+await env.YOUR_QUEUE.sendBatch(messages, delaySeconds=300)
+
+# Do not delay this message.
+# If there is a global delay configured on the queue, ignore it.
+await env.YOUR_QUEUE.sendBatch(messages, delaySeconds=0)
+```
+
+
+
+You can also configure a default, global delay on a per-queue basis by passing `--delivery-delay-secs` when creating a queue via the `wrangler` CLI:
+
+```sh
+# Delay all messages by 5 minutes as a default
+npx wrangler queues create $QUEUE-NAME --delivery-delay-secs=300
+```
+
+### Delay on retry
+
+When [consuming messages from a queue](/queues/reference/how-queues-works/#consumers), you can choose to [explicitly mark messages to be retried](#explicit-acknowledgement-and-retries). Messages can be retried and delayed individually, or as an entire batch.
+
+To delay an individual message within a batch:
+
+
+
+```ts
+export default {
+ async queue(batch, env, ctx): Promise {
+ for (const msg of batch.messages) {
+ // Mark for retry and delay a singular message
+ // by 3600 seconds (1 hour)
+ msg.retry({ delaySeconds: 3600 });
+ }
+ },
+} satisfies ExportedHandler;
+```
+
+
+```python
+from workers import WorkerEntrypoint
+
+class Default(WorkerEntrypoint):
+ async def queue(self, batch):
+ for msg in batch.messages:
+ # Mark for retry and delay a singular message
+ # by 3600 seconds (1 hour)
+ msg.retry(delaySeconds=3600)
+```
+
+
+
+To delay a batch of messages:
+
+
+
+```ts
+export default {
+ async queue(batch, env, ctx): Promise {
+ // Mark for retry and delay a batch of messages
+ // by 600 seconds (10 minutes)
+ batch.retryAll({ delaySeconds: 600 });
+ },
+} satisfies ExportedHandler;
+```
+
+
+```python
+from workers import WorkerEntrypoint
+
+class Default(WorkerEntrypoint):
+ async def queue(self, batch):
+ # Mark for retry and delay a batch of messages
+ # by 600 seconds (10 minutes)
+ batch.retryAll(delaySeconds=600)
+```
+
+
+
+You can also choose to set a default retry delay to any messages that are retried due to either implicit failure or when calling `retry()` explicitly. This is set at the consumer level, and is supported in both push-based (Worker) and pull-based (HTTP) consumers.
+
+Delays can be configured via the `wrangler` CLI:
+
+```sh
+# Push-based consumers
+# Delay any messages that are retried by 60 seconds (1 minute) by default.
+npx wrangler@latest queues consumer worker add $QUEUE-NAME $WORKER_SCRIPT_NAME --retry-delay-secs=60
+
+# Pull-based consumers
+# Delay any messages that are retried by 60 seconds (1 minute) by default.
+npx wrangler@latest queues consumer http add $QUEUE-NAME --retry-delay-secs=60
+```
+
+Delays can also be configured in the [Wrangler configuration file](/workers/wrangler/configuration/#queues) with the `delivery_delay` setting for producers (when sending) and/or the `retry_delay` (when retrying) per-consumer:
+
+
+
+```jsonc
+{
+ "queues": {
+ "producers": [
+ {
+ "binding": "",
+ "queue": "",
+ "delivery_delay": 60 // delay every message delivery by 1 minute
+ }
+ ],
+ "consumers": [
+ {
+ "queue": "my-queue",
+ "retry_delay": 300 // delay any retried message by 5 minutes before re-attempting delivery
+ }
+ ]
+ }
+}
+```
+
+
+
+If you use both the `wrangler` CLI and the [Wrangler configuration file](/workers/wrangler/configuration/) to change the settings associated with a queue or a queue consumer, the most recent configuration change will take effect.
+
+Refer to the [Queues REST API documentation](/api/resources/queues/subresources/consumers/methods/get/) to learn how to configure message delays and retry delays programmatically.
+
+### Message delay precedence
+
+Messages can be delayed by default at the queue level, or per-message (or batch).
+
+- Per-message/batch delay settings take precedence over queue-level settings.
+- Setting `delaySeconds: 0` on a message when sending or retrying will ignore any queue-level delays and cause the message to be delivered in the next batch.
+- A message sent or retried with `delaySeconds: ` to a queue with a shorter default delay will still respect the message-level setting.
+
+### Apply a backoff algorithm
+
+You can apply a backoff algorithm to increasingly delay messages based on the current number of attempts to deliver the message.
+
+Each message delivered to a consumer includes an `attempts` property that tracks the number of delivery attempts made.
+
+For example, to generate an [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) for a message, you can create a helper function that calculates this for you:
+
+
+
+```ts
+function calculateExponentialBackoff(
+ attempts: number,
+ baseDelaySeconds: number,
+): number {
+ return baseDelaySeconds ** attempts;
+}
+```
+
+
+```python
+def calculate_exponential_backoff(attempts, base_delay_seconds):
+ return base_delay_seconds ** attempts
+```
+
+
+
+In your consumer, you then pass the value of `msg.attempts` and your desired delay factor as the argument to `delaySeconds` when calling `retry()` on an individual message:
+
+
+
+```ts
+const BASE_DELAY_SECONDS = 30;
+
+export default {
+ async queue(batch, env, ctx): Promise {
+ for (const msg of batch.messages) {
+ // Mark for retry with exponential backoff
+ msg.retry({
+ delaySeconds: calculateExponentialBackoff(
+ msg.attempts,
+ BASE_DELAY_SECONDS,
+ ),
+ });
+ }
+ },
+} satisfies ExportedHandler;
+```
+
+
+```python
+from workers import WorkerEntrypoint
+
+BASE_DELAY_SECONDS = 30
+
+class Default(WorkerEntrypoint):
+ async def queue(self, batch):
+ for msg in batch.messages:
+ # Mark for retry and delay a singular message
+ # by 3600 seconds (1 hour)
+ msg.retry(
+ delaySeconds=calculate_exponential_backoff(
+ msg.attempts,
+ BASE_DELAY_SECONDS,
+ )
+ )
+```
+
+
+
+## Related
+
+- Review the [JavaScript API](/queues/configuration/javascript-apis/) documentation for Queues.
+- Learn more about [How Queues Works](/queues/reference/how-queues-works/).
+- Understand the [metrics available](/queues/observability/metrics/) for your queues, including backlog and delayed message counts.
\ No newline at end of file
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/configuration/consumer-concurrency.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/configuration/consumer-concurrency.md
new file mode 100644
index 0000000..072aa33
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/configuration/consumer-concurrency.md
@@ -0,0 +1,140 @@
+---
+title: Consumer concurrency
+description: Automatically scale out Queues consumer Workers horizontally to process messages faster.
+pcx_content_type: concept
+sidebar:
+ order: 5
+products:
+ - queues
+---
+
+import { WranglerConfig, DashButton } from "~/components";
+
+Consumer concurrency allows a [consumer Worker](/queues/reference/how-queues-works/#consumers) processing messages from a queue to automatically scale out horizontally to keep up with the rate that messages are being written to a queue.
+
+In many systems, the rate at which you write messages to a queue can easily exceed the rate at which a single consumer can read and process those same messages. This is often because your consumer might be parsing message contents, writing to storage or a database, or making third-party (upstream) API calls.
+
+Note that queue producers are always scalable, up to the [maximum supported messages-per-second](/queues/platform/limits/) (per queue) limit.
+
+## Enable concurrency
+
+By default, all queues have concurrency enabled. Queue consumers will automatically scale up [to the maximum concurrent invocations](/queues/platform/limits/) as needed to manage a queue's backlog and/or error rates.
+
+## How concurrency works
+
+After processing a batch of messages, Queues will check to see if the number of concurrent consumers should be adjusted. The number of concurrent consumers invoked for a queue will autoscale based on several factors, including:
+
+- The number of messages in the queue (backlog) and its rate of growth.
+- The ratio of failed (versus successful) invocations. A failed invocation is when your `queue()` handler returns an uncaught exception instead of `void` (nothing).
+- The value of `max_concurrency` set for that consumer.
+
+Where possible, Queues will optimize for keeping your backlog from growing exponentially, in order to minimize scenarios where the backlog of messages in a queue grows to the point that they would reach the [message retention limit](/queues/platform/limits/) before being processed.
+
+:::note[Consumer concurrency and retried messages]
+
+[Retrying messages with `retry()`](/queues/configuration/batching-retries/#explicit-acknowledgement-and-retries) or calling `retryAll()` on a batch will **not** count as a failed invocation.
+
+:::
+
+### Example
+
+If you are writing 100 messages/second to a queue with a single concurrent consumer that takes 5 seconds to process a batch of 100 messages, the number of messages in-flight will continue to grow at a rate faster than your consumer can keep up.
+
+In this scenario, Queues will notice the growing backlog and will scale the number of concurrent consumer Workers invocations up to a steady-state of (approximately) five (5) until the rate of incoming messages decreases, the consumer processes messages faster, or the consumer begins to generate errors.
+
+### Why are my consumers not autoscaling?
+
+If your consumers are not autoscaling, there are a few likely causes:
+
+- `max_concurrency` has been set to 1.
+- Your consumer Worker is returning errors rather than processing messages. Inspect your consumer to make sure it is healthy.
+- A batch of messages is being processed. Queues checks if it should autoscale consumers only after processing an entire batch of messages, so it will not autoscale while a batch is being processed. Consider reducing batch sizes or refactoring your consumer to process messages faster.
+
+## Limit concurrency
+
+:::caution[Recommended concurrency setting]
+
+Cloudflare recommends leaving the maximum concurrency unset, which will allow your queue consumer to scale up as much as possible. Setting a fixed number means that your consumer will only ever scale up to that maximum, even as Queues increases the maximum supported invocations over time.
+
+:::
+
+If you have a workflow that is limited by an upstream API and/or system, you may prefer for your backlog to grow, trading off increased overall latency in order to avoid overwhelming an upstream system.
+
+You can configure the concurrency of your consumer Worker in two ways:
+
+1. Set concurrency settings in the Cloudflare dashboard
+2. Set concurrency settings via the [Wrangler configuration file](/workers/wrangler/configuration/)
+
+### Set concurrency settings in the Cloudflare dashboard
+
+To configure the concurrency settings for your consumer Worker from the dashboard:
+
+1. In the Cloudflare dashboard, go to the **Queues** page.
+
+
+
+2. Select your queue > **Settings**.
+3. Select **Edit Consumer** under Consumer details.
+4. Set **Maximum consumer invocations** to a value between `1` and `250`. This value represents the maximum number of concurrent consumer invocations available to your queue.
+
+To remove a fixed maximum value, select **auto (recommended)**.
+
+Note that if you are writing messages to a queue faster than you can process them, messages may eventually reach the [maximum retention period](/queues/platform/limits/) set for that queue. Individual messages that reach that limit will expire from the queue and be deleted.
+
+### Set concurrency settings in the [Wrangler configuration file](/workers/wrangler/configuration/)
+
+:::note
+
+Ensure you are using the latest version of [wrangler](/workers/wrangler/install-and-update/). Support for configuring the maximum concurrency of a queue consumer is only supported in wrangler [`2.13.0`](https://github.com/cloudflare/workers-sdk/releases/tag/wrangler%402.13.0) or greater.
+
+:::
+
+To set a fixed maximum number of concurrent consumer invocations for a given queue, configure a `max_concurrency` in your Wrangler file:
+
+
+
+```jsonc
+{
+ "queues": {
+ "consumers": [
+ {
+ "queue": "my-queue",
+ "max_concurrency": 1
+ }
+ ]
+ }
+}
+```
+
+
+
+To remove the limit, remove the `max_concurrency` setting from the `[[queues.consumers]]` configuration for a given queue and call `npx wrangler deploy` to push your configuration update.
+
+ {/* Not yet available but will be very soon
+
+ ### wrangler CLI
+
+ ```sh
+ # where `N` is a positive integer between 1 and 250
+ wrangler queues consumer update --max-concurrency=N
+ ```
+
+ To remove the limit and allow Queues to scale your consumer to the maximum number of invocations, call `consumer update` without any flags:
+
+ ```sh
+ # Call update without passing a flag to allow concurrency to scale to the maximum
+ wrangler queues consumer update
+ ``` */}
+
+## Billing
+
+When multiple consumer Workers are invoked, each Worker invocation incurs [CPU time costs](/workers/platform/pricing/#workers).
+
+- If you intend to process all messages written to a queue, _the effective overall cost is the same_, even with concurrency enabled.
+- Enabling concurrency simply brings those costs forward, and can help prevent messages from reaching the [message retention limit](/queues/platform/limits/).
+
+Billing for consumers follows the [Workers standard usage model](/workers/platform/pricing/#example-pricing) meaning a developer is billed for the request and for CPU time used in the request.
+
+### Example
+
+A consumer Worker that takes 2 seconds to process a batch of messages will incur the same overall costs to process 50 million (50,000,000) messages, whether it does so concurrently (faster) or individually (slower).
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/examples/use-queues-with-durable-objects.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/examples/use-queues-with-durable-objects.md
new file mode 100644
index 0000000..481b9a1
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/examples/use-queues-with-durable-objects.md
@@ -0,0 +1,117 @@
+---
+title: Use Queues from Durable Objects
+summary: Publish to a queue from within a Durable Object.
+pcx_content_type: example
+sidebar:
+ order: 20
+head:
+ - tag: title
+ content: Queues - Use Queues and Durable Objects
+description: Publish to a queue from within a Durable Object.
+reviewed: 2023-09-13
+products:
+ - queues
+ - durable-objects
+---
+
+import { WranglerConfig } from "~/components";
+
+The following example shows you how to write a Worker script to publish to [Cloudflare Queues](/queues/) from within a [Durable Object](/durable-objects/).
+
+Prerequisites:
+
+- A [queue created](/queues/get-started/#3-create-a-queue) via the Cloudflare dashboard or the [wrangler CLI](/workers/wrangler/install-and-update/).
+- A [configured **producer** binding](/queues/configuration/configure-queues/#producer-worker-configuration) in the Cloudflare dashboard or Wrangler file.
+- A [Durable Object namespace binding](/workers/wrangler/configuration/#durable-objects).
+
+Configure your Wrangler file as follows:
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "my-worker",
+ "queues": {
+ "producers": [
+ {
+ "queue": "my-queue",
+ "binding": "YOUR_QUEUE"
+ }
+ ]
+ },
+ "durable_objects": {
+ "bindings": [
+ {
+ "name": "YOUR_DO_CLASS",
+ "class_name": "YourDurableObject"
+ }
+ ]
+ },
+ "migrations": [
+ {
+ "tag": "v1",
+ "new_sqlite_classes": [
+ "YourDurableObject"
+ ]
+ }
+ ]
+}
+```
+
+
+
+The following Worker script:
+
+1. Creates a Durable Object stub, or retrieves an existing one based on a userId.
+2. Passes request data to the Durable Object.
+3. Publishes to a queue from within the Durable Object.
+
+Extending the `DurableObject` base class makes your `Env` available on `this.env` and the Durable Object state available on `this.ctx` within the [`fetch()` handler](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) in the Durable Object.
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+interface Env {
+ YOUR_QUEUE: Queue;
+ YOUR_DO_CLASS: DurableObjectNamespace;
+}
+
+export default {
+ async fetch(req, env, ctx): Promise {
+ // Assume each Durable Object is mapped to a userId in a query parameter
+ // In a production application, this will be a userId defined by your application
+ // that you validate (and/or authenticate) first.
+ const url = new URL(req.url);
+ const userIdParam = url.searchParams.get("userId");
+
+ if (userIdParam) {
+ // Get a stub that allows you to call that Durable Object
+ const durableObjectStub = env.YOUR_DO_CLASS.getByName(userIdParam);
+
+ // Pass the request to that Durable Object and await the response
+ // This invokes the constructor once on your Durable Object class (defined further down)
+ // on the first initialization, and the fetch method on each request.
+ // We pass the original Request to the Durable Object's fetch method
+ const response = await durableObjectStub.fetch(req);
+
+ // This would return "wrote to queue", but you could return any response.
+ return response;
+ }
+ return new Response("userId must be provided", { status: 400 });
+ },
+} satisfies ExportedHandler;
+
+export class YourDurableObject extends DurableObject {
+ async fetch(req: Request): Promise {
+ // Error handling elided for brevity.
+ // Publish to your queue
+ await this.env.YOUR_QUEUE.send({
+ id: this.ctx.id.toString(), // Write the ID of the Durable Object to your queue
+ // Write any other properties to your queue
+ });
+
+ return new Response("wrote to queue");
+ }
+}
+```
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/get-started.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/get-started.md
new file mode 100644
index 0000000..22f0b7d
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/get-started.md
@@ -0,0 +1,265 @@
+---
+title: Getting started
+description: Create your first Cloudflare Queue, a producer Worker, and a consumer Worker.
+pcx_content_type: get-started
+sidebar:
+ order: 2
+head:
+ - tag: title
+ content: Getting started
+products:
+ - queues
+ - workers
+---
+
+import { Render, PackageManagers, WranglerConfig } from "~/components";
+
+Cloudflare Queues is a flexible messaging queue that allows you to queue messages for asynchronous processing. By following this guide, you will create your first queue, a Worker to publish messages to that queue, and a consumer Worker to consume messages from that queue.
+
+## Prerequisites
+
+To use Queues, you will need:
+
+
+
+## 1. Create a Worker project
+
+You will access your queue from a Worker, the producer Worker. You must create at least one producer Worker to publish messages onto your queue. If you are using [R2 Bucket Event Notifications](/r2/buckets/event-notifications/), then you do not need a producer Worker.
+
+To create a producer Worker, run:
+
+
+
+
+
+This will create a new directory, which will include both a `src/index.ts` Worker script, and a [`wrangler.jsonc`](/workers/wrangler/configuration/) configuration file. After you create your Worker, you will create a Queue to access.
+
+Move into the newly created directory:
+
+```sh
+cd producer-worker
+```
+
+## 2. Create a queue
+
+To use queues, you need to create at least one queue to publish messages to and consume messages from.
+
+To create a queue, run:
+
+```sh
+npx wrangler queues create
+```
+
+Choose a name that is descriptive and relates to the types of messages you intend to use this queue for. Descriptive queue names look like: `debug-logs`, `user-clickstream-data`, or `password-reset-prod`.
+
+Queue names must be 1 to 63 characters long. Queue names cannot contain special characters outside dashes (`-`), and must start and end with a letter or number.
+
+You cannot change your queue name after you have set it. After you create your queue, you will set up your producer Worker to access it.
+
+## 3. Set up your producer Worker
+
+To expose your queue to the code inside your Worker, you need to connect your queue to your Worker by creating a binding. [Bindings](/workers/runtime-apis/bindings/) allow your Worker to access resources, such as Queues, on the Cloudflare developer platform.
+
+To create a binding, open your newly generated `wrangler.jsonc` file and add the following:
+
+
+
+```jsonc
+{
+ "queues": {
+ "producers": [
+ {
+ "queue": "MY-QUEUE-NAME",
+ "binding": "MY_QUEUE"
+ }
+ ]
+ }
+}
+```
+
+
+
+Replace `MY-QUEUE-NAME` with the name of the queue you created in [step 2](/queues/get-started/#2-create-a-queue). Next, replace `MY_QUEUE` with the name you want for your `binding`. The binding must be a valid JavaScript variable name. This is the variable you will use to reference this queue in your Worker.
+
+### Write your producer Worker
+
+You will now configure your producer Worker to create messages to publish to your queue. Your producer Worker will:
+
+1. Take a request it receives from the browser.
+2. Transform the request to JSON format.
+3. Write the request directly to your queue.
+
+In your Worker project directory, open the `src` folder and add the following to your `index.ts` file:
+
+```ts null {8}
+export default {
+ async fetch(request, env, ctx): Promise {
+ const log = {
+ url: request.url,
+ method: request.method,
+ headers: Object.fromEntries(request.headers),
+ };
+ await env..send(log);
+ return new Response("Success!");
+ },
+} satisfies ExportedHandler;
+```
+
+Replace `MY_QUEUE` with the name you have set for your binding from your `wrangler.jsonc` file.
+
+Also add the queue to `Env` interface in `index.ts`.
+
+```ts null {2}
+export interface Env {
+ : Queue;
+}
+```
+
+If this write fails, your Worker will return an error (raise an exception). If this write works, it will return `Success` back with a HTTP `200` status code to the browser.
+
+In a production application, you would likely use a [`try...catch`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) statement to catch the exception and handle it directly (for example, return a custom error or even retry).
+
+### Publish your producer Worker
+
+With your Wrangler file and `index.ts` file configured, you are ready to publish your producer Worker. To publish your producer Worker, run:
+
+```sh
+npx wrangler deploy
+```
+
+You should see output that resembles the below, with a `*.workers.dev` URL by default.
+
+```
+Uploaded (0.76 sec)
+Published (0.29 sec)
+ https://..workers.dev
+```
+
+Copy your `*.workers.dev` subdomain and paste it into a new browser tab. Refresh the page a few times to start publishing requests to your queue. Your browser should return the `Success` response after writing the request to the queue each time.
+
+You have built a queue and a producer Worker to publish messages to the queue. You will now create a consumer Worker to consume the messages published to your queue. Without a consumer Worker, the messages will stay on the queue until they expire, which defaults to four (4) days.
+
+## 4. Create your consumer Worker
+
+A consumer Worker receives messages from your queue. When the consumer Worker receives your queue's messages, it can write them to another source, such as a logging console or storage objects.
+
+In this guide, you will create a consumer Worker and use it to log and inspect the messages with [`wrangler tail`](/workers/wrangler/commands/general/#tail). You will create your consumer Worker in the same Worker project that you created your producer Worker.
+
+:::note
+
+Queues also supports [pull-based consumers](/queues/configuration/pull-consumers/), which allows any HTTP-based client to consume messages from a queue. This guide creates a push-based consumer using Cloudflare Workers.
+
+:::
+
+To create a consumer Worker, open your `index.ts` file and add the following `queue` handler to your existing `fetch` handler:
+
+```ts null {11}
+export default {
+ async fetch(request, env, ctx): Promise {
+ const log = {
+ url: request.url,
+ method: request.method,
+ headers: Object.fromEntries(request.headers),
+ };
+ await env..send(log);
+ return new Response("Success!");
+ },
+ async queue(batch, env, ctx): Promise {
+ for (const message of batch.messages) {
+ console.log("consumed from our queue:", JSON.stringify(message.body));
+ }
+ },
+} satisfies ExportedHandler;
+```
+
+Replace `MY_QUEUE` with the name you have set for your binding from your `wrangler.jsonc` file.
+
+Every time messages are published to the queue, your consumer Worker's `queue` handler (`async queue`) is called and it is passed one or more messages.
+
+In this example, your consumer Worker transforms the queue's JSON formatted message into a string and logs that output. In a real world application, your consumer Worker can be configured to write messages to object storage (such as [R2](/r2/)), write to a database (like [D1](/d1/)), further process messages before calling an external API (such as an [email API](/workers/tutorials/)) or a data warehouse with your legacy cloud provider.
+
+When performing asynchronous tasks from within your consumer handler, use `waitUntil()` to ensure the response of the function is handled. Other asynchronous methods are not supported within the scope of this method.
+
+### Connect the consumer Worker to your queue
+
+After you have configured your consumer Worker, you are ready to connect it to your queue.
+
+Each queue can only have one consumer Worker connected to it. If you try to connect multiple consumers to the same queue, you will encounter an error when attempting to publish that Worker.
+
+To connect your queue to your consumer Worker, open your Wrangler file and add this to the bottom:
+
+
+
+```jsonc
+{
+ "queues": {
+ "consumers": [
+ {
+ "queue": "",
+ // Required: this should match the name of the queue you created in step 3.
+ // If you misspell the name, you will receive an error when attempting to publish your Worker.
+ "max_batch_size": 10, // optional: defaults to 10
+ "max_batch_timeout": 5 // optional: defaults to 5 seconds
+ }
+ ]
+ }
+}
+```
+
+
+
+Replace `MY-QUEUE-NAME` with the queue you created in [step 2](/queues/get-started/#2-create-a-queue).
+
+In your consumer Worker, you are using queues to auto batch messages using the `max_batch_size` option and the `max_batch_timeout` option. The consumer Worker will receive messages in batches of `10` or every `5` seconds, whichever happens first.
+
+`max_batch_size` (defaults to 10) helps to reduce the amount of times your consumer Worker needs to be called. Instead of being called for every message, it will only be called after 10 messages have entered the queue.
+
+`max_batch_timeout` (defaults to 5 seconds) helps to reduce wait time. If the producer Worker is not sending up to 10 messages to the queue for the consumer Worker to be called, the consumer Worker will be called every 5 seconds to receive messages that are waiting in the queue.
+
+### Publish your consumer Worker
+
+With your Wrangler file and `index.ts` file configured, publish your consumer Worker by running:
+
+```sh
+npx wrangler deploy
+```
+
+## 5. Read messages from your queue
+
+After you set up consumer Worker, you can read messages from the queue.
+
+Run `wrangler tail` to start waiting for our consumer to log the messages it receives:
+
+```sh
+npx wrangler tail
+```
+
+With `wrangler tail` running, open the Worker URL you opened in [step 3](/queues/get-started/#3-set-up-your-producer-worker).
+
+You should receive a `Success` message in your browser window.
+
+If you receive a `Success` message, refresh the URL a few times to generate messages and push them onto the queue.
+
+With `wrangler tail` running, your consumer Worker will start logging the requests generated by refreshing.
+
+If you refresh less than 10 times, it may take a few seconds for the messages to appear because batch timeout is configured for 10 seconds. After 10 seconds, messages should arrive in your terminal.
+
+If you get errors when you refresh, check that the queue name you created in [step 2](/queues/get-started/#2-create-a-queue) and the queue you referenced in your Wrangler file is the same. You should ensure that your producer Worker is returning `Success` and is not returning an error.
+
+By completing this guide, you have now created a queue, a producer Worker that publishes messages to that queue, and a consumer Worker that consumes those messages from it.
+
+## Related resources
+
+- Learn more about [Cloudflare Workers](/workers/) and the applications you can build on Cloudflare.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/index.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/index.md
new file mode 100644
index 0000000..1d14829
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/index.md
@@ -0,0 +1,112 @@
+---
+title: Cloudflare Queues
+description: Send and receive messages with guaranteed delivery using Cloudflare Queues integrated with Workers.
+pcx_content_type: overview
+sidebar:
+ order: 1
+head:
+ - tag: title
+ content: Overview
+products:
+ - queues
+ - workers
+---
+
+import { CardGrid, Description, Feature, LinkTitleCard, Plan, RelatedProduct, LinkButton } from "~/components"
+
+
+
+
+Send and receive messages with guaranteed delivery and no charges for egress bandwidth.
+
+
+
+
+
+
+Cloudflare Queues integrate with [Cloudflare Workers](/workers/) and enable you to build applications that can [guarantee delivery](/queues/reference/delivery-guarantees/), [offload work from a request](/queues/reference/how-queues-works/), [send data from Worker to Worker](/queues/configuration/configure-queues/), and [buffer or batch data](/queues/configuration/batching-retries/).
+
+Get started
+
+***
+
+## Features
+
+
+
+Cloudflare Queues allows you to batch, retry and delay messages.
+
+
+
+
+
+
+Redirect your messages when a delivery failure occurs.
+
+
+
+
+
+
+Configure pull-based consumers to pull from a queue over HTTP from infrastructure outside of Cloudflare Workers.
+
+
+
+
+***
+
+## Related products
+
+
+
+Cloudflare R2 Storage allows developers to store large amounts of unstructured data without the costly egress bandwidth fees associated with typical cloud storage services.
+
+
+
+
+
+
+Cloudflare Workers allows developers to build serverless applications and deploy instantly across the globe for exceptional performance, reliability, and scale.
+
+
+
+
+***
+
+## More resources
+
+
+
+
+Learn about pricing.
+
+
+
+Learn about Queues limits.
+
+
+
+Try Cloudflare Queues which can run on your local machine.
+
+
+
+Follow @CloudflareDev on Twitter to learn about product announcements, and what is new in Cloudflare Workers.
+
+
+
+Connect with the Workers community on Discord to ask questions, show what you are building, and discuss the platform with other developers.
+
+
+
+Learn how to configure Cloudflare Queues using Wrangler.
+
+
+
+Learn how to use JavaScript APIs to send and receive messages to a Cloudflare Queue.
+
+
+
+Learn how to configure and manage event subscriptions for your queues.
+
+
+
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/reference/delivery-guarantees.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/reference/delivery-guarantees.md
new file mode 100644
index 0000000..1f78e32
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/reference/delivery-guarantees.md
@@ -0,0 +1,19 @@
+---
+title: Delivery guarantees
+description: Cloudflare Queues provides at-least-once message delivery by default.
+pcx_content_type: concept
+sidebar:
+ order: 2
+products:
+ - queues
+---
+
+Delivery guarantees define how strongly a messaging system enforces the delivery of messages it processes.
+
+As you make stronger guarantees about message delivery, the system needs to perform more checks and acknowledgments to ensure that messages are delivered, or maintain state to ensure a message is only delivered the specified number of times. This increases the latency of the system and reduces the overall throughput of the system. Each message may require an additional internal acknowledgements, and an equivalent number of additional roundtrips, before it can be considered delivered.
+
+* **Queues provides *at least once* delivery by default** in order to optimize for reliability.
+* This means that messages are guaranteed to be delivered at least once, and in rare occasions, may be delivered more than once.
+* For the majority of applications, this is the right balance between not losing any messages and minimizing end-to-end latency, as exactly once delivery incurs additional overheads in any messaging system.
+
+In cases where processing the same message more than once would introduce unintended behaviour, generating a unique ID when writing the message to the queue and using that as the primary key on database inserts and/or as an idempotency key to de-duplicate the message after processing. For example, using this idempotency key as the ID in an upstream email API or payment API will allow those services to reject the duplicate on your behalf, without you having to carry additional state in your application.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/reference/how-queues-works.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/reference/how-queues-works.md
new file mode 100644
index 0000000..efc6499
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/queues/reference/how-queues-works.md
@@ -0,0 +1,240 @@
+---
+title: How Queues Works
+description: Learn about Queues architecture including producers, consumers, and message lifecycle.
+pcx_content_type: concept
+sidebar:
+ order: 1
+products:
+ - queues
+---
+
+import { WranglerConfig } from "~/components";
+
+Cloudflare Queues is a flexible messaging queue that allows you to queue messages for asynchronous processing. Message queues are great at decoupling components of applications, like the checkout and order fulfillment services for an e-commerce site. Decoupled services are easier to reason about, deploy, and implement, allowing you to ship features that delight your customers without worrying about synchronizing complex deployments. Queues also allow you to batch and buffer calls to downstream services and APIs.
+
+There are four major concepts to understand with Queues:
+
+1. [Queues](#what-is-a-queue)
+2. [Producers](#producers)
+3. [Consumers](#consumers)
+4. [Messages](#messages)
+
+## What is a queue
+
+A queue is a buffer or list that automatically scales as messages are written to it, and allows a consumer Worker to pull messages from that same queue.
+
+Queues are designed to be reliable, and messages written to a queue should never be lost once the write succeeds. Similarly, messages are not deleted from a queue until the [consumer](#consumers) has successfully consumed the message.
+
+Queues does not guarantee that messages will be delivered to a consumer in the same order in which they are published.
+
+Developers can create multiple queues. Creating multiple queues can be useful to:
+
+* Separate different use-cases and processing requirements: for example, a logging queue vs. a password reset queue.
+* Horizontally scale your overall throughput (messages per second) by using multiple queues to scale out.
+* Configure different batching strategies for each consumer connected to a queue.
+
+For most applications, a single producer Worker per queue, with a single consumer Worker consuming messages from that queue allows you to logically separate the processing for each of your queues.
+
+## Producers
+
+A producer is the term for a client that is publishing or producing messages on to a queue. A producer is configured by [binding](/workers/runtime-apis/bindings/) a queue to a Worker and writing messages to the queue by calling that binding.
+
+For example, if we bound a queue named `my-first-queue` to a binding of `MY_FIRST_QUEUE`, messages can be written to the queue by calling `send()` on the binding:
+
+```ts
+interface Env {
+ readonly MY_FIRST_QUEUE: Queue;
+}
+
+export default {
+ async fetch(req, env, ctx): Promise {
+ const message = {
+ url: req.url,
+ method: req.method,
+ headers: Object.fromEntries(req.headers),
+ };
+
+ await env.MY_FIRST_QUEUE.send(message); // This will throw an exception if the send fails for any reason
+ return new Response("Sent!");
+ },
+} satisfies ExportedHandler;
+```
+
+:::note
+
+
+You can also use [`context.waitUntil()`](/workers/runtime-apis/context/#waituntil) to send the message without blocking the response.
+
+Note that because `waitUntil()` is non-blocking, any errors raised from the `send()` or `sendBatch()` methods on a queue will be implicitly ignored.
+
+
+:::
+
+A queue can have multiple producer Workers. For example, you may have multiple producer Workers writing events or logs to a shared queue based on incoming HTTP requests from users. There is no limit to the total number of producer Workers that can write to a single queue.
+
+Additionally, multiple queues can be bound to a single Worker. That single Worker can decide which queue to write to (or write to multiple) based on any logic you define in your code.
+
+### Content types
+
+Messages published to a queue can be published in different formats, depending on what interoperability is needed with your consumer. The default content type is `json`, which means that any object that can be passed to `JSON.stringify()` will be accepted.
+
+To explicitly set the content type or specify an alternative content type, pass the `contentType` option to the `send()` method of your queue:
+
+```ts
+interface Env {
+ readonly MY_FIRST_QUEUE: Queue;
+}
+
+export default {
+ async fetch(req, env, ctx): Promise {
+ const message = {
+ url: req.url,
+ method: req.method,
+ headers: Object.fromEntries(req.headers),
+ };
+ try {
+ await env.MY_FIRST_QUEUE.send(message, { contentType: "json" }); // "json" is the default
+ return new Response("Sent!");
+ } catch (e) {
+ // Catch cases where send fails, including due to a mismatched content type
+ const msg = e instanceof Error ? e.message : "Unknown error";
+ return Response.json({ error: msg }, { status: 500 });
+ }
+ },
+} satisfies ExportedHandler;
+```
+
+To only accept simple strings when writing to a queue, set `{ contentType: "text" }` instead:
+
+```ts
+interface Env {
+ readonly MY_FIRST_QUEUE: Queue;
+}
+
+export default {
+ async fetch(req, env, ctx): Promise {
+ try {
+ // This will throw an exception (error) if you pass a non-string to the queue,
+ // such as a native JavaScript object or ArrayBuffer.
+ await env.MY_FIRST_QUEUE.send("hello there", { contentType: "text" }); // explicitly set 'text'
+ return new Response("Sent!");
+ } catch (e) {
+ const msg = e instanceof Error ? e.message : "Unknown error";
+ return Response.json({ error: msg }, { status: 500 });
+ }
+ },
+} satisfies ExportedHandler;
+```
+
+The [`QueuesContentType`](/queues/configuration/javascript-apis/#queuescontenttype) API documentation describes how each format is serialized to a queue.
+
+## Consumers
+
+Queues supports two types of consumer:
+
+1. A [consumer Worker](/queues/configuration/configure-queues/), which is push-based: the Worker is invoked when the queue has messages to deliver.
+2. A [HTTP pull consumer](/queues/configuration/pull-consumers/), which is pull-based: the consumer calls the queue endpoint over HTTP to receive and then acknowledge messages.
+
+A queue can only have one type of consumer configured.
+
+### Create a consumer Worker
+
+A consumer is the term for a client that is subscribing to or *consuming* messages from a queue. In its most basic form, a consumer is defined by creating a `queue` handler in a Worker:
+
+```ts
+interface Env {
+ // Add your bindings here, e.g. KV namespaces, R2 buckets, D1 databases
+}
+
+export default {
+ async queue(batch, env, ctx): Promise {
+ // Do something with messages in the batch
+ // i.e. write to R2 storage, D1 database, or POST to an external API
+ for (const msg of batch.messages) {
+ // Process each message
+ console.log(msg.body);
+ }
+ },
+} satisfies ExportedHandler;
+```
+
+You then connect that consumer to a queue with `wrangler queues consumer ` or by defining a `[[queues.consumers]]` configuration in your [Wrangler configuration file](/workers/wrangler/configuration/) manually:
+
+
+
+```jsonc
+{
+ "queues": {
+ "consumers": [
+ {
+ "queue": "",
+ "max_batch_size": 100, // optional
+ "max_batch_timeout": 30 // optional
+ }
+ ]
+ }
+}
+```
+
+
+
+Importantly, each queue can only have one active consumer. This allows Cloudflare Queues to achieve at least once delivery and minimize the risk of duplicate messages beyond that.
+
+:::note[Best practice]
+
+
+Configure a single consumer per queue. This both logically separates your queues, and ensures that errors (failures) in processing messages from one queue do not impact your other queues.
+
+
+:::
+
+Notably, you can use the same consumer with multiple queues. The queue handler that defines your consumer Worker will be invoked by the queues it is connected to.
+
+* The `MessageBatch` that is passed to your `queue` handler includes a `queue` property with the name of the queue the batch was read from.
+* This can reduce the amount of code you need to write, and allow you to process messages based on the name of your queues.
+
+For example, a consumer configured to consume messages from multiple queues would resemble the following:
+
+```ts
+interface Env {
+ // Add your bindings here
+}
+
+export default {
+ async queue(batch, env, ctx): Promise {
+ // MessageBatch has a `queue` property we can switch on
+ switch (batch.queue) {
+ case "log-queue":
+ // Write the batch to R2
+ break;
+ case "debug-queue":
+ // Write the message to the console or to another queue
+ break;
+ case "email-reset":
+ // Trigger a password reset email via an external API
+ break;
+ default:
+ // Handle messages we haven't mentioned explicitly (write a log, push to a DLQ)
+ break;
+ }
+ },
+} satisfies ExportedHandler;
+```
+
+### Remove a consumer
+
+To remove a queue from your project, run `wrangler queues consumer remove ` and then remove the desired queue below the `[[queues.consumers]]` in Wrangler file.
+
+### Pull consumers
+
+A queue can have a HTTP-based consumer that pulls from the queue, instead of messages being pushed to a Worker.
+
+This consumer can be any HTTP-speaking service that can communicate over the Internet. Review the [pull consumer guide](/queues/configuration/pull-consumers/) to learn how to configure a pull-based consumer for a queue.
+
+## Messages
+
+A message is the object you are producing to and consuming from a queue.
+
+Any JSON serializable object can be published to a queue. For most developers, this means either simple strings or JSON objects. You can explicitly [set the content type](#content-types) when sending a message.
+
+Messages themselves can be [batched when delivered to a consumer](/queues/configuration/batching-retries/). By default, messages within a batch are treated as all or nothing when determining retries. If the last message in a batch fails to be processed, the entire batch will be retried. You can also choose to [explicitly acknowledge](/queues/configuration/batching-retries/) messages as they are successfully processed, and/or mark individual messages to be retried.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/api/workers/workers-api-reference.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/api/workers/workers-api-reference.md
new file mode 100644
index 0000000..9c3fbed
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/api/workers/workers-api-reference.md
@@ -0,0 +1,562 @@
+---
+pcx_content_type: reference
+title: Workers API reference
+description: Complete reference for the R2 in-Worker API, including bucket and object operations.
+products:
+ - r2
+---
+
+import { Type, MetaInfo, WranglerConfig, TabItem, Tabs } from "~/components";
+
+The in-Worker R2 API is accessed by binding an R2 bucket to a [Worker](/workers). The Worker you write can expose external access to buckets via a route or manipulate R2 objects internally.
+
+The R2 API includes some extensions and semantic differences from the S3 API. If you need S3 compatibility, consider using the [S3-compatible API](/r2/api/s3/).
+
+## Concepts
+
+R2 organizes the data you store, called objects, into containers, called buckets. Buckets are the fundamental unit of performance, scaling, and access within R2.
+
+## Create a binding
+
+:::note[Bindings]
+
+A binding is how your Worker interacts with external resources such as [KV Namespaces](/kv/concepts/kv-namespaces/), [Durable Objects](/durable-objects/), or [R2 Buckets](/r2/buckets/). A binding is a runtime variable that the Workers runtime provides to your code. You can declare a variable name in your Wrangler file that will be bound to these resources at runtime, and interact with them through this variable. Every binding's variable name and behavior is determined by you when deploying the Worker. Refer to [Environment Variables](/workers/configuration/environment-variables/) for more information.
+
+A binding is defined in the Wrangler file of your Worker project's directory.
+
+:::
+
+To bind your R2 bucket to your Worker, add the following to your Wrangler file. Update the `binding` property to a valid JavaScript variable identifier and `bucket_name` to the name of your R2 bucket:
+
+
+
+```jsonc
+{
+ "r2_buckets": [
+ {
+ "binding": "MY_BUCKET", // <~ valid JavaScript variable name
+ "bucket_name": ""
+ }
+ ]
+}
+```
+
+
+
+Within your Worker, your bucket binding is now available under the `MY_BUCKET` variable and you can begin interacting with it using the [bucket methods](#bucket-method-definitions) described below.
+
+## Bucket method definitions
+
+The following methods are available on the bucket binding object injected into your code.
+
+For example, to issue a `PUT` object request using the binding above:
+
+
+
+```js
+export default {
+ async fetch(request, env) {
+ const url = new URL(request.url);
+ const key = url.pathname.slice(1);
+
+ switch (request.method) {
+ case "PUT":
+ await env.MY_BUCKET.put(key, request.body);
+ return new Response(`Put ${key} successfully!`);
+
+ default:
+ return new Response(`${request.method} is not allowed.`, {
+ status: 405,
+ headers: {
+ Allow: "PUT",
+ },
+ });
+ }
+ },
+};
+```
+
+
+
+
+```py
+from workers import WorkerEntrypoint, Response
+from urllib.parse import urlparse
+
+class Default(WorkerEntrypoint):
+ async def fetch(self, request):
+ url = urlparse(request.url)
+ key = url.path[1:]
+
+ if request.method == "PUT":
+ await self.env.MY_BUCKET.put(key, request.body)
+ return Response(f"Put {key} successfully!")
+ else:
+ return Response(
+ f"{request.method} is not allowed.",
+ status=405,
+ headers={"Allow": "PUT"}
+ )
+```
+
+
+
+
+- `head`
+
+ - Retrieves the `R2Object` for the given key containing only object metadata, if the key exists, and `null` if the key does not exist.
+
+- `get`
+
+ - Retrieves the `R2ObjectBody` for the given key containing object metadata and the object body as a ReadableStream, if the key exists, and `null` if the key does not exist.
+ - In the event that a precondition specified in options fails, get() returns an R2Object with body undefined.
+
+- `put`
+
+ - Stores the given value and metadata under the associated key. Once the write succeeds, returns an `R2Object` containing metadata about the stored Object.
+ - In the event that a precondition specified in options fails, put() returns `null`, and the object will not be stored.
+ - R2 writes are strongly consistent. Once the Promise resolves, all subsequent read operations will see this key value pair globally.
+
+- `delete`
+
+ - Deletes the given values and metadata under the associated keys. Once the delete succeeds, returns void.
+ - R2 deletes are strongly consistent. Once the Promise resolves, all subsequent read operations will no longer see the provided key value pairs globally.
+ - Up to 1000 keys may be deleted per call.
+
+- `list`
+
+ * Returns an R2Objects containing a list of R2Object contained within the bucket.
+ * The returned list of objects is ordered lexicographically.
+ * Returns up to 1000 entries, but may return less in order to minimize memory pressure within the Worker.
+ * To explicitly set the number of objects to list, provide an [R2ListOptions](/r2/api/workers/workers-api-reference/#r2listoptions) object with the `limit` property set.
+
+* `createMultipartUpload`
+
+ - Creates a multipart upload.
+ - Returns Promise which resolves to an `R2MultipartUpload` object representing the newly created multipart upload. Once the multipart upload has been created, the multipart upload can be immediately interacted with globally, either through the Workers API, or through the S3 API.
+
+- `resumeMultipartUpload`
+
+ - Returns an object representing a multipart upload with the given key and uploadId.
+ - The resumeMultipartUpload operation does not perform any checks to ensure the validity of the uploadId, nor does it verify the existence of a corresponding active multipart upload. This is done to minimize latency before being able to call subsequent operations on the `R2MultipartUpload` object.
+
+## `R2Object` definition
+
+`R2Object` is created when you `PUT` an object into an R2 bucket. `R2Object` represents the metadata of an object based on the information provided by the uploader. Every object that you `PUT` into an R2 bucket will have an `R2Object` created.
+
+- `key`
+
+ - The object's key.
+
+- `version`
+
+ - Random unique string associated with a specific upload of a key.
+
+- `size`
+
+ - Size of the object in bytes.
+
+- `etag`
+
+:::note
+
+Cloudflare recommends using the `httpEtag` field when returning an etag in a response header. This ensures the etag is quoted and conforms to [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110#section-8.8.3).
+:::
+
+- The etag associated with the object upload.
+
+- `httpEtag`
+
+ - The object's etag, in quotes so as to be returned as a header.
+
+- `uploaded`
+
+ - A Date object representing the time the object was uploaded.
+
+- `httpMetadata`
+
+ - Various HTTP headers associated with the object. Refer to [HTTP Metadata](#http-metadata).
+
+- `customMetadata`
+
+ - A map of custom, user-defined metadata associated with the object.
+
+- `range`
+
+ - A `R2Range` object containing the returned range of the object.
+
+- `checksums`
+
+ - A `R2Checksums` object containing the stored checksums of the object. Refer to [checksums](#checksums).
+
+- `writeHttpMetadata`
+
+ - Retrieves the `httpMetadata` from the `R2Object` and applies their corresponding HTTP headers to the `Headers` input object. Refer to [HTTP Metadata](#http-metadata).
+
+- `storageClass`
+
+ - The storage class associated with the object. Refer to [Storage Classes](#storage-class).
+
+- `ssecKeyMd5`
+
+ - Hex-encoded MD5 hash of the [SSE-C](/r2/examples/ssec) key used for encryption (if one was provided). Hash can be used to identify which key is needed to decrypt object.
+
+## `R2ObjectBody` definition
+
+`R2ObjectBody` represents an object's metadata combined with its body. It is returned when you `GET` an object from an R2 bucket. The full list of keys for `R2ObjectBody` includes the list below and all keys inherited from [`R2Object`](#r2object-definition).
+
+- `body`
+
+ - The object's value.
+
+- `bodyUsed`
+
+ - Whether the object's value has been consumed or not.
+
+- `arrayBuffer`
+
+ - Returns a Promise that resolves to an `ArrayBuffer` containing the object's value.
+
+- `text`
+
+ - Returns a Promise that resolves to a string containing the object's value.
+
+- `json`
+
+ - Returns a Promise that resolves to the given object containing the object's value.
+
+- `blob`
+
+ - Returns a Promise that resolves to a binary Blob containing the object's value.
+
+## `R2MultipartUpload` definition
+
+An `R2MultipartUpload` object is created when you call `createMultipartUpload` or `resumeMultipartUpload`. `R2MultipartUpload` is a representation of an ongoing multipart upload.
+
+Uncompleted multipart uploads will be automatically aborted after 7 days.
+
+:::note
+
+An `R2MultipartUpload` object does not guarantee that there is an active underlying multipart upload corresponding to that object.
+
+A multipart upload can be completed or aborted at any time, either through the S3 API, or by a parallel invocation of your Worker. Therefore it is important to add the necessary error handling code around each operation on a `R2MultipartUpload` object in case the underlying multipart upload no longer exists.
+
+:::
+
+- `key`
+
+ - The `key` for the multipart upload.
+
+- `uploadId`
+
+ - The `uploadId` for the multipart upload.
+
+- `uploadPart`
+
+ - Uploads a single part with the specified part number to this multipart upload. Each part must be uniform in size with an exception for the final part which can be smaller.
+ - Returns an `R2UploadedPart` object containing the `etag` and `partNumber`. These `R2UploadedPart` objects are required when completing the multipart upload.
+
+- `abort`
+
+ - Aborts the multipart upload. Returns a Promise that resolves when the upload has been successfully aborted.
+
+- `complete`
+
+ - Completes the multipart upload with the given parts.
+ - Returns a Promise that resolves when the complete operation has finished. Once this happens, the object is immediately accessible globally by any subsequent read operation.
+
+## Method-specific types
+
+### R2GetOptions
+
+- `onlyIf`
+
+ - Specifies that the object should only be returned given satisfaction of certain conditions in the `R2Conditional` or in the conditional Headers. Refer to [Conditional operations](#conditional-operations).
+
+- `range`
+
+ - Specifies that only a specific length (from an optional offset) or suffix of bytes from the object should be returned. Refer to [Ranged reads](#ranged-reads).
+
+- `ssecKey`
+
+ - Specifies a key to be used for [SSE-C](/r2/examples/ssec). Key must be 32 bytes in length, in the form of a hex-encoded string or an ArrayBuffer.
+
+#### Ranged reads
+
+`R2GetOptions` accepts a `range` parameter, which can be used to restrict the data returned in `body`.
+
+There are 3 variations of arguments that can be used in a range:
+
+- An offset with an optional length.
+- An optional offset with a length.
+- A suffix.
+
+- `offset`
+
+ - The byte to begin returning data from, inclusive.
+
+- `length`
+
+ - The number of bytes to return. If more bytes are requested than exist in the object, fewer bytes than this number may be returned.
+
+- `suffix`
+
+ - The number of bytes to return from the end of the file, starting from the last byte. If more bytes are requested than exist in the object, fewer bytes than this number may be returned.
+
+### R2PutOptions
+
+- `onlyIf`
+
+ - Specifies that the object should only be stored given satisfaction of certain conditions in the `R2Conditional`. Refer to [Conditional operations](#conditional-operations).
+
+- `httpMetadata`
+
+ - Various HTTP headers associated with the object. Refer to [HTTP Metadata](#http-metadata).
+
+- `customMetadata`
+
+ - A map of custom, user-defined metadata that will be stored with the object.
+
+:::note
+
+Only a single hashing algorithm can be specified at once.
+
+:::
+
+- `md5`
+
+ - A md5 hash to use to check the received object's integrity.
+
+- `sha1`
+
+ - A SHA-1 hash to use to check the received object's integrity.
+
+- `sha256`
+
+ - A SHA-256 hash to use to check the received object's integrity.
+
+- `sha384`
+
+ - A SHA-384 hash to use to check the received object's integrity.
+
+- `sha512`
+
+ - A SHA-512 hash to use to check the received object's integrity.
+
+- `storageClass`
+
+ - Sets the storage class of the object if provided. Otherwise, the object will be stored in the default storage class associated with the bucket. Refer to [Storage Classes](#storage-class).
+
+- `ssecKey`
+
+ - Specifies a key to be used for [SSE-C](/r2/examples/ssec). Key must be 32 bytes in length, in the form of a hex-encoded string or an ArrayBuffer.
+
+### R2MultipartOptions
+
+- `httpMetadata`
+
+ - Various HTTP headers associated with the object. Refer to [HTTP Metadata](#http-metadata).
+
+- `customMetadata`
+
+ - A map of custom, user-defined metadata that will be stored with the object.
+
+- `storageClass`
+
+ - Sets the storage class of the object if provided. Otherwise, the object will be stored in the default storage class associated with the bucket. Refer to [Storage Classes](#storage-class).
+
+- `ssecKey`
+
+ - Specifies a key to be used for [SSE-C](/r2/examples/ssec). Key must be 32 bytes in length, in the form of a hex-encoded string or an ArrayBuffer.
+
+### R2ListOptions
+
+- `limit`
+
+ - The number of results to return. Defaults to `1000`, with a maximum of `1000`.
+
+ - If `include` is set, you may receive fewer than `limit` results in your response to accommodate metadata.
+
+- `prefix`
+
+ - The prefix to match keys against. Keys will only be returned if they start with given prefix.
+
+- `cursor`
+
+ - An opaque token that indicates where to continue listing objects from. A cursor can be retrieved from a previous list operation.
+
+- `delimiter`
+
+ - The character to use when grouping keys.
+
+- `include`
+
+ - Can include `httpMetadata` and/or `customMetadata`. If included, items returned by the list will include the specified metadata.
+
+ - Note that there is a limit on the total amount of data that a single `list` operation can return. If you request data, you may receive fewer than `limit` results in your response to accommodate metadata.
+
+ - The [compatibility date](/workers/configuration/compatibility-dates/) must be set to `2022-08-04` or later in your Wrangler file. If not, then the `r2_list_honor_include` compatibility flag must be set. Otherwise it is treated as `include: ['httpMetadata', 'customMetadata']` regardless of what the `include` option provided actually is.
+
+ This means applications must be careful to avoid comparing the amount of returned objects against your `limit`. Instead, use the `truncated` property to determine if the `list` request has more data to be returned.
+
+
+```js
+const options = {
+ limit: 500,
+ include: ["customMetadata"],
+};
+
+const listed = await env.MY_BUCKET.list(options);
+
+let truncated = listed.truncated;
+let cursor = truncated ? listed.cursor : undefined;
+
+// ❌ - if your limit can't fit into a single response or your
+// bucket has less objects than the limit, it will get stuck here.
+while (listed.objects.length < options.limit) {
+ // ...
+}
+
+// ✅ - use the truncated property to check if there are more
+// objects to be returned
+while (truncated) {
+ const next = await env.MY_BUCKET.list({
+ ...options,
+ cursor: cursor,
+ });
+ listed.objects.push(...next.objects);
+
+ truncated = next.truncated;
+ cursor = next.cursor;
+}
+```
+
+```py
+limit = 500
+include = ["customMetadata"]
+
+listed = await self.env.MY_BUCKET.list(limit=limit, include=include)
+
+truncated = listed.truncated
+cursor = listed.cursor if truncated else None
+
+# ❌ - if your limit can't fit into a single response or your
+# bucket has less objects than the limit, it will get stuck here.
+while len(listed.objects) < limit:
+ ...
+
+# ✅ - use the truncated property to check if there are more
+# objects to be returned
+while truncated:
+ next_page = await self.env.MY_BUCKET.list(limit=limit, include=include, cursor=cursor)
+ listed.objects.extend(next_page.objects)
+
+ truncated = next_page.truncated
+ cursor = next_page.cursor
+```
+
+
+### R2Objects
+
+An object containing an `R2Object` array, returned by `BUCKET_BINDING.list()`.
+
+- `objects`
+
+ - An array of objects matching the `list` request.
+
+- `truncated` boolean
+
+ - If true, indicates there are more results to be retrieved for the current `list` request.
+
+- `cursor`
+
+ - A token that can be passed to future `list` calls to resume listing from that point. Only present if truncated is true.
+
+- `delimitedPrefixes`
+
+ - If a delimiter has been specified, contains all prefixes between the specified prefix and the next occurrence of the delimiter.
+
+ - For example, if no prefix is provided and the delimiter is '/', `foo/bar/baz` would return `foo` as a delimited prefix. If `foo/` was passed as a prefix with the same structure and delimiter, `foo/bar` would be returned as a delimited prefix.
+
+### Conditional operations
+
+You can pass an `R2Conditional` object to `R2GetOptions` and `R2PutOptions`. If the condition check for `get()` fails, the body will not be returned. This will make `get()` have lower latency.
+
+If the condition check for `put()` fails, `null` will be returned instead of the `R2Object`.
+
+- `etagMatches`
+
+ - Performs the operation if the object's etag matches the given string.
+
+- `etagDoesNotMatch`
+
+ - Performs the operation if the object's etag does not match the given string.
+
+- `uploadedBefore`
+
+ - Performs the operation if the object was uploaded before the given date.
+
+- `uploadedAfter`
+
+ - Performs the operation if the object was uploaded after the given date.
+
+Alternatively, you can pass a `Headers` object containing conditional headers to `R2GetOptions` and `R2PutOptions`. For information on these conditional headers, refer to [the MDN docs on conditional requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests#conditional_headers). All conditional headers aside from `If-Range` are supported.
+
+For more specific information about conditional requests, refer to [RFC 7232](https://datatracker.ietf.org/doc/html/rfc7232).
+
+### HTTP Metadata
+
+Generally, these fields match the HTTP metadata passed when the object was created. They can be overridden when issuing `GET` requests, in which case, the given values will be echoed back in the response.
+
+- `contentType`
+
+- `contentLanguage`
+
+- `contentDisposition`
+
+- `contentEncoding`
+
+- `cacheControl`
+
+- `cacheExpiry`
+
+### Checksums
+
+If a checksum was provided when using the `put()` binding, it will be available on the returned object under the `checksums` property. The MD5 checksum will be included by default for non-multipart objects.
+
+- `md5`
+
+ - The MD5 checksum of the object.
+
+- `sha1`
+
+ - The SHA-1 checksum of the object.
+
+- `sha256`
+
+ - The SHA-256 checksum of the object.
+
+- `sha384`
+
+ - The SHA-384 checksum of the object.
+
+- `sha512`
+
+ - The SHA-512 checksum of the object.
+
+### `R2UploadedPart`
+
+An `R2UploadedPart` object represents a part that has been uploaded. `R2UploadedPart` objects are returned from `uploadPart` operations and must be passed to `completeMultipartUpload` operations.
+
+- `partNumber`
+
+ - The number of the part.
+
+- `etag`
+
+ - The `etag` of the part.
+
+### Storage Class
+
+The storage class where an `R2Object` is stored. The available storage classes are `Standard` and `InfrequentAccess`. Refer to [Storage classes](/r2/buckets/storage-classes/)
+for more information.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/buckets/create-buckets.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/buckets/create-buckets.md
new file mode 100644
index 0000000..a5f87a5
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/buckets/create-buckets.md
@@ -0,0 +1,51 @@
+---
+pcx_content_type: how-to
+title: Create new buckets
+description: Create R2 buckets using the Cloudflare dashboard or Wrangler CLI.
+sidebar:
+ order: 1
+products:
+ - r2
+---
+
+You can create a bucket from the Cloudflare dashboard or using Wrangler.
+
+:::note
+
+Wrangler is [a command-line tool](/workers/wrangler/install-and-update/) for building with Cloudflare's developer products, including R2.
+
+The R2 support in Wrangler allows you to manage buckets and perform basic operations against objects in your buckets. For more advanced use-cases, including bulk uploads or mirroring files from legacy object storage providers, we recommend [rclone](/r2/examples/rclone/) or an [S3-compatible](/r2/api/s3/) tool of your choice.
+
+:::
+
+## Bucket-Level Operations
+
+Create a bucket with the [`r2 bucket create`](/workers/wrangler/commands/r2/#r2-bucket-create) command:
+
+```sh
+wrangler r2 bucket create your-bucket-name
+```
+
+:::note
+
+- Bucket names can only contain lowercase letters (a-z), numbers (0-9), and hyphens (-).
+- Bucket names cannot begin or end with a hyphen.
+- Bucket names can only be between 3-63 characters in length.
+
+The placeholder text is only for the example.
+
+:::
+
+List buckets in the current account with the [`r2 bucket list`](/workers/wrangler/commands/r2/#r2-bucket-list) command:
+
+```sh
+wrangler r2 bucket list
+```
+
+To delete a bucket, you must first empty it and then delete it. For detailed instructions, refer to [Delete buckets](/r2/buckets/delete-buckets/).
+
+## Notes
+
+- Bucket names and buckets are not public by default. To allow public access to a bucket, refer to [Public buckets](/r2/buckets/public-buckets/).
+- For information on controlling access to your R2 bucket with Cloudflare Access, refer to [Protect an R2 Bucket with Cloudflare Access](/r2/tutorials/cloudflare-access/).
+- Invalid (unauthorized) access attempts to private buckets do not incur R2 operations charges against that bucket. Refer to the [R2 pricing FAQ](/r2/pricing/#frequently-asked-questions) to understand what operations are billed vs. not billed.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/how-r2-works.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/how-r2-works.md
new file mode 100644
index 0000000..fe276a8
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/how-r2-works.md
@@ -0,0 +1,99 @@
+---
+title: How R2 works
+
+pcx_content_type: concept
+sidebar:
+ order: 2
+description: Find out how R2 works.
+products:
+ - r2
+head:
+ - tag: title
+ content: How R2 works
+---
+
+import { Render, LinkCard } from "~/components";
+
+Cloudflare R2 is an S3-compatible object storage service with no egress fees, built on Cloudflare's global network. It is [strongly consistent](/r2/reference/consistency/) and designed for high [data durability](/r2/reference/durability/).
+
+R2 is ideal for storing and serving unstructured data that needs to be accessed frequently over the internet, without incurring egress fees. It's a good fit for workloads like serving web assets, training AI models, and managing user-generated content.
+
+## Architecture
+
+R2's architecture is composed of multiple components:
+
+- **R2 Gateway:** The entry point for all API requests that handles authentication and routing logic. This service is deployed across Cloudflare's global network via [Cloudflare Workers](/workers/).
+
+- **Metadata Service:** A distributed layer built on [Durable Objects](/durable-objects/) used to store and manage object metadata (e.g. object key, checksum) to ensure strong consistency of the object across the storage system. It includes a built-in cache layer to speed up access to metadata.
+
+- **Tiered Read Cache:** A caching layer that sits in front of the Distributed Storage Infrastructure that speeds up object reads by using [Cloudflare Tiered Cache](/cache/how-to/tiered-cache/) to serve data closer to the client.
+
+- **Distributed Storage Infrastructure:** The underlying infrastructure that persistently stores encrypted object data.
+
+
+
+R2 supports multiple client interfaces including [Cloudflare Workers Binding](/r2/api/workers/workers-api-usage/), [S3-compatible API](/r2/api/s3/api/), and a [REST API](/api/resources/r2/) that powers the Cloudflare Dashboard and Wrangler CLI. All requests are routed through the R2 Gateway, which coordinates with the Metadata Service and Distributed Storage Infrastructure to retrieve the object data.
+
+## Write data to R2
+
+When a write request (e.g. uploading an object) is made to R2, the following sequence occurs:
+
+1. **Request handling:** The request is received by the R2 Gateway at the edge, close to the user, where it is authenticated.
+
+2. **Encryption and routing:** The Gateway reaches out to the Metadata Service to retrieve the [encryption key](/r2/reference/data-security/) and determines which storage cluster to write the encrypted data to within the [location](/r2/reference/data-location/) set for the bucket.
+
+3. **Writing to storage:** The encrypted data is written and stored in the distributed storage infrastructure, and replicated within the region (e.g. ENAM) for [durability](/r2/reference/durability/).
+
+4. **Metadata commit:** Finally, the Metadata Service commits the object's metadata, making it visible in subsequent reads. Only after this commit is an `HTTP 200` success response sent to the client, preventing unacknowledged writes.
+
+
+
+## Read data from R2
+
+When a read request (e.g. fetching an object) is made to R2, the following sequence occurs:
+
+1. **Request handling:** The request is received by the R2 Gateway at the edge, close to the user, where it is authenticated.
+
+2. **Metadata lookup:** The Gateway asks the Metadata Service for the object metadata.
+
+3. **Reading the object:** The Gateway attempts to retrieve the [encrypted](/r2/reference/data-security/) object from the tiered read cache. If it's not available, it retrieves the object from one of the distributed storage data centers within the region that holds the object data.
+
+4. **Serving to client:** The object is decrypted and served to the user.
+
+
+
+## Performance
+
+The performance of your operations can be influenced by factors such as the bucket's geographical location, request origin, and access patterns.
+
+To optimize upload performance for cross-region requests, enable [Local Uploads](/r2/buckets/local-uploads/) on your bucket.
+
+To optimize read performance, enable [Cloudflare Cache](/cache/) when using a [custom domain](/r2/buckets/public-buckets/#custom-domains). When caching is enabled, read requests can bypass the R2 Gateway and be served directly from Cloudflare's edge cache, reducing latency. Note that cached data may not reflect the latest version immediately.
+
+
+
+## Learn more
+
+
+
+
+
+
+
+
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/index.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/index.md
new file mode 100644
index 0000000..fe6d80e
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/index.md
@@ -0,0 +1,122 @@
+---
+title: Cloudflare R2
+
+pcx_content_type: overview
+sidebar:
+ order: 1
+description: Cloudflare R2 is a cost-effective, scalable object storage solution for cloud-native apps, web content, and data lakes without egress fees.
+products:
+ - r2
+head:
+ - tag: title
+ content: Overview
+---
+
+import {
+ CardGrid,
+ Description,
+ Feature,
+ LinkButton,
+ LinkTitleCard,
+ Plan,
+ RelatedProduct,
+} from "~/components";
+
+
+
+Object storage for all your data.
+
+
+
+Cloudflare R2 Storage allows developers to store large amounts of unstructured data without the costly egress bandwidth fees associated with typical cloud storage services.
+
+You can use R2 for multiple scenarios, including but not limited to:
+
+- Storage for cloud-native applications
+- Cloud storage for web content
+- Storage for podcast episodes
+- Data lakes (analytics and big data)
+- Cloud storage output for large batch processes, such as machine learning model artifacts or datasets
+
+
+ Get started
+
+
+ Browse the examples
+
+
+---
+
+## Features
+
+
+
+Location Hints are optional parameters you can provide during bucket creation to indicate the primary geographical location you expect data will be accessed from.
+
+
+
+
+
+Configure CORS to interact with objects in your bucket and configure policies on your bucket.
+
+
+
+
+
+Public buckets expose the contents of your R2 bucket directly to the Internet.
+
+
+
+
+
+Create bucket scoped tokens for granular control over who can access your data.
+
+
+
+---
+
+## Related products
+
+
+
+A [serverless](https://www.cloudflare.com/learning/serverless/what-is-serverless/) execution environment that allows you to create entirely new applications or augment existing ones without configuring or maintaining infrastructure.
+
+
+
+
+
+Upload, store, encode, and deliver live and on-demand video with one API, without configuring or maintaining infrastructure.
+
+
+
+
+
+A suite of products tailored to your image-processing needs.
+
+
+
+---
+
+## More resources
+
+
+
+
+ Understand pricing for free and paid tier rates.
+
+
+
+ Ask questions, show off what you are building, and discuss the platform
+ with other developers.
+
+
+
+ Learn about product announcements, new tutorials, and what is new in
+ Cloudflare Workers.
+
+
+
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/reference/consistency.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/reference/consistency.md
new file mode 100644
index 0000000..366ad73
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/reference/consistency.md
@@ -0,0 +1,64 @@
+---
+title: Consistency model
+description: R2 provides strong global consistency for reads, writes, deletes, and list operations.
+pcx_content_type: concept
+sidebar:
+ order: 7
+products:
+ - r2
+---
+
+This page details R2's consistency model, including where R2 is strongly, globally consistent and which operations this applies to.
+
+R2 can be described as "strongly consistent", especially in comparison to other distributed object storage systems. This strong consistency ensures that operations against R2 see the latest (accurate) state: clients should be able to observe the effects of any write, update and/or delete operation immediately, globally.
+
+## Terminology
+
+In the context of R2, *strong* consistency and *eventual* consistency have the following meanings:
+
+* **Strongly consistent** - The effect of an operation will be observed globally, immediately, by all clients. Clients will not observe 'stale' (inconsistent) state.
+* **Eventually consistent** - Clients may not see the effect of an operation immediately. The state may take a some time (typically seconds to a minute) to propagate globally.
+
+## Operations and Consistency
+
+Operations against R2 buckets and objects adhere to the following consistency guarantees:
+
+
+
+| Action | Consistency |
+| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Read-after-write: Write (upload) an object, then read it | Strongly consistent: readers will immediately see the latest object globally |
+| Metadata: Update an object's metadata | Strongly consistent: readers will immediately see the updated metadata globally |
+| Deletion: Delete an object | Strongly consistent: reads to that object will immediately return a "does not exist" error |
+| Object listing: List the objects in a bucket | Strongly consistent: the list operation will list all objects at that point in time |
+| IAM: Adding/removing R2 Storage permissions | Eventually consistent: A [new or updated API key](/fundamentals/api/get-started/create-token/) may take up to a minute to have permissions reflected globally |
+
+
+
+Additional notes:
+
+* In the event two clients are writing (`PUT` or `DELETE`) to the same key, the last writer to complete "wins".
+* When performing a multipart upload, read-after-write consistency continues to apply once all parts have been successfully uploaded. In the case the same part is uploaded (in error) from multiple writers, the last write will win.
+* Copying an object within the same bucket also follows the same read-after-write consistency that writing a new object would. The "copied" object is immediately readable by all clients once the copy operation completes.
+* To delete an R2 bucket, it must be completely empty before deletion is allowed. If you attempt to delete a bucket that still contains objects, you will receive an error such as: `The bucket you tried to delete (X) is not empty (account Y)` or `Bucket X cannot be deleted because it isn’t empty.` For instructions on emptying and deleting a bucket, refer to [Delete buckets](/r2/buckets/delete-buckets/).
+
+
+## Caching
+
+:::note
+
+
+By default, Cloudflare's cache will cache common, cacheable status codes automatically [per our cache documentation](/cache/how-to/configure-cache-status-code/#edge-ttl).
+
+
+:::
+
+When connecting a [custom domain](/r2/buckets/public-buckets/#custom-domains) to an R2 bucket and enabling caching for objects served from that bucket, the consistency model is necessarily relaxed when accessing content via a domain with caching enabled.
+
+Specifically, you should expect:
+
+* An object you delete from R2, but that is still cached, will still be available. You should [purge the cache](/cache/how-to/purge-cache/) after deleting objects if you need that delete to be reflected.
+* By default, Cloudflare’s cache will [cache HTTP 404 (Not Found) responses](/cache/how-to/configure-cache-status-code/#edge-ttl) automatically. If you upload an object to that same path, the cache may continue to return HTTP 404s until the cache TTL (Time to Live) expires and the new object is fetched from R2 or the [cache is purged](/cache/how-to/purge-cache/).
+* An object for a given key is overwritten with a new object: the old (previous) object will continue to be served to clients until the cache TTL expires (or the object is evicted) or the cache is purged.
+
+The cache does not affect access via [Worker API bindings](/r2/api/workers/) or the [S3 API](/r2/api/s3/), as these operations are made directly against the bucket and do not transit through the cache.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/reference/durability.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/reference/durability.md
new file mode 100644
index 0000000..177e6ed
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/r2/reference/durability.md
@@ -0,0 +1,28 @@
+---
+title: Durability
+description: R2 is designed for 99.999999999% annual durability using replication and erasure coding.
+pcx_content_type: concept
+sidebar:
+ order: 7
+products:
+ - r2
+---
+
+R2 is designed to provide 99.999999999% (eleven 9s) of annual durability. This means that if you store 10,000,000 objects on R2, you can expect to lose an object once every 10,000 years on average.
+
+## How R2 achieves eleven-nines durability
+
+R2's durability is built on multiple layers of redundancy and data protection:
+
+- **Replication**: When you upload an object, R2 stores multiple "copies" of that object through either full replication and/or erasure coding. This ensures that the full or partial failure of any individual disk does not result in data loss. Erasure coding distributes parts of the object across multiple disks, ensuring that even if some disks fail, the object can still be reconstructed from a subset of the available parts, preventing hardware failure or physical impacts to data centers (such as fire or floods) from causing data loss.
+
+- **Hardware redundancy**: Storage clusters are comprised of hardware distributed across several data centers within a geographic region. This physical distribution ensures that localized failures—such as power outages, network disruptions, or hardware malfunctions at a single facility—do not result in data loss.
+
+- **Synchronous writes**: R2 returns an `HTTP 200 (OK)` for a write via API or otherwise indicates success only when data has been persisted to disk. We do not rely on asynchronous replication to support underlying durability guarantees. This is critical to R2’s consistency guarantees and mitigates the chance of a client receiving a successful API response without the underlying metadata and storage infrastructure having persisted the change.
+
+### Considerations
+
+* Durability is not a guarantee of data availability. It is a measure of the likelihood of data loss.
+* R2 provides an availability [SLA of 99.9%](https://www.cloudflare.com/r2-service-level-agreement/)
+* Durability does not prevent intentional or accidental deletion of data. Use [bucket locks](/r2/buckets/bucket-locks/) and/or bucket-scoped [API tokens](/r2/api/tokens/) to limit access to data.
+* Durability is also distinct from [consistency](/r2/reference/consistency/), which describes how reads and writes are reflected in the system's state (e.g. eventual consistency vs. strong consistency).
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/get-started/guide.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/get-started/guide.md
new file mode 100644
index 0000000..66339d6
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/get-started/guide.md
@@ -0,0 +1,210 @@
+---
+title: CLI
+description: Set up and deploy your first Cloudflare Worker using Wrangler, the command-line interface.
+pcx_content_type: get-started
+sidebar:
+ order: 1
+head:
+ - tag: title
+ content: Get started - CLI
+products:
+ - workers
+---
+
+import { Details, Render, PackageManagers } from "~/components";
+
+Set up and deploy your first Worker with Wrangler, the Cloudflare Developer Platform CLI.
+
+This guide will instruct you through setting up and deploying your first Worker.
+
+## Prerequisites
+
+
+
+## 1. Create a new Worker project
+
+Open a terminal window and run C3 to create your Worker project. [C3 (`create-cloudflare-cli`)](https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare) is a command-line tool designed to help you set up and deploy new applications to Cloudflare.
+
+
+
+
+
+Now, you have a new project set up. Move into that project folder.
+
+```sh
+cd my-first-worker
+```
+
+
+
+In your project directory, C3 will have generated the following:
+
+* `wrangler.jsonc`: Your [Wrangler](/workers/wrangler/configuration/#sample-wrangler-configuration) configuration file.
+* `index.js` (in `/src`): A minimal `'Hello World!'` Worker written in [ES module](/workers/reference/migrate-to-module-workers/) syntax.
+* `package.json`: A minimal Node dependencies configuration file.
+* `package-lock.json`: Refer to [`npm` documentation on `package-lock.json`](https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json).
+* `node_modules`: Refer to [`npm` documentation `node_modules`](https://docs.npmjs.com/cli/v7/configuring-npm/folders#node-modules).
+
+
+
+
+
+In addition to creating new projects from C3 templates, C3 also supports creating new projects from existing Git repositories. To create a new project from an existing Git repository, open your terminal and run:
+
+```sh
+npm create cloudflare@latest -- --template
+```
+
+`` may be any of the following:
+
+- `user/repo` (GitHub)
+- `git@github.com:user/repo`
+- `https://github.com/user/repo`
+- `user/repo/some-template` (subdirectories)
+- `user/repo#canary` (branches)
+- `user/repo#1234abcd` (commit hash)
+- `bitbucket:user/repo` (Bitbucket)
+- `gitlab:user/repo` (GitLab)
+
+Your existing template folder must contain the following files, at a minimum, to meet the requirements for Cloudflare Workers:
+
+- `package.json`
+- `wrangler.jsonc` [See sample Wrangler configuration](/workers/wrangler/configuration/#sample-wrangler-configuration)
+- `src/` containing a worker script referenced from `wrangler.jsonc`
+
+
+
+## 2. Develop with Wrangler CLI
+
+C3 installs [Wrangler](/workers/wrangler/install-and-update/), the Workers command-line interface, in Workers projects by default. Wrangler lets you to [create](/workers/wrangler/commands/general/#init), [test](/workers/wrangler/commands/general/#dev), and [deploy](/workers/wrangler/commands/general/#deploy) your Workers projects.
+
+After you have created your first Worker, run the [`wrangler dev`](/workers/wrangler/commands/general/#dev) command in the project directory to start a local server for developing your Worker. This will allow you to preview your Worker locally during development.
+
+```sh
+npx wrangler dev
+```
+
+If you have never used Wrangler before, it will open your web browser so you can login to your Cloudflare account.
+
+Go to [http://localhost:8787](http://localhost:8787) to view your Worker.
+
+
+
+If you have issues with this step or you do not have access to a browser interface, refer to the [`wrangler login`](/workers/wrangler/commands/general/#login) documentation.
+
+
+
+## 3. Write code
+
+With your new project generated and running, you can begin to write and edit your code.
+
+Find the `src/index.js` file. `index.js` will be populated with the code below:
+
+```js title="Original index.js"
+export default {
+ async fetch(request, env, ctx) {
+ return new Response("Hello World!");
+ },
+};
+```
+
+
+
+This code block consists of a few different parts.
+
+```js title="Updated index.js" {1}
+export default {
+ async fetch(request, env, ctx) {
+ return new Response("Hello World!");
+ },
+};
+```
+
+`export default` is JavaScript syntax required for defining [JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#default_exports_versus_named_exports). Your Worker has to have a default export of an object, with properties corresponding to the events your Worker should handle.
+
+```js title="index.js" {2}
+export default {
+ async fetch(request, env, ctx) {
+ return new Response("Hello World!");
+ },
+};
+```
+
+This [`fetch()` handler](/workers/runtime-apis/handlers/fetch/) will be called when your Worker receives an HTTP request. You can define additional event handlers in the exported object to respond to different types of events. For example, add a [`scheduled()` handler](/workers/runtime-apis/handlers/scheduled/) to respond to Worker invocations via a [Cron Trigger](/workers/configuration/cron-triggers/).
+
+Additionally, the `fetch` handler will always be passed three parameters: [`request`, `env` and `context`](/workers/runtime-apis/handlers/fetch/).
+
+```js title="index.js" {3}
+export default {
+ async fetch(request, env, ctx) {
+ return new Response("Hello World!");
+ },
+};
+```
+
+The Workers runtime expects `fetch` handlers to return a `Response` object or a Promise which resolves with a `Response` object. In this example, you will return a new `Response` with the string `"Hello World!"`.
+
+
+
+Replace the content in your current `index.js` file with the content below, which changes the text output.
+
+```js title="index.js" {3}
+export default {
+ async fetch(request, env, ctx) {
+ return new Response("Hello Worker!");
+ },
+};
+```
+
+Then, save the file and reload the page. Your Worker's output will have changed to the new text.
+
+
+
+If the output for your Worker does not change, make sure that:
+
+1. You saved the changes to `index.js`.
+2. You have `wrangler dev` running.
+3. You reloaded your browser.
+
+
+
+## 4. Deploy your project
+
+Deploy your Worker via Wrangler to a `*.workers.dev` subdomain or a [Custom Domain](/workers/configuration/routing/custom-domains/).
+
+```sh
+npx wrangler deploy
+```
+
+If you have not configured any subdomain or domain, Wrangler will prompt you during the publish process to set one up.
+
+Preview your Worker at `..workers.dev`.
+
+
+
+If you see [`523` errors](/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-523/) when pushing your `*.workers.dev` subdomain for the first time, wait a minute or so and the errors will resolve themselves.
+
+
+
+## Next steps
+
+To do more:
+
+- Push your project to a GitHub or GitLab repository then [connect to builds](/workers/ci-cd/builds/#get-started) to enable automatic builds and deployments.
+- Visit the [Cloudflare dashboard](https://dash.cloudflare.com/) for simpler editing.
+- Review our [Examples](/workers/examples/) and [Tutorials](/workers/tutorials/) for inspiration.
+- Set up [bindings](/workers/runtime-apis/bindings/) to allow your Worker to interact with other resources and unlock new functionality.
+- Learn how to [test and debug](/workers/testing/) your Workers.
+- Read about [Workers limits and pricing](/workers/platform/).
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/index.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/index.md
new file mode 100644
index 0000000..e8ada57
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/index.md
@@ -0,0 +1,160 @@
+---
+title: Cloudflare Workers
+description: Build and deploy serverless applications across Cloudflare's global network with Workers.
+pcx_content_type: overview
+sidebar:
+ order: 1
+head:
+ - tag: title
+ content: Overview
+products:
+ - workers
+---
+
+import { Description, RelatedProduct, LinkButton } from "~/components";
+
+
+ A serverless platform for building, deploying, and scaling apps across
+ [Cloudflare's global network](https://www.cloudflare.com/network/) with a
+ single command — no infrastructure to manage, no complex configuration
+
+
+With Cloudflare Workers, you can expect to:
+
+- Deliver fast performance with high reliability anywhere in the world
+- Build full-stack apps with your framework of choice, including [React](/workers/framework-guides/web-apps/react/), [Vue](/workers/framework-guides/web-apps/vue/), [Svelte](/workers/framework-guides/web-apps/sveltekit/), [Next](/workers/framework-guides/web-apps/nextjs/), [Astro](/workers/framework-guides/web-apps/astro/), [React Router](/workers/framework-guides/web-apps/react-router/), [and more](/workers/framework-guides/)
+- Use your preferred language, including [JavaScript](/workers/languages/javascript/), [TypeScript](/workers/languages/typescript/), [Python](/workers/languages/python/), [Rust](/workers/languages/rust/), [and more](/workers/runtime-apis/webassembly/)
+- Gain deep visibility and insight with built-in [observability](/workers/observability/logs/)
+- Get started for free and grow with flexible [pricing](/workers/platform/pricing/), affordable at any scale
+
+Get started with your first project:
+
+
+ Deploy a template
+
+
+
+ Deploy with Wrangler CLI
+
+
+---
+
+## Build with Workers
+
+
+#### Front-end applications
+
+Deploy [static assets](/workers/static-assets/) to Cloudflare's [CDN & cache](/cache/) for fast rendering
+
+
+
+#### Back-end applications
+
+Build APIs and connect to data stores with [Smart Placement](/workers/configuration/placement/) to optimize latency
+
+
+
+#### Serverless AI inference
+
+Run LLMs, generate images, and more with [Workers AI](/workers-ai/)
+
+
+
+#### Background jobs
+
+Schedule [cron jobs](/workers/configuration/cron-triggers/), run durable [Workflows](/workflows/), and integrate with [Queues](/queues/)
+
+
+
+#### Observability & monitoring
+
+Monitor performance, debug issues, and analyze traffic with [real-time logs](/workers/observability/logs/) and [analytics](/workers/observability/metrics-and-analytics/)
+
+
+---
+
+## Integrate with Workers
+
+Connect to external services like databases, APIs, and storage via [Bindings](/workers/runtime-apis/bindings/), enabling functionality with just a few lines of code:
+
+**Storage**
+
+
+
+Scalable stateful storage for real-time coordination.
+
+
+
+
+
+Serverless SQL database built for fast, global queries.
+
+
+
+
+
+Low-latency key-value storage for fast, edge-cached reads.
+
+
+
+
+
+Guaranteed delivery with no charges for egress bandwidth.
+
+
+
+
+
+Connect to your external database with accelerated queries, cached at the edge.
+
+
+
+**Compute**
+
+
+
+Machine learning models powered by serverless GPUs.
+
+
+
+
+
+Durable, long-running operations with automatic retries.
+
+
+
+
+
+Vector database for AI-powered semantic search.
+
+
+
+
+
+Zero-egress object storage for cost-efficient data access.
+
+
+
+
+
+Programmatic serverless browser instances.
+
+
+
+**Media**
+
+
+
+Global caching for high-performance, low-latency delivery.
+
+
+
+
+
+Streamlined image infrastructure from a single API.
+
+
+
+---
+
+Want to connect with the Workers community? [Join our Discord](https://discord.cloudflare.com)
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/R2.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/R2.md
new file mode 100644
index 0000000..1d4eb80
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/R2.md
@@ -0,0 +1,12 @@
+---
+pcx_content_type: navigation
+title: R2
+external_link: /r2/api/workers/workers-api-reference/
+head: []
+description: APIs available in Cloudflare Workers to read from and write to R2
+ buckets. R2 is S3-compatible, zero egress-fee, globally distributed object
+ storage.
+
+products:
+ - workers
+---
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/durable-objects.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/durable-objects.md
new file mode 100644
index 0000000..c6f459b
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/durable-objects.md
@@ -0,0 +1,10 @@
+---
+pcx_content_type: navigation
+title: Durable Objects
+external_link: /durable-objects/api/
+head: []
+description: A globally distributed coordination API with strongly consistent storage.
+
+products:
+ - workers
+---
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/kv.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/kv.md
new file mode 100644
index 0000000..3c269b9
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/kv.md
@@ -0,0 +1,10 @@
+---
+pcx_content_type: navigation
+title: KV
+external_link: /kv/api/
+head: []
+description: Global, low-latency, key-value data storage.
+
+products:
+ - workers
+---
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/queues.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/queues.md
new file mode 100644
index 0000000..d8afce3
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workers/runtime-apis/bindings/queues.md
@@ -0,0 +1,10 @@
+---
+pcx_content_type: navigation
+title: Queues
+external_link: /queues/configuration/javascript-apis/
+head: []
+description: Send and receive messages with guaranteed delivery.
+
+products:
+ - workers
+---
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/rules-of-workflows.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/rules-of-workflows.md
new file mode 100644
index 0000000..19f6e7c
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/rules-of-workflows.md
@@ -0,0 +1,658 @@
+---
+title: Rules of Workflows
+description: Best practices for building resilient Workflows, including idempotency, state management, and error handling.
+pcx_content_type: concept
+sidebar:
+ order: 10
+products:
+ - workflows
+---
+
+import { WranglerConfig, TypeScriptExample } from "~/components";
+
+A Workflow contains one or more steps. Each step is a self-contained, individually retryable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue.
+
+This is a small guidebook on how to build more resilient and correct Workflows.
+
+### Ensure API/Binding calls are idempotent
+
+Because a step might be retried multiple times, your steps should (ideally) be idempotent. For context, idempotency is a logical property where the operation (in this case a step),
+can be applied multiple times without changing the result beyond the initial application.
+
+As an example, let us assume you have a Workflow that charges your customers, and you really do not want to charge them twice by accident. Before charging them, you should
+check if they were already charged:
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ const customer_id = 123456;
+ // ✅ Good: Non-idempotent API/Binding calls are always done **after** checking if the operation is
+ // still needed.
+ await step.do(
+ `charge ${customer_id} for its monthly subscription`,
+ async () => {
+ // API call to check if customer was already charged
+ const subscription = await fetch(
+ `https://payment.processor/subscriptions/${customer_id}`,
+ ).then((res) => res.json());
+
+ // return early if the customer was already charged, this can happen if the destination service dies
+ // in the middle of the request but still commits it, or if the Workflows Engine restarts.
+ if (subscription.charged) {
+ return;
+ }
+
+ // non-idempotent call, this operation can fail and retry but still commit in the payment
+ // processor - which means that, on retry, it would mischarge the customer again if the above checks
+ // were not in place.
+ return await fetch(
+ `https://payment.processor/subscriptions/${customer_id}`,
+ {
+ method: "POST",
+ body: JSON.stringify({ amount: 10.0 }),
+ },
+ );
+ },
+ );
+ }
+}
+```
+
+
+
+:::note
+
+Guaranteeing idempotency might be optional in your specific use-case and implementation, but we recommend that you always try to guarantee it.
+
+:::
+
+### Make your steps granular
+
+Steps should be as self-contained as possible. This allows your own logic to be more durable in case of failures in third-party APIs, network errors, and so on.
+
+You can also think of it as a transaction, or a unit of work.
+
+- ✅ Minimize the number of API/binding calls per step (unless you need multiple calls to prove idempotency).
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // ✅ Good: Unrelated API/Binding calls are self-contained, so that in case one of them fails
+ // it can retry them individually. It also has an extra advantage: you can control retry or
+ // timeout policies for each granular step - you might not to want to overload http.cat in
+ // case of it being down.
+ const httpCat = await step.do("get cutest cat from KV", async () => {
+ return await this.env.KV.get("cutest-http-cat");
+ });
+
+ const image = await step.do("fetch cat image from http.cat", async () => {
+ return await fetch(`https://http.cat/${httpCat}`);
+ });
+ }
+}
+```
+
+
+
+Otherwise, your entire Workflow might not be as durable as you might think, and you may encounter some undefined behaviour. You can avoid them by following the rules below:
+
+- 🔴 Do not encapsulate your entire logic in one single step.
+- 🔴 Do not call separate services in the same step (unless you need it to prove idempotency).
+- 🔴 Do not make too many service calls in the same step (unless you need it to prove idempotency).
+- 🔴 Do not do too much CPU-intensive work inside a single step - sometimes the engine may have to restart, and it will start over from the beginning of that step.
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: you are calling two separate services from within the same step. This might cause
+ // some extra calls to the first service in case the second one fails, and in some cases, makes
+ // the step non-idempotent altogether
+ const image = await step.do("get cutest cat from KV", async () => {
+ const httpCat = await this.env.KV.get("cutest-http-cat");
+ return fetch(`https://http.cat/${httpCat}`);
+ });
+ }
+}
+```
+
+
+
+### Do not rely on state outside of a step
+
+Workflows may hibernate and lose all in-memory state. This will happen when engine detects that there is no pending work and can hibernate until it needs to wake-up (because of a sleep, retry, or event).
+
+This means that you should not store state outside of a step:
+
+
+
+```ts
+function getRandomInt(min, max) {
+ const minCeiled = Math.ceil(min);
+ const maxFloored = Math.floor(max);
+ return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive
+}
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: `imageList` will be not persisted across engine's lifetimes. Which means that after hibernation,
+ // `imageList` will be empty again, even though the following two steps have already ran.
+ const imageList: string[] = [];
+
+ await step.do("get first cutest cat from KV", async () => {
+ const httpCat = await this.env.KV.get("cutest-http-cat-1");
+
+ imageList.push(httpCat);
+ });
+
+ await step.do("get second cutest cat from KV", async () => {
+ const httpCat = await this.env.KV.get("cutest-http-cat-2");
+
+ imageList.push(httpCat);
+ });
+
+ // A long sleep can (and probably will) hibernate the engine which means that the first engine lifetime ends here
+ await step.sleep("💤💤💤💤", "3 hours");
+
+ // When this runs, it will be on the second engine lifetime - which means `imageList` will be empty.
+ await step.do(
+ "choose a random cat from the list and download it",
+ async () => {
+ const randomCat = imageList.at(getRandomInt(0, imageList.length));
+ // this will fail since `randomCat` is undefined because `imageList` is empty
+ return await fetch(`https://http.cat/${randomCat}`);
+ },
+ );
+ }
+}
+```
+
+
+
+Instead, you should build top-level state exclusively comprised of `step.do` returns:
+
+
+
+```ts
+function getRandomInt(min, max) {
+ const minCeiled = Math.ceil(min);
+ const maxFloored = Math.floor(max);
+ return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive
+}
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // ✅ Good: imageList state is exclusively comprised of step returns - this means that in the event of
+ // multiple engine lifetimes, imageList will be built accordingly
+ const imageList: string[] = await Promise.all([
+ step.do("get first cutest cat from KV", async () => {
+ return await this.env.KV.get("cutest-http-cat-1");
+ }),
+
+ step.do("get second cutest cat from KV", async () => {
+ return await this.env.KV.get("cutest-http-cat-2");
+ }),
+ ]);
+
+ // A long sleep can (and probably will) hibernate the engine which means that the first engine lifetime ends here
+ await step.sleep("💤💤💤💤", "3 hours");
+
+ // When this runs, it will be on the second engine lifetime - but this time, imageList will contain
+ // the two most cutest cats
+ await step.do(
+ "choose a random cat from the list and download it",
+ async () => {
+ const randomCat = imageList.at(getRandomInt(0, imageList.length));
+ // this will eventually succeed since `randomCat` is defined
+ return await fetch(`https://http.cat/${randomCat}`);
+ },
+ );
+ }
+}
+```
+
+
+
+### Avoid doing side effects outside of a `step.do`
+
+It is not recommended to write code with any side effects outside of steps, unless you would like it to be repeated, because the Workflow engine may restart while an instance is running. If the engine restarts, the step logic will be preserved, but logic outside of the steps may be duplicated.
+
+For example, a `console.log()` outside of workflow steps may cause the logs to print twice when the engine restarts.
+
+However, logic involving non-serializable resources, like a database connection, should be executed outside of steps. Operations outside of a `step.do` might be repeated more than once, due to the nature of the Workflows' instance lifecycle.
+
+:::note
+If you use [Hyperdrive](/hyperdrive/) in a Workflow, create a new connection inside each `step.do()` and run your queries in that same step. Do not reuse a Hyperdrive-backed connection across steps.
+:::
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: creating instances outside of steps
+ // This might get called more than once creating more instances than expected
+ const badInstance = await this.env.ANOTHER_WORKFLOW.create();
+
+ // 🔴 Bad: using non-deterministic functions outside of steps
+ // this will produce different results if the instance has to restart, different runs of the same instance
+ // might go through different paths
+ const badRandom = Math.random();
+
+ if (badRandom > 0) {
+ // do some stuff
+ }
+
+ // ⚠️ Warning: This log may happen many times
+ console.log("This might be logged more than once");
+
+ await step.do("do some stuff and have a log for when it runs", async () => {
+ // do some stuff
+
+ // this log will only appear once
+ console.log("successfully did stuff");
+ });
+
+ // ✅ Good: wrap non-deterministic function in a step
+ // after running successfully will not run again
+ const goodRandom = await step.do("create a random number", async () => {
+ return Math.random();
+ });
+
+ // ✅ Good: calls that have no side effects can be done outside of steps
+ // For Hyperdrive, create the connection inside each step instead of here.
+ const db = createDBConnection(this.env.DB_URL, this.env.DB_TOKEN);
+
+ // ✅ Good: run functions with side effects inside of a step
+ // after running successfully will not run again
+ const goodInstance = await step.do(
+ "good step that returns state",
+ async () => {
+ const instance = await this.env.ANOTHER_WORKFLOW.create();
+
+ return instance;
+ },
+ );
+ }
+}
+```
+
+
+
+### Do not mutate your incoming events
+
+The `event` passed to your Workflow's `run` method is immutable: changes you make to the event are not persisted across steps and/or Workflow restarts.
+
+
+
+```ts
+interface MyEvent {
+ user: string;
+ data: string;
+}
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: Mutating the event
+ // This will not be persisted across steps and `event.payload` will
+ // take on its original value.
+ await step.do("bad step that mutates the incoming event", async () => {
+ let userData = await this.env.KV.get(event.payload.user);
+ event.payload = userData;
+ });
+
+ // ✅ Good: persist data by returning it as state from your step
+ // Use that state in subsequent steps
+ let userData = await step.do("good step that returns state", async () => {
+ return await this.env.KV.get(event.payload.user);
+ });
+
+ let someOtherData = await step.do(
+ "following step that uses that state",
+ async () => {
+ // Access to userData here
+ // Will always be the same if this step is retried
+ },
+ );
+ }
+}
+```
+
+
+
+### Name steps deterministically
+
+Steps should be named deterministically (that is, not using the current date/time, randomness, etc). This ensures that their state is cached, and prevents the step from being rerun unnecessarily. Step names act as the "cache key" in your Workflow.
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: Naming the step non-deterministically prevents it from being cached
+ // This will cause the step to be re-run if subsequent steps fail.
+ await step.do(`step #1 running at: ${Date.now()}`, async () => {
+ let userData = await this.env.KV.get(event.payload.user);
+ // Do not mutate event.payload
+ event.payload = userData;
+ });
+
+ // ✅ Good: give steps a deterministic name.
+ // Return dynamic values in your state, or log them instead.
+ let state = await step.do("fetch user data from KV", async () => {
+ let userData = await this.env.KV.get(event.payload.user);
+ console.log(`fetched at ${Date.now()}`);
+ return userData;
+ });
+
+ // ✅ Good: steps that are dynamically named are constructed in a deterministic way.
+ // In this case, `catList` is a step output, which is stable, and `catList` is
+ // traversed in a deterministic fashion (no shuffles or random accesses) so,
+ // it's fine to dynamically name steps (e.g: create a step per list entry).
+ let catList = await step.do("get cat list from KV", async () => {
+ return await this.env.KV.get("cat-list");
+ });
+
+ for (const cat of catList) {
+ await step.do(`get cat: ${cat}`, async () => {
+ return await this.env.KV.get(cat);
+ });
+ }
+ }
+}
+```
+
+
+
+### Take care with `Promise.race()` and `Promise.any()`
+
+Workflows allows the usage steps within the `Promise.race()` or `Promise.any()` methods as a way to achieve concurrent steps execution. However, some considerations must be taken.
+
+Due to the nature of Workflows' instance lifecycle, and given that a step inside a Promise will run until it finishes, the step that is returned during the first passage may not be the actual cached step, as [steps are cached by their names](#name-steps-deterministically).
+
+
+
+```ts
+// helper sleep method
+const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: The `Promise.race` is not surrounded by a `step.do`, which may cause undeterministic caching behavior.
+ const race_return = await Promise.race([
+ step.do("Promise first race", async () => {
+ await sleep(1000);
+ return "first";
+ }),
+ step.do("Promise second race", async () => {
+ return "second";
+ }),
+ ]);
+
+ await step.sleep("Sleep step", "2 hours");
+
+ return await step.do("Another step", async () => {
+ // This step will return `first`, even though the `Promise.race` first returned `second`.
+ return race_return;
+ });
+ }
+}
+```
+
+
+
+To ensure consistency, we suggest to surround the `Promise.race()` or `Promise.any()` within a `step.do()`, as this will ensure caching consistency across multiple passages.
+
+
+
+```ts
+// helper sleep method
+const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // ✅ Good: The `Promise.race` is surrounded by a `step.do`, ensuring deterministic caching behavior.
+ const race_return = await step.do("Promise step", async () => {
+ return await Promise.race([
+ step.do("Promise first race", async () => {
+ await sleep(1000);
+ return "first";
+ }),
+ step.do("Promise second race", async () => {
+ return "second";
+ }),
+ ]);
+ });
+
+ await step.sleep("Sleep step", "2 hours");
+
+ return await step.do("Another step", async () => {
+ // This step will return `second` because the `Promise.race` was surround by the `step.do` method.
+ return race_return;
+ });
+ }
+}
+```
+
+
+
+### Instance IDs are unique
+
+Workflow [instance IDs](/workflows/build/workers-api/#workflowinstance) are unique per Workflow. The ID is the unique identifier that associates logs, metrics, state and status of a run to a specific instance, even after completion. Allowing ID re-use would make it hard to understand if a Workflow instance ID referred to an instance that run yesterday, last week or today.
+
+It would also present a problem if you wanted to run multiple different Workflow instances with different [input parameters](/workflows/build/events-and-parameters/) for the same user ID, as you would immediately need to determine a new ID mapping.
+
+If you need to associate multiple instances with a specific user, merchant or other "customer" ID in your system, consider using a composite ID or using randomly generated IDs and storing the mapping in a database like [D1](/d1/).
+
+
+
+```ts
+// This is in the same file as your Workflow definition
+export default {
+ async fetch(req: Request, env: Env): Promise {
+ // 🔴 Bad: Use an ID that isn't unique across future Workflow invocations
+ let userId = getUserId(req); // Returns the userId
+ let badInstance = await env.MY_WORKFLOW.create({
+ id: userId,
+ params: payload,
+ });
+
+ // ✅ Good: use an ID that is unique
+ // e.g. a transaction ID, order ID, or task ID are good options
+ let instanceId = getTransactionId(); // e.g. assuming transaction IDs are unique
+ // or: compose a composite ID and store it in your database
+ // so that you can track all instances associated with a specific user or merchant.
+ instanceId = `${getUserId(req)}-${crypto.randomUUID().slice(0, 6)}`;
+ let { result } = await addNewInstanceToDB(userId, instanceId);
+ let goodInstance = await env.MY_WORKFLOW.create({
+ id: instanceId,
+ params: payload,
+ });
+
+ return Response.json({
+ id: goodInstance.id,
+ details: await goodInstance.status(),
+ });
+ },
+};
+```
+
+
+
+### `await` your steps
+
+When calling `step.do` or `step.sleep`, use `await` to avoid introducing bugs and race conditions into your Workflow code.
+
+If you don't call `await step.do` or `await step.sleep`, you create a dangling Promise. This occurs when a Promise is created but not properly `await`ed, leading to potential bugs and race conditions.
+
+This happens when you do not use the `await` keyword or fail to chain `.then()` methods to handle the result of a Promise. For example, calling `fetch(GITHUB_URL)` without awaiting its response will cause subsequent code to execute immediately, regardless of whether the fetch completed. This can cause issues like premature logging, exceptions being swallowed (and not terminating the Workflow), and lost return values (state).
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: The step isn't await'ed, and any state or errors is swallowed before it returns.
+ const badIssues = step.do(`fetch issues from GitHub`, async () => {
+ // The step will return before this call is done
+ let issues = await getIssues(event.payload.repoName);
+ return issues;
+ });
+
+ // ✅ Good: The step is correctly await'ed.
+ const goodIssues = await step.do(`fetch issues from GitHub`, async () => {
+ let issues = await getIssues(event.payload.repoName);
+ return issues;
+ });
+
+ // Rest of your Workflow goes here!
+ }
+}
+```
+
+
+
+### Use conditional logic carefully
+
+You can use `if` statements, loops, and other control flow outside of steps. However, conditions must be based on **deterministic values** — either values from `event.payload` or return values from previous steps. Non-deterministic conditions (such as `Math.random()` or `Date.now()`) outside of steps can cause unexpected behavior if the Workflow restarts.
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ const config = await step.do("fetch config", async () => {
+ return await this.env.KV.get("feature-flags", { type: "json" });
+ });
+
+ // ✅ Good: Condition based on step output (deterministic)
+ if (config.enableEmailNotifications) {
+ await step.do("send email", async () => {
+ // Send email logic
+ });
+ }
+
+ // ✅ Good: Condition based on event payload (deterministic)
+ if (event.payload.userType === "premium") {
+ await step.do("premium processing", async () => {
+ // Premium-only logic
+ });
+ }
+
+ // 🔴 Bad: Condition based on non-deterministic value outside a step
+ // This could behave differently if the Workflow restarts
+ if (Math.random() > 0.5) {
+ await step.do("maybe do something", async () => {});
+ }
+
+ // ✅ Good: Wrap non-deterministic values in a step
+ const shouldProcess = await step.do("decide randomly", async () => {
+ return Math.random() > 0.5;
+ });
+ if (shouldProcess) {
+ await step.do("conditionally do something", async () => {});
+ }
+ }
+}
+```
+
+
+
+### Batch multiple Workflow invocations
+
+When creating multiple Workflow instances, use the [`createBatch`](/workflows/build/workers-api/#createBatch) method to batch the invocations together. This allows you to create multiple Workflow instances in a single request, which will reduce the number of requests made to the Workflows API. However, each individual instance in the batch will still count towards the [creation rate limit](/workflows/reference/limits/). Unlike `create`, `createBatch` is idempotent: if an existing instance with the same ID is still within its [retention limit](/workflows/reference/limits/), it will be skipped and excluded from the returned array.
+
+
+
+```ts
+export default {
+ async fetch(req: Request, env: Env): Promise {
+ let instances = [
+ { id: "user1", params: { name: "John" } },
+ { id: "user2", params: { name: "Jane" } },
+ { id: "user3", params: { name: "Alice" } },
+ { id: "user4", params: { name: "Bob" } },
+ ];
+
+ // 🔴 Bad: Create them one by one, which is more likely to hit creation rate limits.
+ for (let instance of instances) {
+ await env.MY_WORKFLOW.create({
+ id: instance.id,
+ params: instance.params,
+ });
+ }
+
+ // ✅ Good: Batch calls together
+ // This improves throughput.
+ let createdInstances = await env.MY_WORKFLOW.createBatch(instances);
+ return Response.json({ instances: createdInstances });
+ },
+};
+```
+
+
+
+### Limit timeouts to 30 minutes or less
+
+When setting a [WorkflowStep timeout](/workflows/build/workers-api/#workflowstep), ensure that its duration is 30 minutes or less. If your use case requires a timeout greater than 30 minutes, consider using `step.waitForEvent()` instead.
+
+### Keep non-stream step return values under 1 MiB
+
+A non-stream `step.do()` return value can persist up to 1 MiB (2^20 bytes). If your step returns structured data exceeding this limit, the step will fail. This is a common issue when fetching large API responses or processing large files.
+
+In JavaScript Workflows, `ReadableStream` is a supported serializable return type for larger binary output. When persisting this kind of output, you should:
+
+- Return a new stream from the step callback.
+- Keep individual chunks under 16 MB.
+- Do not return a locked stream or a stream that has already been read.
+- Rely only on streams returned from steps.
+ :::note
+ Only byte streams are supported - use `ReadableStream`.
+
+BYOB streams and BYOB readers are not supported.
+:::
+
+Note that streamed outputs are still considered part of the Workflow instance storage limit.
+
+If these storage limits still do not work for you, consider storing your step outputs externally (for example, in [R2](/r2)) and saving a reference to it.
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // 🔴 Bad: Returning a large response that may exceed 1 MiB
+ const largeData = await step.do("fetch large dataset", async () => {
+ const response = await fetch("https://api.example.com/large-dataset");
+ return await response.json(); // Could exceed 1 MiB
+ });
+
+ // ✅ Good: Store large structured data externally and return a reference
+ const dataRef = await step.do("fetch and store large dataset", async () => {
+ const response = await fetch("https://api.example.com/large-dataset");
+ const data = await response.json();
+ // Store in R2 and return a reference
+ await this.env.MY_BUCKET.put("dataset-123", JSON.stringify(data));
+ return { key: "dataset-123" };
+ });
+
+ // Retrieve the data in a later step when needed
+ const data = await step.do("process dataset", async () => {
+ const stored = await this.env.MY_BUCKET.get(dataRef.key);
+ return processData(await stored.json());
+ });
+ }
+}
+```
+
+
+
+## Related resources
+
+- [Workers Best Practices](/workers/best-practices/workers-best-practices/): code patterns for request handling, observability, and security that apply to the Workers triggering your Workflows.
+- [Rules of Durable Objects](/durable-objects/best-practices/rules-of-durable-objects/): best practices for stateful, coordinated applications — useful when combining Durable Objects with Workflows.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/sleeping-and-retrying.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/sleeping-and-retrying.md
new file mode 100644
index 0000000..580d207
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/sleeping-and-retrying.md
@@ -0,0 +1,253 @@
+---
+title: Sleeping and retrying
+description: Configure sleep durations and retry logic for Workflows steps, including relative and absolute sleep timers.
+pcx_content_type: concept
+sidebar:
+ order: 4
+products:
+ - workflows
+---
+
+import { TypeScriptExample } from "~/components";
+
+This guide details how to sleep a Workflow and/or configure retries for a Workflow step.
+
+## Sleep a Workflow
+
+You can set a Workflow to sleep as an explicit step, which can be useful when you want a Workflow to wait, schedule work ahead, or pause until an input or other external state is ready.
+
+:::note
+
+A Workflow instance that is resuming from sleep will take priority over newly scheduled (queued) instances. This helps ensure that older Workflow instances can run to completion and are not blocked by newer instances.
+
+:::
+
+### Sleep for a relative period
+
+Use `step.sleep` to have a Workflow sleep for a relative period of time:
+
+```ts
+await step.sleep("sleep for a bit", "1 hour");
+```
+
+The second argument to `step.sleep` accepts both `number` (milliseconds) or a human-readable format, such as "1 minute" or "26 hours". The accepted units for `step.sleep` when used this way are as follows:
+
+```ts
+| "second"
+| "minute"
+| "hour"
+| "day"
+| "week"
+| "month"
+| "year"
+```
+
+### Sleep until a fixed date
+
+Use `step.sleepUntil` to have a Workflow sleep to a specific `Date`: this can be useful when you have a timestamp from another system or want to "schedule" work to occur at a specific time (e.g. Sunday, 9AM UTC).
+
+```ts
+// sleepUntil accepts a Date object as its second argument
+const workflowsLaunchDate = Date.parse("24 Oct 2024 13:00:00 UTC");
+await step.sleepUntil("sleep until X times out", workflowsLaunchDate);
+```
+
+You can also provide a UNIX timestamp (milliseconds since the UNIX epoch) directly to `sleepUntil`.
+
+## Retry steps
+
+Each call to `step.do` in a Workflow accepts an optional `StepConfig`, which allows you define the retry behaviour for that step.
+
+If you do not provide your own retry configuration, Workflows applies the following defaults:
+
+```ts
+const defaultConfig: WorkflowStepConfig = {
+ retries: {
+ limit: 5,
+ delay: 10000,
+ backoff: "exponential",
+ },
+ timeout: "10 minutes",
+};
+```
+
+When providing your own `StepConfig`, you can configure:
+
+- The total number of attempts to make for a step (limited to 10,000 retries per step)
+- The delay between attempts. Use a fixed duration as a `number` in milliseconds or a human-readable string, or use a function that returns the next delay.
+- What backoff algorithm to apply between each attempt: any of `constant`, `linear`, or `exponential`
+- When to timeout (in duration) before considering the step as failed (including during a retry attempt, as the timeout is set per attempt)
+
+For example, to limit a step to 10 retries and have it apply an exponential delay (starting at 10 seconds) between each attempt, you would pass the following configuration as an optional object to `step.do`:
+
+```ts
+let someState = await step.do(
+ "call an API",
+ {
+ retries: {
+ limit: 10, // The total number of attempts
+ delay: "10 seconds", // Delay between each retry
+ backoff: "exponential", // Any of "constant" | "linear" | "exponential";
+ },
+ timeout: "30 minutes",
+ },
+ async () => {
+ /* Step code goes here */
+ },
+);
+```
+
+### Set a dynamic retry delay
+
+Use a delay function when the next retry delay should depend on the failed attempt or the thrown error. This gives you more control than a fixed delay with `constant`, `linear`, or `exponential` backoff. It is useful for rate limits, downstream provider recovery, and short network failures.
+
+The delay function receives an object with:
+
+- `ctx` - the current [`WorkflowStepContext`](/workflows/build/step-context/), including `ctx.attempt`.
+- `error` - the error that caused the retry.
+
+Return a duration string, a number in milliseconds, or a promise that resolves to either value.
+
+
+
+```ts
+await step.do(
+ "sync customer",
+ {
+ retries: {
+ limit: 5,
+ delay: ({ ctx, error }) => {
+ if (error.message.includes("rate limit")) {
+ return `${ctx.attempt * 30} seconds`;
+ }
+
+ return "10 seconds";
+ },
+ },
+ },
+ async () => {
+ await syncCustomer();
+ },
+);
+```
+
+
+
+## Force a Workflow instance to fail
+
+You can also force a Workflow instance to fail and _not_ retry by throwing a `NonRetryableError` from within the step.
+
+This can be useful when you detect a terminal (permanent) error from an upstream system (such as an authentication failure) or other errors where retrying would not help.
+
+```ts
+// Import the NonRetryableError definition
+import {
+ WorkflowEntrypoint,
+ WorkflowStep,
+ WorkflowEvent,
+} from "cloudflare:workers";
+import { NonRetryableError } from "cloudflare:workflows";
+
+// In your step code:
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ await step.do("some step", async () => {
+ if (!event.payload.data) {
+ throw new NonRetryableError(
+ "event.payload.data did not contain the expected payload",
+ );
+ }
+ });
+ }
+}
+```
+
+The Workflow instance itself will fail immediately, no further steps will be invoked, and the Workflow will not be retried.
+
+If earlier steps registered rollback handlers, those handlers will still run before the instance settles into its terminal state.
+
+## Register rollback handlers
+
+You can attach a rollback handler to `step.do()` to implement saga-style compensation. When the Workflow later fails, Workflows runs registered rollback handlers in reverse `step-start` order.
+
+A failed step with rollback options can also participate in rollback alongside any completed steps which have a rollback handler registered. For example, if a steps throws a `NonRetryableError` after registering rollback, its rollback handler runs with `output` set to `undefined`.
+
+
+
+```ts
+import {
+ WorkflowEntrypoint,
+ type WorkflowEvent,
+ type WorkflowStep,
+} from "cloudflare:workers";
+import { NonRetryableError } from "cloudflare:workflows";
+
+export class OrderWorkflow extends WorkflowEntrypoint {
+ async run(_event: WorkflowEvent, step: WorkflowStep) {
+ await step.do(
+ "reserve inventory",
+ async () => {
+ const reservation = await reserveInventory();
+ return { reservationId: reservation.id };
+ },
+ {
+ rollback: async ({ output }) => {
+ const { reservationId } = output as { reservationId: string };
+ await releaseInventory(reservationId);
+ },
+ rollbackConfig: {
+ retries: { limit: 3, delay: "10 seconds", backoff: "linear" },
+ timeout: "2 minutes",
+ },
+ },
+ );
+
+ await step.do("charge card", async () => {
+ throw new NonRetryableError("payment processor rejected the charge");
+ });
+ }
+}
+```
+
+
+
+Rollback handlers receive:
+
+- `error` - the error that caused the Workflow to fail.
+- `output` - the value returned by the forward step, or `undefined` if the step failed before returning
+
+You can use `rollbackConfig` to control retry behavior for the rollback handler. Throw a `NonRetryableError` from the rollback handler to stop retrying it immediately.
+
+## Catch Workflow errors
+
+Any uncaught exceptions that propagate to the top level, or any steps that reach their retry limit, will cause the Workflow to end execution in an `Errored` state.
+
+If you want to avoid this, you can catch exceptions emitted by a `step`. This can be useful if you need to trigger clean-up tasks or have conditional logic that triggers additional steps.
+
+To allow the Workflow to continue its execution, surround the intended steps that are allowed to fail with a `try...catch` block.
+
+```ts
+...
+await step.do('task', async () => {
+ // work to be done
+});
+
+try {
+ await step.do('non-retryable-task', async () => {
+ // work not to be retried
+ throw new NonRetryableError('oh no');
+ });
+} catch (e) {
+ console.log(`Step failed: ${e.message}`);
+ await step.do('clean-up-task', async () => {
+ // Clean up code here
+ });
+}
+
+// the Workflow will not fail and will continue its execution
+
+await step.do('next-task', async() => {
+ // more work to be done
+});
+...
+```
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/step-context.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/step-context.md
new file mode 100644
index 0000000..18bd00f
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/step-context.md
@@ -0,0 +1,114 @@
+---
+title: Step context
+description: Access runtime information in Workflows steps using the WorkflowStepContext object, including step name and retry attempt.
+pcx_content_type: concept
+sidebar:
+ order: 5
+products:
+ - workflows
+---
+
+Every `step.do` callback receives a **context object** (`WorkflowStepContext`) as its first argument. The context gives your step code runtime information about the step itself, the current retry attempt, and the resolved configuration for that step.
+
+## WorkflowStepContext
+
+```ts
+type WorkflowStepContext = {
+ step: {
+ name: string;
+ count: number;
+ };
+ attempt: number;
+ config: WorkflowStepConfig;
+};
+```
+
+### Properties
+
+| Property | Type | Description |
+| ------------ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
+| `step.name` | `string` | The name you passed to `step.do`. |
+| `step.count` | `number` | How many times `step.do` has been called with this name so far in the current Workflow run. Starts at `1` for the first call with a given name. |
+| `attempt` | `number` | The current attempt number (1-indexed). `1` on the first try, `2` on the first retry, and so on. |
+| `config` | [`WorkflowStepConfig`](/workflows/build/workers-api/#workflowstepconfig) | The resolved retry and timeout configuration for this step, including any defaults applied by the runtime. |
+
+If a step config's `retries.delay` is a function, the dynamic delay is not exposed on `ctx.config.retries.delay`. The delay function receives its own context object with the current step context and the error that caused the retry.
+
+## Access the context
+
+Pass a parameter to your `step.do` callback to receive the context object:
+
+```ts
+await step.do("my-step", async (ctx) => {
+ console.log(ctx.step.name); // "my-step"
+ console.log(ctx.step.count); // 1
+ console.log(ctx.attempt); // 1 on first try, 2 on first retry, etc.
+ console.log(ctx.config); // { retries: { limit: 5, ... }, timeout: "10 minutes" }
+});
+```
+
+The context is also available when you pass a custom `WorkflowStepConfig`:
+
+```ts
+await step.do(
+ "call an API",
+ {
+ retries: {
+ limit: 10,
+ delay: "10 seconds",
+ backoff: "exponential",
+ },
+ timeout: "30 minutes",
+ },
+ async (ctx) => {
+ console.log(ctx.config.retries.limit); // 10
+ console.log(ctx.config.timeout); // "30 minutes"
+ },
+);
+```
+
+To configure delay functions, refer to [Set a dynamic retry delay](/workflows/build/sleeping-and-retrying/#set-a-dynamic-retry-delay).
+
+## Examples
+
+### Adjust behavior based on retry attempt
+
+Use `ctx.attempt` to change how your step behaves on retries. For example, you might use a fallback endpoint after a certain number of retries:
+
+```ts
+await step.do(
+ "fetch data",
+ { retries: { limit: 5, delay: "5 seconds", backoff: "linear" } },
+ async (ctx) => {
+ const url =
+ ctx.attempt <= 3
+ ? "https://api.example.com/primary"
+ : "https://api.example.com/fallback";
+
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(`Request failed with status ${response.status}`);
+ }
+ return await response.json();
+ },
+);
+```
+
+### Log step metadata for observability
+
+Use `ctx.step` to add structured metadata to your logs:
+
+```ts
+await step.do("process-order", async (ctx) => {
+ console.log(
+ JSON.stringify({
+ step: ctx.step.name,
+ stepCount: ctx.step.count,
+ attempt: ctx.attempt,
+ retryLimit: ctx.config.retries?.limit,
+ }),
+ );
+
+ // Your step logic here
+});
+```
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/trigger-workflows.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/trigger-workflows.md
new file mode 100644
index 0000000..4ce0805
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/trigger-workflows.md
@@ -0,0 +1,293 @@
+---
+title: Trigger Workflows
+description: Trigger Workflows from Workers bindings, the REST API, or the Wrangler CLI.
+pcx_content_type: concept
+tags:
+ - Bindings
+sidebar:
+ order: 3
+products:
+ - workflows
+---
+
+import { TypeScriptExample, WranglerConfig } from "~/components";
+
+You can trigger Workflows both programmatically and via the Workflows APIs, including:
+
+1. With [Workers](/workers) via HTTP requests in a `fetch` handler, or bindings from a `queue` or `scheduled` handler
+2. On a recurring interval by defining `schedules` on a Workflow binding in your Wrangler configuration
+3. Using the [Workflows REST API](/api/resources/workflows/methods/list/)
+4. Via the [wrangler CLI](/workers/wrangler/commands/workflows/#workflows) in your terminal
+
+## Workers API (Bindings)
+
+You can interact with Workflows programmatically from any Worker script by creating a binding to a Workflow. A Worker can bind to multiple Workflows, including Workflows defined in other Workers projects (scripts) within your account.
+
+You can trigger a Workflow:
+
+- Directly over HTTP via the [`fetch`](/workers/runtime-apis/handlers/fetch/) handler
+- From a [Queue consumer](/queues/configuration/javascript-apis/#consumer) inside a `queue` handler
+- On a recurring schedule by defining `schedules` on the Workflow binding in `wrangler.jsonc`
+- From a [Cron Trigger](/workers/configuration/cron-triggers/) inside a `scheduled` handler
+- Within a [Durable Object](/durable-objects/)
+
+:::note
+
+New to Workflows? Start with the [Workflows tutorial](/workflows/get-started/guide/) to deploy your first Workflow and familiarize yourself with Workflows concepts.
+
+:::
+
+To bind to a Workflow from your Workers code, you need to define a [binding](/workers/wrangler/configuration/) to a specific Workflow. For example, to bind to the Workflow defined in the [get started guide](/workflows/get-started/guide/), you would configure the [Wrangler configuration file](/workers/wrangler/configuration/) with the below:
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "workflows-tutorial",
+ "main": "src/index.ts",
+ "compatibility_date": "$today",
+ "workflows": [
+ {
+ // The name of the Workflow
+ "name": "workflows-tutorial",
+ // The binding name, which must be a valid JavaScript variable name. This will
+ // be how you call (run) your Workflow from your other Workers handlers or
+ // scripts.
+ "binding": "MY_WORKFLOW",
+ // Must match the class defined in your code that extends the Workflow class
+ "class_name": "MyWorkflow"
+ }
+ ]
+}
+```
+
+
+
+The `binding = "MY_WORKFLOW"` line defines the JavaScript variable that our Workflow methods are accessible on, including `create` (which triggers a new instance) or `get` (which returns the status of an existing instance).
+
+### Schedule a Workflow directly
+
+If you want to create Workflow instances on a recurring interval, add a `schedules` array (up to 100 cron expressions per account) to the Workflow binding in your Wrangler configuration:
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "workflows-tutorial",
+ "main": "src/index.ts",
+ "compatibility_date": "$today",
+ "workflows": [
+ {
+ "name": "workflows-tutorial",
+ "binding": "MY_WORKFLOW",
+ "class_name": "MyWorkflow",
+ "schedules": ["0 * * * *"]
+ }
+ ]
+}
+```
+
+
+
+Each matching cron expression creates a new Workflow instance automatically. Use this when you want to run a Workflow on a schedule without defining top-level `triggers.crons` and a separate `scheduled` handler.
+
+Scheduled instances include the matching cron expression and scheduled trigger time on `event.schedule`:
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ if (event.schedule) {
+ console.log(event.schedule.cron);
+ console.log(new Date(event.schedule.scheduledTime));
+ }
+ }
+}
+```
+
+On [Workers Paid](/workers/platform/pricing/#workers), Workflow instances created by `schedules` can run for up to one hour per cron firing without consuming a Workflow concurrency slot. If the instance pauses or sleeps after that window, the instance yields and enters the normal concurrency queue upon resume. It resumes when a concurrency slot is available.
+
+Use the latest Wrangler release when configuring Workflow schedules. If your local Wrangler schema does not recognize `schedules` yet, update Wrangler before deploying.
+
+The following example shows how you can manage Workflows from within a Worker, including:
+
+- Retrieving the status of an existing Workflow instance by its ID
+- Creating (triggering) a new Workflow instance
+- Returning the status of a given instance ID
+
+```ts title="src/index.ts"
+interface Env {
+ MY_WORKFLOW: Workflow;
+}
+
+export default {
+ async fetch(req: Request, env: Env) {
+ // Get instanceId from query parameters
+ const instanceId = new URL(req.url).searchParams.get("instanceId");
+
+ // If an ?instanceId= query parameter is provided, fetch the status
+ // of an existing Workflow by its ID.
+ if (instanceId) {
+ let instance = await env.MY_WORKFLOW.get(instanceId);
+ return Response.json({
+ status: await instance.status(),
+ });
+ }
+
+ // Else, create a new instance of our Workflow, passing in any (optional)
+ // params and return the ID.
+ const newId = crypto.randomUUID();
+ let instance = await env.MY_WORKFLOW.create({ id: newId });
+ return Response.json({
+ id: instance.id,
+ details: await instance.status(),
+ });
+ },
+};
+```
+
+### Inspect a Workflow's status
+
+You can inspect the status of any running Workflow instance by calling `status` against a specific instance ID. This allows you to programmatically inspect whether an instance is queued (waiting to be scheduled), actively running, paused, or errored.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+let status = await instance.status(); // Returns an InstanceStatus
+```
+
+The possible values of status are as follows:
+
+```ts
+ status:
+ | "queued" // means that instance is waiting to be started (see concurrency limits)
+ | "running"
+ | "paused"
+ | "errored"
+ | "terminated" // user terminated the instance while it was running
+ | "complete"
+ | "waiting" // instance is hibernating and waiting for sleep or event to finish
+ | "waitingForPause" // instance is finishing the current work to pause
+ | "unknown";
+ error?: {
+ name: string,
+ message: string
+ };
+ output?: unknown;
+ rollback:
+ | {
+ outcome: "complete" | "failed";
+ error: {
+ name: string,
+ message: string,
+ } | null,
+ }
+ | null;
+```
+
+If your Workflow registers rollback handlers on `step.do()`, inspect `rollback` after the instance finishes to see whether the compensating steps completed successfully. While rollback is actively running, the Workers API continues to return `status: "running"`.
+
+### Explicitly pause a Workflow
+
+You can explicitly pause a Workflow instance (and later resume it) by calling `pause` against a specific instance ID.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+await instance.pause(); // Returns Promise
+```
+
+### Resume a Workflow
+
+You can resume a paused Workflow instance by calling `resume` against a specific instance ID.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+await instance.resume(); // Returns Promise
+```
+
+Calling `resume` on an instance that is not currently paused will have no effect.
+
+:::caution
+If you have reached the maximum concurrent instances for your Workflow, resuming an instance may not restart it immediately. The instance will be queued until a concurrency slot becomes available.
+:::
+
+### Stop a Workflow
+
+You can stop/terminate a Workflow instance by calling `terminate` against a specific instance ID.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+await instance.terminate(); // Returns Promise
+```
+
+To run registered rollback handlers before terminating, pass `rollback: true`:
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+await instance.terminate({ rollback: true }); // Returns Promise
+```
+
+You can also run rollback handlers from Wrangler:
+
+```sh
+npx wrangler workflows instances terminate --rollback
+# For a local Workflows instance during wrangler dev:
+npx wrangler workflows instances terminate --local --rollback
+```
+
+Once stopped/terminated, the Workflow instance _cannot_ be resumed.
+
+### Restart a Workflow
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+await instance.restart(); // Returns Promise
+```
+
+Restarting an instance will immediately cancel any in-progress steps, erase any intermediate state, and treat the Workflow as if it was run for the first time.
+
+To restart an instance from a specific step instead of the beginning, refer to [`restart`](/workflows/build/workers-api/#restart) in the Workers API reference.
+
+### Trigger a Workflow from another Workflow
+
+You can create a new Workflow instance from within a step of another Workflow. The parent Workflow will not block waiting for the child Workflow to complete — it continues execution immediately after the child instance is successfully created.
+
+
+
+```ts
+export class ParentWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // Perform initial work
+ const result = await step.do("initial processing", async () => {
+ // ... processing logic
+ return { fileKey: "output.pdf" };
+ });
+
+ // Trigger a child workflow for additional processing
+ const childInstance = await step.do("trigger child workflow", async () => {
+ return await this.env.CHILD_WORKFLOW.create({
+ id: `child-${event.instanceId}`,
+ params: { fileKey: result.fileKey },
+ });
+ });
+
+ // Parent continues immediately - not blocked by child workflow
+ await step.do("continue with other work", async () => {
+ console.log(`Started child workflow: ${childInstance.id}`);
+ // This runs right away, regardless of child workflow status
+ });
+ }
+}
+```
+
+
+
+If the child Workflow fails to start, the step will fail and be retried according to your retry configuration. Once the child instance is successfully created, it runs independently from the parent.
+
+## REST API (HTTP)
+
+Refer to the [Workflows REST API documentation](/api/resources/workflows/subresources/instances/methods/create/).
+
+## Command line (CLI)
+
+Refer to the [CLI quick start](/workflows/get-started/guide/) to learn more about how to manage and trigger Workflows via the command-line.
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/workers-api.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/workers-api.md
new file mode 100644
index 0000000..4988c27
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/build/workers-api.md
@@ -0,0 +1,796 @@
+---
+title: Workers API
+description: Reference for the Workflows Workers API, including WorkflowEntrypoint, step methods, and instance management.
+pcx_content_type: concept
+sidebar:
+ order: 2
+products:
+ - workflows
+---
+
+import {
+ MetaInfo,
+ Render,
+ Type,
+ TypeScriptExample,
+ WranglerConfig,
+} from "~/components";
+
+This guide details the Workflows API within Cloudflare Workers, including methods, types, and usage examples.
+
+## WorkflowEntrypoint
+
+The `WorkflowEntrypoint` class is the core element of a Workflow definition. A Workflow must extend this class and define a `run` method with at least one `step` call to be considered a valid Workflow.
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // Steps here
+ }
+}
+```
+
+### run
+
+- run(event: WorkflowEvent<T>, step: WorkflowStep): Promise<T>
+ - `event` - the event passed to the Workflow, including an optional `payload` containing data (parameters)
+ - `step` - the `WorkflowStep` type that provides the step methods for your Workflow
+
+The `run` method can optionally return data, which is available when querying the instance status via the [Workers API](/workflows/build/workers-api/#instancestatus), [REST API](/api/resources/workflows/subresources/instances/subresources/status/) and the Workflows dashboard. This can be useful if your Workflow is computing a result, returning the key to data stored in object storage, or generating some kind of identifier you need to act on.
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // Steps here
+ let someComputedState = await step.do("my step", async () => {});
+
+ // Optional: return state from our run() method
+ return someComputedState;
+ }
+}
+```
+
+The `WorkflowEvent` type accepts an optional [type parameter](https://www.typescriptlang.org/docs/handbook/2/generics.html#working-with-generic-type-variables) that allows you to provide a type for the `payload` property within the `WorkflowEvent`.
+
+Refer to the [events and parameters](/workflows/build/events-and-parameters/) documentation for how to handle events within your Workflow code.
+
+Finally, any JS control-flow primitive (if conditions, loops, `try...catch` blocks, promises, and more) can be used to manage steps inside the `run` method.
+
+## WorkflowEvent
+
+```ts
+export type WorkflowCronSchedule = {
+ /** Cron expression that triggered this event. */
+ cron: string;
+ /** Timestamp of the scheduled trigger, in milliseconds since the Unix epoch. */
+ scheduledTime: number;
+};
+
+export type WorkflowEvent = {
+ payload: Readonly;
+ timestamp: Date;
+ instanceId: string;
+ workflowName: string;
+ schedule?: WorkflowCronSchedule;
+};
+```
+
+- The `WorkflowEvent` is the first argument to a Workflow's `run` method.
+ - `payload` - a default type of `any` or type `T` if a type parameter is provided.
+ - `timestamp` - a `Date` object set to the time the Workflow instance was created (triggered).
+ - `instanceId` - the ID of the associated instance.
+ - `workflowName` - the name of the associated Workflow.
+ - `schedule` - metadata for Workflow instances created by a cron schedule, including the `cron` expression and `scheduledTime` in milliseconds since the Unix epoch.
+
+Refer to the [events and parameters](/workflows/build/events-and-parameters/) documentation for how to handle events within your Workflow code.
+
+## WorkflowStep
+
+### step
+
+{/* prettier-ignore */}
+- step.do(name: string, callback: (ctx: WorkflowStepContext): RpcSerializable): Promise<T>
+- step.do(name: string, callback: (ctx: WorkflowStepContext): RpcSerializable, rollbackOptions?: WorkflowStepRollbackOptions<T>): Promise<T>
+- step.do(name: string, config?: WorkflowStepConfig, callback: (ctx: WorkflowStepContext):
+ RpcSerializable): Promise<T>
+ - `name` - the name of the step, up to 256 characters.
+ - `config` (optional) - an optional `WorkflowStepConfig` for configuring [step specific retry behaviour](/workflows/build/sleeping-and-retrying/).
+ - `callback` - an asynchronous function that receives a [`WorkflowStepContext`](/workflows/build/step-context/) and optionally returns serializable state for the Workflow to persist. In JavaScript Workflows, this includes a fresh, unlocked `ReadableStream` for large binary output.
+- step.do(name: string, config?: WorkflowStepConfig, callback: (ctx: WorkflowStepContext):
+ RpcSerializable, rollbackOptions?: WorkflowStepRollbackOptions<T>): Promise<T>
+ - `name` - the name of the step, up to 256 characters.
+ - `config` (optional) - an optional `WorkflowStepConfig` for configuring [step specific retry behaviour](/workflows/build/sleeping-and-retrying/).
+ - `callback` - an asynchronous function that receives a [`WorkflowStepContext`](/workflows/build/step-context/) and optionally returns serializable state for the Workflow to persist. In JavaScript Workflows, this includes a fresh, unlocked `ReadableStream` for large binary output.
+ - `rollbackOptions` (optional) - register rollback logic for the step. If the Workflow later fails, registered rollbacks run in reverse step-start order.
+
+:::note[Returning state]
+
+When returning state from a `step`, ensure that the object you return is _serializable_.
+
+Primitive types like `string`, `number`, and `boolean`, along with composite structures such as `Array` and `Object` (provided they only contain serializable values), can be serialized. Any [structured-cloneable](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) type can be serialized, as long it is no longer than 1 MB.
+
+On the other hand, objects that include `Function` or `Symbol` types, and objects with circular references, cannot be serialized. The Workflow instance will throw an error if objects with those types is returned.
+
+In JavaScript Workflows, `ReadableStream` is a supported serializable return type when a step needs to persist larger binary output than the normal 1 MiB non-stream step-result limit.
+
+Return a new stream from the callback.
+
+:::caution
+Do not return a locked stream or a stream that has already been read. BYOB streams and BYOB readers are not supported.
+:::
+
+After a `ReadableStream` object has been persisted within a step, it should not be reused - rely on the new fresh stream that gets returned from step. The bytes are preserved from the original stream, but the implementation might differ.
+
+:::
+
+
+
+```ts
+type Env = {
+ MY_BUCKET: R2Bucket;
+};
+
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(_event: WorkflowEvent, step: WorkflowStep) {
+ const reportStream = await step.do("read report from R2", async () => {
+ const object = await this.env.MY_BUCKET.get("reports/latest.csv");
+
+ if (!object?.body) {
+ throw new Error("Could not read reports/latest.csv from R2.");
+ }
+
+ return object.body;
+ });
+
+ const preview = await new Response(reportStream).text();
+ return { preview };
+ }
+}
+```
+
+
+
+- step.sleep(name: string, duration: WorkflowDuration): Promise<void>
+ - `name` - the name of the step.
+ - `duration` - the duration to sleep until, in either seconds or as a `WorkflowDuration` compatible string.
+ - Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how Workflows are retried.
+
+- step.sleepUntil(name: string, timestamp: Date | number): Promise<void>
+ - `name` - the name of the step.
+ - `timestamp` - a JavaScript `Date` object or milliseconds from the Unix epoch to sleep the Workflow instance until.
+
+:::note
+
+`step.sleep` and `step.sleepUntil` methods do not count towards the maximum Workflow steps limit.
+
+More information about the limits imposed on Workflow can be found in the [Workflows limits documentation](/workflows/reference/limits/).
+
+:::
+
+- step.waitForEvent(name: string, options: ): Promise<void>-
+ `name` - the name of the step. - `options` - an object with properties for
+ `type` (up to 100 characters [^1]), which determines which event type this
+ `waitForEvent` call will match on when calling `instance.sendEvent`, and an
+ optional `timeout` property, which defines how long the `waitForEvent` call
+ will block for before throwing a timeout exception. The default timeout is 24
+ hours.
+
+
+
+```ts
+export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ // Other steps in your Workflow
+ let stripeEvent = await step.waitForEvent(
+ "receive invoice paid webhook from Stripe",
+ { type: "stripe-webhook", timeout: "1 hour" },
+ );
+ // Rest of your Workflow
+ }
+}
+```
+
+
+
+Review the documentation on [events and parameters](/workflows/build/events-and-parameters/) to learn how to send events to a running Workflow instance.
+
+## WorkflowStepConfig
+
+```ts
+export type WorkflowDynamicDelayContext = {
+ ctx: WorkflowStepContext;
+ error: Error;
+};
+
+export type WorkflowDelayFunction = (
+ input: WorkflowDynamicDelayContext,
+) => string | number | Promise;
+
+export type WorkflowStepConfig = {
+ retries?: {
+ limit: number;
+ delay: string | number | WorkflowDelayFunction;
+ backoff?: WorkflowBackoff;
+ };
+ timeout?: string | number;
+};
+```
+
+- A `WorkflowStepConfig` is an optional argument to the `do` method of a `WorkflowStep` and defines properties that allow you to configure the retry behaviour of that step.
+- Set `retries.delay` to a fixed duration, or pass a `WorkflowDelayFunction` to calculate the next retry delay from the current step context and thrown error.
+
+Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how Workflows are retried.
+
+## Rollback options
+
+```ts
+type WorkflowRollbackContext = {
+ ctx: WorkflowStepContext;
+ error: Error;
+ output: T | undefined;
+};
+
+type WorkflowRollbackHandler = (
+ ctx: WorkflowRollbackContext,
+) => Promise;
+
+type WorkflowStepRollbackConfig = Pick<
+ WorkflowStepConfig,
+ "retries" | "timeout"
+>;
+
+type WorkflowStepRollbackOptions = {
+ rollback: WorkflowRollbackHandler;
+ rollbackConfig?: WorkflowStepRollbackConfig;
+};
+```
+
+- Pass this `WorkflowStepRollbackOptions` object as the final argument to `step.do()` to register a compensating action for a successful step.
+- `rollback` receives the original step context, the error that caused the Workflow to fail, and the step output returned by the forward step.
+- `rollbackConfig` applies retry and timeout settings to the rollback handler itself.
+
+
+
+```ts
+export class BillingWorkflow extends WorkflowEntrypoint {
+ async run(_event: WorkflowEvent, step: WorkflowStep) {
+ await step.do(
+ "create charge",
+ async () => {
+ const charge = await createCharge();
+ return { chargeId: charge.id };
+ },
+ {
+ rollback: async ({ ctx, output, error }) => {
+ const { chargeId } = output as { chargeId: string };
+ await refundCharge(chargeId, {
+ reason: `${ctx.step.name}: ${error.message}`,
+ });
+ },
+ rollbackConfig: {
+ retries: {
+ limit: 3,
+ delay: "30 seconds",
+ backoff: "linear",
+ },
+ timeout: "5 minutes",
+ },
+ },
+ );
+ }
+}
+```
+
+
+
+## WorkflowStepContext
+
+```ts
+export type WorkflowStepContext = {
+ step: {
+ name: string;
+ count: number;
+ };
+ attempt: number;
+ config: WorkflowStepConfig;
+};
+```
+
+- The `WorkflowStepContext` is passed as the first argument to the `step.do` callback function. It provides runtime information about the current step.
+ - `step.name` - the name of the step as passed to `step.do`.
+ - `step.count` - how many times `step.do` has been called with this name in the current Workflow run (1-indexed).
+ - `attempt` - the current attempt number (1-indexed). `1` on the first try, `2` on the first retry, and so on.
+ - `config` - the resolved `WorkflowStepConfig` for this step, including any defaults applied by the runtime.
+
+Refer to the [step context documentation](/workflows/build/step-context/) for usage examples.
+
+## Workflow step limits
+
+Each workflow on Workers Paid supports 10,000 steps by default. You can increase this up to 25,000 steps by configuring `steps` within the `limits` property of your Workflow definition in your Wrangler configuration:
+
+
+
+```toml
+[[workflows]]
+name = "my-workflow"
+binding = "MY_WORKFLOW"
+class_name = "MyWorkflow"
+
+[workflows.limits]
+steps = 25_000
+```
+
+
+
+`step.sleep` does not count towards the maximum steps limit.
+
+Note that Workflows on Workers Free have a limit of 1,024 steps. Refer to [Workflow limits](/workflows/reference/limits/) for more information.
+
+## NonRetryableError
+
+- throw new NonRetryableError(message: , name ):
+ - When thrown inside [`step.do()`](/workflows/build/workers-api/#step), this error stops step retries, propagating the error to the top level (the [run](/workflows/build/workers-api/#run) function). Any error not handled at this top level will cause the Workflow instance to fail.
+ - Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how Workflows steps are retried.
+
+## Call Workflows from Workers
+
+Workflows exposes an API directly to your Workers scripts via the [bindings](/workers/runtime-apis/bindings/#what-is-a-binding) concept. Bindings allow you to securely call a Workflow without having to manage API keys or clients.
+
+You can bind to a Workflow by defining a `[[workflows]]` binding within your Wrangler configuration.
+
+For example, to bind to a Workflow called `workflows-starter` and to make it available on the `MY_WORKFLOW` variable to your Worker script, you would configure the following fields within the `[[workflows]]` binding definition:
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "workflows-starter",
+ "main": "src/index.ts",
+ "compatibility_date": "$today",
+ "workflows": [
+ {
+ // name of your workflow
+ "name": "workflows-starter",
+ // binding name env.MY_WORKFLOW
+ "binding": "MY_WORKFLOW",
+ // this is class that extends the Workflow class in src/index.ts
+ "class_name": "MyWorkflow",
+ },
+ ],
+}
+```
+
+
+
+### Bind from Pages
+
+You can bind and trigger Workflows from [Pages Functions](/pages/functions/) by deploying a Workers project with your Workflow definition and then invoking that Worker using [service bindings](/pages/functions/bindings/#service-bindings) or a standard `fetch()` call.
+
+Visit the documentation on [calling Workflows from Pages](/workflows/build/call-workflows-from-pages/) for examples.
+
+### Cross-script calls
+
+You can also bind to a Workflow that is defined in a different Worker script from the script your Workflow definition is in. To do this, provide the `script_name` key with the name of the script to the `[[workflows]]` binding definition in your Wrangler configuration.
+
+For example, if your Workflow is defined in a Worker script named `billing-worker`, but you are calling it from your `web-api-worker` script, your [Wrangler configuration file](/workers/wrangler/configuration/) would resemble the following:
+
+
+
+```jsonc
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "web-api-worker",
+ "main": "src/index.ts",
+ "compatibility_date": "$today",
+ "workflows": [
+ {
+ // name of your workflow
+ "name": "billing-workflow",
+ // binding name env.MY_WORKFLOW
+ "binding": "MY_WORKFLOW",
+ // this is class that extends the Workflow class in src/index.ts
+ "class_name": "MyWorkflow",
+ // the script name where the Workflow is defined.
+ // required if the Workflow is defined in another script.
+ "script_name": "billing-worker",
+ },
+ ],
+}
+```
+
+
+
+
+
+## Workflow
+
+:::note
+
+Ensure you have a compatibility date `2024-10-22` or later installed when binding to Workflows from within a Workers project.
+
+:::
+
+The `Workflow` type provides methods that allow you to create, inspect the status, and manage running Workflow instances from within a Worker script.
+It is part of the generated types produced by [`wrangler types`](/workers/wrangler/commands/general/#types).
+
+```ts title="./worker-configuration.d.ts"
+interface Env {
+ // The 'MY_WORKFLOW' variable should match the "binding" value set in the Wrangler config file
+ MY_WORKFLOW: Workflow;
+}
+```
+
+The `Workflow` type exports the following methods:
+
+### create
+
+Create (trigger) a new instance of the given Workflow.
+
+- create(options?: WorkflowInstanceCreateOptions): Promise<WorkflowInstance>
+ - `options` - optional properties to pass when creating an instance, including a user-provided ID and payload parameters.
+
+An ID is automatically generated, but a user-provided ID can be specified (up to 100 characters [^1]). This can be useful when mapping Workflows to users, merchants or other identifiers in your system. You can also provide a JSON object as the `params` property, allowing you to pass data for the Workflow instance to act on as its [`WorkflowEvent`](/workflows/build/events-and-parameters/).
+
+```ts
+// Create a new Workflow instance with your own ID and pass params to the Workflow instance
+let instance = await env.MY_WORKFLOW.create({
+ id: myIdDefinedFromOtherSystem,
+ params: { hello: "world" },
+});
+return Response.json({
+ id: instance.id,
+ details: await instance.status(),
+});
+```
+
+Returns a `WorkflowInstance`.
+
+Throws an error if the provided ID is already used by an existing instance that has not yet passed its [retention limit](/workflows/reference/limits/). To re-run a workflow with the same ID, you can [`restart`](/workflows/build/trigger-workflows/#restart-a-workflow) the existing instance.
+
+
+
+To provide an optional type parameter to the `Workflow`, pass a type argument with your type when defining your Workflow bindings:
+
+```ts
+interface User {
+ email: string;
+ createdTimestamp: number;
+}
+
+interface Env {
+ // Pass our User type as the type parameter to the Workflow definition
+ MY_WORKFLOW: Workflow;
+}
+
+export default {
+ async fetch(request, env, ctx) {
+ // More likely to come from your database or via the request body!
+ const user: User = {
+ email: user@example.com,
+ createdTimestamp: Date.now()
+ }
+
+ let instance = await env.MY_WORKFLOW.create({
+ // params expects the type User
+ params: user
+ })
+
+ return Response.json({
+ id: instance.id,
+ details: await instance.status(),
+ });
+ }
+}
+```
+
+### createBatch
+
+Create (trigger) a batch of new instance of the given Workflow, up to 100 instances at a time.
+
+This is useful when you are scheduling multiple instances at once. A call to `createBatch` is treated the same as a call to `create` (for a single instance) and allows you to work within the [instance creation limit](/workflows/reference/limits/).
+
+- createBatch(batch: WorkflowInstanceCreateOptions[]): Promise<WorkflowInstance[]>
+ - `batch` - list of Options to pass when creating an instance, including a user-provided ID and payload parameters.
+
+Each element of the `batch` list is expected to include both `id` and `params` properties:
+
+```ts
+// Create a new batch of 3 Workflow instances, each with its own ID and pass params to the Workflow instances
+const listOfInstances = [
+ { id: "id-abc123", params: { hello: "world-0" } },
+ { id: "id-def456", params: { hello: "world-1" } },
+ { id: "id-ghi789", params: { hello: "world-2" } },
+];
+let instances = await env.MY_WORKFLOW.createBatch(listOfInstances);
+```
+
+Returns an array of `WorkflowInstance`.
+
+Unlike [`create`](/workflows/build/workers-api/#create), this operation is idempotent and will not fail if an ID is already in use. If an existing instance with the same ID is still within its [retention limit](/workflows/reference/limits/), it will be skipped and excluded from the returned array.
+
+### get
+
+Get a specific Workflow instance by ID.
+
+- get(id: string): Promise<WorkflowInstance>- `id` - the ID
+ of the Workflow instance.
+
+Returns a `WorkflowInstance`. Throws an exception if the instance ID does not exist.
+
+```ts
+// Fetch an existing Workflow instance by ID:
+try {
+ let instance = await env.MY_WORKFLOW.get(id);
+ return Response.json({
+ id: instance.id,
+ details: await instance.status(),
+ });
+} catch (e: any) {
+ // Handle errors
+ // .get will throw an exception if the ID doesn't exist or is invalid.
+ const msg = `failed to get instance ${id}: ${e.message}`;
+ console.error(msg);
+ return Response.json({ error: msg }, { status: 400 });
+}
+```
+
+## WorkflowInstanceCreateOptions
+
+Optional properties to pass when creating an instance.
+
+```ts
+interface WorkflowInstanceCreateOptions {
+ /**
+ * An id for your Workflow instance. Must be unique within the Workflow.
+ */
+ id?: string;
+ /**
+ * The event payload the Workflow instance is triggered with
+ */
+ params?: unknown;
+ /**
+ * The retention policy for the Workflow instance.
+ * Defaults to the maximum retention period available for the owner's account.
+ */
+ retention?: {
+ /**
+ * How long to retain instance state after the Workflow completes successfully.
+ */
+ successRetention?: WorkflowRetentionDuration;
+ /**
+ * How long to retain instance state after the Workflow ends in an errored or terminated state.
+ */
+ errorRetention?: WorkflowRetentionDuration;
+ };
+}
+
+type WorkflowRetentionDuration = WorkflowSleepDuration;
+```
+
+If `retention` is not set, instance state is retained for the maximum retention period available on your account (3 days on the Workers Free plan, 30 days on the Workers Paid plan). Refer to the [retention limit](/workflows/reference/limits/) for more information.
+
+The following example creates an instance that retains state for 1 day after success and 7 days after an error:
+
+```ts
+let instance = await env.MY_WORKFLOW.create({
+ id: myIdDefinedFromOtherSystem,
+ params: { hello: "world" },
+ retention: {
+ successRetention: "1 day",
+ errorRetention: "7 days",
+ },
+});
+```
+
+## WorkflowInstance
+
+Represents a specific instance of a Workflow, and provides methods to manage the instance.
+
+```ts
+declare abstract class WorkflowInstance {
+ public id: string;
+ /**
+ * Pause the instance.
+ */
+ public pause(): Promise;
+ /**
+ * Resume the instance. If it is already running, an error will be thrown.
+ */
+ public resume(): Promise;
+ /**
+ * Terminate the instance. If it is errored, terminated or complete, an error will be thrown.
+ */
+ public terminate(options?: WorkflowInstanceTerminateOptions): Promise;
+ /**
+ * Restart the instance from the beginning, or from a specific step.
+ */
+ public restart(options?: WorkflowInstanceRestartOptions): Promise;
+ /**
+ * Returns the current status of the instance.
+ */
+ public status(): Promise;
+}
+```
+
+### id
+
+Return the id of a Workflow.
+
+- id: string
+
+### status
+
+Return the status of a running Workflow instance.
+
+- status(): Promise<InstanceStatus>
+
+### pause
+
+Pause a running Workflow instance.
+
+- pause(): Promise<void>
+
+### resume
+
+Resume a paused Workflow instance.
+
+- resume(): Promise<void>
+
+### restart
+
+Restart a Workflow instance from the beginning, or from a specific step.
+
+- restart(options?: WorkflowInstanceRestartOptions): Promise<void>
+ - `options` - optional properties that control from where the instance restarts.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+
+// Restart the instance from the beginning.
+await instance.restart();
+
+// Restart the instance from the step named "aggregate".
+await instance.restart({ from: { name: "aggregate" } });
+
+// Restart the instance from the third call to a step named "process".
+await instance.restart({ from: { name: "process", count: 3 } });
+```
+
+When restarting from a specific step, the cached results of every earlier step are reused, while the target step and any steps that follow it run again. The call throws an error if no step matching `from` is found in the instance's execution history.
+
+#### WorkflowInstanceRestartOptions
+
+```ts
+interface WorkflowInstanceRestartOptions {
+ /**
+ * The step to restart the instance from.
+ * If omitted, the instance restarts from the beginning.
+ */
+ from?: {
+ /**
+ * The name of the step.
+ */
+ name: string;
+ /**
+ * The 1-based index of the step, used when multiple steps share the same name and type. Defaults to 1 (the first occurrence).
+ */
+ count?: number;
+ /**
+ * The step type. Use this to disambiguate when the same name is shared across step types. Defaults to "do".
+ */
+ type?: "do" | "sleep" | "waitForEvent";
+ };
+}
+```
+
+The `from` object identifies the step to restart from. Only `name` is required; `count` and `type` are only needed when the same step name appears more than once in the run.
+
+- `name` - the name of the step.
+- `count` - the 1-based index of the step, used when multiple steps share the same name and type (for example, inside a loop). Defaults to `1` (the first occurrence). Corresponds to `step.count` in the [step context](/workflows/build/step-context/).
+- `type` - the step type (`"do"`, `"sleep"`, or `"waitForEvent"`). Defaults to `"do"`. Use this when the same name is shared across different step types.
+
+### terminate
+
+Terminate a Workflow instance.
+
+- terminate(options?: WorkflowInstanceTerminateOptions): Promise<void>
+ - `options` - optional properties that control how the instance is terminated.
+
+```ts
+let instance = await env.MY_WORKFLOW.get("abc-123");
+
+// Terminate without running rollback handlers.
+await instance.terminate();
+
+// Run registered rollback handlers before terminating.
+await instance.terminate({ rollback: true });
+```
+
+If `rollback` is `true`, Workflows runs the rollback handlers registered by completed or eligible steps before the instance reaches the `terminated` state. Steps without rollback handlers are skipped.
+
+#### WorkflowInstanceTerminateOptions
+
+```ts
+interface WorkflowInstanceTerminateOptions {
+ /**
+ * If true, run registered rollback handlers before terminating the instance.
+ */
+ rollback?: boolean;
+}
+```
+
+### sendEvent
+
+[Send an event](/workflows/build/events-and-parameters/) to a running Workflow instance.
+
+- sendEvent(): Promise<void>- `options` - the event `type`
+ (up to 100 characters [^1]) and `payload` to send to the Workflow instance.
+ The `type` must match the `type` in the corresponding `waitForEvent` call in
+ your Workflow.
+
+Return `void` on success; throws an exception if the Workflow is not running or is an errored state.
+
+
+
+```ts
+export default {
+ async fetch(req: Request, env: Env) {
+ const instanceId = new URL(req.url).searchParams.get("instanceId");
+ const webhookPayload = await req.json();
+
+ let instance = await env.MY_WORKFLOW.get(instanceId);
+ // Send our event, with `type` matching the event type defined in
+ // our step.waitForEvent call
+ await instance.sendEvent({
+ type: "stripe-webhook",
+ payload: webhookPayload,
+ });
+
+ return Response.json({
+ status: await instance.status(),
+ });
+ },
+};
+```
+
+
+
+You can call `sendEvent` multiple times, setting the value of the `type` property to match the specific `waitForEvent` calls in your Workflow.
+
+This allows you to wait for multiple events at once, or use `Promise.race` to wait for multiple events and allow the first event to progress the Workflow.
+
+### InstanceStatus
+
+Details the status of a Workflow instance.
+
+```ts
+type InstanceStatus = {
+ status:
+ | "queued" // means that instance is waiting to be started (see concurrency limits)
+ | "running"
+ | "paused"
+ | "errored"
+ | "terminated" // user terminated the instance while it was running
+ | "complete"
+ | "waiting" // instance is hibernating and waiting for sleep or event to finish
+ | "waitingForPause" // instance is finishing the current work to pause
+ | "unknown";
+ error?: {
+ name: string;
+ message: string;
+ };
+ output?: unknown;
+ rollback: {
+ outcome: "complete" | "failed";
+ error: {
+ name: string;
+ message: string;
+ } | null;
+ } | null;
+};
+```
+
+If a Workflow enters rollback, the Workers API continues to report `status: "running"` for compatibility while the rollback is executing. After the instance reaches a terminal state, inspect `rollback` to determine whether compensating steps completed successfully or failed.
+
+[^1]: Match pattern: `^[a-zA-Z0-9_][a-zA-Z0-9-_]*$`
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/get-started/guide.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/get-started/guide.md
new file mode 100644
index 0000000..38405bd
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/get-started/guide.md
@@ -0,0 +1,328 @@
+---
+title: Build your first Workflow
+description: Create and deploy your first Cloudflare Workflow with durable, multi-step execution on the Workers platform.
+pcx_content_type: get-started
+sidebar:
+ order: 1
+products:
+ - workflows
+---
+
+import {
+ Details,
+ LinkCard,
+ Render,
+ PackageManagers,
+ WranglerConfig,
+ Steps,
+} from "~/components";
+
+Workflows allow you to build durable, multi-step applications using the Workers platform. A Workflow can automatically retry, persist state, run for hours or days, and coordinate between third-party APIs.
+
+You can build Workflows to post-process file uploads to [R2 object storage](/r2/), automate generation of [Workers AI](/workers-ai/) embeddings into a [Vectorize](/vectorize/) vector database, or to trigger user lifecycle emails using [Email Service](/email-service/).
+
+:::note
+The term "Durable Execution" is widely used to describe this programming model.
+
+"Durable" describes the ability of the program to implicitly persist state without you having to manually write to an external store or serialize program state.
+:::
+
+In this guide, you will create and deploy a Workflow that fetches data, pauses, and processes results.
+
+## Quick start
+
+If you want to skip the steps and pull down the complete Workflow we are building in this guide, run:
+
+```sh
+npm create cloudflare@latest workflows-starter -- --template "cloudflare/workflows-starter"
+```
+
+Use this option if you are familiar with Cloudflare Workers or want to explore the code first and learn the details later.
+
+Follow the steps below to learn how to build a Workflow from scratch.
+
+## Prerequisites
+
+
+
+## 1. Create a new Worker project
+
+
+
+1. Open a terminal and run the `create cloudflare` (C3) CLI tool to create your Worker project:
+
+
+
+
+
+2. Move into your new project directory:
+
+ ```sh
+ cd my-workflow
+ ```
+
+
+
+ In your project directory, C3 will have generated the following:
+ - `wrangler.jsonc`: Your [Wrangler configuration file](/workers/wrangler/configuration/#sample-wrangler-configuration).
+ - `src/index.ts`: A minimal Worker written in TypeScript.
+ - `package.json`: A minimal Node dependencies configuration file.
+ - `tsconfig.json`: TypeScript configuration.
+
+
+
+
+
+## 2. Write your Workflow
+
+
+
+1. Create a new file `src/workflow.ts`:
+
+ ```ts title="src/workflow.ts"
+ import { WorkflowEntrypoint, WorkflowStep } from "cloudflare:workers";
+ import type { WorkflowEvent } from "cloudflare:workers";
+
+ type Params = { name?: string };
+ type IPResponse = { result: { ipv4_cidrs: string[] } };
+
+ export class MyWorkflow extends WorkflowEntrypoint {
+ async run(event: WorkflowEvent, step: WorkflowStep) {
+ const data = await step.do("fetch data", async () => {
+ const response = await fetch(
+ "https://api.cloudflare.com/client/v4/ips",
+ );
+ return await response.json();
+ });
+
+ await step.sleep("pause", "20 seconds");
+
+ const result = await step.do(
+ "process data",
+ { retries: { limit: 3, delay: "5 seconds", backoff: "linear" } },
+ async () => {
+ return {
+ name: event.payload.name ?? "World",
+ ipCount: data.result.ipv4_cidrs.length,
+ };
+ },
+ );
+
+ return result;
+ }
+ }
+ ```
+
+ A Workflow extends `WorkflowEntrypoint` and implements a `run` method. This code also passes in our `Params` type as a [type parameter](/workflows/build/events-and-parameters/) so that events that trigger our Workflow are typed.
+
+ The [`step`](/workflows/build/workers-api/#step) object is the core of the Workflows API. It provides methods to define durable steps in your Workflow:
+ - `step.do(name, callback)` - Executes code and persists the result. If the Workflow is interrupted or retried, it resumes from the last successful step rather than re-running completed work. The callback returns serializable data, including `ReadableStream` for large binary output in JavaScript Workflows.
+ - `step.sleep(name, duration)` - Pauses the Workflow for a duration (for example, `"10 seconds"`, `"1 hour"`).
+
+ If you return a stream, return a fresh, unlocked `ReadableStream`. BYOB streams and BYOB readers are not supported.
+
+ You can pass a [retry configuration](/workflows/build/sleeping-and-retrying/) to `step.do()` to customize how failures are handled. See the [full step API](/workflows/build/workers-api/#step) for stream requirements, limits, and additional methods like `sleepUntil` and `waitForEvent`.
+
+ When deciding whether to break code into separate steps, ask yourself: "Do I want all of this code to run again if just one part fails?" Separate steps are ideal for operations like calling external APIs, querying databases, or reading files from storage - if a later step fails, your Workflow can retry from that point using data already fetched, avoiding redundant API calls or database queries.
+
+ For more guidance on how to define your Workflow logic, refer to [Rules of Workflows](/workflows/build/rules-of-workflows/).
+
+
+
+## 3. Configure your Workflow
+
+
+
+1. Open `wrangler.jsonc`, which is your [Wrangler configuration file](/workers/wrangler/configuration/) for your Workers project and your Workflow, and add the `workflows` configuration:
+
+
+
+ ```json title="wrangler.jsonc"
+ {
+ "$schema": "node_modules/wrangler/config-schema.json",
+ "name": "my-workflow",
+ "main": "src/index.ts",
+ "compatibility_date": "$today",
+ "observability": {
+ "enabled": true
+ },
+ "workflows": [
+ {
+ "name": "my-workflow",
+ "binding": "MY_WORKFLOW",
+ "class_name": "MyWorkflow"
+ }
+ ]
+ }
+ ```
+
+
+
+ The `class_name` must match your exported class, and `binding` is the variable name you use to access the Workflow in your code (like `env.MY_WORKFLOW`).
+
+ If you want the same Workflow to run automatically on a recurring interval, add `schedules` to the Workflow definition:
+
+
+
+ ```jsonc
+ {
+ "$schema": "node_modules/wrangler/config-schema.json",
+ "name": "my-workflow",
+ "main": "src/index.ts",
+ "compatibility_date": "$today",
+ "workflows": [
+ {
+ "name": "my-workflow",
+ "binding": "MY_WORKFLOW",
+ "class_name": "MyWorkflow",
+ "schedules": ["0 * * * *"]
+ }
+ ]
+ }
+ ```
+
+
+
+ Each matching cron expression creates a new Workflow instance automatically, so you do not need top-level `triggers.crons` and a separate `scheduled` handler for Workflow-specific recurring runs.
+
+ Scheduled instances include the matching cron expression and scheduled trigger time on `event.schedule`.
+
+ Use the latest Wrangler release when configuring Workflow schedules. If your local Wrangler schema does not recognize `schedules` yet, update Wrangler before deploying.
+
+ You can also access [bindings](/workers/runtime-apis/bindings/) (such as [KV](/kv/), [R2](/r2/), or [D1](/d1/)) via `this.env` within your Workflow. For more information on bindings within Workers, refer to [Bindings (env)](/workers/runtime-apis/bindings/).
+
+2. Now, generate types for your bindings:
+
+ ```sh
+ npx wrangler types
+ ```
+
+ This creates a `worker-configuration.d.ts` file with the `Env` type that includes your `MY_WORKFLOW` binding.
+
+
+
+## 4. Write your API
+
+Now, you'll need a place to call your Workflow.
+
+
+
+1. Replace `src/index.ts` with a [fetch handler](/workers/runtime-apis/handlers/fetch/) to start and check Workflow instances:
+
+ ```ts title="src/index.ts"
+ export { MyWorkflow } from "./workflow";
+
+ export default {
+ async fetch(request: Request, env: Env): Promise {
+ const url = new URL(request.url);
+ const instanceId = url.searchParams.get("instanceId");
+
+ if (instanceId) {
+ const instance = await env.MY_WORKFLOW.get(instanceId);
+ return Response.json(await instance.status());
+ }
+
+ const instance = await env.MY_WORKFLOW.create();
+ return Response.json({ instanceId: instance.id });
+ },
+ } satisfies ExportedHandler;
+ ```
+
+
+
+## 5. Develop locally
+
+
+
+1. Start a local development server:
+
+ ```sh
+ npx wrangler dev
+ ```
+
+2. To start a Workflow instance, open a new terminal window and run:
+
+ ```sh
+ curl http://localhost:8787
+ ```
+
+ An `instanceId` will be automatically generated:
+
+ ```json output
+ { "instanceId": "abc-123-def" }
+ ```
+
+3. Check the status using the returned `instanceId`:
+
+ ```sh
+ curl "http://localhost:8787?instanceId=abc-123-def"
+ ```
+
+ The Workflow will progress through its steps. After about 20 seconds (the sleep duration), it will complete.
+
+
+
+## 6. Deploy your Workflow
+
+
+
+1. Deploy your Workflow:
+
+ ```sh
+ npx wrangler deploy
+ ```
+
+ Test in production using the same curl commands against your deployed URL. You can also [trigger a workflow instance](/workflows/build/trigger-workflows/) in production via Workers, Wrangler, or the Cloudflare dashboard.
+
+ Once deployed, you can also inspect Workflow instances with the CLI:
+
+ ```sh
+ npx wrangler workflows instances describe my-workflow latest
+ ```
+
+ The output of `instances describe` shows:
+ - The status (success, failure, running) of each step
+ - Any state emitted by the step. For streamed output, the CLI shows a preview or summary instead of the full contents.
+ - Any `sleep` state, including when the Workflow will wake up
+ - Retries associated with each step
+ - Errors, including exception messages
+
+
+
+## Learn more
+
+
+
+
+
+
+
+
diff --git a/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/index.md b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/index.md
new file mode 100644
index 0000000..be52e49
--- /dev/null
+++ b/assets/seed/v2/corpora/cloudflare-state-v1/files/workflows/index.md
@@ -0,0 +1,168 @@
+---
+title: Cloudflare Workflows
+description: Build durable, multi-step applications on Cloudflare Workers that automatically retry and persist state.
+order: 0
+pcx_content_type: overview
+sidebar:
+ order: 1
+head:
+ - tag: title
+ content: Overview
+products:
+ - workflows
+---
+
+import { AnimatedWorkflowDiagram, CardGrid, Description, Feature, Flex, LinkTitleCard, Plan, RelatedProduct, Tabs, TabItem, LinkButton } from "~/components"
+
+
+
+Build durable multi-step applications on Cloudflare Workers with Workflows.
+
+
+
+
+
+With Workflows, you can build applications that chain together multiple steps, automatically retry failed tasks,
+and persist state for minutes, hours, or even weeks - with no infrastructure to manage.
+
+Use Workflows to build reliable AI applications, process data pipelines, manage user lifecycle with automated emails and trial expirations, and implement human-in-the-loop approval systems.
+
+
+
+
+
+
+
+
+
+**Workflows give you:**
+
+- Durable multi-step execution without timeouts
+- The ability to pause for external events or approvals
+- Automatic retries and error handling
+- Built-in observability and debugging
+
+
In the generated code: \- Edit the path parameter in the last line of code \- Remove the comment tagging
To read data, see [Reading data from a data source](https://www.ibm.com/support/knowledgecenter/en/SSEP7J_11.1.0/com.ibm.swg.ba.cognos.ca_notebook.doc/c_read_notebook.html) To search data, see [Searching for data objects](https://www.ibm.com/support/knowledgecenter/en/SSEP7J_11.1.0/com.ibm.swg.ba.cognos.ca_notebook.doc/c_search_for_data_objects_notebook.html) To write data, see [Writing data to a data source](https://www.ibm.com/support/knowledgecenter/en/SSEP7J_11.1.0/com.ibm.swg.ba.cognos.ca_notebook.doc/c_write_notebook.html) |
+| | | With Spark | No data load support |
+| | R | Anaconda R distribution | Load data into R data frame
In the generated code: \- Edit the path parameter in the last line of code \- Remove the comment tagging
Other formats might or might not be supported depending on the application type\. |
+| `oaas.oplRunConfig` | String | Specifies the name of the OPL run configuration to be executed\. |
+| `oaas.docplex.python` | `3.10` | You can use this parameter to set the Python version for the run in your deployed model\. If not specified, 3\.10 is used by default\. |
+| `oaas.logTailEnabled` | Boolean | Use this parameter to include the log tail in the solve status\. |
+| `oaas.logAttachmentName` | String | If defined, engine logs will be defined as a job output attachment\. |
+| `oaas.engineLogLevel` | Enum
| You can use this parameter to define the level of detail that is provided by the engine log\. The default value is `INFO`\. |
+| `oaas.logLimit` | Number | Maximum log\-size limit in number of characters\. |
+| `oaas.dumpZipName` | Can be viewed as Boolean (see Description) | If defined, a job dump (inputs and outputs) `.zip` file is provided with this name as a job output attachment\. The name can contain a placeholder `${job_id}`\. If defined with no value, `dump_${job_id}.zip attachmentName` is used\. If not defined, by default, no job dump `.zip` file is attached\. |
+| `oaas.dumpZipRules` | String | If defined, ta `.zip` file is generated according to specific job rules (RFC 1960\-based Filter)\. It must be used in conjunction with the `{@link DUMP_ZIP_NAME}` parameter\. Filters can be defined on the duration and the following `{@link com.ibm.optim.executionservice.model.solve.SolveState}` properties:
`(duration>=1000) or (&(duration<1000)(!(solveState.solveStatus=OPTIMAL_SOLUTION))) or (|(solveState.interruptionStatus=OUT_OF_MEMORY) (solveState.failureInfo.type=INFRASTRUCTURE))`
(duration>=1000) or (&(duration<1000)(\!(solveState\.solveStatus=OPTIMAL\_SOLUTION))) or (\|(solveState\.interruptionStatus=OUT\_OF\_MEMORY) (solveState\.failureInfo\.type=INFRASTRUCTURE)) |
+| `oaas.outputUploadPeriod` | Number | Intermediate output in minutes\. This parameter can be used to set up intermediate output publication (if any)\. |
+| `oaas.outputUploadFiles` | String (RegExp) | RegExp filter for files to be included in the output upload\. If nothing is defined, all outputs are added\.
Example:
`job_${job_id}_log_${update_time}.txt` |
+
+
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1399cd9c09634e30c0f099c0fae66a756153dab1.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1399cd9c09634e30c0f099c0fae66a756153dab1.md
new file mode 100644
index 0000000..ff079dc
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1399cd9c09634e30c0f099c0fae66a756153dab1.md
@@ -0,0 +1,15 @@
+# Learning and testing (SPSS Modeler)
+
+# Learning and testing #
+
+The flow trains a neural network and a decision tree to make this prediction of revenue increase\.
+
+Figure 1\. Retail Sales Promotion example flow
+
+
+
+After you run the flow to generate the model nuggets, you can test the results of the learning process\. You do this by connecting the decision tree and network in series between the Type node and a new Analysis node, changing the Data Asset import node to point to goods2n\.csv, and running the Analysis node\. From the output of this node, in particular from the linear correlation between the predicted increase and the correct answer, you will find that the trained systems predict the increase in revenue with a high degree of success\.
+
+Further exploration might focus on the cases where the trained systems make relatively large errors\. These could be identified by plotting the predicted increase in revenue against the actual increase\. Outliers on this graph could be selected using the interactive graphics within SPSS Modeler, and from their properties, it might be possible to tune the data description or learning process to improve accuracy\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/13a1ff3338f4ac1eb2cf3ff6781283b49ac8b5a6.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/13a1ff3338f4ac1eb2cf3ff6781283b49ac8b5a6.md
new file mode 100644
index 0000000..4df01dc
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/13a1ff3338f4ac1eb2cf3ff6781283b49ac8b5a6.md
@@ -0,0 +1,15 @@
+# K-Means node (SPSS Modeler)
+
+# K\-Means node #
+
+The K\-Means node provides a method of cluster analysis\. It can be used to cluster the dataset into distinct groups when you don't know what those groups are at the beginning\. Unlike most learning methods in SPSS Modeler, K\-Means models do not use a target field\. This type of learning, with no target field, is called unsupervised learning\. Instead of trying to predict an outcome, K\-Means tries to uncover patterns in the set of input fields\. Records are grouped so that records within a group or cluster tend to be similar to each other, but records in different groups are dissimilar\.
+
+K\-Means works by defining a set of starting cluster centers derived from data\. It then assigns each record to the cluster to which it is most similar, based on the record's input field values\. After all cases have been assigned, the cluster centers are updated to reflect the new set of records assigned to each cluster\. The records are then checked again to see whether they should be reassigned to a different cluster, and the record assignment/cluster iteration process continues until either the maximum number of iterations is reached, or the change between one iteration and the next fails to exceed a specified threshold\.
+
+Note: The resulting model depends to a certain extent on the order of the training data\. Reordering the data and rebuilding the model may lead to a different final cluster model\.
+
+Requirements\. To train a K\-Means model, you need one or more fields with the role set to `Input`\. Fields with the role set to `Output`, `Both`, or `None` are ignored\.
+
+Strengths\. You do not need to have data on group membership to build a K\-Means model\. The K\-Means model is often the fastest method of clustering for large datasets\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/13d83af5ccd616f312472fbab4ac7d7a56d0f41d.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/13d83af5ccd616f312472fbab4ac7d7a56d0f41d.md
new file mode 100644
index 0000000..d7f292e
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/13d83af5ccd616f312472fbab4ac7d7a56d0f41d.md
@@ -0,0 +1,14 @@
+# Using an Analysis node (SPSS Modeler)
+
+# Using an Analysis node #
+
+You can assess the accuracy of the model using an Analysis node\. From the Palette, under Outputs, place an Analysis node on the canvas and attach it to the C5\.0 model nugget\. Then right\-click the Analysis node and select Run\.
+
+Figure 1\. Analysis node
+
+The Analysis node output shows that with this artificial dataset, the model correctly predicted the choice of drug for every record in the dataset\. With a real dataset you are unlikely to see 100% accuracy, but you can use the Analysis node to help determine whether the model is acceptably accurate for your particular application\.
+
+Figure 2\. Analysis node output
+
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/13f7c9c7b52ec7152f2b3d81b6eb42db0319a6f4.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/13f7c9c7b52ec7152f2b3d81b6eb42db0319a6f4.md
new file mode 100644
index 0000000..c741fa8
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/13f7c9c7b52ec7152f2b3d81b6eb42db0319a6f4.md
@@ -0,0 +1,8 @@
+# Histogram node (SPSS Modeler)
+
+# Histogram node #
+
+Histogram nodes show the occurrence of values for numeric fields\. They are often used to explore the data before manipulations and model building\. Similar to the Distribution node, Histogram nodes are frequently used to reveal imbalances in the data\.
+
+Note: To show the occurrence of values for symbolic fields, you should use a Distribution node\.
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14005f26f286b03f8ac692d42e9f3dfce1f66962.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14005f26f286b03f8ac692d42e9f3dfce1f66962.md
new file mode 100644
index 0000000..2aa9677
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14005f26f286b03f8ac692d42e9f3dfce1f66962.md
@@ -0,0 +1,32 @@
+# extensionoutputnode properties
+
+# extensionoutputnode properties #
+
+With the Extension Output node, you can analyze data and the results of model scoring using your own custom R or Python for Spark script\. The output of the analysis can be text or graphical\.
+
+Note that many of the properties on this page are for streams from SPSS Modeler desktop\.
+
+
+
+extensionoutputnode properties
+
+Table 1\. extensionoutputnode properties
+
+| `extensionoutputnode` properties | Data type | Property description |
+| -------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------- |
+| `syntax_type` | *R**Python* | Specify which script runs: R or Python (R is the default)\. |
+| `r_syntax` | *string* | R scripting syntax for model scoring\. |
+| `python_syntax` | *string* | Python scripting syntax for model scoring\. |
+| `convert_flags` | `StringsAndDoubles LogicalValues` | Option to convert flag fields\. |
+| `convert_missing` | *flag* | Option to convert missing values to the R NA value\. |
+| `convert_datetime` | *flag* | Option to convert variables with date or datetime formats to R date/time formats\. |
+| `convert_datetime_class` | `POSIXct POSIXlt` | Options to specify to what format variables with date or datetime formats are converted\. |
+| `output_to` | `Screen File` | Specify the output type (`Screen` or `File`)\. |
+| `output_type` | `Graph Text` | Specify whether to produce graphical or text output\. |
+| `full_filename` | *string* | File name to use for the generated output\. |
+| `graph_file_type` | `HTML COU` | File type for the output file ( \.html or \.cou)\. |
+| `text_file_type` | `HTML TEXT COU` | Specify the file type for text output ( \.html, \.txt, or \.cou)\. |
+
+
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14416203d840c788359110b18cfd9ce922de0d67.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14416203d840c788359110b18cfd9ce922de0d67.md
new file mode 100644
index 0000000..cad2f05
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14416203d840c788359110b18cfd9ce922de0d67.md
@@ -0,0 +1,7 @@
+# applyautoclusternode properties
+
+# applyautoclusternode properties #
+
+You can use Auto Cluster modeling nodes to generate an Auto Cluster model nugget\. The scripting name of this model nugget is *applyautoclusternode*\. No other properties exist for this model nugget\. For more information on scripting the modeling node itself, see [autoclusternode properties](https://dataplatform.cloud.ibm.com/docs/content/wsd/nodes/scripting_guide/clementine/autoclusternodeslots.html#autoclusternodeslots)\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1453d1cad565842eea24c8d92963bd73338ef0f1.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1453d1cad565842eea24c8d92963bd73338ef0f1.md
new file mode 100644
index 0000000..b171324
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1453d1cad565842eea24c8d92963bd73338ef0f1.md
@@ -0,0 +1,20 @@
+# Histogram charts
+
+# Histogram charts #
+
+A histogram is similar in appearance to a bar chart, but instead of comparing categories or looking for trends over time, each bar represents how data is distributed in a single category\. Each bar represents a continuous range of data or the number of frequencies for a specific data point\.
+
+Histograms are useful for showing the distribution of a single scale variable\. Data are binned and summarized by using a count or percentage statistic\. A variation of a histogram is a frequency polygon, which is like a typical histogram except that the area graphic element is used instead of the bar graphic element\.
+
+Another variation of the histogram is the population pyramid\. Its name is derived from its most common use: summarizing population data\. When used with population data, it is split by gender to provide two back\-to\-back, horizontal histograms of age data\. In countries with a young population, the shape of the resulting graph resembles a pyramid\.
+
+Footnote
+: The chart footnote, which is placed beneath the chart\.
+
+XAxis label
+: The x\-axis label, which is placed beneath the x\-axis\.
+
+YAxis label
+: The y\-axis label, which is placed above the y\-axis\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1483016be71021f31b8193239d319f34d8e01c9c.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1483016be71021f31b8193239d319f34d8e01c9c.md
new file mode 100644
index 0000000..f70c9f8
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1483016be71021f31b8193239d319f34d8e01c9c.md
@@ -0,0 +1,71 @@
+# Supported machine learning tools, libraries, frameworks, and software specifications
+
+# Supported machine learning tools, libraries, frameworks, and software specifications #
+
+In IBM Watson Machine Learning, you can use popular tools, libraries, and frameworks to train and deploy machine learning models and functions\. The environment for these models and functions is made up of specific hardware and software specifications\.
+
+Software specifications define the language and version that you use for a model or function\. You can use software specifications to configure the software that is used for running your models and functions\. By using software specifications, you can precisely define the software version to be used and include your own extensions (for example, by using conda \.yml files or custom libraries)\.
+
+You can get a list of available software and hardware specifications and then use their names and IDs for use with your deployment\. For more information, see [Python client](https://ibm.github.io/watson-machine-learning-sdk/) or [REST API](https://cloud.ibm.com/apidocs/machine-learning)\.
+
+## Predefined software specifications ##
+
+You can use popular tools, libraries, and frameworks to train and deploy machine learning models and functions\.
+
+This table lists the predefined (base) model types and software specifications\.
+
+
+
+List of predefined (base) model types and software specifications
+
+| Framework\*\* | Versions | Model Type | Default software specification |
+| --------------------- | ------------ | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| AutoAI | 0\.1 | NA | autoai\-kb\_rt22\.2\-py3\.10 autoai\-ts\_rt22\.2\-py3\.10 hybrid\_0\.1 autoai\-kb\_rt23\.1\-py3\.10 autoai\-ts\_rt23\.1\-py3\.10 autoai\-tsad\_rt23\.1\-py3\.10 autoai\-tsad\_rt22\.2\-py3\.10 |
+| Decision Optimization | 20\.1 | do\-docplex\_20\.1 do\-opl\_20\.1 do\-cplex\_20\.1 do\-cpo\_20\.1 | do\_20\.1 |
+| Decision Optimization | 22\.1 | do\-docplex\_22\.1 do\-opl\_22\.1 do\-cplex\_22\.1 do\-cpo\_22\.1 | do\_22\.1 |
+| Hybrid/AutoML | 0\.1 | wml\-hybrid\_0\.1 | hybrid\_0\.1 |
+| PMML | 3\.0 to 4\.3 | pmml*\. (or) pmml*\.\.\*3\.0 \- 4\.3 | pmml\-3\.0\_4\.3 |
+| PyTorch | 1\.12 | pytorch\-onnx\_1\.12 pytorch\-onnx\_rt22\.2 | runtime\-22\.2\-py3\.10 pytorch\-onnx\_rt22\.2\-py3\.10 pytorch\-onnx\_rt22\.2\-py3\.10\-edt |
+| PyTorch | 2\.0 | pytorch\-onnx\_2\.0 pytorch\-onnx\_rt23\.1 | runtime\-23\.1\-py3\.10 pytorch\-onnx\_rt23\.1\-py3\.10 pytorch\-onnx\_rt23\.1\-py3\.10\-edt pytorch\-onnx\_rt23\.1\-py3\.10\-dist |
+| Python Functions | 0\.1 | NA | runtime\-22\.2\-py3\.10 runtime\-23\.1\-py3\.10 |
+| Python Scripts | 0\.1 | NA | runtime\-22\.2\-py3\.10 runtime\-23\.1\-py3\.10 |
+| Scikit\-learn | 1\.1 | scikit\-learn\_1\.1 | runtime\-22\.2\-py3\.10 runtime\-23\.1\-py3\.10 |
+| Spark | 3\.3 | mllib\_3\.3 | spark\-mllib\_3\.3 |
+| SPSS | 17\.1 | spss\-modeler\_17\.1 | spss\-modeler\_17\.1 |
+| SPSS | 18\.1 | spss\-modeler\_18\.1 | spss\-modeler\_18\.1 |
+| SPSS | 18\.2 | spss\-modeler\_18\.2 | spss\-modeler\_18\.2 |
+| Tensorflow | 2\.9 | tensorflow\_2\.9 tensorflow\_rt22\.2 | runtime\-22\.2\-py3\.10 tensorflow\_rt22\.2\-py3\.10 |
+| Tensorflow | 2\.12 | tensorflow\_2\.12 tensorflow\_rt23\.1 | runtime\-23\.1\-py3\.10 tensorflow\_rt23\.1\-py3\.10\-dist tensorflow\_rt23\.1\-py3\.10\-edt tensorflow\_rt23\.1\-py3\.10 |
+| XGBoost | 1\.6 | xgboost\_1\.6 or scikit\-learn\_1\.1 (see notes) | runtime\-22\.2\-py3\.10 runtime\-23\.1\-py3\.10 |
+
+
+
+When you have assets that rely on discontinued software specifications or frameworks, in some cases the migration is seamless\. In other cases, your action is required to retrain or redeploy assets\.
+
+
+
+ * Existing deployments of models that are built with discontinued framework versions or software specifications are removed on the date of discontinuation\.
+ * No new deployments of models that are built with discontinued framework versions or software specifications are allowed\.
+
+
+
+## Learn more ##
+
+
+
+ * To learn more about how to customize software specifications, see [Customizing with third\-party and private Python libraries](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-create-custom-software-spec.html)\.
+ * To learn more about how to use and customize environments, see [Environments](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/environments-parent.html)\.
+ * To learn more about how to use software specifications for deployments, see the following Jupyter notebooks:
+
+
+
+ * [Using REST API and cURL](https://github.com/IBM/watson-machine-learning-samples/tree/master/cloud/notebooks/rest_api/curl/deployments)
+ * [Using the Python client](https://github.com/IBM/watson-machine-learning-samples/tree/master/cloud/notebooks/python_sdk/deployments)
+
+
+
+
+
+**Parent topic:**[Frameworks and software specifications](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-manage-frame-and-specs.html)
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14a06de43e6b08188a7672b5be8068a572de5b7c.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14a06de43e6b08188a7672b5be8068a572de5b7c.md
new file mode 100644
index 0000000..db139a6
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14a06de43e6b08188a7672b5be8068a572de5b7c.md
@@ -0,0 +1,19 @@
+# Scripting and automation
+
+# Scripting and automation #
+
+Scripting in SPSS Modeler is a powerful tool for automating processes in the user interface\. Scripts can perform the same types of actions that you perform with a mouse or a keyboard, and you can use them to automate tasks that would be highly repetitive or time consuming to perform manually\.
+
+You can use scripts to:
+
+
+
+ * Impose a specific order for node executions in a flow\.
+ * Set properties for a node as well as perform derivations using a subset of CLEM (Control Language for Expression Manipulation)\.
+ * Specify an automatic sequence of actions that normally involves user interaction—for example, you can build a model and then test it\.
+ * Set up complex processes that require substantial user interaction—for example, cross\-validation procedures that require repeated model generation and testing\.
+ * Set up processes that manipulate flows—for example, you can take a model training flow, run it, and produce the corresponding model\-testing flow automatically\.
+
+
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14f850b810e969ce2646d5641300fb407a6c49c5.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14f850b810e969ce2646d5641300fb407a6c49c5.md
new file mode 100644
index 0000000..68bb5c3
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/14f850b810e969ce2646d5641300fb407a6c49c5.md
@@ -0,0 +1,11 @@
+# Strings
+
+# Strings #
+
+A string is an immutable sequence of characters that's treated as a value\. Strings support all of the immutable sequence functions and operators that result in a new string\. For example, `"abcdef"[1:4]` results in the output `"bcd"`\.
+
+In Python, characters are represented by strings of length one\.
+
+Strings literals are defined by the use of single or triple quoting\. Strings that are defined using single quotes can't span lines, while strings that are defined using triple quotes can\. You can enclose a string in single quotes (`'`) or double quotes (`"`)\. A quoting character may contain the other quoting character un\-escaped or the quoting character escaped, that's preceded by the backslash (`\`) character\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/156f8a58809d3a4d8f80d02481e5adde513edeaa.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/156f8a58809d3a4d8f80d02481e5adde513edeaa.md
new file mode 100644
index 0000000..c1480ee
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/156f8a58809d3a4d8f80d02481e5adde513edeaa.md
@@ -0,0 +1,108 @@
+# Concepts extraction block
+
+# Concepts extraction block #
+
+The Watson Natural Language Processing Concepts block extracts general DBPedia concepts (concepts drawn from language\-specific Wikipedia versions) that are directly referenced or alluded to, but not directly referenced, in the input text\.
+
+**Block name**
+
+`concepts_alchemy__stock`
+
+**Supported languages**
+
+The Concepts block is available for the following languages\. For a list of the language codes and the corresponding language, see [Language codes](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-catalog.html#lang-codes)\.
+
+de, en, es, fr, it, ja, ko, pt
+
+**Capabilities**
+
+Use this block to assign concepts from [DBPedia](https://www.dbpedia.org/) (2016 edition)\. The output types are based on DBPedia\.
+
+**Dependencies on other blocks**
+
+The following block must run before you can run the Concepts extraction block:
+
+
+
+ * `syntax_izumo__stock`
+
+
+
+**Code sample**
+
+ import watson_nlp
+
+ # Load Syntax and a Concepts model for English
+ syntax_model = watson_nlp.load('syntax_izumo_en_stock')
+ concepts_model = watson_nlp.load('concepts_alchemy_en_stock')
+ # Run the syntax model on the input text
+ syntax_prediction = syntax_model.run('IBM announced new advances in quantum computing')
+
+ # Run the concepts model on the result of syntax
+ concepts = concepts_model.run(syntax_prediction)
+ print(concepts)
+
+Output of the code sample:
+
+ {
+ "concepts": [
+ {
+ "text": "IBM",
+ "relevance": 0.9842190146446228,
+ "dbpedia_resource": "http://dbpedia.org/resource/IBM"
+ },
+ {
+ "text": "Quantum_computing",
+ "relevance": 0.9797260165214539,
+ "dbpedia_resource": "http://dbpedia.org/resource/Quantum_computing"
+ },
+ {
+ "text": "Computing",
+ "relevance": 0.9080164432525635,
+ "dbpedia_resource": "http://dbpedia.org/resource/Computing"
+ },
+ {
+ "text": "Shor's_algorithm",
+ "relevance": 0.7580527067184448,
+ "dbpedia_resource": "http://dbpedia.org/resource/Shor's_algorithm"
+ },
+ {
+ "text": "Quantum_dot",
+ "relevance": 0.7069802284240723,
+ "dbpedia_resource": "http://dbpedia.org/resource/Quantum_dot"
+ },
+ {
+ "text": "Quantum_algorithm",
+ "relevance": 0.7063655853271484,
+ "dbpedia_resource": "http://dbpedia.org/resource/Quantum_algorithm"
+ },
+ {
+ "text": "Qubit",
+ "relevance": 0.7063655853271484,
+ "dbpedia_resource": "http://dbpedia.org/resource/Qubit"
+ },
+ {
+ "text": "DNA_computing",
+ "relevance": 0.7044616341590881,
+ "dbpedia_resource": "http://dbpedia.org/resource/DNA_computing"
+ },
+ {
+ "text": "Computation",
+ "relevance": 0.7044616341590881,
+ "dbpedia_resource": "http://dbpedia.org/resource/Computation"
+ },
+ {
+ "text": "Computer",
+ "relevance": 0.7044616341590881,
+ "dbpedia_resource": "http://dbpedia.org/resource/Computer"
+ }
+ ],
+ "producer_id": {
+ "name": "Alchemy Concepts",
+ "version": "0.0.1"
+ }
+ }
+
+**Parent topic:**[Watson Natural Language Processing block catalog](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-catalog.html)
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/15a014c514b00ff78c689585f393e21bae922db2.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/15a014c514b00ff78c689585f393e21bae922db2.md
new file mode 100644
index 0000000..ef3b2cc
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/15a014c514b00ff78c689585f393e21bae922db2.md
@@ -0,0 +1,46 @@
+# Methods for tuning foundation models
+
+# Methods for tuning foundation models #
+
+Learn more about different tuning methods and how they work\.
+
+Models can be tuned in the following ways:
+
+
+
+ * **Fine\-tuning**: Changes the parameters of the underlying foundation model to guide the model to generate output that is optimized for a task\.
+
+ Note: You currently cannot fine-tune models in Tuning Studio.
+ * **Prompt\-tuning**: Adjusts the content of the prompt that is passed to the model to guide the model to generate output that matches a pattern you specify\. The underlying foundation model and its parameters are not edited\. Only the prompt input is altered\.
+
+ When you prompt-tune a model, the underlying foundation model can be used to address different business needs without being retrained each time. As a result, you reduce computational needs and inference costs.
+
+
+
+## How prompt\-tuning works ##
+
+Foundation models are sensitive to the input that you give them\. Your input, or how you *prompt* the model, can introduce context that the model will use to tailor its generated output\. Prompt engineering to find the *right* prompt often works well\. However, it can be time\-consuming, error\-prone, and its effectiveness can be restricted by the context window length that is allowed by the underlying model\.
+
+Prompt\-tuning a model in the Tuning Studio applies machine learning to the task of prompt engineering\. Instead of adding words to the input itself, prompt\-tuning is a method for finding a sequence of values that, when added as a prefix to the input text, improve the model's ability to generate the output you want\. This sequence of values is called a *prompt vector*\.
+
+Normally, words in the prompt are vectorized by the model\. Vectorization is the process of converting text to tokens, and then to numbers defined by the model's tokenizer to identify the tokens\. Lastly, the token IDs are encoded, meaning they are converted into a vector representation, which is the input format that is expected by the embedding layer of the model\. Prompt\-tuning bypasses the model's text\-vectorization process and instead crafts a prompt vector directly\. This changeable prompt vector is concatenated to the vectorized input text and the two are passed as one input to the embedding layer of the model\. Values from this crafted prompt vector affect the word embedding weights that are set by the model and influence the words that the model chooses to add to the output\.
+
+To find the best values for the prompt vector, you run a tuning experiment\. You demonstrate the type of output that you want for a corresponding input by providing the model with input and output example pairs in training data\. With each training run of the experiment, the generated output is compared to the training data output\. Based on what it learns from differences between the two, the experiment adjusts the values in the prompt vector\. After many runs through the training data, the model finds the prompt vector that works best\.
+
+You can choose to start the training process by providing text that is vectorized by the experiment\. Or you can let the experiment use random values in the prompt vector\. Either way, unless the initial values are exactly right, they will be changed repeatedly as part of the training process\. Providing your own initialization text can help the experiment reach a good result more quickly\.
+
+The result of the experiment is a tuned version of the underlying model\. You submit input to the tuned model for inferencing and the model generates output that follows the tuned\-for pattern\.
+
+For more information about this tuning method, read the research paper named [The Power of Scale for Parameter\-Efficient Prompt Tuning](https://arxiv.org/abs/2104.08691)\.
+
+## Learn more ##
+
+
+
+ * [Tuning parameters](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-tuning-parameters.html)
+
+
+
+**Parent topic:**[Tuning Studio](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-tuning-studio.html)
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/15d57c8193b99b8525bc2999ef82ef1cd7eae8ad.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/15d57c8193b99b8525bc2999ef82ef1cd7eae8ad.md
new file mode 100644
index 0000000..afa4b1a
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/15d57c8193b99b8525bc2999ef82ef1cd7eae8ad.md
@@ -0,0 +1,58 @@
+# Watson Natural Language Processing task catalog
+
+# Watson Natural Language Processing task catalog #
+
+Watson Natural Language Processing encapsulates natural language functionality in standardized components called blocks or workflows\. Each block or workflow can be loaded and run in a notebook, some directly on input data, others in a given order\.
+
+This topic contains descriptions of the natural language processing tasks supported in the Watson Natural Language Processing library\. It lists the task names, the languages that are supported, dependencies to other blocks and includes sample code of how you use the natural language processing functionality in a Python notebook\.
+
+The following natural language processing tasks are supported as blocks or workflows in the Watson Natural Language Processing library:
+
+
+
+ * [Language detection](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-language-detection.html)
+ * [Syntax analysis](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-syntax.html)
+ * [Noun phrase extraction](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-noun-phrase.html)
+ * [Keyword extraction and ranking](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-keyword.html)
+ * [Entity extraction](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-entity-enhanced.html)
+ * [Sentiment classification](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-sentiment.html)
+ * [Tone classification](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-tone.html)
+ * [Emotion classification](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-emotion.html)
+ * [Concepts extraction](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-concept-ext.html)
+ * [Relations extraction](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-relation-extraction.html)
+ * [Hierarchical text categorization](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-block-hierarchical-cat.html)
+
+
+
+## Language codes ##
+
+Many of the pre\-trained models are available in many languages\. The following table lists the language codes and the corresponding language\.
+
+
+
+Language codes and their corresponding language equivalents
+
+| Language code | Corresponding language | Language code | Corresponding language |
+| ------------- | ---------------------- | ------------- | ---------------------- |
+| af | Afrikaans | ar | Arabic |
+| bs | Bosnian | ca | Catalan |
+| cs | Czech | da | Danish |
+| de | German | el | Greek |
+| en | English | es | Spanish |
+| fi | Finnish | fr | French |
+| he | Hebrew | hi | Hindi |
+| hr | Croatian | it | Italian |
+| ja | Japanese | ko | Korean |
+| nb | Norwegian Bokmål | nl | Dutch |
+| nn | Norwegian Nynorsk | pl | Polish |
+| pt | Portuguese | ro | Romanian |
+| ru | Russian | sk | Slovak |
+| sr | Serbian | sv | Swedish |
+| tr | Turkish | zh\_cn | Chinese (Simplified) |
+| zh\_tw | Chinese (Traditional) |
+
+
+
+**Parent topic:**[Watson Natural language Processing library](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp.html)
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/16048584b029b9be5da50d7f9d9ae85ffe740718.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/16048584b029b9be5da50d7f9d9ae85ffe740718.md
new file mode 100644
index 0000000..78838f3
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/16048584b029b9be5da50d7f9d9ae85ffe740718.md
@@ -0,0 +1,53 @@
+# discriminantnode properties
+
+# discriminantnode properties #
+
+Discriminant analysis makes more stringent assumptions than logistic regression, but can be a valuable alternative or supplement to a logistic regression analysis when those assumptions are met\.
+
+
+
+discriminantnode properties
+
+Table 1\. discriminantnode properties
+
+| `discriminantnode` Properties | Values | Property description |
+| --------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `target` | *field* | Discriminant models require a single target field and one or more input fields\. Weight and frequency fields aren't used\. See [Common modeling node properties](https://dataplatform.cloud.ibm.com/docs/content/wsd/nodes/scripting_guide/clementine/modelingnodeslots_common.html#modelingnodeslots_common) for more information\. |
+| `method` | `Enter` `Stepwise` | |
+| `mode` | `Simple` `Expert` | |
+| `prior_probabilities` | `AllEqual` `ComputeFromSizes` | |
+| `covariance_matrix` | `WithinGroups` `SeparateGroups` | |
+| `means` | *flag* | Statistics options in the node properties under Expert Options\. |
+| `univariate_anovas` | *flag* | |
+| `box_m` | *flag* | |
+| `within_group_covariance` | *flag* | |
+| `within_groups_correlation` | *flag* | |
+| `separate_groups_covariance` | *flag* | |
+| `total_covariance` | *flag* | |
+| `fishers` | *flag* | |
+| `unstandardized` | *flag* | |
+| `casewise_results` | *flag* | Classification options in the node properties under Expert Options\. |
+| `limit_to_first` | *number* | Default value is 10\. |
+| `summary_table` | *flag* | |
+| `leave_one_classification` | *flag* | |
+| `separate_groups_covariance` | *flag* | Matrices option Separate\-groups covariance\. |
+| `territorial_map` | *flag* | |
+| `combined_groups` | *flag* | Plot option Combined\-groups\. |
+| `separate_groups` | *flag* | Plot option Separate\-groups\. |
+| `summary_of_steps` | *flag* | |
+| `F_pairwise` | *flag* | |
+| `stepwise_method``` | `WilksLambda` `UnexplainedVariance` `MahalanobisDistance` `SmallestF` `RaosV` | |
+| `V_to_enter` | *number* | |
+| `criteria` | `UseValue` `UseProbability` | |
+| `F_value_entry` | *number* | Default value is 3\.84\. |
+| `F_value_removal` | *number* | Default value is 2\.71\. |
+| `probability_entry` | *number* | Default value is 0\.05\. |
+| `probability_removal` | *number* | Default value is 0\.10\. |
+| `calculate_variable_importance` | *flag* | |
+| `calculate_raw_propensities` | *flag* | |
+| `calculate_adjusted_propensities` | *flag* | |
+| `adjusted_propensity_partition` | `Test` `Validation` | |
+
+
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/163eeb3dbaff3b01d831f717eeb7487642c93080.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/163eeb3dbaff3b01d831f717eeb7487642c93080.md
new file mode 100644
index 0000000..7361a13
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/163eeb3dbaff3b01d831f717eeb7487642c93080.md
@@ -0,0 +1,62 @@
+# Troubleshooting AutoAI experiments
+
+# Troubleshooting AutoAI experiments #
+
+The following list contains the common problems that are known for AutoAI\. If your AutoAI experiment fails to run or deploy successfully, review some of these common problems and resolutions\.
+
+## Passing incomplete or outlier input value to deployment can lead to outlier prediction ##
+
+After you deploy your machine learning model, note that providing input data that is markedly different from data that is used to train the model can produce an outlier prediction\. When linear regression algorithms such as Ridge and LinearRegression are passed an out of scale input value, the model extrapolates the values and assigns a relatively large weight to it, producing a score that is not in line with conforming data\.
+
+## Time Series pipeline with supporting features fails on retrieval ##
+
+If you train an AutoAI Time Series experiment by using supporting features and you get the error 'Error: name 'tspy\_interpolators' is not defined' when the system tries to retrieve the pipeline for predictions, check to make sure your system is running Java 8 or higher\.
+
+## Running a pipeline or experiment notebook fails with a software specification error ##
+
+If supported software specifications for AutoAI experiments change, you might get an error when you run a notebook built with an older software specification, such as an older version of Python\. In this case, run the experiment again, then save a new notebook and try again\.
+
+## Resolving an Out of Memory error ##
+
+If you get a memory error when you run a cell from an AutoAI generated notebook, create a notebook runtime with more resources for the AutoAI notebook and execute the cell again\.
+
+## Notebook for an experiment with subsampling can fail generating predictions ##
+
+If you do pipeline refinery to prepare the model, and the experiment uses subsampling of the data during training, you might encounter an “unknown class” error when you run a notebook that is saved from the experiment\.
+
+The problem stems from an unknown class that is not included in the training data set\. The workaround is to use the entire data set for training or re\-create the subsampling that is used in the experiment\.
+
+To subsample the training data (before `fit()`), provide sample size by number of rows or by fraction of the sample (as done in the experiment)\.
+
+
+
+ * If number of records was used in subsampling settings, you can increase the value of `n`\. For example:
+
+ train_df = train_df.sample(n=1000)
+ * If subsampling is represented as a fraction of the data set, increase the value of `frac`\. For example:
+
+ train_df = train_df.sample(frac=0.4, random_state=experiment_metadata['random_state'])
+
+
+
+## Pipeline creation fails for binary classification ##
+
+AutoAI analyzes a subset of the data to determine the best fit for experiment type\. If the sample data in the prediction column contains only two values, AutoAI recommends a binary classification experiment and applies the related algorithms\. However, if the full data set contains more than two values in the prediction column the binary classification fails and you get an error that indicates that AutoAI cannot create the pipelines\.
+
+In this case, manually change the experiment type from binary to either multiclass, for a defined set of values, or regression, for an unspecified set of values\.
+
+
+
+1. Click the **Reconfigure Experiment** icon to edit the experiment settings\.
+2. On the *Prediction* page of Experiment Settings, change the prediction type to the one that best matches the data in the prediction column\.
+3. Save the changes and run the experiment again\.
+
+
+
+## Next steps ##
+
+[AutoAI overview](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/autoai-overview.html)
+
+**Parent topic:**[AutoAI overview](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/autoai-overview.html)
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/167d5677958594ba275e34b8748f7e8091782560.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/167d5677958594ba275e34b8748f7e8091782560.md
new file mode 100644
index 0000000..d0d2b89
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/167d5677958594ba275e34b8748f7e8091782560.md
@@ -0,0 +1,48 @@
+# Decision Optimization experiment UI views and scenarios
+
+# Decision Optimization experiment views and scenarios #
+
+The Decision Optimization experiment UI has different views in which you can select data, create models, solve different scenarios, and visualize the results\.
+
+Quick links to sections:
+
+
+
+ * [ Overview](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__section_overview)
+ * [Hardware and software configuration](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__section_environment)
+ * [Prepare data view](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__section_preparedata)
+ * [Build model view](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__ModelView)
+ * [Multiple model files](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__section_g21_p5n_plb)
+ * [Run models](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__runmodel)
+ * [Run configuration](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__section_runconfig)
+ * [Run environment tab](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__envtabConfigRun)
+ * [Explore solution view](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__solution)
+ * [Scenario pane](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__scenariopanel)
+ * [Generating notebooks from scenarios](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__generateNB)
+ * [Importing scenarios](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__p_Importingscenarios)
+ * [Exporting scenarios](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html?context=cdpaas&locale=en#ModelBuilderInterface__p_Exportingscenarios)
+
+
+
+Note: To create and run Optimization models, you must have both a Machine Learning service added to your project and a deployment space that is associated with your experiment:
+
+
+
+1. Add a [**Machine Learning** service](https://cloud.ibm.com/catalog/services/machine-learning) to your project\. You can either add this service at the project level (see [Creating a Watson Machine Learning Service instance](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-service-instance.html)), or you can add it when you first create a new Decision Optimization experiment: click Add a Machine Learning service, select, or create a New service, click Associate, then close the window\.
+2. Associate a [**deployment space**](https://dataplatform.cloud.ibm.com/ml-runtime/spaces) with your Decision Optimization experiment (see [Deployment spaces](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-spaces_local.html#create))\. A deployment space can be created or selected when you first create a new Decision Optimization experiment: click Create a deployment space, enter a name for your deployment space, and click Create\. For existing models, you can also create, or select a space in the [Overview](https://dataplatform.cloud.ibm.com/docs/content/DO/DODS_Introduction/modelbuilderUI.html#ModelBuilderInterface__section_overview) information pane\.
+
+
+
+When you add a **Decision Optimization experiment** as an asset in your project, you open the **Decision Optimization experiment UI**\.
+
+With the Decision Optimization experiment UI, you can create and solve prescriptive optimization models that focus on the specific business problem that you want to solve\. To edit and solve models, you must have Admin or Editor roles in the project\. Viewers of shared projects can only see experiments, but cannot modify or run them\.
+
+You can create a Decision Optimization model from scratch by entering a name or by choosing a `.zip` file, and then selecting Create\. Scenario 1 opens\.
+
+With the Decision Optimization experiment UI, you can create several scenarios, with different data sets and optimization models\. Thus, you, can create and compare different scenarios and see what impact changes can have on a problem\.
+
+For a step\-by\-step guide to build, solve and deploy a Decision Optimization model, by using the user interface, see the [Quick start tutorial with video](https://dataplatform.cloud.ibm.com/docs/content/wsj/getting-started/get-started-do.html)\.
+
+For each of the following views, you can organize your screen as full\-screen or as a **split\-screen**\. To do so, hover over one of the view tabs ( Prepare data, Build model, Explore solution) for a second or two\. A menu then appears where you can select Full Screen, Left or Right\. For example, if you choose Left for the Prepare data view, and then choose Right for the Explore solution view, you can see both these views on the same screen\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/17470065afc59337b207721ab539b4622bbb3055.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/17470065afc59337b207721ab539b4622bbb3055.md
new file mode 100644
index 0000000..3db8f66
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/17470065afc59337b207721ab539b4622bbb3055.md
@@ -0,0 +1,9 @@
+# Scripting with Python for Spark (SPSS Modeler)
+
+# Scripting with Python for Spark #
+
+SPSS Modeler can run Python scripts using the Apache Spark framework to process data\. This documentation provides the Python API description for the interfaces provided\.
+
+The SPSS Modeler installation includes a Spark distribution\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/174d6fdf73627d7b2258d7f351c3d0156c06d1dc.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/174d6fdf73627d7b2258d7f351c3d0156c06d1dc.md
new file mode 100644
index 0000000..9361961
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/174d6fdf73627d7b2258d7f351c3d0156c06d1dc.md
@@ -0,0 +1,714 @@
+# Category types
+
+# Category types #
+
+The categories that are returned by the the Watson Natural Language Processing Categories block are based on the IAB Tech Lab Content Taxonomy, which provides common language categories that can be used when describing content\.
+
+The following table lists the IAB categories taxonomy returned by the Categories block\.
+
+
+
+| LEVEL 1 | LEVEL 2 | LEVEL 3 | LEVEL 4 |
+| ------------------------ | ----------------------------------- | ---------------------------------------------- | ------------------------------------ |
+| Automotive | | | |
+| Automotive | Auto Body Styles | | |
+| Automotive | Auto Body Styles | Commercial Trucks | |
+| Automotive | Auto Body Styles | Sedan | |
+| Automotive | Auto Body Styles | Station Wagon | |
+| Automotive | Auto Body Styles | SUV | |
+| Automotive | Auto Body Styles | Van | |
+| Automotive | Auto Body Styles | Convertible | |
+| Automotive | Auto Body Styles | Coupe | |
+| Automotive | Auto Body Styles | Crossover | |
+| Automotive | Auto Body Styles | Hatchback | |
+| Automotive | Auto Body Styles | Microcar | |
+| Automotive | Auto Body Styles | Minivan | |
+| Automotive | Auto Body Styles | Off\-Road Vehicles | |
+| Automotive | Auto Body Styles | Pickup Trucks | |
+| Automotive | Auto Type | | |
+| Automotive | Auto Type | Budget Cars | |
+| Automotive | Auto Type | Certified Pre\-Owned Cars | |
+| Automotive | Auto Type | Classic Cars | |
+| Automotive | Auto Type | Concept Cars | |
+| Automotive | Auto Type | Driverless Cars | |
+| Automotive | Auto Type | Green Vehicles | |
+| Automotive | Auto Type | Luxury Cars | |
+| Automotive | Auto Type | Performance Cars | |
+| Automotive | Car Culture | | |
+| Automotive | Dash Cam Videos | | |
+| Automotive | Motorcycles | | |
+| Automotive | Road\-Side Assistance | | |
+| Automotive | Scooters | | |
+| Automotive | Auto Buying and Selling | | |
+| Automotive | Auto Insurance | | |
+| Automotive | Auto Parts | | |
+| Automotive | Auto Recalls | | |
+| Automotive | Auto Repair | | |
+| Automotive | Auto Safety | | |
+| Automotive | Auto Shows | | |
+| Automotive | Auto Technology | | |
+| Automotive | Auto Technology | Auto Infotainment Technologies | |
+| Automotive | Auto Technology | Auto Navigation Systems | |
+| Automotive | Auto Technology | Auto Safety Technologies | |
+| Automotive | Auto Rentals | | |
+| Books and Literature | | | |
+| Books and Literature | Art and Photography Books | | |
+| Books and Literature | Biographies | | |
+| Books and Literature | Children's Literature | | |
+| Books and Literature | Comics and Graphic Novels | | |
+| Books and Literature | Cookbooks | | |
+| Books and Literature | Fiction | | |
+| Books and Literature | Poetry | | |
+| Books and Literature | Travel Books | | |
+| Books and Literature | Young Adult Literature | | |
+| Business and Finance | | | |
+| Business and Finance | Business | | |
+| Business and Finance | Business | Business Accounting & Finance | |
+| Business and Finance | Business | Human Resources | |
+| Business and Finance | Business | Large Business | |
+| Business and Finance | Business | Logistics | |
+| Business and Finance | Business | Marketing and Advertising | |
+| Business and Finance | Business | Sales | |
+| Business and Finance | Business | Small and Medium\-sized Business | |
+| Business and Finance | Business | Startups | |
+| Business and Finance | Business | Business Administration | |
+| Business and Finance | Business | Business Banking & Finance | |
+| Business and Finance | Business | Business Banking & Finance | Angel Investment |
+| Business and Finance | Business | Business Banking & Finance | Bankruptcy |
+| Business and Finance | Business | Business Banking & Finance | Business Loans |
+| Business and Finance | Business | Business Banking & Finance | Debt Factoring & Invoice Discounting |
+| Business and Finance | Business | Business Banking & Finance | Mergers and Acquisitions |
+| Business and Finance | Business | Business Banking & Finance | Private Equity |
+| Business and Finance | Business | Business Banking & Finance | Sale & Lease Back |
+| Business and Finance | Business | Business Banking & Finance | Venture Capital |
+| Business and Finance | Business | Business I\.T\. | |
+| Business and Finance | Business | Business Operations | |
+| Business and Finance | Business | Consumer Issues | |
+| Business and Finance | Business | Consumer Issues | Recalls |
+| Business and Finance | Business | Executive Leadership & Management | |
+| Business and Finance | Business | Government Business | |
+| Business and Finance | Business | Green Solutions | |
+| Business and Finance | Business | Business Utilities | |
+| Business and Finance | Economy | | |
+| Business and Finance | Economy | Commodities | |
+| Business and Finance | Economy | Currencies | |
+| Business and Finance | Economy | Financial Crisis | |
+| Business and Finance | Economy | Financial Reform | |
+| Business and Finance | Economy | Financial Regulation | |
+| Business and Finance | Economy | Gasoline Prices | |
+| Business and Finance | Economy | Housing Market | |
+| Business and Finance | Economy | Interest Rates | |
+| Business and Finance | Economy | Job Market | |
+| Business and Finance | Industries | | |
+| Business and Finance | Industries | Advertising Industry | |
+| Business and Finance | Industries | Education industry | |
+| Business and Finance | Industries | Entertainment Industry | |
+| Business and Finance | Industries | Environmental Services Industry | |
+| Business and Finance | Industries | Financial Industry | |
+| Business and Finance | Industries | Food Industry | |
+| Business and Finance | Industries | Healthcare Industry | |
+| Business and Finance | Industries | Hospitality Industry | |
+| Business and Finance | Industries | Information Services Industry | |
+| Business and Finance | Industries | Legal Services Industry | |
+| Business and Finance | Industries | Logistics and Transportation Industry | |
+| Business and Finance | Industries | Agriculture | |
+| Business and Finance | Industries | Management Consulting Industry | |
+| Business and Finance | Industries | Manufacturing Industry | |
+| Business and Finance | Industries | Mechanical and Industrial Engineering Industry | |
+| Business and Finance | Industries | Media Industry | |
+| Business and Finance | Industries | Metals Industry | |
+| Business and Finance | Industries | Non\-Profit Organizations | |
+| Business and Finance | Industries | Pharmaceutical Industry | |
+| Business and Finance | Industries | Power and Energy Industry | |
+| Business and Finance | Industries | Publishing Industry | |
+| Business and Finance | Industries | Real Estate Industry | |
+| Business and Finance | Industries | Apparel Industry | |
+| Business and Finance | Industries | Retail Industry | |
+| Business and Finance | Industries | Technology Industry | |
+| Business and Finance | Industries | Telecommunications Industry | |
+| Business and Finance | Industries | Automotive Industry | |
+| Business and Finance | Industries | Aviation Industry | |
+| Business and Finance | Industries | Biotech and Biomedical Industry | |
+| Business and Finance | Industries | Civil Engineering Industry | |
+| Business and Finance | Industries | Construction Industry | |
+| Business and Finance | Industries | Defense Industry | |
+| Careers | | | |
+| Careers | Apprenticeships | | |
+| Careers | Career Advice | | |
+| Careers | Career Planning | | |
+| Careers | Job Search | | |
+| Careers | Job Search | Job Fairs | |
+| Careers | Job Search | Resume Writing and Advice | |
+| Careers | Remote Working | | |
+| Careers | Vocational Training | | |
+| Education | | | |
+| Education | Adult Education | | |
+| Education | Private School | | |
+| Education | Secondary Education | | |
+| Education | Special Education | | |
+| Education | College Education | | |
+| Education | College Education | College Planning | |
+| Education | College Education | Postgraduate Education | |
+| Education | College Education | Postgraduate Education | Professional School |
+| Education | College Education | Undergraduate Education | |
+| Education | Early Childhood Education | | |
+| Education | Educational Assessment | | |
+| Education | Educational Assessment | Standardized Testing | |
+| Education | Homeschooling | | |
+| Education | Homework and Study | | |
+| Education | Language Learning | | |
+| Education | Online Education | | |
+| Education | Primary Education | | |
+| Events and Attractions | | | |
+| Events and Attractions | Amusement and Theme Parks | | |
+| Events and Attractions | Fashion Events | | |
+| Events and Attractions | Historic Site and Landmark Tours | | |
+| Events and Attractions | Malls & Shopping Centers | | |
+| Events and Attractions | Museums & Galleries | | |
+| Events and Attractions | Musicals | | |
+| Events and Attractions | National & Civic Holidays | | |
+| Events and Attractions | Nightclubs | | |
+| Events and Attractions | Outdoor Activities | | |
+| Events and Attractions | Parks & Nature | | |
+| Events and Attractions | Party Supplies and Decorations | | |
+| Events and Attractions | Awards Shows | | |
+| Events and Attractions | Personal Celebrations & Life Events | | |
+| Events and Attractions | Personal Celebrations & Life Events | Anniversary | |
+| Events and Attractions | Personal Celebrations & Life Events | Wedding | |
+| Events and Attractions | Personal Celebrations & Life Events | Baby Shower | |
+| Events and Attractions | Personal Celebrations & Life Events | Bachelor Party | |
+| Events and Attractions | Personal Celebrations & Life Events | Bachelorette Party | |
+| Events and Attractions | Personal Celebrations & Life Events | Birth | |
+| Events and Attractions | Personal Celebrations & Life Events | Birthday | |
+| Events and Attractions | Personal Celebrations & Life Events | Funeral | |
+| Events and Attractions | Personal Celebrations & Life Events | Graduation | |
+| Events and Attractions | Personal Celebrations & Life Events | Prom | |
+| Events and Attractions | Political Event | | |
+| Events and Attractions | Religious Events | | |
+| Events and Attractions | Sporting Events | | |
+| Events and Attractions | Theater Venues and Events | | |
+| Events and Attractions | Zoos & Aquariums | | |
+| Events and Attractions | Bars & Restaurants | | |
+| Events and Attractions | Business Expos & Conferences | | |
+| Events and Attractions | Casinos & Gambling | | |
+| Events and Attractions | Cinemas and Events | | |
+| Events and Attractions | Comedy Events | | |
+| Events and Attractions | Concerts & Music Events | | |
+| Events and Attractions | Fan Conventions | | |
+| Family and Relationships | | | |
+| Family and Relationships | Bereavement | | |
+| Family and Relationships | Dating | | |
+| Family and Relationships | Divorce | | |
+| Family and Relationships | Eldercare | | |
+| Family and Relationships | Marriage and Civil Unions | | |
+| Family and Relationships | Parenting | | |
+| Family and Relationships | Parenting | Adoption and Fostering | |
+| Family and Relationships | Parenting | Daycare and Pre\-School | |
+| Family and Relationships | Parenting | Internet Safety | |
+| Family and Relationships | Parenting | Parenting Babies and Toddlers | |
+| Family and Relationships | Parenting | Parenting Children Aged 4\-11 | |
+| Family and Relationships | Parenting | Parenting Teens | |
+| Family and Relationships | Parenting | Special Needs Kids | |
+| Family and Relationships | Single Life | | |
+| Fine Art | | | |
+| Fine Art | Costume | | |
+| Fine Art | Dance | | |
+| Fine Art | Design | | |
+| Fine Art | Digital Arts | | |
+| Fine Art | Fine Art Photography | | |
+| Fine Art | Modern Art | | |
+| Fine Art | Opera | | |
+| Fine Art | Theater | | |
+| Food & Drink | | | |
+| Food & Drink | Alcoholic Beverages | | |
+| Food & Drink | Vegan Diets | | |
+| Food & Drink | Vegetarian Diets | | |
+| Food & Drink | World Cuisines | | |
+| Food & Drink | Barbecues and Grilling | | |
+| Food & Drink | Cooking | | |
+| Food & Drink | Desserts and Baking | | |
+| Food & Drink | Dining Out | | |
+| Food & Drink | Food Allergies | | |
+| Food & Drink | Food Movements | | |
+| Food & Drink | Healthy Cooking and Eating | | |
+| Food & Drink | Non\-Alcoholic Beverages | | |
+| Healthy Living | | | |
+| Healthy Living | Children's Health | | |
+| Healthy Living | Fitness and Exercise | | |
+| Healthy Living | Fitness and Exercise | Participant Sports | |
+| Healthy Living | Fitness and Exercise | Running and Jogging | |
+| Healthy Living | Men's Health | | |
+| Healthy Living | Nutrition | | |
+| Healthy Living | Senior Health | | |
+| Healthy Living | Weight Loss | | |
+| Healthy Living | Wellness | | |
+| Healthy Living | Wellness | Alternative Medicine | |
+| Healthy Living | Wellness | Alternative Medicine | Herbs and Supplements |
+| Healthy Living | Wellness | Alternative Medicine | Holistic Health |
+| Healthy Living | Wellness | Physical Therapy | |
+| Healthy Living | Wellness | Smoking Cessation | |
+| Healthy Living | Women's Health | | |
+| Hobbies & Interests | | | |
+| Hobbies & Interests | Antiquing and Antiques | | |
+| Hobbies & Interests | Magic and Illusion | | |
+| Hobbies & Interests | Model Toys | | |
+| Hobbies & Interests | Musical Instruments | | |
+| Hobbies & Interests | Paranormal Phenomena | | |
+| Hobbies & Interests | Radio Control | | |
+| Hobbies & Interests | Sci\-fi and Fantasy | | |
+| Hobbies & Interests | Workshops and Classes | | |
+| Hobbies & Interests | Arts and Crafts | | |
+| Hobbies & Interests | Arts and Crafts | Beadwork | |
+| Hobbies & Interests | Arts and Crafts | Candle and Soap Making | |
+| Hobbies & Interests | Arts and Crafts | Drawing and Sketching | |
+| Hobbies & Interests | Arts and Crafts | Jewelry Making | |
+| Hobbies & Interests | Arts and Crafts | Needlework | |
+| Hobbies & Interests | Arts and Crafts | Painting | |
+| Hobbies & Interests | Arts and Crafts | Photography | |
+| Hobbies & Interests | Arts and Crafts | Scrapbooking | |
+| Hobbies & Interests | Arts and Crafts | Woodworking | |
+| Hobbies & Interests | Beekeeping | | |
+| Hobbies & Interests | Birdwatching | | |
+| Hobbies & Interests | Cigars | | |
+| Hobbies & Interests | Collecting | | |
+| Hobbies & Interests | Collecting | Comic Books | |
+| Hobbies & Interests | Collecting | Stamps and Coins | |
+| Hobbies & Interests | Content Production | | |
+| Hobbies & Interests | Content Production | Audio Production | |
+| Hobbies & Interests | Content Production | Freelance Writing | |
+| Hobbies & Interests | Content Production | Screenwriting | |
+| Hobbies & Interests | Content Production | Video Production | |
+| Hobbies & Interests | Games and Puzzles | | |
+| Hobbies & Interests | Games and Puzzles | Board Games and Puzzles | |
+| Hobbies & Interests | Games and Puzzles | Card Games | |
+| Hobbies & Interests | Games and Puzzles | Roleplaying Games | |
+| Hobbies & Interests | Genealogy and Ancestry | | |
+| Home & Garden | | | |
+| Home & Garden | Gardening | | |
+| Home & Garden | Remodeling & Construction | | |
+| Home & Garden | Smart Home | | |
+| Home & Garden | Home Appliances | | |
+| Home & Garden | Home Entertaining | | |
+| Home & Garden | Home Improvement | | |
+| Home & Garden | Home Security | | |
+| Home & Garden | Indoor Environmental Quality | | |
+| Home & Garden | Interior Decorating | | |
+| Home & Garden | Landscaping | | |
+| Home & Garden | Outdoor Decorating | | |
+| Medical Health | | | |
+| Medical Health | Diseases and Conditions | | |
+| Medical Health | Diseases and Conditions | Allergies | |
+| Medical Health | Diseases and Conditions | Ear, Nose and Throat Conditions | |
+| Medical Health | Diseases and Conditions | Endocrine and Metabolic Diseases | |
+| Medical Health | Diseases and Conditions | Endocrine and Metabolic Diseases | Hormonal Disorders |
+| Medical Health | Diseases and Conditions | Endocrine and Metabolic Diseases | Menopause |
+| Medical Health | Diseases and Conditions | Endocrine and Metabolic Diseases | Thyroid Disorders |
+| Medical Health | Diseases and Conditions | Eye and Vision Conditions | |
+| Medical Health | Diseases and Conditions | Foot Health | |
+| Medical Health | Diseases and Conditions | Heart and Cardiovascular Diseases | |
+| Medical Health | Diseases and Conditions | Infectious Diseases | |
+| Medical Health | Diseases and Conditions | Injuries | |
+| Medical Health | Diseases and Conditions | Injuries | First Aid |
+| Medical Health | Diseases and Conditions | Lung and Respiratory Health | |
+| Medical Health | Diseases and Conditions | Mental Health | |
+| Medical Health | Diseases and Conditions | Reproductive Health | |
+| Medical Health | Diseases and Conditions | Reproductive Health | Birth Control |
+| Medical Health | Diseases and Conditions | Reproductive Health | Infertility |
+| Medical Health | Diseases and Conditions | Reproductive Health | Pregnancy |
+| Medical Health | Diseases and Conditions | Blood Disorders | |
+| Medical Health | Diseases and Conditions | Sexual Health | |
+| Medical Health | Diseases and Conditions | Sexual Health | Sexual Conditions |
+| Medical Health | Diseases and Conditions | Skin and Dermatology | |
+| Medical Health | Diseases and Conditions | Sleep Disorders | |
+| Medical Health | Diseases and Conditions | Substance Abuse | |
+| Medical Health | Diseases and Conditions | Bone and Joint Conditions | |
+| Medical Health | Diseases and Conditions | Brain and Nervous System Disorders | |
+| Medical Health | Diseases and Conditions | Cancer | |
+| Medical Health | Diseases and Conditions | Cold and Flu | |
+| Medical Health | Diseases and Conditions | Dental Health | |
+| Medical Health | Diseases and Conditions | Diabetes | |
+| Medical Health | Diseases and Conditions | Digestive Disorders | |
+| Medical Health | Medical Tests | | |
+| Medical Health | Pharmaceutical Drugs | | |
+| Medical Health | Surgery | | |
+| Medical Health | Vaccines | | |
+| Medical Health | Cosmetic Medical Services | | |
+| Movies | | | |
+| Movies | Action and Adventure Movies | | |
+| Movies | Romance Movies | | |
+| Movies | Science Fiction Movies | | |
+| Movies | Indie and Arthouse Movies | | |
+| Movies | Animation Movies | | |
+| Movies | Comedy Movies | | |
+| Movies | Crime and Mystery Movies | | |
+| Movies | Documentary Movies | | |
+| Movies | Drama Movies | | |
+| Movies | Family and Children Movies | | |
+| Movies | Fantasy Movies | | |
+| Movies | Horror Movies | | |
+| Movies | World Movies | | |
+| Music and Audio | | | |
+| Music and Audio | Adult Contemporary Music | | |
+| Music and Audio | Adult Contemporary Music | Soft AC Music | |
+| Music and Audio | Adult Contemporary Music | Urban AC Music | |
+| Music and Audio | Adult Album Alternative | | |
+| Music and Audio | Alternative Music | | |
+| Music and Audio | Children's Music | | |
+| Music and Audio | Classic Hits | | |
+| Music and Audio | Classical Music | | |
+| Music and Audio | College Radio | | |
+| Music and Audio | Comedy (Music and Audio) | | |
+| Music and Audio | Contemporary Hits/Pop/Top 40 | | |
+| Music and Audio | Country Music | | |
+| Music and Audio | Dance and Electronic Music | | |
+| Music and Audio | World/International Music | | |
+| Music and Audio | Songwriters/Folk | | |
+| Music and Audio | Gospel Music | | |
+| Music and Audio | Hip Hop Music | | |
+| Music and Audio | Inspirational/New Age Music | | |
+| Music and Audio | Jazz | | |
+| Music and Audio | Oldies/Adult Standards | | |
+| Music and Audio | Reggae | | |
+| Music and Audio | Blues | | |
+| Music and Audio | Religious (Music and Audio) | | |
+| Music and Audio | R&B/Soul/Funk | | |
+| Music and Audio | Rock Music | | |
+| Music and Audio | Rock Music | Album\-oriented Rock | |
+| Music and Audio | Rock Music | Alternative Rock | |
+| Music and Audio | Rock Music | Classic Rock | |
+| Music and Audio | Rock Music | Hard Rock | |
+| Music and Audio | Rock Music | Soft Rock | |
+| Music and Audio | Soundtracks, TV and Showtunes | | |
+| Music and Audio | Sports Radio | | |
+| Music and Audio | Talk Radio | | |
+| Music and Audio | Talk Radio | Business News Radio | |
+| Music and Audio | Talk Radio | Educational Radio | |
+| Music and Audio | Talk Radio | News Radio | |
+| Music and Audio | Talk Radio | News/Talk Radio | |
+| Music and Audio | Talk Radio | Public Radio | |
+| Music and Audio | Urban Contemporary Music | | |
+| Music and Audio | Variety (Music and Audio) | | |
+| News and Politics | | | |
+| News and Politics | Crime | | |
+| News and Politics | Disasters | | |
+| News and Politics | International News | | |
+| News and Politics | Law | | |
+| News and Politics | Local News | | |
+| News and Politics | National News | | |
+| News and Politics | Politics | | |
+| News and Politics | Politics | Elections | |
+| News and Politics | Politics | Political Issues | |
+| News and Politics | Politics | War and Conflicts | |
+| News and Politics | Weather | | |
+| Personal Finance | | | |
+| Personal Finance | Consumer Banking | | |
+| Personal Finance | Financial Assistance | | |
+| Personal Finance | Financial Assistance | Government Support and Welfare | |
+| Personal Finance | Financial Assistance | Student Financial Aid | |
+| Personal Finance | Financial Planning | | |
+| Personal Finance | Frugal Living | | |
+| Personal Finance | Insurance | | |
+| Personal Finance | Insurance | Health Insurance | |
+| Personal Finance | Insurance | Home Insurance | |
+| Personal Finance | Insurance | Life Insurance | |
+| Personal Finance | Insurance | Motor Insurance | |
+| Personal Finance | Insurance | Pet Insurance | |
+| Personal Finance | Insurance | Travel Insurance | |
+| Personal Finance | Personal Debt | | |
+| Personal Finance | Personal Debt | Credit Cards | |
+| Personal Finance | Personal Debt | Home Financing | |
+| Personal Finance | Personal Debt | Personal Loans | |
+| Personal Finance | Personal Debt | Student Loans | |
+| Personal Finance | Personal Investing | | |
+| Personal Finance | Personal Investing | Hedge Funds | |
+| Personal Finance | Personal Investing | Mutual Funds | |
+| Personal Finance | Personal Investing | Options | |
+| Personal Finance | Personal Investing | Stocks and Bonds | |
+| Personal Finance | Personal Taxes | | |
+| Personal Finance | Retirement Planning | | |
+| Personal Finance | Home Utilities | | |
+| Personal Finance | Home Utilities | Gas and Electric | |
+| Personal Finance | Home Utilities | Internet Service Providers | |
+| Personal Finance | Home Utilities | Phone Services | |
+| Personal Finance | Home Utilities | Water Services | |
+| Pets | | | |
+| Pets | Birds | | |
+| Pets | Cats | | |
+| Pets | Dogs | | |
+| Pets | Fish and Aquariums | | |
+| Pets | Large Animals | | |
+| Pets | Pet Adoptions | | |
+| Pets | Reptiles | | |
+| Pets | Veterinary Medicine | | |
+| Pets | Pet Supplies | | |
+| Pop Culture | | | |
+| Pop Culture | Celebrity Deaths | | |
+| Pop Culture | Celebrity Families | | |
+| Pop Culture | Celebrity Homes | | |
+| Pop Culture | Celebrity Pregnancy | | |
+| Pop Culture | Celebrity Relationships | | |
+| Pop Culture | Celebrity Scandal | | |
+| Pop Culture | Celebrity Style | | |
+| Pop Culture | Humor and Satire | | |
+| Real Estate | | | |
+| Real Estate | Apartments | | |
+| Real Estate | Retail Property | | |
+| Real Estate | Vacation Properties | | |
+| Real Estate | Developmental Sites | | |
+| Real Estate | Hotel Properties | | |
+| Real Estate | Houses | | |
+| Real Estate | Industrial Property | | |
+| Real Estate | Land and Farms | | |
+| Real Estate | Office Property | | |
+| Real Estate | Real Estate Buying and Selling | | |
+| Real Estate | Real Estate Renting and Leasing | | |
+| Religion & Spirituality | | | |
+| Religion & Spirituality | Agnosticism | | |
+| Religion & Spirituality | Spirituality | | |
+| Religion & Spirituality | Astrology | | |
+| Religion & Spirituality | Atheism | | |
+| Religion & Spirituality | Buddhism | | |
+| Religion & Spirituality | Christianity | | |
+| Religion & Spirituality | Hinduism | | |
+| Religion & Spirituality | Islam | | |
+| Religion & Spirituality | Judaism | | |
+| Religion & Spirituality | Sikhism | | |
+| Science | | | |
+| Science | Biological Sciences | | |
+| Science | Chemistry | | |
+| Science | Environment | | |
+| Science | Genetics | | |
+| Science | Geography | | |
+| Science | Geology | | |
+| Science | Physics | | |
+| Science | Space and Astronomy | | |
+| Shopping | | | |
+| Shopping | Coupons and Discounts | | |
+| Shopping | Flower Shopping | | |
+| Shopping | Gifts and Greetings Cards | | |
+| Shopping | Grocery Shopping | | |
+| Shopping | Holiday Shopping | | |
+| Shopping | Household Supplies | | |
+| Shopping | Lotteries and Scratchcards | | |
+| Shopping | Sales and Promotions | | |
+| Shopping | Children's Games and Toys | | |
+| Sports | | | |
+| Sports | American Football | | |
+| Sports | Boxing | | |
+| Sports | Cheerleading | | |
+| Sports | College Sports | | |
+| Sports | College Sports | College Football | |
+| Sports | College Sports | College Basketball | |
+| Sports | College Sports | College Baseball | |
+| Sports | Cricket | | |
+| Sports | Cycling | | |
+| Sports | Darts | | |
+| Sports | Disabled Sports | | |
+| Sports | Diving | | |
+| Sports | Equine Sports | | |
+| Sports | Equine Sports | Horse Racing | |
+| Sports | Extreme Sports | | |
+| Sports | Extreme Sports | Canoeing and Kayaking | |
+| Sports | Extreme Sports | Climbing | |
+| Sports | Extreme Sports | Paintball | |
+| Sports | Extreme Sports | Scuba Diving | |
+| Sports | Extreme Sports | Skateboarding | |
+| Sports | Extreme Sports | Snowboarding | |
+| Sports | Extreme Sports | Surfing and Bodyboarding | |
+| Sports | Extreme Sports | Waterskiing and Wakeboarding | |
+| Sports | Australian Rules Football | | |
+| Sports | Fantasy Sports | | |
+| Sports | Field Hockey | | |
+| Sports | Figure Skating | | |
+| Sports | Fishing Sports | | |
+| Sports | Golf | | |
+| Sports | Gymnastics | | |
+| Sports | Hunting and Shooting | | |
+| Sports | Ice Hockey | | |
+| Sports | Inline Skating | | |
+| Sports | Lacrosse | | |
+| Sports | Auto Racing | | |
+| Sports | Auto Racing | Motorcycle Sports | |
+| Sports | Martial Arts | | |
+| Sports | Olympic Sports | | |
+| Sports | Olympic Sports | Summer Olympic Sports | |
+| Sports | Olympic Sports | Winter Olympic Sports | |
+| Sports | Poker and Professional Gambling | | |
+| Sports | Rodeo | | |
+| Sports | Rowing | | |
+| Sports | Rugby | | |
+| Sports | Rugby | Rugby League | |
+| Sports | Rugby | Rugby Union | |
+| Sports | Sailing | | |
+| Sports | Skiing | | |
+| Sports | Snooker/Pool/Billiards | | |
+| Sports | Soccer | | |
+| Sports | Badminton | | |
+| Sports | Softball | | |
+| Sports | Squash | | |
+| Sports | Swimming | | |
+| Sports | Table Tennis | | |
+| Sports | Tennis | | |
+| Sports | Track and Field | | |
+| Sports | Volleyball | | |
+| Sports | Walking | | |
+| Sports | Water Polo | | |
+| Sports | Weightlifting | | |
+| Sports | Baseball | | |
+| Sports | Wrestling | | |
+| Sports | Basketball | | |
+| Sports | Beach Volleyball | | |
+| Sports | Bodybuilding | | |
+| Sports | Bowling | | |
+| Sports | Sports Equipment | | |
+| Style & Fashion | | | |
+| Style & Fashion | Beauty | | |
+| Style & Fashion | Beauty | Hair Care | |
+| Style & Fashion | Beauty | Makeup and Accessories | |
+| Style & Fashion | Beauty | Nail Care | |
+| Style & Fashion | Beauty | Natural and Organic Beauty | |
+| Style & Fashion | Beauty | Perfume and Fragrance | |
+| Style & Fashion | Beauty | Skin Care | |
+| Style & Fashion | Women's Fashion | | |
+| Style & Fashion | Women's Fashion | Women's Accessories | |
+| Style & Fashion | Women's Fashion | Women's Accessories | Women's Glasses |
+| Style & Fashion | Women's Fashion | Women's Accessories | Women's Handbags and Wallets |
+| Style & Fashion | Women's Fashion | Women's Accessories | Women's Hats and Scarves |
+| Style & Fashion | Women's Fashion | Women's Accessories | Women's Jewelry and Watches |
+| Style & Fashion | Women's Fashion | Women's Clothing | |
+| Style & Fashion | Women's Fashion | Women's Clothing | Women's Business Wear |
+| Style & Fashion | Women's Fashion | Women's Clothing | Women's Casual Wear |
+| Style & Fashion | Women's Fashion | Women's Clothing | Women's Formal Wear |
+| Style & Fashion | Women's Fashion | Women's Clothing | Women's Intimates and Sleepwear |
+| Style & Fashion | Women's Fashion | Women's Clothing | Women's Outerwear |
+| Style & Fashion | Women's Fashion | Women's Clothing | Women's Sportswear |
+| Style & Fashion | Women's Fashion | Women's Shoes and Footwear | |
+| Style & Fashion | Body Art | | |
+| Style & Fashion | Children's Clothing | | |
+| Style & Fashion | Designer Clothing | | |
+| Style & Fashion | Fashion Trends | | |
+| Style & Fashion | High Fashion | | |
+| Style & Fashion | Men's Fashion | | |
+| Style & Fashion | Men's Fashion | Men's Accessories | |
+| Style & Fashion | Men's Fashion | Men's Accessories | Men's Jewelry and Watches |
+| Style & Fashion | Men's Fashion | Men's Clothing | |
+| Style & Fashion | Men's Fashion | Men's Clothing | Men's Business Wear |
+| Style & Fashion | Men's Fashion | Men's Clothing | Men's Casual Wear |
+| Style & Fashion | Men's Fashion | Men's Clothing | Men's Formal Wear |
+| Style & Fashion | Men's Fashion | Men's Clothing | Men's Outerwear |
+| Style & Fashion | Men's Fashion | Men's Clothing | Men's Sportswear |
+| Style & Fashion | Men's Fashion | Men's Clothing | Men's Underwear and Sleepwear |
+| Style & Fashion | Men's Fashion | Men's Shoes and Footwear | |
+| Style & Fashion | Personal Care | | |
+| Style & Fashion | Personal Care | Bath and Shower | |
+| Style & Fashion | Personal Care | Deodorant and Antiperspirant | |
+| Style & Fashion | Personal Care | Oral care | |
+| Style & Fashion | Personal Care | Shaving | |
+| Style & Fashion | Street Style | | |
+| Technology & Computing | | | |
+| Technology & Computing | Artificial Intelligence | | |
+| Technology & Computing | Augmented Reality | | |
+| Technology & Computing | Computing | | |
+| Technology & Computing | Computing | Computer Networking | |
+| Technology & Computing | Computing | Computer Peripherals | |
+| Technology & Computing | Computing | Computer Software and Applications | |
+| Technology & Computing | Computing | Computer Software and Applications | 3\-D Graphics |
+| Technology & Computing | Computing | Computer Software and Applications | Photo Editing Software |
+| Technology & Computing | Computing | Computer Software and Applications | Shareware and Freeware |
+| Technology & Computing | Computing | Computer Software and Applications | Video Software |
+| Technology & Computing | Computing | Computer Software and Applications | Web Conferencing |
+| Technology & Computing | Computing | Computer Software and Applications | Antivirus Software |
+| Technology & Computing | Computing | Computer Software and Applications | Browsers |
+| Technology & Computing | Computing | Computer Software and Applications | Computer Animation |
+| Technology & Computing | Computing | Computer Software and Applications | Databases |
+| Technology & Computing | Computing | Computer Software and Applications | Desktop Publishing |
+| Technology & Computing | Computing | Computer Software and Applications | Digital Audio |
+| Technology & Computing | Computing | Computer Software and Applications | Graphics Software |
+| Technology & Computing | Computing | Computer Software and Applications | Operating Systems |
+| Technology & Computing | Computing | Data Storage and Warehousing | |
+| Technology & Computing | Computing | Desktops | |
+| Technology & Computing | Computing | Information and Network Security | |
+| Technology & Computing | Computing | Internet | |
+| Technology & Computing | Computing | Internet | Cloud Computing |
+| Technology & Computing | Computing | Internet | Web Development |
+| Technology & Computing | Computing | Internet | Web Hosting |
+| Technology & Computing | Computing | Internet | Email |
+| Technology & Computing | Computing | Internet | Internet for Beginners |
+| Technology & Computing | Computing | Internet | Internet of Things |
+| Technology & Computing | Computing | Internet | IT and Internet Support |
+| Technology & Computing | Computing | Internet | Search |
+| Technology & Computing | Computing | Internet | Social Networking |
+| Technology & Computing | Computing | Internet | Web Design and HTML |
+| Technology & Computing | Computing | Laptops | |
+| Technology & Computing | Computing | Programming Languages | |
+| Technology & Computing | Consumer Electronics | | |
+| Technology & Computing | Consumer Electronics | Cameras and Camcorders | |
+| Technology & Computing | Consumer Electronics | Home Entertainment Systems | |
+| Technology & Computing | Consumer Electronics | Smartphones | |
+| Technology & Computing | Consumer Electronics | Tablets and E\-readers | |
+| Technology & Computing | Consumer Electronics | Wearable Technology | |
+| Technology & Computing | Robotics | | |
+| Technology & Computing | Virtual Reality | | |
+| Television | | | |
+| Television | Animation TV | | |
+| Television | Soap Opera TV | | |
+| Television | Special Interest TV | | |
+| Television | Sports TV | | |
+| Television | Children's TV | | |
+| Television | Comedy TV | | |
+| Television | Drama TV | | |
+| Television | Factual TV | | |
+| Television | Holiday TV | | |
+| Television | Music TV | | |
+| Television | Reality TV | | |
+| Television | Science Fiction TV | | |
+| Travel | | | |
+| Travel | Travel Accessories | | |
+| Travel | Travel Locations | | |
+| Travel | Travel Locations | Africa Travel | |
+| Travel | Travel Locations | Asia Travel | |
+| Travel | Travel Locations | Australia and Oceania Travel | |
+| Travel | Travel Locations | Europe Travel | |
+| Travel | Travel Locations | North America Travel | |
+| Travel | Travel Locations | Polar Travel | |
+| Travel | Travel Locations | South America Travel | |
+| Travel | Travel Preparation and Advice | | |
+| Travel | Travel Type | | |
+| Travel | Travel Type | Adventure Travel | |
+| Travel | Travel Type | Family Travel | |
+| Travel | Travel Type | Honeymoons and Getaways | |
+| Travel | Travel Type | Hotels and Motels | |
+| Travel | Travel Type | Rail Travel | |
+| Travel | Travel Type | Road Trips | |
+| Travel | Travel Type | Spas | |
+| Travel | Travel Type | Air Travel | |
+| Travel | Travel Type | Beach Travel | |
+| Travel | Travel Type | Bed & Breakfasts | |
+| Travel | Travel Type | Budget Travel | |
+| Travel | Travel Type | Business Travel | |
+| Travel | Travel Type | Camping | |
+| Travel | Travel Type | Cruises | |
+| Travel | Travel Type | Day Trips | |
+| Video Gaming | | | |
+| Video Gaming | Console Games | | |
+| Video Gaming | eSports | | |
+| Video Gaming | Mobile Games | | |
+| Video Gaming | PC Games | | |
+| Video Gaming | Video Game Genres | | |
+| Video Gaming | Video Game Genres | Action Video Games | |
+| Video Gaming | Video Game Genres | Role\-Playing Video Games | |
+| Video Gaming | Video Game Genres | Simulation Video Games | |
+| Video Gaming | Video Game Genres | Sports Video Games | |
+| Video Gaming | Video Game Genres | Strategy Video Games | |
+| Video Gaming | Video Game Genres | Action\-Adventure Video Games | |
+| Video Gaming | Video Game Genres | Adventure Video Games | |
+| Video Gaming | Video Game Genres | Casual Games | |
+| Video Gaming | Video Game Genres | Educational Video Games | |
+| Video Gaming | Video Game Genres | Exercise and Fitness Video Games | |
+| Video Gaming | Video Game Genres | MMOs | |
+| Video Gaming | Video Game Genres | Music and Party Video Games | |
+| Video Gaming | Video Game Genres | Puzzle Video Games |
+
+
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/179bdefa68b788a2c197f0094c43979d9265ba77.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/179bdefa68b788a2c197f0094c43979d9265ba77.md
new file mode 100644
index 0000000..5b7a632
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/179bdefa68b788a2c197f0094c43979d9265ba77.md
@@ -0,0 +1,7 @@
+# Data Asset Import node properties
+
+# Data Asset Import node properties #
+
+Refer to this section for a list of available properties for Import nodes\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/17ac1becae0867381bc236d4c0cc8fc4b8921a0a.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/17ac1becae0867381bc236d4c0cc8fc4b8921a0a.md
new file mode 100644
index 0000000..a085972
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/17ac1becae0867381bc236d4c0cc8fc4b8921a0a.md
@@ -0,0 +1,39 @@
+# Compute resource options for Tuning Studio experiments in projects
+
+# Compute resource options for Tuning Studio experiments in projects #
+
+A Tuning Studio experiment has a single hardware configuration\.
+
+The following table shows the hardware configuration that is used when tuning foundation models in a tuning experiment\.
+
+
+
+Hardware configuration available in projects for Tuning Studio
+
+| Capacity type | Capacity units per hour |
+| ------------- | ----------------------- |
+
+
+
+NVIDIA A100 80GB GPU\|43\|
+
+## Compute usage in projects ##
+
+Tuning Studio consumes compute resources as CUH from the Watson Machine Learning service\.
+
+You can monitor the total monthly amount of CUH consumption for the Watson Machine Learning service on the **Resource usage** page on the **Manage** tab of your project\.
+
+## Learn more ##
+
+
+
+ * [Tuning Studio](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-tuning-studio.html)
+ * [Watson Machine Learning plans](https://dataplatform.cloud.ibm.com/docs/content/wsj/getting-started/wml-plans.html)
+ * [Compute resource options for assets and deployments in spaces](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/run-cuh-deploy-spaces.html)
+ * [Monitoring account resource usage](https://dataplatform.cloud.ibm.com/docs/content/wsj/admin/monitor-resources.html)
+
+
+
+**Parent topic:**[Choosing compute resources for tools](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/environments-parent.html)
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/17e39c164e92d0646c4dddadfdf178bf3b5e2ad0.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/17e39c164e92d0646c4dddadfdf178bf3b5e2ad0.md
new file mode 100644
index 0000000..15a1e3e
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/17e39c164e92d0646c4dddadfdf178bf3b5e2ad0.md
@@ -0,0 +1,26 @@
+# settoflagnode properties
+
+# settoflagnode properties #
+
+The SetToFlag node derives multiple flag fields based on the categorical values defined for one or more nominal fields\.
+
+
+
+settoflagnode properties
+
+Table 1\. settoflagnode properties
+
+| `settoflagnode` properties | Data type | Property description |
+| -------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
+| `fields_from` | \[*category category category*\] `all` | |
+| `true_value` | *string* | Specifies the true value used by the node when setting a flag\. The default is `T`\. |
+| `false_value` | *string* | Specifies the false value used by the node when setting a flag\. The default is `F`\. |
+| `use_extension` | *flag* | Use an extension as a suffix or prefix to the new flag field\. |
+| `extension` | *string* | |
+| `add_as` | `Suffix``Prefix` | Specifies whether the extension is added as a suffix or prefix\. |
+| `aggregate` | *flag* | Groups records together based on key fields\. All flag fields in a group are enabled if any record is set to true\. |
+| `keys` | *list* | Key fields\. |
+
+
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/185c42ab06de9ff515dcd03213f5c4608c6faebf.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/185c42ab06de9ff515dcd03213f5c4608c6faebf.md
new file mode 100644
index 0000000..4363049
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/185c42ab06de9ff515dcd03213f5c4608c6faebf.md
@@ -0,0 +1,18 @@
+# Reals (SPSS Modeler)
+
+# Reals #
+
+*Real* refers to a floating\-point number\. Reals are represented by one or more digits followed by a decimal point followed by one or more digits\. CLEM reals are held in double precision\.
+
+Optionally, you can place a minus sign (−) before the real to denote a negative number (for example, `1.234`, `0.999`, −`77.001`)\. Use the form <*number*> e <*exponent*> to express a real number in exponential notation (for example, `1234.0e5`, `1.7e`−`2`)\. When SPSS Modeler reads number strings from files and converts them automatically to numbers, numbers with no leading digit before the decimal point or with no digit after the point are accepted (for example, `999.` or `.11`)\. However, these forms are illegal in CLEM expressions\.
+
+Note: When referencing real numbers in CLEM expressions, a period must be used as the decimal separator, regardless of any settings for the current flow or locale\. For example, specify
+
+ Na > 0.6
+
+rather than
+
+ Na > 0,6
+
+This applies even if a comma is selected as the decimal symbol in the flow properties and is consistent with the general guideline that code syntax should be independent of any specific locale or convention\.
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/189f970cf3b162e67b98b2a928b36193169e3caf.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/189f970cf3b162e67b98b2a928b36193169e3caf.md
new file mode 100644
index 0000000..b25158b
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/189f970cf3b162e67b98b2a928b36193169e3caf.md
@@ -0,0 +1,15 @@
+# Working with your data (SPSS Modeler)
+
+# Working with your data #
+
+To see a quick sample of a flow's data, right\-click a node a select Preview\. To more thoroughly examine your data, use a Charts node to launch the chart builder\.
+
+With the chart builder, you can use advanced visualizations to explore your data from different perspectives and identify patterns, connections, and relationships within your data\. You can also visualize your data with these same charts in a Data Refinery flow\.
+
+Figure 1\. Sample visualizations available for a flow
+
+
+
+For more information, see [Visualizing your data](https://dataplatform.cloud.ibm.com/docs/content/wsj/refinery/visualizations.html)\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/18a7a354c4b46e26df8304755c8be954bb922b04.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/18a7a354c4b46e26df8304755c8be954bb922b04.md
new file mode 100644
index 0000000..a0cca53
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/18a7a354c4b46e26df8304755c8be954bb922b04.md
@@ -0,0 +1,15 @@
+# Browsing a model (SPSS Modeler)
+
+# Browsing the model #
+
+When the C5\.0 node runs, its model nugget is added to the flow\. To browse the model, right\-click the model nugget and choose View Model\.
+
+The Tree Diagram displays the set of rules generated by the C5\.0 node in a tree format\. Now you can see the missing pieces of the puzzle\. For people with an `Na-to-K` ratio less than `14.829` and high blood pressure, age determines the choice of drug\. For people with low blood pressure, cholesterol level seems to be the best predictor\.
+
+Figure 1\. Tree diagram
+
+
+
+You can hover over the nodes in the tree to see more details such as the number of cases for each blood pressure category and the confidence percentage of cases\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/18c44d2a29b576f708bc515cede91227b6b4fc4e.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/18c44d2a29b576f708bc515cede91227b6b4fc4e.md
new file mode 100644
index 0000000..7823d82
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/18c44d2a29b576f708bc515cede91227b6b4fc4e.md
@@ -0,0 +1,17 @@
+# Time Series node (SPSS Modeler)
+
+# Time Series node #
+
+The Time Series node can be used with data in either a local or distributed environment\. With this node, you can choose to estimate and build exponential smoothing, univariate Autoregressive Integrated Moving Average (ARIMA), or multivariate ARIMA (or transfer function) models for time series, and produce forecasts based on the time series data\.
+
+Exponential smoothing is a method of forecasting that uses weighted values of previous series observations to predict future values\. As such, exponential smoothing is not based on a theoretical understanding of the data\. It forecasts one point at a time, adjusting its forecasts as new data come in\. The technique is useful for forecasting series that exhibit trend, seasonality, or both\. You can choose from various exponential smoothing models that differ in their treatment of trend and seasonality\.
+
+ARIMA models provide more sophisticated methods for modeling trend and seasonal components than do exponential smoothing models, and, in particular, they allow the added benefit of including independent (predictor) variables in the model\. This involves explicitly specifying autoregressive and moving average orders as well as the degree of differencing\. You can include predictor variables and define transfer functions for any or all of them, as well as specify automatic detection of outliers or an explicit set of outliers\.
+
+Note: In practical terms, ARIMA models are most useful if you want to include predictors that might help to explain the behavior of the series that is being forecast, such as the number of catalogs that are mailed or the number of hits to a company web page\. Exponential smoothing models describe the behavior of the time series without attempting to understand why it behaves as it does\. For example, a series that historically peaks every 12 months will probably continue to do so even if you don't know why\.
+
+An Expert Modeler option is also available, which attempts to automatically identify and estimate the best\-fitting ARIMA or exponential smoothing model for one or more target variables, thus eliminating the need to identify an appropriate model through trial and error\. If in doubt, use the Expert Modeler option\.
+
+If predictor variables are specified, the Expert Modeler selects those variables that have a statistically significant relationship with the dependent series for inclusion in ARIMA models\. Model variables are transformed where appropriate using differencing and/or a square root or natural log transformation\. By default, the Expert Modeler considers all exponential smoothing models and all ARIMA models and picks the best model among them for each target field\. You can, however, limit the Expert Modeler only to pick the best of the exponential smoothing models or only to pick the best of the ARIMA models\. You can also specify automatic detection of outliers\.
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1924ae74643c2d9d416204693c9bb84d5212e3b0.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1924ae74643c2d9d416204693c9bb84d5212e3b0.md
new file mode 100644
index 0000000..00d2e86
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/1924ae74643c2d9d416204693c9bb84d5212e3b0.md
@@ -0,0 +1,53 @@
+# Building an deploying the model (SPSS Modeler)
+
+# Building and deploying the model #
+
+
+
+1. When your model is ready, click Generate a model to generate a text nugget\.
+
+ Figure 1. Generate a new model
+
+ 
+
+ Figure 2. Build a category model
+
+ 
+2. If you want to save the Text Analytics Workbench session, instead click Return to flow and then Save and exit\.
+
+ Figure 3. Saving your session
+
+ The generated text nugget appears on your flow canvas.
+
+ Figure 4. Generated text nugget
+
+ After the category model has been validated and generated in the Text Analytics Workbench, you can deploy it in your flow and score the same data set or score a new one.
+
+ Figure 5. Example flow with two modes for scoring
+
+ This example flow illustrates the two modes for scoring:
+
+
+
+ * Categories as fields. With this option, there are just as many output records as there were in the input. However, each record now contains one new field for every category that was selected on the Model tab. For each field, enter a flag value for true and for false, such as `True/False`, or `1/0`. In this flow, values are set to `1` and `0` to aggregate results and count the number of positive, negative, mixed (both positive and negative), or no score (no opinion) answers.
+
+ Figure 6. Model results - categories as fields
+
+ 
+ * Categories as records. With this option, a new record is created for each `category, document` pair. Typically, there are more records in the output than there were in the input. Along with the input fields, new fields are also added to the data depending on what kind of model it is.
+
+ Figure 7. Model results - categories as records
+
+ 
+
+
+
+3. You can add a Select node after the DeriveSentiment SuperNode, include `Sentiments=Pos`, and add a Charts node to gain quick insight about what guests appreciate about the hotel:
+
+ Figure 8. Chart of positive opinions
+
+ 
+
+
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/198246e6e7f694d36936989d23b2255b15c2a92b.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/198246e6e7f694d36936989d23b2255b15c2a92b.md
new file mode 100644
index 0000000..7fe7043
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/198246e6e7f694d36936989d23b2255b15c2a92b.md
@@ -0,0 +1,32 @@
+# XML content model
+
+# XML content model #
+
+The XML content model provides access to XML\-based content\.
+
+The XML content model supports the ability to access components based on XPath expressions\. XPath expressions are strings that define which elements or attributes are required by the caller\. The XML content model hides the details of constructing various objects and compiling expressions that are typically required by XPath support\. It is simpler to call from Python scripting\.
+
+The XML content model includes a function that returns the XML document as a string, so Python script users can use their preferred Python library to parse the XML\.
+
+
+
+Methods for the XML content model
+
+Table 1\. Methods for the XML content model
+
+| Method | Return types | Description |
+| ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `getXMLAsString()` | `String` | Returns the XML as a string\. |
+| `getNumericValue(String xpath)` | `number` | Returns the result of evaluating the path with return type of numeric (for example, count the number of elements that match the path expression)\. |
+| `getBooleanValue(String xpath)` | `boolean` | Returns the boolean result of evaluating the specified path expression\. |
+| `getStringValue(String xpath, String attribute)` | `String` | Returns either the attribute value or XML node value that matches the specified path\. |
+| `getStringValues(String xpath, String attribute)` | `List of strings` | Returns a list of all attribute values or XML node values that match the specified path\. |
+| `getValuesList(String xpath, attributes, boolean includeValue)` | `List of lists of strings` | Returns a list of all attribute values that match the specified path along with the XML node value if required\. |
+| `getValuesMap(String xpath, String keyAttribute, attributes, boolean includeValue)` | `Hash table (key:string, value:list of string)` | Returns a hash table that uses either the key attribute or XML node value as key, and the list of specified attribute values as table values\. |
+| `isNamespaceAware()` | `boolean` | Returns whether the XML parsers should be aware of namespaces\. Default is `False`\. |
+| `setNamespaceAware(boolean value)` | `void` | Sets whether the XML parsers should be aware of namespaces\. This also calls `reset()` to ensure changes are picked up by subsequent calls\. |
+| `reset()` | `void` | Flushes any internal storage associated with this content model (for example, a cached DOM object)\. |
+
+
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/19ae3adcf2da2ffe5186553229fef07cb2b55043.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/19ae3adcf2da2ffe5186553229fef07cb2b55043.md
new file mode 100644
index 0000000..c065662
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/19ae3adcf2da2ffe5186553229fef07cb2b55043.md
@@ -0,0 +1,50 @@
+# autonumericnode properties
+
+# autonumericnode properties #
+
+The Auto Numeric node estimates and compares models for continuous numeric range outcomes using a number of different methods\. The node works in the same manner as the Auto Classifier node, allowing you to choose the algorithms to use and to experiment with multiple combinations of options in a single modeling pass\. Supported algorithms include neural networks, C&R Tree, CHAID, linear regression, generalized linear regression, and support vector machines (SVM)\. Models can be compared based on correlation, relative error, or number of variables used\.
+
+
+
+autonumericnode properties
+
+Table 1\. autonumericnode properties
+
+| `autonumericnode` Properties | Values | Property description |
+| ------------------------------------ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `custom_fields` | *flag* | If True, custom field settings will be used instead of type node settings\. |
+| `target` | *field* | The Auto Numeric node requires a single target and one or more input fields\. Weight and frequency fields can also be specified\. See [Common modeling node properties](https://dataplatform.cloud.ibm.com/docs/content/wsd/nodes/scripting_guide/clementine/modelingnodeslots_common.html#modelingnodeslots_common) for more information\. |
+| `inputs` | *\[field1 … field2\]* | |
+| `partition` | *field* | |
+| `use_frequency` | *flag* | |
+| `frequency_field` | *field* | |
+| `use_weight` | *flag* | |
+| `weight_field` | *field* | |
+| `use_partitioned_data` | *flag* | If a partition field is defined, only the training data is used for model building\. |
+| `ranking_measure` | `Correlation``NumberOfFields` | |
+| `ranking_dataset` | `Test``Training` | |
+| `number_of_models` | *integer* | Number of models to include in the model nugget\. Specify an integer between 1 and 100\. |
+| `calculate_variable_importance` | *flag* | |
+| `enable_correlation_limit` | *flag* | |
+| `correlation_limit` | *integer* | |
+| `enable_number_of_fields_limit` | *flag* | |
+| `number_of_fields_limit` | *integer* | |
+| `enable_relative_error_limit` | *flag* | |
+| `relative_error_limit` | *integer* | |
+| `enable_model_build_time_limit` | *flag* | |
+| `model_build_time_limit` | *integer* | |
+| `enable_stop_after_time_limit` | *flag* | |
+| `stop_after_time_limit` | *integer* | |
+| `stop_if_valid_model` | *flag* | |
+| `` | *flag* | Enables or disables the use of a specific algorithm\. |
+| `.` | *string* | Sets a property value for a specific algorithm\. See [Setting algorithm properties](https://dataplatform.cloud.ibm.com/docs/content/wsd/nodes/scripting_guide/clementine/factorymodeling_algorithmproperties.html#factorymodeling_algorithmproperties) for more information\. |
+| `use_cross_validation` | *boolean* | Instead of using a single partition, a cross validation partition is used\. |
+| `number_of_folds` | *integer* | N fold parameter for cross validation, with range from 3 to 10\. |
+| `set_random_seed` | *boolean* | Setting a random seed allows you to replicate analyses\. Specify an integer or click Generate, which will create a pseudo\-random integer between 1 and 2147483647, inclusive\. By default, analyses are replicated with seed 229176228\. |
+| `random_seed` | *integer* | Random seed |
+| `filter_individual_model_output` | *boolean* | Removes from the output all of the additional fields generated by the individual models that feed into the Ensemble node\. Select this option if you're interested only in the combined score from all of the input models\. Ensure that this option is deselected if, for example, you want to use an Analysis node or Evaluation node to compare the accuracy of the combined score with that of each of the individual input models\. |
+| `calculate_standard_error` | *boolean* | For a continuous (numeric range) target, a standard error calculation runs by default to calculate the difference between the measured or estimated values and the true values; and to show how close those estimates matched\. |
+
+
+
+
diff --git a/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/19ba0bfc40b6212b42f38487f1533bb65647850e.md b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/19ba0bfc40b6212b42f38487f1533bb65647850e.md
new file mode 100644
index 0000000..b171b39
--- /dev/null
+++ b/assets/seed/v2/corpora/watsonxdocsqa-v1/files/docs/19ba0bfc40b6212b42f38487f1533bb65647850e.md
@@ -0,0 +1,284 @@
+# Importing models to a deployment space
+
+# Importing models to a deployment space #
+
+Import machine learning models trained outside of IBM Watson Machine Learning so that you can deploy and test the models\. Review the model frameworks that are available for importing models\.
+
+Here, *to import a trained model* means:
+
+
+
+1. Store the trained model in your Watson Machine Learning repository
+2. Optional: Deploy the stored model in your Watson Machine Learning service
+
+
+
+and *repository* means a Cloud Object Storage bucket\. For more information, see [Creating deployment spaces](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-space-create.html)\.
+
+You can import a model in these ways:
+
+
+
+ * [Directly through the UI](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#ui-import)
+ * [By using a path to a file](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#path-file-import)
+ * [By using a path to a directory](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#path-dir-import)
+ * [Import a model object](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#object-import)
+
+
+
+For more information, see [Importing models by ML framework](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#supported-formats)\.
+
+For more information, see [Things to consider when you import models](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#model-import-considerations)\.
+
+For an example of how to add a model programmatically by using the Python client, refer to this notebook:
+
+
+
+ * [Use PMML to predict iris species\.](https://github.com/IBM/watson-machine-learning-samples/blob/df8e5122a521638cb37245254fe35d3a18cd3f59/cloud/notebooks/python_sdk/deployments/pmml/Use%20PMML%20to%20predict%20iris%20species.ipynb)
+
+
+
+For an example of how to add a model programmatically by using the REST API, refer to this notebook:
+
+
+
+ * [Use scikit\-learn to predict diabetes progression](https://github.com/IBM/watson-machine-learning-samples/blob/be84bcd25d17211f41fb34ec262b418f6cd6c87b/cloud/notebooks/rest_api/curl/deployments/scikit/Use%20scikit-learn%20to%20predict%20diabetes%20progression.ipynb)
+
+
+
+## Available ways to import models, per framework type ##
+
+This table lists the available ways to import models to Watson Machine Learning, per framework type\.
+
+
+
+Import options for models, per framework type
+
+| Import option | Spark MLlib | Scikit\-learn | XGBoost | TensorFlow | PyTorch |
+| ---------------------------------------------------------------------------------- | ----------- | ------------- | ------- | ---------- | ------- |
+| [Importing a model object](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#object-import) | ✓ | ✓ | ✓ | | |
+| [Importing a model by using a path to a file](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#path-file-import) | | ✓ | ✓ | ✓ | ✓ |
+| [Importing a model by using a path to a directory](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#path-dir-import) | | ✓ | ✓ | ✓ | ✓ |
+
+
+
+### Adding a model by using UI ###
+
+Note:If you want to import a model in the PMML format, you can directly import the model `.xml` file\.
+
+To import a model by using UI:
+
+
+
+1. From the **Assets** tab of your space in Watson Machine Learning, click **Import assets**\.
+2. Select `Local file` and then select **Model**\.
+3. Select the model file that you want to import and click **Import**\.
+
+
+
+The importing mechanism automatically selects a matching model type and software specification based on the version string in the `.xml` file\.
+
+### Importing a model object ###
+
+Note:This import method is supported by a limited number of ML frameworks\. For more information, see [Available ways to import models, per framework type](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#supported-formats)\.
+
+To import a model object:
+
+
+
+1. If your model is located in a remote location, follow [Downloading a model that is stored in a remote location](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#model-download)\.
+2. Store the model object in your Watson Machine Learning repository\. For more information, see [Storing model in Watson Machine Learning repository](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#store-in-repo)\.
+
+
+
+### Importing a model by using a path to a file ###
+
+Note:This import method is supported by a limited number of ML frameworks\. For more information, see [Available ways to import models, per framework type](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#supported-formats)\.
+
+To import a model by using a path to a file:
+
+
+
+1. If your model is located in a remote location, follow [Downloading a model that is stored in a remote location](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#model-download) to download it\.
+2. If your model is located locally, place it in a specific directory:
+
+ !cp
+ !cd
+3. For **Scikit\-learn**, **XGBoost**, **Tensorflow**, and **PyTorch** models, if the downloaded file is not a `.tar.gz` archive, make an archive:
+
+ !tar -zcvf .tar.gz
+
+ The model file must be at the top-level folder of the directory, for example:
+
+ assets/
+
+ variables/
+ variables/variables.data-00000-of-00001
+ variables/variables.index
+4. Use the path to the saved file to store the model file in your Watson Machine Learning repository\. For more information, see [Storing model in Watson Machine Learning repository](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#store-in-repo)\.
+
+
+
+### Importing a model by using a path to a directory ###
+
+Note:This import method is supported by a limited number of ML frameworks\. For more information, see [Available ways to import models, per framework type](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#supported-formats)\.
+
+To import a model by using a path to a directory:
+
+
+
+1. If your model is located in a remote location, refer to [Downloading a model stored in a remote location](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#model-download)\.
+2. If your model is located locally, place it in a specific directory:
+
+ !cp
+ !cd
+
+ For **scikit-learn**, **XGBoost**, **Tensorflow**, and **PyTorch** models, the model file must be at the top-level folder of the directory, for example:
+
+ assets/
+
+ variables/
+ variables/variables.data-00000-of-00001
+ variables/variables.index
+3. Use the directory path to store the model file in your Watson Machine Learning repository\. For more information, see [Storing model in Watson Machine Learning repository](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#store-in-repo)\.
+
+
+
+### Downloading a model stored in a remote location ###
+
+Follow this sample code to download your model from a remote location:
+
+ import os
+ from wget import download
+
+ target_dir = ''
+ if not os.path.isdir(target_dir):
+ os.mkdir(target_dir)
+ filename = os.path.join(target_dir, '')
+ if not os.path.isfile(filename):
+ filename = download('', out = target_dir)
+
+## Things to consider when you import models ##
+
+To learn more about importing a specific model type, see:
+
+
+
+ * [Models saved in PMML format](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#pmml-import)
+ * [Spark MLlib models](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#spark-ml-lib-import)
+ * [Scikit\-learn models](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#scikit-learn-import)
+ * [XGBoost models](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#xgboost-import)
+ * [TensorFlow models](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#tf-import)
+ * [PyTorch models](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-importing-model.html?context=cdpaas&locale=en#pt-import)
+
+
+
+To learn more about frameworks that you can use with Watson Machine Learning, see [Supported frameworks](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/pm_service_supported_frameworks.html)\.
+
+### Models saved in PMML format ###
+
+
+
+ * The only available deployment type for models that are imported from PMML is online deployment\.
+ * The PMML file must have the `.xml` file extension\.
+ * PMML models cannot be used in an SPSS stream flow\.
+ * The PMML file must not contain a prolog\. Depending on the library that you are using when you save your model, a prolog might be added to the beginning of the file by default\. For example, if your file contains a prolog string such as `spark-mllib-lr-model-pmml.xml`, remove the string before you import the PMML file to the deployment space\.
+
+
+
+Depending on the library that you are using when you save your model, a prolog might be added to the beginning of the file by default, like in this example:
+
+ ::::::::::::::
+ spark-mllib-lr-model-pmml.xml
+ ::::::::::::::
+
+You must remove that prolog before you can import the PMML file to Watson Machine Learning\.
+
+### Spark MLlib models ###
+
+
+
+ * Only classification and regression models are available\.
+ * Custom transformers, user\-defined functions, and classes are not available\.
+
+
+
+### Scikit\-learn models ###
+
+
+
+ * `.pkl` and `.pickle` are the available import formats\.
+ * To serialize or pickle the model, use the `joblib` package\.
+ * Only classification and regression models are available\.
+ * Pandas Dataframe input type for `predict()` API is not available\.
+ * The only available deployment type for scikit\-learn models is online deployment\.
+
+
+
+### XGBoost models ###
+
+
+
+ * `.pkl` and `.pickle` are the available import formats\.
+ * To serialize or pickle the model, use the `joblib` package\.
+ * Only classification and regression models are available\.
+ * Pandas Dataframe input type for `predict()` API is not available\.
+ * The only available deployment type for XGBoost models is online deployment\.
+
+
+
+### TensorFlow models ###
+
+
+
+ * `.pb`, `.h5`, and `.hdf5` are the available import formats\.
+ * To save or serialize a TensorFlow model, use the `tf.saved_model.save()` method\.
+ * `tf.estimator` is not available\.
+ * The only available deployment types for TensorFlow models are: online deployment and batch deployment\.
+
+
+
+### PyTorch models ###
+
+
+
+ * The only available deployment type for PyTorch models is online deployment\.
+ * For a Pytorch model to be importable to Watson Machine Learning, it must be previously exported to `.onnx` format\. Refer to this code\.
+
+ torch.onnx.export(, , ".onnx", verbose=True, input_names=, output_names=